From dad70eeac9bc831c4bad843171c8a4fee6905d4e Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 01:01:15 +0300 Subject: [PATCH 01/18] Replace the priority queue's storage with a vector The queue holds its elements itself rather than pointing into a pool beside it, so a veinhole monster's frontier travels in a save as the cells it holds and the save format changes. The sift is spelled out rather than taken from the standard heap algorithms, which are free to reorder elements of equal score. --- code/astar.cpp | 127 +++--- code/astar.h | 36 +- code/mapgen.cpp | 458 ++++++++++------------ code/mapgen.h | 4 +- code/nodes.h | 16 + code/priority.h | 301 +++++--------- code/tiberium.cpp | 96 ++--- code/tiberium.h | 26 +- code/vein.cpp | 121 ++---- code/vein.h | 15 +- manual/content/keys/maxveinholegrowth.md | 2 +- tests/CMakeLists.txt | 1 + tests/priorityqueue/CMakeLists.txt | 15 + tests/priorityqueue/priorityqueuetest.cpp | 445 +++++++++++++++++++++ 14 files changed, 951 insertions(+), 712 deletions(-) create mode 100644 tests/priorityqueue/CMakeLists.txt create mode 100644 tests/priorityqueue/priorityqueuetest.cpp diff --git a/code/astar.cpp b/code/astar.cpp index 1d914447f..711a6b7d4 100644 --- a/code/astar.cpp +++ b/code/astar.cpp @@ -312,7 +312,7 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F HierNodeIndex = 0; HierLastNodeCell = from; int * fine_final_ids = HierOnPath[0]; - RegularOpenNode * working_node = Create_Node(0, from_pptr, to, 0.0); + std::optional working_node = Create_Node(std::nullopt, from_pptr, to, 0.0); if (from == to && CurrentCellHeight == DestCellHeight) { return(NULL); @@ -373,7 +373,7 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F break; } Cell & from_id = working_from[0]->CellID; - RegularOpenNode * temp_node = NULL; + std::optional temp_node; int from_index = from_id.X + MapCellStride * from_id.Y; for (face = FACING_FIRST; face <= FACING_COUNT; face++) { @@ -445,24 +445,24 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F continue; } - RegularOpenNode * new_node = Create_Node(working_node, working_to, to, movement_cost); - if (temp_node == NULL) { + RegularOpenNode new_node = Create_Node(working_node, working_to, to, movement_cost); + if (!temp_node) { temp_node = new_node; } else { - if (new_node->Score < temp_node->Score) { + if (new_node.Score < temp_node->Score) { RegularQueue->Insert(*temp_node); temp_node = new_node; } else { - RegularQueue->Insert(*new_node); + RegularQueue->Insert(new_node); } } if (base_level) { RegularVisited[node_index] = UniqueID; - RegularMovementCosts[node_index] = new_node->MovementCost; + RegularMovementCosts[node_index] = new_node.MovementCost; } else { RegularBridgeVisited[node_index] = UniqueID; - RegularBridgeMovementCosts[node_index] = new_node->MovementCost; + RegularBridgeMovementCosts[node_index] = new_node.MovementCost; } if (subzone_id == HierSubzonePath[SUBZONE_FINE][HierNodeIndex + 1]) { HierNodeIndex++; @@ -489,14 +489,14 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F breakout: - if (tries == 10000 || working_node == NULL || tries == max_loops || working_node->PathLength < 2) { + if (tries == 10000 || !working_node || tries == max_loops || working_node->PathLength < 2) { if (Avoidance != AVOIDANCE_NONE) { Apply_Path_Collision_Avoidance(foot); } return(NULL); } - PathStruct * final_path = Build_Final_Path(working_node, moves); + PathStruct * final_path = Build_Final_Path(*working_node, moves); Cut_Corners(final_path, foot); /* @@ -522,14 +522,14 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F /// The map array slot of the cell the node stands for. /// The destination cell, used to estimate the remaining cost. /// The cost of the step from the parent into this cell. -/// Returns with a pointer to the new node, taken from the search's node pool. -AStarClass::RegularOpenNode * AStarClass::Create_Node(RegularOpenNode * parent, CellClass ** cell, Cell const & to, float movement_cost) +/// Returns with the new open set entry, naming a cell record taken from the pool. +AStarClass::RegularOpenNode AStarClass::Create_Node(std::optional const & parent, CellClass ** cell, Cell const & to, float movement_cost) { - RegularOpenNode * open_node = &RegularOpenNodes->Nodes[RegularOpenNodes->ActiveCount++]; + RegularOpenNode open_node; RegularNode * node_data = &RegularNodes->Nodes[RegularNodes->ActiveCount++]; node_data->CellSlot = cell; - if (parent != NULL) { + if (parent) { node_data->Parent = parent->Node; CellClass * current_cell = *node_data->CellSlot; CellClass * parent_cell = *parent->Node->CellSlot; @@ -547,20 +547,20 @@ AStarClass::RegularOpenNode * AStarClass::Create_Node(RegularOpenNode * parent, node_data->CellHeight = CurrentCellHeight; } - open_node->Node = node_data; - if (parent != NULL) { - open_node->MovementCost = movement_cost + parent->MovementCost; - open_node->PathLength = parent->PathLength + 1; + open_node.Node = node_data; + if (parent) { + open_node.MovementCost = movement_cost + parent->MovementCost; + open_node.PathLength = parent->PathLength + 1; } else { - open_node->MovementCost = 0.0; - open_node->PathLength = 1; + open_node.MovementCost = 0.0; + open_node.PathLength = 1; } Cell & cell_id = (*cell)->CellID; int dx = abs(cell_id.X - to.X); int dy = abs(cell_id.Y - to.Y); - open_node->Score = open_node->MovementCost + std::sqrt(dx * dx + dy * dy); + open_node.Score = open_node.MovementCost + std::sqrt(dx * dx + dy * dy); return(open_node); } @@ -575,7 +575,6 @@ void AStarClass::Clear(void) { int i; RegularNodes->ActiveCount = 0; - RegularOpenNodes->ActiveCount = 0; RegularQueue->Clear(); HierQueue->Clear(); @@ -654,11 +653,9 @@ AStarClass::AStarClass(void) : { RegularQueue = new PriorityQueueClass(65536); HierQueue = new PriorityQueueClass(10000); - RegularOpenNodes = new RegularOpenNodePool; RegularNodes = new RegularNodePool; RegularNodes->ActiveCount = 0; - RegularOpenNodes->ActiveCount = 0; RegularQueue->Clear(); HierQueue->Clear(); @@ -670,7 +667,7 @@ AStarClass::AStarClass(void) : memset(HierSubzonePath[i], 0, sizeof(HierSubzonePath[i])); HierSubzonePathCount[i] = 0; } - HierNodePool = new AStarHierarchicalNode[10000]; + HierNodePool.reserve(10000); } @@ -685,11 +682,6 @@ AStarClass::~AStarClass(void) delete HierQueue; HierQueue = NULL; - if (RegularOpenNodes != NULL) { - delete RegularOpenNodes; - } - RegularOpenNodes = NULL; - if (RegularNodes != NULL) { delete RegularNodes; } @@ -732,7 +724,6 @@ AStarClass::~AStarClass(void) } } - delete [] HierNodePool; } @@ -766,22 +757,22 @@ FacingType Facing_Between(Cell const & cell1, Cell const & cell2) /// Returns with a pointer to the completed path control structure. /// There is only one path control structure, so building a new path invalidates /// the last one this routine handed out. -PathStruct * AStarClass::Build_Final_Path(RegularOpenNode *final_node, FacingType *moves) +PathStruct * AStarClass::Build_Final_Path(RegularOpenNode const & final_node, FacingType * moves) { static PathStruct path; // Main path control. - path.Cost = int(final_node->Score); - path.Length = final_node->PathLength; + path.Cost = int(final_node.Score); + path.Length = final_node.PathLength; path.Overlap = 0; path.LastOverlap.X = 0; path.LastOverlap.Y = 0; path.Command = moves; path.Height = CellHeights; - RegularNode * current = final_node->Node; + RegularNode * current = final_node.Node; RegularNode * parent = current->Parent; - for (int i = (final_node->PathLength - 2); i >= 0; i--) { + for (int i = (final_node.PathLength - 2); i >= 0; i--) { if (parent != NULL) { CellHeights[i] = parent->CellHeight; moves[i] = Facing_Between((*current->CellSlot)->CellID, (*parent->CellSlot)->CellID); @@ -790,7 +781,7 @@ PathStruct * AStarClass::Build_Final_Path(RegularOpenNode *final_node, FacingTyp parent = parent->Parent; } - moves[final_node->PathLength - 1] = FACING_NONE; + moves[final_node.PathLength - 1] = FACING_NONE; path.Start = (*current->CellSlot)->CellID; @@ -1604,6 +1595,7 @@ bool AStarClass::Find_Path_Hierarchical(Cell const & from, Cell const & to, MZon for (int subzone_level = SUBZONE_COARSE; subzone_level >= SUBZONE_FINE; subzone_level--) { HierQueue->Clear(); + HierNodePool.clear(); CellSubzoneStruct & start_cell_subzones = Map.CellSubzones[Map.Get_Cell_Zone_Index(from)]; int start_subzone = start_cell_subzones.SubzoneID[subzone_level]; @@ -1625,30 +1617,25 @@ bool AStarClass::Find_Path_Hierarchical(Cell const & from, Cell const & to, MZon final_ids[end_subzone] = UniqueID; if (start_subzone == end_subzone) { - if (subzone_level == SUBZONE_FINE) { - AStarHierarchicalNode * node = &HierNodePool[0]; - node->Depth = 0; - node->SubzoneID = start_subzone; - } HierSubzonePath[subzone_level][0] = start_subzone; HierSubzonePathCount[subzone_level] = 1; } else { - int node_count = 0; - AStarHierarchicalNode * start_node = &HierNodePool[0]; - start_node->ParentIndex = -1; - start_node->SubzoneID = start_subzone; - start_node->Score = 0.0; - start_node->Depth = 0; - - HierQueue->Insert(*start_node); - node_count++; + AStarHierarchicalNode start_node; + start_node.PoolIndex = 0; + start_node.ParentIndex = -1; + start_node.SubzoneID = start_subzone; + start_node.Score = 0.0; + start_node.Depth = 0; + + HierNodePool.push_back(start_node); + HierQueue->Insert(start_node); working_ids[start_subzone] = UniqueID; costs[start_subzone] = 0.0; - AStarHierarchicalNode * best_node = HierQueue->Extract_Min(); + std::optional best_node = HierQueue->Extract_Min(); bool no_banned_edges = HierBannedEdges[subzone_level].Count() == 0; - while (best_node != NULL) { + while (best_node) { int from_subzone = best_node->SubzoneID; if (from_subzone == end_subzone) { break; @@ -1675,41 +1662,43 @@ bool AStarClass::Find_Path_Hierarchical(Cell const & from, Cell const & to, MZon if ((working_ids[to_subzone] != UniqueID || costs[to_subzone] > score) && (is_coarse || coarser_final_ids[to_coarser_subzone] == UniqueID || passability == PASSABLE_CRUSH) && pass_table[passability] == TRAVERSAL_PASSABLE) { if (no_banned_edges || !Subzone_Edge_Banned(from_subzone, to_subzone, subzone_level)) { - AStarHierarchicalNode * new_node = &HierNodePool[node_count]; - new_node->ParentIndex = best_node - HierNodePool; - new_node->SubzoneID = to_subzone; - new_node->Score = score; - new_node->Depth = best_node->Depth + 1; - HierQueue->Insert(*new_node); + AStarHierarchicalNode new_node; + new_node.PoolIndex = (int)HierNodePool.size(); + new_node.ParentIndex = best_node->PoolIndex; + new_node.SubzoneID = to_subzone; + new_node.Score = score; + new_node.Depth = best_node->Depth + 1; + HierNodePool.push_back(new_node); + HierQueue->Insert(new_node); working_ids[to_subzone] = UniqueID; costs[to_subzone] = score; - node_count++; } } } best_node = HierQueue->Extract_Min(); } - if (best_node == NULL) { + if (!best_node) { return(false); } - AStarHierarchicalNode * tail_node = best_node; - while (best_node->ParentIndex != -1) { - final_ids[best_node->SubzoneID] = UniqueID; - best_node = &HierNodePool[best_node->ParentIndex]; + int trace = best_node->PoolIndex; + while (HierNodePool[trace].ParentIndex != -1) { + final_ids[HierNodePool[trace].SubzoneID] = UniqueID; + trace = HierNodePool[trace].ParentIndex; } - int path_count = tail_node->Depth + 1; + int tail_node = best_node->PoolIndex; + int path_count = HierNodePool[tail_node].Depth + 1; HierSubzonePathCount[subzone_level] = path_count; path_count--; while (path_count > 0) { - HierSubzonePath[subzone_level][path_count] = tail_node->SubzoneID; - tail_node = &HierNodePool[tail_node->ParentIndex]; + HierSubzonePath[subzone_level][path_count] = HierNodePool[tail_node].SubzoneID; + tail_node = HierNodePool[tail_node].ParentIndex; path_count--; } - HierSubzonePath[subzone_level][0] = tail_node->SubzoneID; + HierSubzonePath[subzone_level][0] = HierNodePool[tail_node].SubzoneID; } } return(true); diff --git a/code/astar.h b/code/astar.h index 30c8d23de..00b2b975a 100644 --- a/code/astar.h +++ b/code/astar.h @@ -19,6 +19,9 @@ #include "priority.h" #include "vector.h" +#include +#include + #include "facing.hh" #include "move.hh" #include "mzone.hh" @@ -135,32 +138,17 @@ class AStarClass RegularNodePool(void) : ActiveCount(0) {} }; - struct RegularOpenNodePool { - /* - * These are all the open set entries one path attempt may use, handed out in - * order as the search reaches new cells. - */ - RegularOpenNode Nodes[65536]; - - /* - * This is the number of entries handed out so far, and so the index the next - * one comes from. - */ - int ActiveCount; - RegularOpenNodePool(void) : ActiveCount(0) {} - }; - private: /* ---------------------------------------------------------------------------------- * Regular pathfinder (cell-level A*) */ PathStruct * Find_Path_Regular(Cell const & from, Cell const & to, FootClass * foot, FacingType * moves, int max_loops, bool with_hs); - RegularOpenNode * Create_Node(RegularOpenNode * parent, CellClass ** cell, Cell const & to, float movement_cost); + RegularOpenNode Create_Node(std::optional const & parent, CellClass ** cell, Cell const & to, float movement_cost); bool Is_Visited(int, bool base_level, int index); double Get_Movement_Cost(CellClass ** from, CellClass ** to, bool bridge, MoveType move, FootClass * foot); void Apply_Path_Collision_Avoidance(FootClass * foot); FootClass * Find_Moving_Blocker(Cell const & cell, int cell_height); - PathStruct * Build_Final_Path(RegularOpenNode * nodes, FacingType * moves); + PathStruct * Build_Final_Path(RegularOpenNode const & final_node, FacingType * moves); void Cut_Corners(PathStruct * path, FootClass * foot); int Try_Diagonal_Shortcut(FootClass * foot, FacingType * moves, unsigned int * heights, int initial_first_leg_length, int initial_second_leg_length, Cell & cell); void Optimize_Moves(PathStruct * path, FootClass * foot); @@ -214,12 +202,11 @@ class AStarClass bool UseLocomotorEnterCheck; /* - * These are the two node pools the cell level search draws on -- one for the cell and - * parent records that the finished route is recovered from, and one for the open set - * entries handed to the RegularQueue. + * This is the pool of cell and parent records the finished route is recovered from. + * The open set entries handed to the RegularQueue name these rather than holding a + * cell of their own. */ RegularNodePool * RegularNodes; - RegularOpenNodePool * RegularOpenNodes; /* * This is the open set for the cell level search, ordered by score so that the most @@ -314,10 +301,11 @@ class AStarClass float * HierCosts[SUBZONE_COUNT]; /* - * Linear pool of hierarchical A* nodes, allocated by index during expansion. - * The priority queue itself is HierQueue; this is only the node storage. + * The nodes one level of the hierarchical search reaches, in the order it reached + * them. A node names its parent by its position here, so this outlives the queue + * entries taken from it and is emptied for every level. */ - AStarHierarchicalNode * HierNodePool; + std::vector HierNodePool; /* * This is the open set for the hierarchical search, ordered by score so that the diff --git a/code/mapgen.cpp b/code/mapgen.cpp index a60e88511..477996f0c 100644 --- a/code/mapgen.cpp +++ b/code/mapgen.cpp @@ -41,6 +41,7 @@ #include "language/language.h" #include "netshare.h" #include "nodes.h" +#include "priority.h" #include "overtype.h" #include "ownrdraw.h" #include "pcx.h" @@ -66,6 +67,8 @@ #include #include +#include +#include bool (*RMGCallback)() = MapGen_Call_Back; @@ -949,9 +952,8 @@ bool MapRegionClass::Split_Region(void) } int heap_size = (2 * CellCount) + 10; - CellNode * nodes = new CellNode[heap_size]; - PriorityQueueClass * queue = new PriorityQueueClass((2 * CellCount) + 10); - queue->Clear(); + PriorityQueueClass queue((2 * CellCount) + 10); + queue.Clear(); CellData::Reset_Spreads(false); @@ -963,17 +965,17 @@ bool MapRegionClass::Split_Region(void) unsigned int target_count = Pick_Random_UInt(CellCount / 8, CellCount / 3); unsigned int seed_index = Pick_Random_UInt(0, Cells.Count() - 1); - int node_count = 1; Cell seed = Cells[seed_index]; - nodes[0].Element = seed; - nodes[0].Score = 0.0f; + CellNode seed_node; + seed_node.Element = seed; + seed_node.Score = 0.0f; MapRegionClass::Get_Cell_Data(seed).SpreadID = -3; - queue->Insert(nodes[0]); + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); double dist = (unsigned int)RMGRandom() * (DEG_TO_RAD(360) / UINT_MAX); - while (node != NULL) { + while (node) { if (spread_count >= (int)target_count) { break; } @@ -981,39 +983,36 @@ bool MapRegionClass::Split_Region(void) CellData::Set_Region(node->Element, -3); int cell_index = node->Element.X + MapCellStride * node->Element.Y; - CellNode * newnode = &nodes[node_count]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = (FacingType)(dir + FACING_90)) { Cell ncell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(ncell)) { if (CellData::Get_Region(ncell) == -2 && CellData::Get_Spread(ncell) != -3 && Map[ncell].Is_Tile_Clear()) { - newnode->Element = ncell; - newnode->Score = Get_Angle_Score(seed, ncell, dist); + CellNode newnode; + newnode.Element = ncell; + newnode.Score = Get_Angle_Score(seed, ncell, dist); MapRegionClass::Get_Cell_Data(cell_index + AStarFacingToOffset[dir]).SpreadID = -3; - node_count++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } spread_count++; dist += Sample_Normal(0, DEG_TO_RAD(22.5)); - node = queue->Extract_Min(); + node = queue.Extract_Min(); } CellData::Reset_Spreads(false); - CellNode * remaining = queue->Extract_Min(); + std::optional remaining = queue.Extract_Min(); DynamicVectorClass ncells; - while (remaining != NULL) { + while (remaining) { Cell c = remaining->Element; MapRegionClass::Get_Cell_Data(c).RegionID = -3; - remaining = queue->Extract_Min(); + remaining = queue.Extract_Min(); } - delete[] nodes; - delete queue; DynamicVectorClass new_regions; for (i = Cells.Count() - 1; i >= 0; i--) { @@ -5506,8 +5505,7 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) return(false); } - CellNode *nodes = new CellNode[std::max((2 * spread_limit) + 2, 100)]; - PriorityQueueClass *queue = new PriorityQueueClass(std::max((2 * spread_limit) + 2, 100)); + PriorityQueueClass queue(std::max((2 * spread_limit) + 2, 100)); Map.Reset_Iterator(); CellClass *iter = Map.Iterate(); @@ -5574,21 +5572,21 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) double mean = (double)(spread_limit / 3); int spread_count = (int)Sample_Truncated_Normal(mean, scale, 75.0, (double)max_spread); - queue->Clear(); + queue.Clear(); - int marker_index = 1; - nodes[0].Element = seed_cell; - nodes[0].Score = 0.0f; + CellNode seed_node; + seed_node.Element = seed_cell; + seed_node.Score = 0.0f; Set_Cell_Data_Spread(seed_cell, WorkingRegionID); - queue->Insert(nodes[0]); + queue.Insert(seed_node); DynamicVectorClass cells; cells.Set_Growth_Step(2000); - CellNode *node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); if (spread_count > 0) { do { - if (node == NULL) { + if (!node) { break; } if (!success) { @@ -5606,7 +5604,6 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) cells.Add(cellptr->CellID); - CellNode *newnode = &nodes[marker_index]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = FacingType(dir + FACING_90)) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { @@ -5616,22 +5613,22 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) success = false; } } else { - newnode->Element = newcell; - newnode->Score = Get_Spread_Score(seed_cell, newcell, seeded_count); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Spread_Score(seed_cell, newcell, seeded_count); Set_Cell_Data_Spread(newcell, WorkingRegionID); - marker_index++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } seeded_count++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } while (seeded_count < spread_count); } - CellNode *remaining = queue->Extract_Min(); - while (remaining != NULL) { + std::optional remaining = queue.Extract_Min(); + while (remaining) { if (!success) { break; } @@ -5650,7 +5647,7 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) success = false; } - remaining = queue->Extract_Min(); + remaining = queue.Extract_Min(); seeded_count++; } @@ -5669,7 +5666,7 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) int swamp_count = Pick_Random_UInt(0, 2); for (int i = 0; i < swamp_count; i++) { - Generate_Swamp(cells, seeded_count, nodes, queue); + Generate_Swamp(cells, seeded_count); } } } @@ -5677,8 +5674,6 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) } } - delete queue; - delete[] nodes; if (success) { SeededWaterAmount += seeded_count; @@ -5700,9 +5695,7 @@ bool MapGeneratorClass::Seed_Lake(const Cell & cell) /// The water cells the swamp may claim. /// The size of the water body, which decides how far the swamp may /// spread. -/// Scratch storage for the spread. -/// The queue that orders the spread. -void MapGeneratorClass::Generate_Swamp(DynamicVectorClass &cells, int last, CellNode *nodes, PriorityQueueClass *queue) +void MapGeneratorClass::Generate_Swamp(DynamicVectorClass &cells, int last) { Cell swamp_origin = CELL_NONE; int tries = 0; @@ -5732,39 +5725,40 @@ void MapGeneratorClass::Generate_Swamp(DynamicVectorClass &cells, int last int min_spread = std::min(last / 8, 50); int spread_count = Pick_Random_UInt(min_spread, max_spread); - queue->Clear(); + PriorityQueueClass queue(std::max((2 * spread_count) + 2, 100)); int marker_index = 1; - nodes[0].Element = swamp_origin; - nodes[0].Score = 0; - queue->Insert(nodes[0]); + CellNode seed_node; + seed_node.Element = swamp_origin; + seed_node.Score = 0; + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); int spread_step = 0; - while (spread_step < spread_count && node != NULL) { + while (spread_step < spread_count && node) { CellClass * cptr = &Map[node->Element]; cptr->ITType = IsometricTileTypeClass::SwampTile; cptr->SubTile = 0; swamp_cells.Add(cptr->CellID); - CellNode * newnode = &nodes[marker_index]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = FacingType(dir + FACING_90)) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { MapRegionClass::CellData & data = MapRegionClass::Get_Cell_Data(newcell); CellClass * newcellptr = &Map[newcell]; if (newcellptr->ITType >= IsometricTileTypeClass::WaterSet && newcellptr->ITType < IsometricTileTypeClass::WaterSet + WATER_COUNT) { - newnode->Element = newcell; - newnode->Score = Get_Spread_Score(swamp_origin, newcell, spread_step); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Spread_Score(swamp_origin, newcell, spread_step); data.SpreadID = WorkingRegionID; marker_index++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } spread_step++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } int i; @@ -5867,8 +5861,7 @@ bool MapGeneratorClass::Seed_Arctic_Lake(const Cell & cell) return(false); } - CellNode *nodes = new CellNode[std::max((2 * spread_limit) + 2, 100)]; - PriorityQueueClass *queue = new PriorityQueueClass(std::max((2 * spread_limit) + 2, 100)); + PriorityQueueClass queue(std::max((2 * spread_limit) + 2, 100)); Map.Reset_Iterator(); CellClass *cptr = Map.Iterate(); @@ -5911,20 +5904,20 @@ bool MapGeneratorClass::Seed_Arctic_Lake(const Cell & cell) int spread_count = (int)Sample_Truncated_Normal(mean, scale, 75.0, (double)max_spread); - queue->Clear(); + queue.Clear(); - int marker_index = 1; - nodes[0].Element = seed_cell; - nodes[0].Score = 0.0f; + CellNode seed_node; + seed_node.Element = seed_cell; + seed_node.Score = 0.0f; Set_Cell_Data_Spread(seed_cell, WorkingRegionID); - queue->Insert(nodes[0]); + queue.Insert(seed_node); - CellNode *node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); DynamicVectorClass *ice_cells = new DynamicVectorClass(); ice_cells->Set_Growth_Step(spread_count + 1); - while (seeded_count < spread_count && node != NULL) { + while (seeded_count < spread_count && node) { Set_Cell_Data_Region(node->Element, WorkingRegionID); CellClass *node_cellptr = &Map[node->Element]; @@ -5933,7 +5926,6 @@ bool MapGeneratorClass::Seed_Arctic_Lake(const Cell & cell) node_cellptr->IsIceGrowthAllowed = true; ice_cells->Add(node_cellptr->Fetch_CellID()); - CellNode *newnode = &nodes[marker_index]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = FacingType(dir + FACING_90)) { Cell newcell = Adjacent_Cell(node->Element, dir); @@ -5944,11 +5936,11 @@ bool MapGeneratorClass::Seed_Arctic_Lake(const Cell & cell) if (!data.RegionID && data.SpreadID != WorkingRegionID) { CellClass *newcellptr = &Map[newcell]; if (newcellptr->Is_Tile_Clear()) { - newnode->Element = newcell; - newnode->Score = Get_Spread_Score(seed_cell, newcell, seeded_count); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Spread_Score(seed_cell, newcell, seeded_count); Set_Cell_Data_Spread(newcell, WorkingRegionID); - marker_index++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } @@ -5956,11 +5948,11 @@ bool MapGeneratorClass::Seed_Arctic_Lake(const Cell & cell) } seeded_count++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } - CellNode *remaining = queue->Extract_Min(); - while (remaining != NULL) { + std::optional remaining = queue.Extract_Min(); + while (remaining) { if (!success) { break; } @@ -5978,15 +5970,13 @@ bool MapGeneratorClass::Seed_Arctic_Lake(const Cell & cell) ice_cells->Add(cellptr->CellID); } - remaining = queue->Extract_Min(); + remaining = queue.Extract_Min(); seeded_count++; } Seed_Ice(*ice_cells, (IsometricTileType)(IsometricTileTypeClass::Ice1Set + ICE_CRACKED)); Seed_Ice(*ice_cells, (IsometricTileType)(IsometricTileTypeClass::Ice1Set + ICE_EDGE)); - delete queue; - delete[] nodes; delete ice_cells; if (success) { @@ -6013,8 +6003,7 @@ void MapGeneratorClass::Seed_Ice(DynamicVectorClass & cells, IsometricTile int seed_count = Pick_Random_UInt(0, 15); - CellNode * nodes = new CellNode[std::max(cells.Count() * 2, 64)]; - PriorityQueueClass * queue = new PriorityQueueClass(std::max(cells.Count() * 2, 64)); + PriorityQueueClass queue(std::max(cells.Count() * 2, 64)); for (int i = cells.Count() - 1; i >= 0; i--) { MapRegionClass::Get_Cell_Data(cells[i]).Iced = false; @@ -6024,47 +6013,44 @@ void MapGeneratorClass::Seed_Ice(DynamicVectorClass & cells, IsometricTile int spread_step = 0; int spread_limit = std::max(4, cells.Count() / 20); int spread_count = Pick_Random_UInt(3, spread_limit); - queue->Clear(); + queue.Clear(); int seed_index = Pick_Random_UInt(0, cells.Count() - 1); - int node_count = 1; Cell seed_cell = cells[seed_index]; - nodes[0].Element = seed_cell; - nodes[0].Score = 0.0f; + CellNode seed_node; + seed_node.Element = seed_cell; + seed_node.Score = 0.0f; MapRegionClass::Get_Cell_Data(seed_cell).Iced = true; - queue->Insert(nodes[0]); + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); - while (spread_step < spread_count && node != NULL) { + std::optional node = queue.Extract_Min(); + while (spread_step < spread_count && node) { Map[node->Element].ITType = (IsometricTileType)last; - CellNode * newnode = &nodes[node_count]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = FacingType(dir + FACING_90)) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { CellClass * newcellptr = &Map[newcell]; MapRegionClass::CellData & data = MapRegionClass::Get_Cell_Data(newcell); if (newcellptr->Is_Tile_Ice() && !data.Iced) { - newnode->Element = newcell; - newnode->Score = Get_Ice_Score(seed_cell, newcell, rand_scale); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Ice_Score(seed_cell, newcell, rand_scale); data.Iced = true; - node_count++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } spread_step++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } seed_count--; } - delete[] nodes; - delete queue; } @@ -7243,10 +7229,8 @@ DynamicVectorClass *MapGeneratorClass::Build_Region_Border_Cell_List(int i /// True if the region grew without colliding with another region. bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rect const & bounds, Cell const & origin, bool claim_frontier) { - CellNode * nodes = new CellNode[std::max(2 * LocalHeight * LocalWidth, 100)]; - PriorityQueueClass * queue = new PriorityQueueClass(std::max(2 * LocalHeight * LocalWidth, 100)); + PriorityQueueClass queue(std::max(2 * LocalHeight * LocalWidth, 100)); - int marker_index = 0; bool result_flag = true; /* @@ -7274,16 +7258,15 @@ bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rec int spread_count = 0; if (seed_count > 0) { - CellNode * newnode = nodes; do { Cell seed = (*seed_cells)[spread_count]; if (seed.X >= bounds.X && seed.X < bounds.X + bounds.Width && seed.Y >= bounds.Y && seed.Y < bounds.Y + bounds.Height) { - newnode->Element = seed; - newnode->Score = Get_Angle_Score(origin, seed, angle); - marker_index++; - queue->Insert(*newnode++); + CellNode newnode; + newnode.Element = seed; + newnode.Score = Get_Angle_Score(origin, seed, angle); + queue.Insert(newnode); } } while (++spread_count < seed_count); } @@ -7301,12 +7284,12 @@ bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rec spread_count = (int)(1.0f / ((float)(1.0 / scale) * spread_scale) * 0.5f); spread_count += Pick_Random_UInt(0, spread_count / 2); - CellNode * node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); int spread_step = 0; if (spread_count > 0) { while (true) { - if (!result_flag || node == NULL) { + if (!result_flag || !node) { break; } @@ -7318,7 +7301,6 @@ bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rec } } - CellNode * newnode = &nodes[marker_index]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = FacingType(dir + FACING_90)) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { @@ -7330,11 +7312,11 @@ bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rec newcell.Y >= bounds.Y && newcell.Y < bounds.Y + bounds.Height && Map[newcell].Is_Tile_Clear()) { - newnode->Element = newcell; - newnode->Score = Get_Angle_Score(origin, newcell, angle); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Angle_Score(origin, newcell, angle); Set_Cell_Data_Spread(newcell, region_id); - marker_index++; - queue->Insert(*newnode++); + queue.Insert(newnode); continue; } other_region = MapRegionClass::Get_Cell_Data(newcell).RegionID; @@ -7353,7 +7335,7 @@ bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rec spread_step++; angle += Sample_Normal(0, M_PI_4); - node = queue->Extract_Min(); + node = queue.Extract_Min(); if (spread_step >= spread_count) { break; } @@ -7364,8 +7346,8 @@ bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rec * Optionally drain the remaining frontier and assign it to the region. */ if (claim_frontier) { - CellNode * remaining = queue->Extract_Min(); - while (remaining != NULL) { + std::optional remaining = queue.Extract_Min(); + while (remaining) { if (!result_flag) { break; } @@ -7385,12 +7367,10 @@ bool MapGeneratorClass::Grow_Water_Region(int region_id, float spread_scale, Rec } } - remaining = queue->Extract_Min(); + remaining = queue.Extract_Min(); } } - delete[] nodes; - delete queue; return(result_flag); } @@ -7581,22 +7561,21 @@ bool MapGeneratorClass::Init_Start_Points(void) while (true) { Cell seed = Scen->Get_Waypoint_Cell((WAYPOINT)player_index); - CellNode * nodes = new CellNode[800]; - PriorityQueueClass * queue = new PriorityQueueClass(800); + PriorityQueueClass queue(800); Clear_Cell_Data_Spreads(); - queue->Clear(); + queue.Clear(); int patch_id = player_index + 1; - int node_count = 1; int spread_count = 0; - nodes[0].Element = seed; - nodes[0].Score = 0.0f; + CellNode seed_node; + seed_node.Element = seed; + seed_node.Score = 0.0f; MapRegionClass::Get_Cell_Data(seed).SpreadID = patch_id; - queue->Insert(nodes[0]); + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); - if (node == NULL) { + std::optional node = queue.Extract_Min(); + if (!node) { break; } @@ -7607,25 +7586,24 @@ bool MapGeneratorClass::Init_Start_Points(void) MapRegionClass::Get_Cell_Data(node->Element).Inviolate = true; - CellNode * newnode = &nodes[node_count]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir++) { Cell ncell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(ncell) && MapRegionClass::Get_Cell_Data(ncell).SpreadID == 0) { if (Map[ncell].Is_Tile_Clear()) { - newnode->Element = ncell; + CellNode newnode; + newnode.Element = ncell; int dx = ncell.X - seed.X; int dy = ncell.Y - seed.Y; - newnode->Score = std::sqrt((double)(dx * dx + dy * dy)); + newnode.Score = std::sqrt((double)(dx * dx + dy * dy)); MapRegionClass::Get_Cell_Data(ncell).SpreadID = patch_id; - node_count++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } spread_count++; - node = queue->Extract_Min(); - if (node == NULL) { + node = queue.Extract_Min(); + if (!node) { break; } } @@ -7634,8 +7612,6 @@ bool MapGeneratorClass::Init_Start_Points(void) break; } - delete[] nodes; - delete queue; player_index = patch_id; if (patch_id >= SeedData.NumPlayers) { @@ -7768,9 +7744,21 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p "TIBTRE03" }; + /* + * A slot in the node pool below, ordered by the score that slot carries. The queue holds + * these rather than the nodes themselves so that the spread keeps reading its cell out + * of the pool, which is what the restart further down depends on. + */ + struct PoolSlot { + int Index; + float Score; + + bool operator<(PoolSlot const & other) const { return((double)Score < (double)other.Score); } + }; + int placed_count = 0; - CellNode *nodes = new CellNode[10 * count]; - PriorityQueueClass *queue = new PriorityQueueClass(10 * count); + std::vector nodes(10 * count); + PriorityQueueClass queue(10 * count); bool queue_was_reset_for_spread = false; Cell spread_origin = cell; @@ -7786,8 +7774,8 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p int wildlife_trigger = Pick_Random_UInt(0, count); HouseClass *neutral = House_From_HousesType(HouseTypeClass::From_Name("Neutral")); - queue->Clear(); - CellNode *node = NULL; + queue.Clear(); + int node_index = -1; int marker_index = 0; int visited = 1; @@ -7796,8 +7784,8 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p break; } - if (node == NULL) { - queue->Clear(); + if (node_index < 0) { + queue.Clear(); Map.Reset_Iterator(); CellClass *iter = Map.Iterate(); @@ -7811,27 +7799,28 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p nodes[0].Element = cell; nodes[0].Score = 0; Set_Cell_Data_Spread(cell, patch_id); - queue->Insert(nodes[0]); - node = queue->Extract_Min(); + queue.Insert(PoolSlot{0, nodes[0].Score}); + + std::optional taken = queue.Extract_Min(); + node_index = taken ? taken->Index : -1; queue_was_reset_for_spread = false; restart_count++; spread_origin = cell; } /* - * Every use below reads node->Element THROUGH the node pointer, and the loop - * re-reads it on every pass. This is load-bearing: the neighbor writes go - * to nodes[marker_index], and marker_index is reset to 0 in the block below - * while node still points at nodes[0], so the first neighbor stored OVERWRITES - * node->Element and the remaining directions are taken from the new cell. - * Caching the cell in a local would silently scan the true 8-neighborhood and - * generate different tiberium fields. + * Every use below reads the cell back out of its pool slot, and the loop re-reads + * it on every pass. This is load-bearing: the neighbor writes go to + * nodes[marker_index], and marker_index is reset to 0 in the block below while the + * slot in hand is still nodes[0], so the first neighbor stored OVERWRITES that cell + * and the remaining directions are taken from the new one. Holding the cell in a + * local would silently scan the true 8-neighborhood and generate different fields. */ - CellClass *cellptr = &Map[node->Element]; - if (MapRegionClass::CellData::Is_Not_Inviolate(node->Element)) { + CellClass *cellptr = &Map[nodes[node_index].Element]; + if (MapRegionClass::CellData::Is_Not_Inviolate(nodes[node_index].Element)) { if (!queue_was_reset_for_spread) { - spread_origin = node->Element; - queue->Clear(); + spread_origin = nodes[node_index].Element; + queue.Clear(); marker_index = 0; queue_was_reset_for_spread = true; } @@ -7853,7 +7842,7 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p } if (first_placed_cell == CELL_NONE) { - first_placed_cell = node->Element; + first_placed_cell = nodes[node_index].Element; } if (placed_count == wildlife_trigger && wildlife_count > 0 && spawn_wildlife) { @@ -7866,7 +7855,7 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p } if (foot != NULL) { - if (!foot->Unlimbo(node->Element.As_Coord(), DIR_N)) { + if (!foot->Unlimbo(nodes[node_index].Element.As_Coord(), DIR_N)) { delete foot; } } @@ -7880,7 +7869,7 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p CellNode *newnode = &nodes[marker_index]; for (int dir = FACING_FIRST; dir < FACING_COUNT; dir++) { - Cell newcell = Adjacent_Cell(node->Element, (FacingType)dir); + Cell newcell = Adjacent_Cell(nodes[node_index].Element, (FacingType)dir); if (Map.In_Local_Radar(newcell, true)) { CellClass *newcellptr = &Map[newcell]; if (newcellptr->Is_Tile_Clear()) { @@ -7890,13 +7879,15 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p newnode->Score = Get_Tiberium_Score(spread_origin, newcell); Set_Cell_Data_Spread(newcell, patch_id); marker_index++; - queue->Insert(*newnode++); + queue.Insert(PoolSlot{(int)(newnode - nodes.data()), newnode->Score}); + newnode++; } } } } - node = queue->Extract_Min(); + std::optional taken = queue.Extract_Min(); + node_index = taken ? taken->Index : -1; } if (place_tree) { @@ -7906,8 +7897,6 @@ void MapGeneratorClass::Create_Tiberium_Patch(Cell const &cell, int count, int p } } - delete[] nodes; - delete queue; } @@ -8397,21 +8386,20 @@ void MapGeneratorClass::Generate_Arctic_Vegetation(void) /// The chance, from 0 to 1, that a cell reached is given a tree. void MapGeneratorClass::Place_Forest(CellClass const * cellptr, int count, double density) { - CellNode * nodes = new CellNode[10 * count]; - PriorityQueueClass * queue = new PriorityQueueClass(10 * count); + PriorityQueueClass queue(10 * count); int i = 0; - queue->Clear(); + queue.Clear(); - int node_count = 1; Cell cell = cellptr->Fetch_CellID(); - nodes->Element = cell; - nodes->Score = 0; + CellNode seed_node; + seed_node.Element = cell; + seed_node.Score = 0; MapRegionClass::Get_Cell_Data(cellptr->Fetch_CellID()).Forested = true; - queue->Insert(*nodes); + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); int min_tree = 1; int max_tree = 25; @@ -8423,7 +8411,7 @@ void MapGeneratorClass::Place_Forest(CellClass const * cellptr, int count, doubl min_tree = 21; } - while (i < count && node != NULL) { + while (i < count && node) { CellClass * cptr = &Map[node->Element]; if (cptr->Is_Tile_Clear() && cptr->Cell_Occupier() == NULL && cptr->Overlay == OVERLAY_NONE && cptr->Land_Type() != LAND_ROCK) { if (Random_Fraction() < density) { @@ -8434,27 +8422,24 @@ void MapGeneratorClass::Place_Forest(CellClass const * cellptr, int count, doubl } } - CellNode * newnode = &nodes[node_count]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir++) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { MapRegionClass::CellData & data = MapRegionClass::Get_Cell_Data(newcell); if (!data.Forested && !data.Inviolate) { - newnode->Element = newcell; - newnode->Score = Get_Forest_Score(cellptr->Fetch_CellID(), newcell); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Forest_Score(cellptr->Fetch_CellID(), newcell); data.Forested = true; - node_count++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } i++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } - delete[] nodes; - delete queue; } @@ -8491,48 +8476,44 @@ double MapGeneratorClass::Get_Forest_Score(const Cell & cell1, const Cell & cell /// May the patch spread over pavement as well as open ground? void MapGeneratorClass::Place_Tile_Patch(CellClass * cellptr, IsometricTileType ittype, int count, int origin_id, bool on_pavement) { - CellNode * nodes = new CellNode[10 * count]; - PriorityQueueClass * queue = new PriorityQueueClass(10 * count); + PriorityQueueClass queue(10 * count); int i = 0; - queue->Clear(); + queue.Clear(); - int node_count = 1; Cell cell = cellptr->Fetch_CellID(); - nodes->Element = cell; - nodes->Score = 0; + CellNode seed_node; + seed_node.Element = cell; + seed_node.Score = 0; MapRegionClass::Get_Cell_Data(cellptr->Fetch_CellID()).SpreadID = origin_id; - queue->Insert(*nodes); + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); - while (i < count && node != NULL) { + std::optional node = queue.Extract_Min(); + while (i < count && node) { CellClass * cptr = &Map[node->Element]; cptr->ITType = ittype; - CellNode * newnode = &nodes[node_count]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir++) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { CellClass * newcellptr = &Map[newcell]; if (newcellptr->Is_Tile_Clear() || on_pavement && (newcellptr->Is_Tile_Pavement() || newcellptr->Is_Tile_Misc_Pavement())) { if (MapRegionClass::Get_Cell_Data(newcell).SpreadID != origin_id && newcellptr->Ramp == RAMP_NONE && newcellptr->Overlay == OVERLAY_NONE && newcellptr->Cell_Occupier() == NULL) { - newnode->Element = newcell; - newnode->Score = Get_Tile_Patch_Score(cellptr->Fetch_CellID(), newcell); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Tile_Patch_Score(cellptr->Fetch_CellID(), newcell); MapRegionClass::Get_Cell_Data(newcell).SpreadID = origin_id; - node_count++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } } i++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } - delete[] nodes; - delete queue; } @@ -8588,24 +8569,23 @@ void MapGeneratorClass::Generate_Mold(void) int spread_count = Pick_Random_UInt(30, 150); int heap_size = std::max(100, 6 * spread_count); - CellNode * nodes = new CellNode[heap_size]; - PriorityQueueClass * queue = new PriorityQueueClass(heap_size); - queue->Clear(); + PriorityQueueClass queue(heap_size); + queue.Clear(); int marker_index = 1; - nodes[0].Element = mold_origin; - nodes[0].Score = 0; - queue->Insert(nodes[0]); + CellNode seed_node; + seed_node.Element = mold_origin; + seed_node.Score = 0; + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); int spread_step = 0; - while (spread_step < spread_count && node != NULL) { + while (spread_step < spread_count && node) { CellClass * cptr = &Map[node->Element]; cptr->ITType = IsometricTileTypeClass::BlueMoldTile; cptr->SubTile = 0; cells.Add(cptr->CellID); - CellNode * newnode = &nodes[marker_index]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = FacingType(dir + FACING_90)) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { @@ -8622,17 +8602,18 @@ void MapGeneratorClass::Generate_Mold(void) } if (can_spread && !data.Inviolate && marker_index < heap_size) { - newnode->Element = newcell; - newnode->Score = Get_Spread_Score(mold_origin, newcell, spread_step); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Spread_Score(mold_origin, newcell, spread_step); data.SpreadID = WorkingRegionID; marker_index++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } spread_step++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } int i; @@ -8673,8 +8654,6 @@ void MapGeneratorClass::Generate_Mold(void) DebugString("Mold heap size: %d -- Marker Index: %d\n", heap_size, marker_index); - delete queue; - delete[] nodes; } @@ -8743,27 +8722,28 @@ void MapGeneratorClass::Generate_Crystals(const Cell & cell) heap_size = 100; } - CellNode *nodes = new CellNode[heap_size]; - PriorityQueueClass *queue = new PriorityQueueClass(heap_size); - queue->Clear(); + PriorityQueueClass queue(heap_size); + queue.Clear(); int marker_index = 0; if (on_cliff) { - nodes[0].Element = cliff_seed; - nodes[0].Score = 0; + CellNode seed_node; + seed_node.Element = cliff_seed; + seed_node.Score = 0; marker_index = 1; - queue->Insert(nodes[0]); + queue.Insert(seed_node); } - CellNode *seed = &nodes[marker_index++]; - seed->Element = crystal_origin; - seed->Score = 0; - queue->Insert(*seed); + CellNode seed; + seed.Element = crystal_origin; + seed.Score = 0; + marker_index++; + queue.Insert(seed); - CellNode *node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); int spread_step = 0; while (spread_step < spread_count) { - if (node == NULL) { + if (!node) { break; } CellClass *cptr = &Map[node->Element]; @@ -8771,23 +8751,23 @@ void MapGeneratorClass::Generate_Crystals(const Cell & cell) cptr->SubTile = 0; cells.Add(cptr->CellID); - CellNode *newnode = &nodes[marker_index]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir = FacingType(dir + FACING_90)) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { MapRegionClass::CellData & data = MapRegionClass::Get_Cell_Data(newcell); if (Map[newcell].Is_Tile_Clear() && Map[newcell].Overlay == OVERLAY_NONE && marker_index < heap_size) { - newnode->Element = newcell; - newnode->Score = Get_Spread_Score(crystal_origin, newcell, spread_step); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Spread_Score(crystal_origin, newcell, spread_step); data.SpreadID = WorkingRegionID; marker_index++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } spread_step++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } int i; @@ -8823,8 +8803,6 @@ void MapGeneratorClass::Generate_Crystals(const Cell & cell) DebugString("Mold heap size: %d -- Marker Index: %d\n", heap_size, marker_index); - delete queue; - delete[] nodes; } double ConditionRedChances[BIOME_COUNT] = {0.5, 0.2, 0.2, 0.6, 0.8}; @@ -8899,9 +8877,8 @@ void MapGeneratorClass::Generate_Urban_Areas(void) /// The collected urban area cells, or NULL if the area is too small or sparse. DynamicVectorClass * MapGeneratorClass::Create_Urban_Area(Cell const & cell, int size) { - CellNode * nodes = new CellNode[10 * size]; - PriorityQueueClass * queue = new PriorityQueueClass(10 * size); - queue->Clear(); + PriorityQueueClass queue(10 * size); + queue.Clear(); int processed = 0; @@ -8913,22 +8890,21 @@ DynamicVectorClass * MapGeneratorClass::Create_Urban_Area(Cell const & cel cptr = Map.Iterate(); } - nodes[0].Element = (Cell &)cell; - nodes[0].Score = 0; + CellNode seed_node; + seed_node.Element = (Cell &)cell; + seed_node.Score = 0; Set_Cell_Data_Spread(cell, 1); - int node_count = 1; - queue->Insert(nodes[0]); + queue.Insert(seed_node); - CellNode * node = queue->Extract_Min(); + std::optional node = queue.Extract_Min(); DynamicVectorClass * cells = new DynamicVectorClass; cells->Set_Growth_Step(size + 2); - while (size > processed && node != NULL) { + while (size > processed && node) { Map[node->Element].ITType = IsometricTileTypeClass::PaveTile; cells->Add(node->Element); - CellNode * newnode = &nodes[node_count]; for (FacingType dir = FACING_FIRST; dir < FACING_COUNT; dir++) { Cell newcell = Adjacent_Cell(node->Element, dir); if (My_In_Radar(newcell)) { @@ -8936,22 +8912,20 @@ DynamicVectorClass * MapGeneratorClass::Create_Urban_Area(Cell const & cel if (newcellptr->Is_Tile_Clear() && newcellptr->Overlay == OVERLAY_NONE) { MapRegionClass::CellData & data = MapRegionClass::Get_Cell_Data(newcell); if (data.SpreadID != 1 && !data.Inviolate) { - newnode->Element = newcell; - newnode->Score = Get_Urban_Score(cell, newcell); + CellNode newnode; + newnode.Element = newcell; + newnode.Score = Get_Urban_Score(cell, newcell); Set_Cell_Data_Spread(newcell, 1); - node_count++; - queue->Insert(*newnode++); + queue.Insert(newnode); } } } } processed++; - node = queue->Extract_Min(); + node = queue.Extract_Min(); } - delete[] nodes; - delete queue; Rect bounds = Get_Cell_Bounding_Rect(*cells); diff --git a/code/mapgen.h b/code/mapgen.h index e72914c36..ff84f9624 100644 --- a/code/mapgen.h +++ b/code/mapgen.h @@ -15,8 +15,6 @@ class CellClass; class MapPreviewClass; -struct CellNode; -template class PriorityQueueClass; #define RANDOM_MAP_FILE_NAME "RandMap.Sed" @@ -537,7 +535,7 @@ class MapGeneratorClass bool Grow_Water_Region(int region_id, float spread_scale, Rect const & bounds, Cell const & origin, bool claim_frontier); bool Place_Waterfall(int region_id, Cell const & cell1, Cell const & cell2, int direction, bool & placed, double & head_x, double & head_y); int Get_Target_Water_Amount(void); - void Generate_Swamp(DynamicVectorClass &cells, int last, CellNode *nodes, PriorityQueueClass *queue); + void Generate_Swamp(DynamicVectorClass & cells, int last); void Seed_Ice(DynamicVectorClass &cells, IsometricTileType last); void Smooth_Ice(void); diff --git a/code/nodes.h b/code/nodes.h index 8ad8e9015..7f0ac490e 100644 --- a/code/nodes.h +++ b/code/nodes.h @@ -31,17 +31,33 @@ struct CellNode { Element = cell; } + CellNode(Cell const & cell, float score) : Element(cell), Score(score) {} + bool operator==(const CellNode & other) const { return((double)Score == (double)other.Score); } bool operator!=(const CellNode & other) const { return((double)Score != (double)other.Score); } bool operator<(const CellNode & other) const { return((double)Score < (double)other.Score); } bool operator>(const CellNode & other) const { return((double)Score > (double)other.Score); } bool operator<=(const CellNode & other) const { return((double)Score <= (double)other.Score); } bool operator>=(const CellNode & other) const { return((double)Score >= (double)other.Score); } + + // Carries the node to or from a save game. + template + void Serialize(S & stream) + { + stream.Serialize(Element); + stream.Serialize(Score); + } }; struct AStarHierarchicalNode { + /* + * This is the node's own slot in the pool the search hands nodes out from, and it is + * what the nodes reached through this one record as their ParentIndex. + */ + int PoolIndex; + /* * This is the index within the node pool of the node that this one was reached from, * or -1 for the node the search started at. When the destination is reached, the diff --git a/code/priority.h b/code/priority.h index 0873ee6fc..8308b7e0e 100644 --- a/code/priority.h +++ b/code/priority.h @@ -9,283 +9,182 @@ #pragma once -#include "dbgprint.h" - -#include -#include -#include - - -#define PARENT(index) (index >> 1) -#define LEFT_CHILD(index) ((index << 1)) -#define RIGHT_CHILD(index) ((index << 1) + 1) -#define SWAP(left, right) \ -{ \ - T * temp = Heap[left]; \ - Heap[left] = Heap[right]; \ - Heap[right] = temp; \ -} +#include +#include +#include +#include +/* + * A binary min heap ordered by the Score each element carries. + * + * The sift is spelled out here because the order equal scores come out in is part of the + * simulation, and the standard heap algorithms may settle such a tie either way. + * tests/priorityqueue holds that order. + */ template class PriorityQueueClass { - friend class AStarClass; public: - PriorityQueueClass(int size); - ~PriorityQueueClass(void); + // The size is how much room to take up front, not a limit on what the queue holds. + explicit PriorityQueueClass(int size = 0); - void Clear(void); + void Clear(void) { Heap.clear(); } + void Reserve(int size) { if (size > 0) { Heap.reserve((std::size_t)size); } } + int Count(void) const { return((int)Heap.size()); } - bool Insert(T & node); - T * Extract_Min(void); - T * Replace_Root(T & node); + void Insert(T node); + std::optional Extract_Min(void); + T Replace_Root(T node); bool Remove_Matching(T const & item); - void Heapify(int index); - - int Count(void) const { return(ActiveCount); } /* - * Carries the queue to or from a save game. The heap holds pointers into the - * caller's node array, so each slot travels as its index into that array and the - * array has to be handed back in on the way in. + * Carries the queue to or from a save game as its length followed by its elements, + * so the element type has to describe its own members to the stream. The elements + * travel in heap order rather than score order, because draining the queue to sort + * them would build a different heap and reorder the ties within it. */ template - void Serialize(S & stream, T * nodes); + void Serialize(S & stream) { stream.Serialize(Heap); } private: - /* - * This is the number of nodes currently in the queue, which is also the index of the - * last of them. - */ - int ActiveCount; + void Heapify(std::size_t index); - /* - * This is the number of slots the queue was created with. An insert that would run - * past the last slot is refused, since the queue never grows. - */ - int Size; + static std::size_t Parent(std::size_t index) { return((index - 1) / 2); } + static std::size_t Left_Child(std::size_t index) { return((2 * index) + 1); } + static std::size_t Right_Child(std::size_t index) { return((2 * index) + 2); } /* - * This points to the array of node pointers that makes up the heap. Slot zero is not - * used -- the lowest scoring node sits at slot one, and the children of any node lie - * at twice its index. + * The lowest scoring element sits at slot zero, and the children of the element at + * any slot lie at twice its index plus one and plus two. */ - T ** Heap; - - /// Unused - uintptr_t MaxNodePointer; - uintptr_t MinNodePointer; + std::vector Heap; }; template PriorityQueueClass::PriorityQueueClass(int size) { - MaxNodePointer = 0; - MinNodePointer = UINTPTR_MAX; - ActiveCount = 0; - Size = size; - Heap = new T * [size + 1](); - for (int index = 0; index <= Size; index++) { - Heap[index] = NULL; - } -} - - -template -PriorityQueueClass::~PriorityQueueClass(void) -{ - delete[] Heap; -} - - -template -void PriorityQueueClass::Clear(void) -{ - for (int index = 0; index <= ActiveCount; index++) { - Heap[index] = NULL; - } - ActiveCount = 0; + Reserve(size); } template -inline bool PriorityQueueClass::Insert(T & node) +inline void PriorityQueueClass::Insert(T node) { - unsigned index = ActiveCount + 1; - unsigned parent_index = PARENT(index); float score = node.Score; + std::size_t index = Heap.size(); - if (index >= (unsigned)Size) { - return(false); - } + Heap.emplace_back(); - while (index > 1) { - if (Heap[parent_index]->Score <= score) { + while (index > 0) { + std::size_t parent_index = Parent(index); + if (Heap[parent_index].Score <= score) { break; } Heap[index] = Heap[parent_index]; index = parent_index; - parent_index = PARENT(index); } - Heap[index] = &node; - ActiveCount++; - - if ((uintptr_t)&node > MaxNodePointer) { - MaxNodePointer = (uintptr_t)&node; - } - - if ((uintptr_t)&node < MinNodePointer) { - MinNodePointer = (uintptr_t)&node; - } - - return(true); + Heap[index] = node; } -template -inline T * PriorityQueueClass ::Extract_Min(void) +template +inline std::optional PriorityQueueClass::Extract_Min(void) { - if (ActiveCount == 0) { - return(NULL); + if (Heap.empty()) { + return(std::nullopt); } - T * min = Heap[1]; - Heap[1] = Heap[ActiveCount]; - Heap[ActiveCount] = NULL; - ActiveCount--; + T min = Heap.front(); - Heapify(1); + Heap.front() = Heap.back(); + Heap.pop_back(); + + Heapify(0); return(min); } -template -inline T * PriorityQueueClass::Replace_Root(T & node) +template +inline T PriorityQueueClass::Replace_Root(T node) { - if (ActiveCount == 0) { - return(&node); + if (Heap.empty()) { + return(node); } - T * old_root = Heap[1]; - - if (node < *old_root) { - return(&node); + if (node < Heap.front()) { + return(node); } - Heap[1] = &node; + T old_root = Heap.front(); + Heap.front() = node; - Heapify(1); + Heapify(0); return(old_root); } -template -inline bool PriorityQueueClass ::Remove_Matching(T const & item) +template +inline bool PriorityQueueClass::Remove_Matching(T const & item) { - for (int index = 1; index <= ActiveCount; index++) { - if (Heap[index]->Element == item.Element) { - if (index == ActiveCount) { - ActiveCount--; - } else { - T * last = Heap[ActiveCount]; - float last_score = last->Score; - int parent = PARENT(index); - ActiveCount--; - if (index != 1 && Heap[parent]->Score >= last_score) { - while ((unsigned)index > 1) { - if (Heap[parent]->Score <= last_score) break; - Heap[index] = Heap[parent]; - index = parent; - parent = PARENT(index); - } - Heap[index] = last; - } else { - Heap[index] = last; - Heapify(index); - } - } + for (std::size_t index = 0; index < Heap.size(); index++) { + if (!(Heap[index].Element == item.Element)) { + continue; + } - DebugString("Exiting Remove_Matching\n"); + if (index + 1 == Heap.size()) { + Heap.pop_back(); return(true); } - } - - DebugString("Exiting Remove_Matching\n"); - return(false); -} - - -template -inline void PriorityQueueClass::Heapify(int index) -{ - int smallest = index; - - int left = LEFT_CHILD(index); - int right = RIGHT_CHILD(index); - - smallest = left <= Count() && *Heap[left] < *Heap[index] ? left : index; - smallest = right <= Count() && *Heap[right] < *Heap[smallest] ? right : smallest; - - while (smallest != index) { - - SWAP(index, smallest) - index = smallest; - - left = LEFT_CHILD(index); - right = RIGHT_CHILD(index); + T last = Heap.back(); + float last_score = last.Score; + Heap.pop_back(); + + // A parent scoring exactly what the replacement scores leaves it where it stands, + // never sifted back down. That is what the extraction order was built on. + if (index != 0 && Heap[Parent(index)].Score >= last_score) { + std::size_t hole = index; + while (hole > 0) { + std::size_t parent = Parent(hole); + if (Heap[parent].Score <= last_score) { + break; + } + Heap[hole] = Heap[parent]; + hole = parent; + } + Heap[hole] = last; + } else { + Heap[index] = last; + Heapify(index); + } - smallest = left <= Count() && *Heap[left] < *Heap[index] ? left : index; - smallest = right <= Count() && *Heap[right] < *Heap[smallest] ? right : smallest; + return(true); } + + return(false); } template -template -void PriorityQueueClass::Serialize(S & stream, T * nodes) +inline void PriorityQueueClass::Heapify(std::size_t index) { - int count = ActiveCount; - int size = Size; - - stream.Serialize(count); - stream.Serialize(size); - - /* - * The heap was sized when the queue was built and never grows, so a save describing - * a different one, or more nodes than fit, cannot be read into this queue. - */ - if (stream.Is_Loading()) { - if (size != Size || count < 0 || count > Size) { - stream.Fail(); - return; - } - ActiveCount = count; - } - - for (int slot = 0; slot < Size; slot++) { - int index = stream.Is_Saving() ? (int)(Heap[slot] - nodes) : 0; - stream.Serialize(index); + for (;;) { + std::size_t left = Left_Child(index); + std::size_t right = Right_Child(index); - if (stream.Is_Loading()) { - Heap[slot] = &nodes[index]; + std::size_t smallest = left < Heap.size() && Heap[left] < Heap[index] ? left : index; + smallest = right < Heap.size() && Heap[right] < Heap[smallest] ? right : smallest; - if ((uintptr_t)Heap[slot] > MaxNodePointer) { - MaxNodePointer = (uintptr_t)Heap[slot]; - } - if ((uintptr_t)Heap[slot] < MinNodePointer) { - MinNodePointer = (uintptr_t)Heap[slot]; - } + if (smallest == index) { + break; } + + std::swap(Heap[index], Heap[smallest]); + index = smallest; } } - -#undef PARENT -#undef LEFT_CHILD -#undef RIGHT_CHILD -#undef SWAP diff --git a/code/tiberium.cpp b/code/tiberium.cpp index 6bfb58dae..a1b7fbde4 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -29,6 +29,7 @@ #include "tracker.h" #include +#include #define MAX_SPREAD_DELAY 50 #define MAX_GROWTH_DELAY 50 @@ -61,12 +62,10 @@ TiberiumClass::TiberiumClass(char const * ininame) : SpreadCount(0), SpreadQueue(NULL), SpreadState(NULL), - SpreadNodes(NULL), SpreadTimer(), GrowthCount(0), GrowthQueue(NULL), GrowthState(NULL), - GrowthNodes(NULL), GrowthTimer() { HeapID = (TiberiumType)Tiberiums.Count(); @@ -247,15 +246,13 @@ void TiberiumClass::Serialize(SaveStreamClass & stream) stream.Serialize(Variety); stream.Serialize(RampVariety); stream.Serialize(SpreadCount); - // SpreadQueue -- pools sized to the map rather than saved state; Load drops them and the + // SpreadQueue -- sized to the map rather than to saved state; Load drops these and the // tiberium systems build them again from the map itself. // SpreadState - // SpreadNodes stream.Serialize(SpreadTimer); stream.Serialize(GrowthCount); - // GrowthQueue -- the growth pools, dropped and rebuilt the same way. + // GrowthQueue -- the growth records, dropped and rebuilt the same way. // GrowthState - // GrowthNodes stream.Serialize(GrowthTimer); } @@ -332,27 +329,27 @@ void TiberiumClass::Deinit_Tiberium_Spread_System(void) /// void TiberiumClass::Spread_AI(void) { - if (SpreadQueue && SpreadQueue->Count() && SpreadPercentage > 0.00001) { + if (SpreadQueue.Count() && SpreadPercentage > 0.00001) { /* * The amount we spread depends on how many spreads are enqueued. * Randomize it so that it feels more natural. */ - int count = std::min(25, std::max(5, (int)(SpreadQueue->Count() * SpreadPercentage))); + int count = std::min(25, std::max(5, (int)(SpreadQueue.Count() * SpreadPercentage))); count = (abs(Scen->RandomNumber()) % count) + 1; /* * SpreadQueue does not recycle its entries. * When space runs low, we need to clear and recalculate it. */ - if (SpreadQueue->Count() > Map_Cell_Count() - 20) { + if (SpreadQueue.Count() > Map_Cell_Count() - 20) { Recalc_Spread(); } int index = 0; - CellNode * node = SpreadQueue->Extract_Min(); + std::optional node = SpreadQueue.Extract_Min(); - while (index < count && node != NULL) { + while (index < count && node) { CellClass * cellptr = &Map[node->Element]; int possible_spreads = 0; @@ -374,9 +371,8 @@ void TiberiumClass::Spread_AI(void) * If there's more than one possibility, then re-enqueue this cell to spread again later. */ if (possible_spreads > 1) { - SpreadNodes[SpreadCount].Element = cellptr->CellID; - SpreadNodes[SpreadCount].Score = 0; - SpreadQueue->Insert(SpreadNodes[SpreadCount++]); + SpreadQueue.Insert(CellNode(cellptr->CellID, 0.0f)); + SpreadCount++; SpreadState[Map_Cell_Index(cellptr->CellID)] = true; } } else { @@ -384,7 +380,7 @@ void TiberiumClass::Spread_AI(void) } if (index < count) { - node = SpreadQueue->Extract_Min(); + node = SpreadQueue.Extract_Min(); } } } @@ -400,9 +396,8 @@ void TiberiumClass::Init_Spread(void) { Clear_Spread(); - SpreadNodes = new CellNode[Map_Cell_Count()]; SpreadState = new bool [Map_Cell_Count()]; - SpreadQueue = new PriorityQueueClass(Map_Cell_Count()); + SpreadQueue.Reserve(Map_Cell_Count()); Recalc_Spread(); } @@ -416,7 +411,7 @@ void TiberiumClass::Init_Spread(void) void TiberiumClass::Recalc_Spread(void) { SpreadCount = 0; - SpreadQueue->Clear(); + SpreadQueue.Clear(); for (int i = Map_Cell_Count() - 1; i >= 0; i--) { SpreadState[i] = false; @@ -427,9 +422,8 @@ void TiberiumClass::Recalc_Spread(void) while (iter) { if (iter->Tiberium_Type_Here() == HeapID && iter->Can_Tiberium_Spread()) { - SpreadNodes[SpreadCount].Element = iter->CellID; - SpreadNodes[SpreadCount].Score = 0.0; - SpreadQueue->Insert(SpreadNodes[SpreadCount++]); + SpreadQueue.Insert(CellNode(iter->CellID, 0.0f)); + SpreadCount++; SpreadState[Map_Cell_Index(iter->CellID)] = true; } @@ -445,16 +439,7 @@ void TiberiumClass::Recalc_Spread(void) /// void TiberiumClass::Clear_Spread(void) { - if (SpreadQueue) { - SpreadQueue->Clear(); - delete SpreadQueue; - SpreadQueue = NULL; - } - - if (SpreadNodes) { - delete [] SpreadNodes; - SpreadNodes = NULL; - } + SpreadQueue.Clear(); if (SpreadState) { delete [] SpreadState; @@ -497,9 +482,8 @@ void TiberiumClass::Queue_Spread(Cell const & cell) Recalc_Spread(); } - SpreadNodes[SpreadCount].Element = cell; - SpreadNodes[SpreadCount].Score = float(Frame + abs(Scen->RandomNumber()) % MAX_SPREAD_DELAY); - SpreadQueue->Insert(SpreadNodes[SpreadCount++]); + SpreadQueue.Insert(CellNode(cell, float(Frame + abs(Scen->RandomNumber()) % MAX_SPREAD_DELAY))); + SpreadCount++; SpreadState[Map_Cell_Index(cell)] = true; } } @@ -558,27 +542,27 @@ void TiberiumClass::Deinit_Tiberium_Growth_System(void) /// void TiberiumClass::Growth_AI(void) { - if (GrowthQueue && GrowthQueue->Count() && GrowthPercentage > 0.00001) { + if (GrowthQueue.Count() && GrowthPercentage > 0.00001) { /* * The amount we grow depends on how many growths are enqueued. * Randomize it so that it feels more natural. */ - int count = std::min(50, std::max(5, (int)(GrowthQueue->Count() * GrowthPercentage))); + int count = std::min(50, std::max(5, (int)(GrowthQueue.Count() * GrowthPercentage))); count = (abs(Scen->RandomNumber()) % count) + 1; /* * GrowthQueue does not recycle its entries. * When space runs low, we need to clear and recalculate it. */ - if (GrowthQueue->Count() > Map_Cell_Count() - 2 * count) { + if (GrowthQueue.Count() > Map_Cell_Count() - 2 * count) { Recalc_Growth(); } int index = 0; - CellNode * node = GrowthQueue->Extract_Min(); + std::optional node = GrowthQueue.Extract_Min(); - while (index < count && node != NULL) { + while (index < count && node) { CellClass * cellptr = &Map[node->Element]; if (cellptr->Tiberium_Type_Here() == HeapID) { @@ -589,10 +573,10 @@ void TiberiumClass::Growth_AI(void) * Also, take this opportunity to queue this cell to spread, if possible. */ if (cellptr->OverlayData < MAX_GROWTH_STAGE) { - GrowthNodes[GrowthCount].Element = node->Element; - GrowthNodes[GrowthCount].Score = float(Frame + abs(Scen->RandomNumber() % MAX_GROWTH_DELAY)); + float score = float(Frame + abs(Scen->RandomNumber() % MAX_GROWTH_DELAY)); GrowthState[Map_Cell_Index(node->Element)] = true; - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + GrowthQueue.Insert(CellNode(node->Element, score)); + GrowthCount++; Queue_Spread(node->Element); } else { GrowthState[Map_Cell_Index(node->Element)] = false; @@ -601,7 +585,7 @@ void TiberiumClass::Growth_AI(void) index++; if (index < count) { - node = GrowthQueue->Extract_Min(); + node = GrowthQueue.Extract_Min(); } } } @@ -617,9 +601,8 @@ void TiberiumClass::Init_Growth(void) { Clear_Growth(); - GrowthNodes = new CellNode[Map_Cell_Count()]; GrowthState = new bool [Map_Cell_Count()]; - GrowthQueue = new PriorityQueueClass(Map_Cell_Count()); + GrowthQueue.Reserve(Map_Cell_Count()); Recalc_Growth(); } @@ -633,7 +616,7 @@ void TiberiumClass::Init_Growth(void) void TiberiumClass::Recalc_Growth(void) { GrowthCount = 0; - GrowthQueue->Clear(); + GrowthQueue.Clear(); for (int i = Map_Cell_Count() - 1; i >= 0; i--) { GrowthState[i] = false; @@ -643,9 +626,8 @@ void TiberiumClass::Recalc_Growth(void) CellClass * iter = Map.Iterate(); while (iter) { if (iter->Tiberium_Type_Here() == HeapID && iter->Can_Tiberium_Grow()) { - GrowthNodes[GrowthCount].Element = iter->CellID; - GrowthNodes[GrowthCount].Score = 0.0; - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + GrowthQueue.Insert(CellNode(iter->CellID, 0.0f)); + GrowthCount++; GrowthState[Map_Cell_Index(iter->CellID)] = true; } iter = Map.Iterate(); @@ -660,16 +642,7 @@ void TiberiumClass::Recalc_Growth(void) /// void TiberiumClass::Clear_Growth(void) { - if (GrowthQueue) { - GrowthQueue->Clear(); - delete GrowthQueue; - GrowthQueue = NULL; - } - - if (GrowthNodes) { - delete [] GrowthNodes; - GrowthNodes = NULL; - } + GrowthQueue.Clear(); if (GrowthState) { delete [] GrowthState; @@ -698,9 +671,8 @@ void TiberiumClass::Queue_Growth(Cell const & cell) Recalc_Growth(); } - GrowthNodes[GrowthCount].Element = cell; - GrowthNodes[GrowthCount].Score = float(Frame + abs(Scen->RandomNumber()) % MAX_GROWTH_DELAY); - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + GrowthQueue.Insert(CellNode(cell, float(Frame + abs(Scen->RandomNumber()) % MAX_GROWTH_DELAY))); + GrowthCount++; GrowthState[cellindex] = true; } } diff --git a/code/tiberium.h b/code/tiberium.h index 650aa07ee..f9e3c187c 100644 --- a/code/tiberium.h +++ b/code/tiberium.h @@ -156,8 +156,9 @@ class TiberiumClass : public AbstractTypeClass int RampVariety; /* - * This is the number of records handed out of the SpreadNodes pool so far. The pool is - * never recycled, so the queue is rebuilt from the map once the pool runs low. + * This is the number of cells enqueued to seed since the queue was last rebuilt. + * Nothing takes a stale or duplicate entry back out, so the queue is rebuilt from the + * map once this approaches the map's cell count. */ int SpreadCount; @@ -165,7 +166,7 @@ class TiberiumClass : public AbstractTypeClass * This is the queue of cells waiting to seed their neighbors, ordered by the game * frame at which each becomes due. */ - PriorityQueueClass * SpreadQueue; + PriorityQueueClass SpreadQueue; /* * This is one flag per map cell, true while the cell is sitting in the SpreadQueue. It @@ -173,20 +174,15 @@ class TiberiumClass : public AbstractTypeClass */ bool * SpreadState; - /* - * This is the pool of queue records the SpreadQueue is built out of -- one per map - * cell, handed out in order by the SpreadCount cursor. - */ - CellNode * SpreadNodes; - /* * This counts down the frames remaining until this tiberium's next spread pass. */ CDTimerClass SpreadTimer; /* - * This is the number of records handed out of the GrowthNodes pool so far. The pool is - * never recycled, so the queue is rebuilt from the map once the pool runs low. + * This is the number of cells enqueued to ripen since the queue was last rebuilt. + * Nothing takes a stale or duplicate entry back out, so the queue is rebuilt from the + * map once this approaches the map's cell count. */ int GrowthCount; @@ -194,7 +190,7 @@ class TiberiumClass : public AbstractTypeClass * This is the queue of cells waiting to ripen, ordered by the game frame at which each * becomes due. */ - PriorityQueueClass * GrowthQueue; + PriorityQueueClass GrowthQueue; /* * This is one flag per map cell, true while the cell is sitting in the GrowthQueue. It @@ -202,12 +198,6 @@ class TiberiumClass : public AbstractTypeClass */ bool * GrowthState; - /* - * This is the pool of queue records the GrowthQueue is built out of -- one per map - * cell, handed out in order by the GrowthCount cursor. - */ - CellNode * GrowthNodes; - /* * This counts down the frames remaining until this tiberium's next growth pass. */ diff --git a/code/vein.cpp b/code/vein.cpp index 26ae0a4f8..89de17ba7 100644 --- a/code/vein.cpp +++ b/code/vein.cpp @@ -50,6 +50,7 @@ #include "ramp.hh" #include +#include DynamicVectorClass VeinholeMonsterClass::VeinholeMonsters; @@ -65,8 +66,6 @@ static bool * GlobalGrowthState = NULL; VeinholeMonsterClass::VeinholeMonsterClass(void) : BASECLASS(), GrowthCount(0), - GrowthQueue(NULL), - GrowthNodes(NULL), GrowthTimer(0), GrowthState(NULL), CurrentState(IDLE), @@ -80,9 +79,8 @@ VeinholeMonsterClass::VeinholeMonsterClass(void) : VeinCount(0) { VeinholeMonsters.Add(this); - GrowthNodes = new CellNode[Rule->MaxVeinholeGrowth]; - GrowthQueue = new PriorityQueueClass(Rule->MaxVeinholeGrowth); - GrowthQueue->Clear(); + GrowthQueue.Clear(); + GrowthQueue.Reserve(Rule->MaxVeinholeGrowth); if (GlobalGrowthState == NULL) { GlobalGrowthState = new bool [Map_Cell_Count()]; } @@ -100,8 +98,6 @@ VeinholeMonsterClass::VeinholeMonsterClass(void) : VeinholeMonsterClass::VeinholeMonsterClass(Cell const & cell) : BASECLASS(), GrowthCount(0), - GrowthQueue(NULL), - GrowthNodes(NULL), GrowthTimer(Rule->VeinholeGrowthRate), GrowthState(NULL), CurrentState(IDLE), @@ -145,9 +141,8 @@ VeinholeMonsterClass::VeinholeMonsterClass(Cell const & cell) : Control.Set_Step(0); Control.Set_Stage(-1); - GrowthNodes = new CellNode[Rule->MaxVeinholeGrowth]; - GrowthQueue = new PriorityQueueClass(Rule->MaxVeinholeGrowth); - GrowthQueue->Clear(); + GrowthQueue.Clear(); + GrowthQueue.Reserve(Rule->MaxVeinholeGrowth); if (GlobalGrowthState == NULL) { GlobalGrowthState = new bool[Map_Cell_Count()]; @@ -526,16 +521,14 @@ void VeinholeMonsterClass::Grow(void) { static const int _mod = 5; - if (GrowthQueue && GrowthCount <= Rule->MaxVeinholeGrowth - 40 && VeinCount <= Rule->MaxVeinholeGrowth - 100 && Scen->IsVeinGrowth) { - - double amount = GrowthQueue->Count(); + if (GrowthCount <= Rule->MaxVeinholeGrowth - 40 && VeinCount <= Rule->MaxVeinholeGrowth - 100 && Scen->IsVeinGrowth) { int index = 0; int count = (abs(Scen->RandomNumber) % _mod) + 1; - CellNode * node = GrowthQueue->Extract_Min(); + std::optional node = GrowthQueue.Extract_Min(); - while (index < count && node != NULL) { + while (index < count && node) { CellClass & cell = Map[node->Element]; if (cell.OverlayData < OVERLAYDATA_FIRST_SOLID_VEIN) { @@ -560,10 +553,10 @@ void VeinholeMonsterClass::Grow(void) int cindex = Map_Cell_Index(adjacent); if (cindex >= 0 && cindex < Map_Cell_Count()) { if (adj_cell.Can_Place_Veins() && !GlobalGrowthState[cindex] && GrowthCount < Rule->MaxVeinholeGrowth) { - GrowthNodes[GrowthCount].Element = adjacent; - GrowthNodes[GrowthCount].Score = float(Frame / 50 + abs(Scen->RandomNumber() % 50) + 1); + float score = float(Frame / 50 + abs(Scen->RandomNumber() % 50) + 1); GlobalGrowthState[cindex] = true; - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + GrowthQueue.Insert(CellNode(adjacent, score)); + GrowthCount++; } GrowthState[cindex] = true; } @@ -574,7 +567,7 @@ void VeinholeMonsterClass::Grow(void) index++; if (index < count) { - node = GrowthQueue->Extract_Min(); + node = GrowthQueue.Extract_Min(); } } } @@ -589,17 +582,15 @@ void VeinholeMonsterClass::Shrink(void) { static const int _mod = 4; - if (GrowthQueue != NULL) { - int index = 0; - int count = abs(Scen->RandomNumber) % _mod + 1; - CellNode * node = GrowthQueue->Extract_Min(); + int index = 0; + int count = abs(Scen->RandomNumber) % _mod + 1; + std::optional node = GrowthQueue.Extract_Min(); - while (index < count && node != NULL) { - Reduce_Veins_At(&Map[Map[node->Element].CellID]); - index++; - if (index < count) { - node = GrowthQueue->Extract_Min(); - } + while (index < count && node) { + Reduce_Veins_At(&Map[Map[node->Element].CellID]); + index++; + if (index < count) { + node = GrowthQueue.Extract_Min(); } } } @@ -621,11 +612,10 @@ void VeinholeMonsterClass::Init_Vein_Growth_System(bool clear) Deinit_Vein_Growth_System(); for (i = VeinholeMonsters.Count() - 1; i >= 0; i--) { - VeinholeMonsters[i]->GrowthNodes = new CellNode[Rule->MaxVeinholeGrowth]; - VeinholeMonsters[i]->GrowthQueue = new PriorityQueueClass(Rule->MaxVeinholeGrowth); + VeinholeMonsters[i]->GrowthQueue.Reserve(Rule->MaxVeinholeGrowth); VeinholeMonsters[i]->GrowthState = new bool[cell_count]; memset(VeinholeMonsters[i]->GrowthState, 0, cell_count); - VeinholeMonsters[i]->GrowthQueue->Clear(); + VeinholeMonsters[i]->GrowthQueue.Clear(); } } @@ -687,7 +677,7 @@ void VeinholeMonsterClass::Build_Growth_Queue(void) GrowthCount = 0; VeinCount = 0; - GrowthQueue->Clear(); + GrowthQueue.Clear(); for (int i = Map_Cell_Count() - 1; i >= 0; i--) { GrowthState[i] = false; @@ -707,9 +697,8 @@ void VeinholeMonsterClass::Build_Growth_Queue(void) VeinCount++; } else { if (GrowthCount < Rule->MaxVeinholeGrowth) { - GrowthNodes[GrowthCount].Element = cellptr->CellID; - GrowthNodes[GrowthCount].Score = 0; - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + GrowthQueue.Insert(CellNode(cellptr->CellID, 0.0f)); + GrowthCount++; } } } @@ -731,9 +720,8 @@ void VeinholeMonsterClass::Build_Growth_Queue(void) VeinCount++; } else { if (GrowthCount < Rule->MaxVeinholeGrowth) { - GrowthNodes[GrowthCount].Element = adjacent->CellID; - GrowthNodes[GrowthCount].Score = 0; - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + GrowthQueue.Insert(CellNode(adjacent->CellID, 0.0f)); + GrowthCount++; if (adjacent->OverlayData >= OVERLAYDATA_FIRST_SOLID_VEIN) { cells.Add(adjacent->CellID); } @@ -761,7 +749,7 @@ void VeinholeMonsterClass::Build_Growth_Queue(void) void VeinholeMonsterClass::Build_Shrinking_Queue(void) { GrowthCount = 0; - GrowthQueue->Clear(); + GrowthQueue.Clear(); VeinCount = 0; Map.Reset_Iterator(); @@ -771,9 +759,9 @@ void VeinholeMonsterClass::Build_Shrinking_Queue(void) int index = Map_Cell_Index(iter->CellID); if (index >= 0 && index < Map_Cell_Count() && GrowthState[index]) { if (GrowthCount < Rule->MaxVeinholeGrowth) { - GrowthNodes[GrowthCount].Element = iter->CellID; - GrowthNodes[GrowthCount].Score = 1000 - Point2D(iter->CellID.X, iter->CellID.Y).Distance_To(Point2D(CellID.X, CellID.Y)); - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + float score = 1000 - Point2D(iter->CellID.X, iter->CellID.Y).Distance_To(Point2D(CellID.X, CellID.Y)); + GrowthQueue.Insert(CellNode(iter->CellID, score)); + GrowthCount++; VeinCount++; } } @@ -800,19 +788,10 @@ void VeinholeMonsterClass::Clear_Global_Data(void) /// void VeinholeMonsterClass::Clear_Growth(void) { - if (GrowthQueue) { - GrowthQueue->Clear(); - delete GrowthQueue; - GrowthQueue = NULL; - } - - if (GrowthNodes) { - delete GrowthNodes; - GrowthNodes = NULL; - } + GrowthQueue.Clear(); if (GrowthState) { - delete GrowthState; + delete [] GrowthState; GrowthState = NULL; } @@ -867,7 +846,7 @@ void VeinholeMonsterClass::Destroy_Monster(void) void VeinholeMonsterClass::Remove_Dead(void) { for (int i = VeinholeMonsters.Count() - 1; i >= 0; i--) { - if (VeinholeMonsters[i]->IsDead && VeinholeMonsters[i]->GrowthQueue->Count() == 0) { + if (VeinholeMonsters[i]->IsDead && VeinholeMonsters[i]->GrowthQueue.Count() == 0) { delete VeinholeMonsters[i]; } } @@ -923,15 +902,6 @@ bool VeinholeMonsterClass::Load_All(IStream * stream) return(false); } - if (FAILED(stream->Read(monster->GrowthNodes, sizeof(CellNode) * Rule->MaxVeinholeGrowth, NULL))) { - return(false); - } - - monster->GrowthQueue->Serialize(savestream, monster->GrowthNodes); - if (FAILED(savestream.Result())) { - return(false); - } - TargetTracker.Add_Index(monster->Fetch_ID(), monster); } @@ -948,12 +918,9 @@ void VeinholeMonsterClass::Serialize(SaveStreamClass & stream) BASECLASS::Serialize(stream); stream.Serialize(GrowthCount); - // GrowthQueue -- pools sized to the map and the growth limit in the rules. The monster - // allocates them for itself, and Load_All and Save_All carry their contents alongside this - // record. - // GrowthNodes + GrowthQueue.Serialize(stream); stream.Serialize(GrowthTimer); - // GrowthState -- part of the same set of pools. + // GrowthState -- one flag per map cell, carried beside this record by Load_All and Save_All. stream.Serialize(CurrentState); stream.Serialize(DesiredState); stream.Serialize(Control); @@ -1000,14 +967,6 @@ bool VeinholeMonsterClass::Save_All(IStream * stream) return(false); } - if (FAILED(stream->Write(VeinholeMonsters[i]->GrowthNodes, sizeof(CellNode) * Rule->MaxVeinholeGrowth, NULL))) { - return(false); - } - - VeinholeMonsters[i]->GrowthQueue->Serialize(savestream, VeinholeMonsters[i]->GrowthNodes); - if (FAILED(savestream.Result())) { - return(false); - } } return(true); @@ -1055,7 +1014,7 @@ void VeinholeMonsterClass::Reduce_Veins_At(CellClass * cellptr) } if (facingbools[facing / 2] == true && !IsDead) { - GrowthQueue->Remove_Matching(CellNode(adjacent->CellID)); + GrowthQueue.Remove_Matching(CellNode(adjacent->CellID)); } } } @@ -1069,9 +1028,9 @@ void VeinholeMonsterClass::Reduce_Veins_At(CellClass * cellptr) GlobalGrowthState[cindex] = false; } else if (!IsDead) { if (GrowthCount < Rule->MaxVeinholeGrowth) { - GrowthNodes[GrowthCount].Element = cell; - GrowthNodes[GrowthCount].Score = float(Frame / 50 + abs(Scen->RandomNumber() % 50) + 1); - GrowthQueue->Insert(GrowthNodes[GrowthCount++]); + float score = float(Frame / 50 + abs(Scen->RandomNumber() % 50) + 1); + GrowthQueue.Insert(CellNode(cell, score)); + GrowthCount++; GrowthState[cindex] = true; GlobalGrowthState[cindex] = true; } diff --git a/code/vein.h b/code/vein.h index 92106eb5c..4b53f0acc 100644 --- a/code/vein.h +++ b/code/vein.h @@ -74,9 +74,9 @@ class VeinholeMonsterClass : public ObjectClass public: /* - * This is the number of nodes handed out of the GrowthNodes pool. It never falls - * until the queue is rebuilt, so it also serves as the check that stops a monster - * spreading past the vein limit in the rules. + * This is the number of cells enqueued since the queue was last rebuilt. It never + * falls until then, so it is also the check that stops a monster spreading past the + * vein limit in the rules. */ int GrowthCount; @@ -86,14 +86,7 @@ class VeinholeMonsterClass : public ObjectClass * with the veins it already owns, farthest out first, so the patch withers back from * its edges. */ - PriorityQueueClass * GrowthQueue; - - /* - * This is the block of nodes the GrowthQueue's entries live in, sized to the vein - * limit in the rules. Holding them in one array is what lets the queue be saved as - * indices into it. - */ - CellNode * GrowthNodes; + PriorityQueueClass GrowthQueue; /* * This is the countdown to the next growth step, restarted with a jittered copy of diff --git a/manual/content/keys/maxveinholegrowth.md b/manual/content/keys/maxveinholegrowth.md index 84e367ca9..f367bc586 100644 --- a/manual/content/keys/maxveinholegrowth.md +++ b/manual/content/keys/maxveinholegrowth.md @@ -7,7 +7,7 @@ when_omitted: value: "1000" --- -Every monster is measured against the figure twice before each [growth step](/systems/veins/#growth): it may have handed out at most this many less 40 frontier entries, and it may cover at most this many less 100 mature cells. The same figure sizes the frontier record each monster allocates, so it is a hard ceiling on one monster's field rather than a target the field settles at. Every monster in the scenario shares the one setting. +Every monster is measured against the figure twice before each [growth step](/systems/veins/#growth): it may have handed out at most this many less 40 frontier entries, and it may cover at most this many less 100 mature cells. Neither test is ever relaxed, so the figure is a hard ceiling on one monster's field rather than a target the field settles at. Every monster in the scenario shares the one setting. :::caution[A figure below 100 stops growth outright] The coverage test compares a monster's mature-cell count against the figure less 100. A monster covering nothing at all already fails that test when the figure is below 100, so no vein grows anywhere in the scenario. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a7d3ef23a..63027f2d9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -98,3 +98,4 @@ add_subdirectory(utf8) add_subdirectory(shapefacing) add_subdirectory(cstream) add_subdirectory(zbufring) +add_subdirectory(priorityqueue) diff --git a/tests/priorityqueue/CMakeLists.txt b/tests/priorityqueue/CMakeLists.txt new file mode 100644 index 000000000..ab070d7c1 --- /dev/null +++ b/tests/priorityqueue/CMakeLists.txt @@ -0,0 +1,15 @@ +# The queue is a template with no source of its own, so the harness compiles nothing from +# code/. It lives outside code/ so that the recursive glob building OpenTS cannot pick this +# target's entry point up. +# +# FLOAT is not decoration here: the order two nodes of equal score come out in turns on +# exact float comparison, so the harness has to carry the float semantics the engine builds +# with to measure what the engine measures. + +opents_add_test(PriorityQueue + NAME priorityqueue + SOURCES priorityqueuetest.cpp + INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} + DEFINITIONS WIN32 _WINDOWS _MBCS NOMINMAX + FLOAT +) diff --git a/tests/priorityqueue/priorityqueuetest.cpp b/tests/priorityqueue/priorityqueuetest.cpp new file mode 100644 index 000000000..8b9948289 --- /dev/null +++ b/tests/priorityqueue/priorityqueuetest.cpp @@ -0,0 +1,445 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// Holds the priority queue to its extraction order: what comes out for a given set of +// scores, and above all what comes out when several nodes carry the same score. +// +// The tie expectations are why this file exists. Random maps are built locally on every +// peer from a shared seed, and map generation seeds its spreads at exactly Score = 0.0f, +// so a reordering among equal scores would change the maps peers build from one build to +// the next. Every sequence below was recorded from the queue as it stood before its +// storage was rewritten, and every result crosses the API through Element_Of, so the same +// expectations read the same whether extraction hands back a pointer into the caller's +// nodes or a value. +// +// Needs no game data. + +#include "priority.h" + +#include +#include +#include + + +namespace { + +int Failures = 0; +int Checked = 0; + + +/// Stands in for a node the queue orders. It carries CellNode's contract rather than the +/// type itself, which would drag the map and COM headers in behind it: the queue orders on +/// Score alone and matches on Element alone, and every comparison widens to double exactly +/// as code/nodes.h does. +struct TestNode { + int Element; + float Score; + + bool operator==(TestNode const & other) const { return((double)Score == (double)other.Score); } + bool operator!=(TestNode const & other) const { return((double)Score != (double)other.Score); } + bool operator<(TestNode const & other) const { return((double)Score < (double)other.Score); } + bool operator>(TestNode const & other) const { return((double)Score > (double)other.Score); } + bool operator<=(TestNode const & other) const { return((double)Score <= (double)other.Score); } + bool operator>=(TestNode const & other) const { return((double)Score >= (double)other.Score); } +}; + + +/// Stands for the queue handing nothing back. +int const EMPTY = -1; + + +void Check(bool passed, char const * what) +{ + Checked++; + if (!passed) { + std::printf("FAILED %s\n", what); + Failures++; + } +} + + +/* + * These three name the element behind whatever the queue handed back. Extraction and + * Replace_Root return a pointer into the caller's nodes today and a value once the queue + * holds its elements itself, and every expectation below is written against the element + * number so that neither spelling reaches the cases. + */ +int Element_Of(TestNode const * node) +{ + return(node != NULL ? node->Element : EMPTY); +} + + +int Element_Of(TestNode const & node) +{ + return(node.Element); +} + + +int Element_Of(std::optional const & node) +{ + return(node ? node->Element : EMPTY); +} + + +template +int Pop(Q & queue) +{ + return(Element_Of(queue.Extract_Min())); +} + + +template +int Replace(Q & queue, TestNode & node) +{ + return(Element_Of(queue.Replace_Root(node))); +} + + +/// Fills the queue from a score list, numbering each node by its position in that list +/// counting from one. The caller's array has to outlive the queue, because the queue held +/// pointers into it before it held values. +template +void Fill(Q & queue, TestNode * nodes, float const * scores, int count) +{ + for (int index = 0; index < count; index++) { + nodes[index].Element = index + 1; + nodes[index].Score = scores[index]; + queue.Insert(nodes[index]); + } +} + + +/// Empties the queue into a comma separated list of element numbers, which is the form +/// every expectation below is written in. +template +void Drain(Q & queue, char * out, int size) +{ + int used = 0; + out[0] = '\0'; + + for (;;) { + int element = Pop(queue); + if (element == EMPTY) { + break; + } + + int written = std::snprintf(out + used, size - used, used == 0 ? "%d" : ",%d", element); + if (written <= 0 || written >= size - used) { + break; + } + used += written; + } +} + + +template +void Check_Drain(Q & queue, char const * expected, char const * what) +{ + char got[256]; + Drain(queue, got, sizeof(got)); + + Checked++; + if (std::strcmp(got, expected) != 0) { + std::printf("FAILED %s\n expected %s\n got %s\n", what, expected, got); + Failures++; + } +} + + +void Check_Ordering(void) +{ + { + PriorityQueueClass queue(64); + TestNode nodes[8]; + float const scores[8] = {5.0f, 1.0f, 8.0f, 3.0f, 7.0f, 2.0f, 6.0f, 4.0f}; + Fill(queue, nodes, scores, 8); + + Check(queue.Count() == 8, "the queue counts what went in"); + Check_Drain(queue, "2,6,4,8,1,7,5,3", "a shuffled set comes out in score order"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[5]; + float const scores[5] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}; + Fill(queue, nodes, scores, 5); + + Check_Drain(queue, "1,2,3,4,5", "an ascending set comes out unchanged"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[5]; + float const scores[5] = {5.0f, 4.0f, 3.0f, 2.0f, 1.0f}; + Fill(queue, nodes, scores, 5); + + Check_Drain(queue, "5,4,3,2,1", "a descending set comes out reversed"); + } + + { + PriorityQueueClass queue(64); + TestNode node; + node.Element = 1; + node.Score = 0.0f; + queue.Insert(node); + + Check_Drain(queue, "1", "a queue holding one node hands it back"); + } + + { + PriorityQueueClass queue(64); + + Check(queue.Count() == 0, "a fresh queue counts nothing"); + Check(Pop(queue) == EMPTY, "an empty queue hands nothing back"); + Check(Pop(queue) == EMPTY, "an empty queue hands nothing back twice running"); + } +} + + +void Check_Ties(void) +{ + { + PriorityQueueClass queue(64); + TestNode nodes[8]; + float const scores[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + Fill(queue, nodes, scores, 8); + + Check_Drain(queue, "1,8,7,6,5,4,3,2", "eight nodes at one score come out in a fixed order"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[8]; + float const scores[8] = {5.0f, 1.0f, 5.0f, 3.0f, 1.0f, 9.0f, 3.0f, 1.0f}; + Fill(queue, nodes, scores, 8); + + Check_Drain(queue, "2,5,8,4,7,1,3,6", "nodes sharing a score come out in a fixed order"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[4]; + float const scores[4] = {0.0f, -0.0f, 0.0f, -0.0f}; + Fill(queue, nodes, scores, 4); + + Check_Drain(queue, "1,4,3,2", "a negative zero ties with a positive one"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[8]; + float const scores[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + + Fill(queue, nodes, scores, 8); + queue.Clear(); + Fill(queue, nodes, scores, 8); + + Check_Drain(queue, "1,8,7,6,5,4,3,2", "a cleared queue ties the same way a fresh one does"); + } +} + + +void Check_Replace_Root(void) +{ + { + PriorityQueueClass queue(64); + TestNode offered; + offered.Element = 9; + offered.Score = 4.0f; + + Check(Replace(queue, offered) == 9, "an empty queue hands the offered node straight back"); + Check(queue.Count() == 0, "an empty queue stays empty"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[4]; + float const scores[4] = {5.0f, 7.0f, 6.0f, 8.0f}; + Fill(queue, nodes, scores, 4); + + TestNode offered; + offered.Element = 9; + offered.Score = 2.0f; + + Check(Replace(queue, offered) == 9, "a node below the root comes straight back"); + Check(queue.Count() == 4, "a node below the root does not enter"); + Check_Drain(queue, "1,3,2,4", "the queue is untouched by a node below the root"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[4]; + float const scores[4] = {5.0f, 7.0f, 6.0f, 8.0f}; + Fill(queue, nodes, scores, 4); + + TestNode offered; + offered.Element = 9; + offered.Score = 6.5f; + + Check(Replace(queue, offered) == 1, "a node above the root turns the root out"); + Check(queue.Count() == 4, "replacing the root leaves the count alone"); + Check_Drain(queue, "3,9,2,4", "the offered node takes its place in order"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[4]; + float const scores[4] = {5.0f, 7.0f, 6.0f, 8.0f}; + Fill(queue, nodes, scores, 4); + + TestNode offered; + offered.Element = 9; + offered.Score = 5.0f; + + Check(Replace(queue, offered) == 1, "a node level with the root turns the root out"); + Check_Drain(queue, "9,3,2,4", "a node level with the root takes its place"); + } +} + + +void Check_Remove_Matching(void) +{ + float const scores[7] = {5.0f, 7.0f, 6.0f, 9.0f, 8.0f, 10.0f, 11.0f}; + + { + PriorityQueueClass queue(64); + TestNode nodes[7]; + Fill(queue, nodes, scores, 7); + + TestNode wanted; + wanted.Element = 7; + wanted.Score = 0.0f; + + Check(queue.Remove_Matching(wanted) == true, "a node in the last slot is found"); + Check(queue.Count() == 6, "removing a node drops the count"); + Check_Drain(queue, "1,3,2,5,4,6", "the rest come out in score order"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[7]; + Fill(queue, nodes, scores, 7); + + TestNode wanted; + wanted.Element = 1; + wanted.Score = 0.0f; + + Check(queue.Remove_Matching(wanted) == true, "the node at the root is found"); + Check_Drain(queue, "3,2,5,4,6,7", "the queue re-forms around a removed root"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[7]; + Fill(queue, nodes, scores, 7); + + TestNode wanted; + wanted.Element = 3; + wanted.Score = 0.0f; + + Check(queue.Remove_Matching(wanted) == true, "a node in a middle slot is found"); + Check_Drain(queue, "1,2,5,4,6,7", "the queue re-forms around a removed middle node"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[7]; + Fill(queue, nodes, scores, 7); + + TestNode wanted; + wanted.Element = 2; + wanted.Score = 0.0f; + + Check(queue.Remove_Matching(wanted) == true, "a node with a lighter parent is found"); + Check_Drain(queue, "1,3,5,4,6,7", "the queue re-forms around it"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[7]; + Fill(queue, nodes, scores, 7); + + TestNode wanted; + wanted.Element = 99; + wanted.Score = 0.0f; + + Check(queue.Remove_Matching(wanted) == false, "a node that is not held is not found"); + Check(queue.Count() == 7, "a search that finds nothing removes nothing"); + Check_Drain(queue, "1,3,2,5,4,6,7", "a search that finds nothing leaves the order alone"); + } + + { + PriorityQueueClass queue(64); + TestNode nodes[4]; + float const shared[4] = {5.0f, 7.0f, 6.0f, 9.0f}; + + for (int index = 0; index < 4; index++) { + nodes[index].Element = (index == 1 || index == 3) ? 2 : index + 1; + nodes[index].Score = shared[index]; + queue.Insert(nodes[index]); + } + + TestNode wanted; + wanted.Element = 2; + wanted.Score = 0.0f; + + Check(queue.Remove_Matching(wanted) == true, "one of two nodes sharing an element is found"); + Check_Drain(queue, "1,3,2", "the queue walks its slots rather than the order they went in"); + } +} + + +void Check_Clear(void) +{ + PriorityQueueClass queue(64); + TestNode nodes[6]; + float const scores[6] = {4.0f, 2.0f, 6.0f, 1.0f, 5.0f, 3.0f}; + + Fill(queue, nodes, scores, 6); + queue.Clear(); + + Check(queue.Count() == 0, "a cleared queue counts nothing"); + Check(Pop(queue) == EMPTY, "a cleared queue hands nothing back"); + + Fill(queue, nodes, scores, 6); + Check_Drain(queue, "4,2,6,1,5,3", "a refilled queue orders as a fresh one does"); +} + + +void Check_Capacity(void) +{ + // The size a queue is built with is only how much room it takes up front. + PriorityQueueClass queue(8); + TestNode nodes[10]; + float const scores[10] = {9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f, 0.0f}; + Fill(queue, nodes, scores, 10); + + Check(queue.Count() == 10, "a queue built with eight slots takes ten nodes"); + Check_Drain(queue, "10,9,8,7,6,5,4,3,2,1", "every node it took comes out in score order"); +} + +} // namespace + + +int main(void) +{ + Check_Ordering(); + Check_Ties(); + Check_Replace_Root(); + Check_Remove_Matching(); + Check_Clear(); + Check_Capacity(); + + std::printf("%-52s %s\n", "Priority queue extraction order", + Failures == 0 ? "ok" : "FAILED"); + std::printf("checked %d cases, %d mismatches\n", Checked, Failures); + + return(Failures == 0 ? 0 : 1); +} From 3f67994afa75d8c4e4594e01b0c6dc5ab51a7c5f Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 01:01:39 +0300 Subject: [PATCH 02/18] Bound the route search's cell record and corridor list --- code/astar.cpp | 20 ++++++++++++++++++++ manual/content/systems/route-search.md | 16 +++++++--------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/code/astar.cpp b/code/astar.cpp index 711a6b7d4..71cd05e55 100644 --- a/code/astar.cpp +++ b/code/astar.cpp @@ -445,6 +445,16 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F continue; } + /* + * The pool is handed out and never released within a pass, and nothing else + * bounds how many cells a pass may reach. Passing the cell over costs the + * search a route. Writing past the pool would land on the count of what it + * holds. + */ + if (RegularNodes->ActiveCount >= ARRAY_SIZE(RegularNodes->Nodes)) { + continue; + } + RegularOpenNode new_node = Create_Node(working_node, working_to, to, movement_cost); if (!temp_node) { temp_node = new_node; @@ -1682,6 +1692,16 @@ bool AStarClass::Find_Path_Hierarchical(Cell const & from, Cell const & to, MZon return(false); } + /* + * The chain is written into a list of a fixed length at whatever length the + * block search settled on. Giving up on a corridor too long to record leaves + * the caller to search the map unrestricted. Writing it out would run over the + * lists for the coarser block sizes behind this one. + */ + if (best_node->Depth + 1 > ARRAY_SIZE(HierSubzonePath[subzone_level])) { + return(false); + } + int trace = best_node->PoolIndex; while (HierNodePool[trace].ParentIndex != -1) { final_ids[HierNodePool[trace].SubzoneID] = UniqueID; diff --git a/manual/content/systems/route-search.md b/manual/content/systems/route-search.md index 6bde576fb..3af001360 100644 --- a/manual/content/systems/route-search.md +++ b/manual/content/systems/route-search.md @@ -56,14 +56,12 @@ The corridor stage is skipped outright under **any of:** With the stage skipped the cell search is free to spread anywhere on the playfield. -:::danger[A corridor of more than 500 blocks is written past the end of the list holding it] -Each of the three block sizes has a list of 500 entries to record its chain in. The chain is written into that list at the length the block search settled on, and nothing compares that length against the 500 the list holds, so a longer chain simply runs off the end. +:::caution[A corridor of more than 500 blocks is abandoned] +Each of the three block sizes has a list of 500 entries to record its chain in. A chain longer than the list is refused, and the cell search runs unrestricted instead. -The 2-by-2 chain is the long one. A route crosses about one 2-by-2 block every two cells, and four cells of route is the most that can ever fall inside a single block, so 500 of them is a walk of roughly a thousand cells — half of what it takes to fill the move list at the foot of this page. +The 2-by-2 chain is the long one. A route crosses about one 2-by-2 block every two cells, and four cells of route is the most that can ever fall inside a single block, so 500 of them is a walk of roughly a thousand cells -- half of what it takes to fill the move list at the foot of this page. A journey that long gives up the corridor and prices every cell it passes, which costs more effort than a corridor would have. -The three lists sit one after another, smallest blocks first, with the record of how long each chain is behind them. Entries past the end of the 2-by-2 list land on the 4-by-4 chain; a chain past a thousand blocks reaches the 8-by-8 chain as well; and one past fifteen hundred runs over the three recorded lengths and then past the end of the pathfinder itself. The chain is written from its far end backwards, so the furthest write is the first one made. A retry reads the chains it has overwritten, and strikes out block links accordingly. - -The block stage runs before the cell search, so this happens before a single cell has been priced. It also runs on its own whenever the game measures how far an object would have to walk between two cells, and that measurement takes none of the skips above. +The block stage runs before the cell search, so this is settled before a single cell has been priced. It also runs on its own whenever the game measures how far an object would have to walk between two cells, and that measurement takes none of the skips above. ::: ### The cell-by-cell search @@ -86,10 +84,10 @@ A request that ends with no route at all arms the object's [`PathDelay`](/keys/p Alongside the effort limit, a finished pass is checked against a second figure. A pass that reaches the destination having taken up exactly 10,000 cells is treated as a failure and hands back no route, although the route was found and is complete. No other count is treated that way, and nothing about such a route distinguishes it from one found a cell earlier or later. ::: -:::danger[A pass that reaches more than 65,536 cells writes past the end of its record of them] -The effort limit counts the cells a pass takes up, but a cell is recorded the moment it is first priced — well before it is taken up, and for many cells that never are. The cells on the search's outer edge count against the record too, and the record holds 65,536 of them against an effort limit of 65,527. Nothing checks it as cells are added, so a pass that reaches a 65,537th cell writes that cell over the tally of how many the record holds and over whatever follows it. The tally is then a nonsense figure, and every cell reached afterwards is written wherever it points. +:::caution[A pass that reaches more than 131,072 cells stops taking new ones] +The effort limit counts the cells a pass takes up, but a cell is recorded the moment it is first priced -- well before it is taken up, and for many cells that never are. The cells on the search's outer edge count against the record too. The record holds 131,072 of them. A pass that has filled it passes over every further cell it reaches, so it runs out of candidates and hands back no route. -A cell is recorded once and no more, so reaching that many needs ground to match: more than 65,536 cells the object may enter in a single pass, which a square playable area of open ground passes at around 181 cells on a side. A cell spanned by a bridge is recorded twice over, once for the ground and once for the deck. The destination has also to be far enough off, or awkward enough to arrive at, that the search spreads over all of that ground before it settles on a route. +A cell is recorded once and no more, so reaching that many needs ground to match: more than 131,072 cells the object may enter in a single pass, which a square playable area of open ground passes at around 256 cells on a side. A cell spanned by a bridge is recorded twice over, once for the ground and once for the deck. The destination has also to be far enough off, or awkward enough to arrive at, that the search spreads over all of that ground before it settles on a route. ::: ## Why a route is not the shortest one From 213cf97175462877af33e67070dd9998135c1945 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 01:01:55 +0300 Subject: [PATCH 03/18] Drop a tiberium cell that can no longer spread --- code/tiberium.cpp | 14 ++++++++++++++ manual/changes/tiberium-spread-dead-cells.md | 16 ++++++++++++++++ manual/content/systems/tiberium.md | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 manual/changes/tiberium-spread-dead-cells.md diff --git a/code/tiberium.cpp b/code/tiberium.cpp index a1b7fbde4..bb021262a 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -353,6 +353,20 @@ void TiberiumClass::Spread_AI(void) CellClass * cellptr = &Map[node->Element]; int possible_spreads = 0; + /* + * A cell that has since lost its tiberium seeds nothing, and re-enqueuing it at + * score zero would bring it back ahead of every other entry on every later pass. + * Drop it instead, without charging it against this pass. + */ + if (!cellptr->Can_Tiberium_Spread()) { + SpreadState[Map_Cell_Index(cellptr->CellID)] = false; + + if (index < count) { + node = SpreadQueue.Extract_Min(); + } + continue; + } + /* * Count how many neighbors we can spread Tiberium to. */ diff --git a/manual/changes/tiberium-spread-dead-cells.md b/manual/changes/tiberium-spread-dead-cells.md new file mode 100644 index 000000000..75d173511 --- /dev/null +++ b/manual/changes/tiberium-spread-dead-cells.md @@ -0,0 +1,16 @@ +--- +title: Drop a Tiberium cell that can no longer spread +category: fix +release: 0.2.0 +targets: +- type: system + id: tiberium + effect: changed +credit: +- ZivDero +--- + +A cell whose Tiberium has gone since it was queued to spread is now dropped from the +spread queue. It was previously re-queued at the head of the queue whether or not it still +carried anything to seed from, so a handful of harvested cells could come back first on +every pass and leave that Tiberium type looking as though it had stopped spreading. diff --git a/manual/content/systems/tiberium.md b/manual/content/systems/tiberium.md index 91026bf5c..6c6bc3cdc 100644 --- a/manual/content/systems/tiberium.md +++ b/manual/content/systems/tiberium.md @@ -115,7 +115,7 @@ Removing stages from a cell standing at stage 11 tries to put it back into the g ## Spread -Spread passes are scheduled the same way, from the [`Spread`](/keys/spread/#scope-tiberium) delay, and no flag shortens them. The budget is the queued count multiplied by [`SpreadPercentage`](/keys/spreadpercentage/), clamped to between 5 and 25, with a random figure drawn from 1 up to it. Only a cell that finds somewhere to seed counts against that budget; a cell hemmed in on all eight sides is dropped from the queue without spending any of it, and a cell with more than one free neighbor is re-queued to run again on the next pass. +Spread passes are scheduled the same way, from the [`Spread`](/keys/spread/#scope-tiberium) delay, and no flag shortens them. The budget is the queued count multiplied by [`SpreadPercentage`](/keys/spreadpercentage/), clamped to between 5 and 25, with a random figure drawn from 1 up to it. Only a cell that finds somewhere to seed counts against that budget. A cell hemmed in on all eight sides is dropped from the queue without spending any of it, as is one that no longer carries Tiberium of its own; a cell with more than one free neighbor is re-queued to run again on the next pass. A cell may spread when all of the following hold, tested in this order: From 6c6b862100901faac58b49a84cd0e4895540dfaa Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 02:01:24 +0300 Subject: [PATCH 04/18] Queue a tiberium cell to grow only once --- code/tiberium.cpp | 4 +++- code/tiberium.h | 2 +- .../changes/tiberium-growth-duplicate-entries.md | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 manual/changes/tiberium-growth-duplicate-entries.md diff --git a/code/tiberium.cpp b/code/tiberium.cpp index bb021262a..4908a18a4 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -595,6 +595,8 @@ void TiberiumClass::Growth_AI(void) } else { GrowthState[Map_Cell_Index(node->Element)] = false; } + } else { + GrowthState[Map_Cell_Index(node->Element)] = false; } index++; @@ -675,7 +677,7 @@ void TiberiumClass::Clear_Growth(void) void TiberiumClass::Queue_Growth(Cell const & cell) { int cellindex = Map_Cell_Index(cell); - if (Map[cell].OverlayData < MAX_GROWTH_STAGE) { + if (Map[cell].OverlayData < MAX_GROWTH_STAGE && !GrowthState[cellindex]) { /* * GrowthQueue does not recycle its entries. diff --git a/code/tiberium.h b/code/tiberium.h index f9e3c187c..9a71bf265 100644 --- a/code/tiberium.h +++ b/code/tiberium.h @@ -180,7 +180,7 @@ class TiberiumClass : public AbstractTypeClass CDTimerClass SpreadTimer; /* - * This is the number of cells enqueued to ripen since the queue was last rebuilt. + * This is the number of cells enqueued to grow since the queue was last rebuilt. * Nothing takes a stale or duplicate entry back out, so the queue is rebuilt from the * map once this approaches the map's cell count. */ diff --git a/manual/changes/tiberium-growth-duplicate-entries.md b/manual/changes/tiberium-growth-duplicate-entries.md new file mode 100644 index 000000000..973068085 --- /dev/null +++ b/manual/changes/tiberium-growth-duplicate-entries.md @@ -0,0 +1,16 @@ +--- +title: Queue a Tiberium cell to grow only once +category: fix +release: 0.2.0 +targets: +- type: system + id: tiberium + effect: changed +credit: +- ZivDero +--- + +A cell could be queued to grow many times over, most often after a chain reaction across a +field, and every stale copy taken off the queue spent one of the pass's slots without +growing anything. Growth crawled until the pile drained. A cell now holds at most one place +in the queue. From d1d7c0615d8b23977802ef8de64dd3f5805dd849 Mon Sep 17 00:00:00 2001 From: Jakub Vesely <1251980+tinix0@users.noreply.github.com> Date: Thu, 10 Sep 2026 01:31:17 +0200 Subject: [PATCH 05/18] Include target architecture in the build stamp. (#152) --- CMakeLists.txt | 7 +++++++ cmake/GitStamp.cmake | 10 ++++++---- code/dbgprint.cpp | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ac8022f7..1d4cdc1ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ option(OPENTS_OFFICIAL_BUILD "Build as an official release of the declared versi option(OPENTS_EXPERIMENTAL_CLANG_CL "Build with clang-cl using the MSVC ABI" OFF) option(OPENTS_EXPERIMENTAL_X64 "Configure an unsupported 64-bit Windows build" OFF) + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") if(NOT OPENTS_EXPERIMENTAL_CLANG_CL) @@ -41,6 +42,11 @@ else() "-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake.") endif() +string(TOLOWER "${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" OPENTS_ARCH) +if(NOT OPENTS_ARCH) + set(OPENTS_ARCH "unknown") +endif() + enable_language(RC) set(CMAKE_CXX_STANDARD 20) @@ -71,6 +77,7 @@ set(OPENTS_STAMP_ARGS "-DOPENTS_SOURCE_DIR=${CMAKE_SOURCE_DIR}" "-DOPENTS_VERSION_HEADER=${OPENTS_VERSION_HEADER}" "-DOPENTS_STAMP_HEADER=${OPENTS_STAMP_HEADER}" + "-DOPENTS_ARCH=${OPENTS_ARCH}" -P "${CMAKE_SOURCE_DIR}/cmake/GitStamp.cmake" ) diff --git a/cmake/GitStamp.cmake b/cmake/GitStamp.cmake index 3438dc44d..b13c7aaff 100644 --- a/cmake/GitStamp.cmake +++ b/cmake/GitStamp.cmake @@ -71,6 +71,8 @@ set(VERSION_CONTENT #else #define OPENTS_RC_PRERELEASE_FLAG 0x0L #endif + +#define OPENTS_ARCH \"${OPENTS_ARCH}\" ") set(OPENTS_COMMIT "unknown") @@ -173,13 +175,13 @@ if(OPENTS_COMMIT_DIRTY) endif() if(OPENTS_OFFICIAL_BUILD) - set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION}") + set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION} (${OPENTS_ARCH})") elseif(OPENTS_COMMIT STREQUAL "unknown") - set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION}") + set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION} (${OPENTS_ARCH})") elseif(OPENTS_COMMIT_DIRTY) - set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION} (${OPENTS_COMMIT}, modified)") + set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION} (${OPENTS_COMMIT}, modified, ${OPENTS_ARCH})") else() - set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION} (${OPENTS_COMMIT})") + set(OPENTS_VERSION_DISPLAY "${OPENTS_VERSION} (${OPENTS_COMMIT}, ${OPENTS_ARCH})") endif() set(STAMP_CONTENT diff --git a/code/dbgprint.cpp b/code/dbgprint.cpp index 41bcbb097..97ef4f21b 100644 --- a/code/dbgprint.cpp +++ b/code/dbgprint.cpp @@ -385,7 +385,7 @@ R"ART( char line[512]; - snprintf(line, sizeof(line), "Version : OpenTS %s (%s build)\n", OPENTS_VERSION, BuildType); + snprintf(line, sizeof(line), "Version : OpenTS %s (%s %s build)\n", OPENTS_VERSION, OPENTS_ARCH, BuildType); Write_Message_Locked(line, false); snprintf(line, sizeof(line), "Commit : %s on %s%s\n", OPENTS_COMMIT, OPENTS_BRANCH, From 1807aa28fe491fd8326b045a20f3ceeeb04a8f1a Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Thu, 10 Sep 2026 01:31:43 +0200 Subject: [PATCH 06/18] Assert the on-disk structure sizes (#149) --- code/animfile.h | 3 +++ code/audio/audiodecode.h | 2 ++ code/iff.h | 1 + code/isotype.h | 2 ++ code/mixfile.h | 2 ++ code/pcx.h | 2 ++ code/preview.h | 1 + code/rgb.h | 1 + code/shapeset.h | 2 ++ code/srfcache.cpp | 1 + code/voxel.h | 1 + code/voxel.hh | 4 ++++ code/vqalib/loader.cpp | 2 ++ code/vqalib/vqafile.h | 1 + 14 files changed, 25 insertions(+) diff --git a/code/animfile.h b/code/animfile.h index f684e4a6d..8ab0f1144 100644 --- a/code/animfile.h +++ b/code/animfile.h @@ -108,6 +108,7 @@ class AnimFile : public Animate unsigned short framesPerSecond; /// Number of frames to play per second. unsigned short pad2[29]; /// 58 bytes of filler to round up to 128 bytes total. }; + static_assert(sizeof(LPFHeader) == 128, "the LPF header is 128 bytes on disk"); struct LPDescriptor { unsigned short baseRecord; /// Number of first record in this large page. @@ -116,6 +117,7 @@ class AnimFile : public Animate /// bit 14 of "nRecords" == "final record continues on next lp". unsigned short nBytes; /// Total number of bytes of contents, excluding header. }; + static_assert(sizeof(LPDescriptor) == 6, "a large page descriptor is 6 bytes on disk"); /* * Structure of a single Large Page in an anim file. @@ -134,6 +136,7 @@ class AnimFile : public Animate unsigned short RecordSizes[1]; /// [nRecords] Array of lengths of each record in the large page. }; + static_assert(sizeof(LPStruct) == 10, "a large page header is 8 bytes on disk, and the struct carries the first of the record sizes that follow it"); /* * These are the color cycling ranges Deluxe Paint stores after the header, each diff --git a/code/audio/audiodecode.h b/code/audio/audiodecode.h index e4c393bfa..3a1b08116 100644 --- a/code/audio/audiodecode.h +++ b/code/audio/audiodecode.h @@ -32,12 +32,14 @@ struct AUDHeaderType { uint8_t Flags; uint8_t Compression; }; +static_assert(sizeof(AUDHeaderType) == 12, "the AUD header is 12 bytes on disk"); struct AUDChunkHeaderType { uint16_t CompSize; uint16_t UncompSize; uint32_t Magic; }; +static_assert(sizeof(AUDChunkHeaderType) == 8, "an AUD chunk header is 8 bytes on disk"); #pragma pack(pop) diff --git a/code/iff.h b/code/iff.h index fc4eec5c0..9363f3476 100644 --- a/code/iff.h +++ b/code/iff.h @@ -79,6 +79,7 @@ struct CompHeaderType { int Size; // Size of the uncompressed data. short Skip; // Number of bytes to skip before data. }; +static_assert(sizeof(CompHeaderType) == 8, "the compressed file header is 8 bytes on disk"); #pragma pack(pop) diff --git a/code/isotype.h b/code/isotype.h index c7de7f9a7..70693e2d0 100644 --- a/code/isotype.h +++ b/code/isotype.h @@ -104,6 +104,7 @@ struct IsoTileRecord RGBStruct LowColor; RGBStruct HighColor; }; +static_assert(sizeof(IsoTileRecord) == 52, "a TMP tile record is 52 bytes on disk"); #pragma pack() #pragma pack(4) @@ -189,6 +190,7 @@ class IsoTileSet IsoTileSet(IsoTileSet const & rvalue); IsoTileSet const & operator = (IsoTileSet const & rvalue); }; +static_assert(sizeof(IsoTileSet) == 20, "the TMP header is 16 bytes on disk, followed by the four-byte tile offsets"); #pragma pack() diff --git a/code/mixfile.h b/code/mixfile.h index 12af50954..20d50ec54 100644 --- a/code/mixfile.h +++ b/code/mixfile.h @@ -48,6 +48,7 @@ class MixFileClass : public Node int operator > (const SubBlock & two) const {return(CRC > two.CRC);}; int operator == (const SubBlock & two) const {return(CRC == two.CRC);}; }; + static_assert(sizeof(SubBlock) == 12, "a MIX directory entry is 12 bytes on disk"); private: static MixFileClass * Finder(char const * filename); @@ -82,6 +83,7 @@ class MixFileClass : public Node short count; int size; }; + static_assert(sizeof(FileHeader) == 6, "the MIX header is 6 bytes on disk"); #pragma pack() /* diff --git a/code/pcx.h b/code/pcx.h index 36a252ba7..f51d87224 100644 --- a/code/pcx.h +++ b/code/pcx.h @@ -43,6 +43,7 @@ struct RGB { unsigned char green; unsigned char blue; }; +static_assert(sizeof(RGB) == 3, "a PCX palette entry is 3 bytes on disk"); struct PCX_HEADER { @@ -65,6 +66,7 @@ struct PCX_HEADER short vert_screen_size; char filler[54]; }; +static_assert(sizeof(PCX_HEADER) == 128, "the PCX header is 128 bytes on disk"); #pragma pack(pop) bool Read_PCX_Size(FileClass & file, int & width, int & height); diff --git a/code/preview.h b/code/preview.h index f63ba77fb..96da1f17d 100644 --- a/code/preview.h +++ b/code/preview.h @@ -50,6 +50,7 @@ class MapPreviewClass int Width; int Height; }; + static_assert(sizeof(Header) == 8, "the preview header is 8 bytes on disk"); private: diff --git a/code/rgb.h b/code/rgb.h index 66f76ab6d..d06bdd2af 100644 --- a/code/rgb.h +++ b/code/rgb.h @@ -51,6 +51,7 @@ struct RGBStruct unsigned char Green; unsigned char Blue; }; +static_assert(sizeof(RGBStruct) == 3, "a palette entry is 3 bytes on disk"); #pragma pack() diff --git a/code/shapeset.h b/code/shapeset.h index e74d3b442..2deb396a5 100644 --- a/code/shapeset.h +++ b/code/shapeset.h @@ -167,6 +167,7 @@ class ShapeSet void Flag_RLE_Compressed(void) {Flags |= SFLAG_RLE;} void Set_Size(short size) {Size = size;} }; + static_assert(sizeof(ShapeRecord) == 24, "a SHP frame record is 24 bytes on disk"); bool Is_Shape_Index_Valid(int index) const {return(unsigned(index) < unsigned(Count));} @@ -185,6 +186,7 @@ class ShapeSet ShapeSet(ShapeSet const & rvalue); ShapeSet const & operator = (ShapeSet const & rvalue); }; +static_assert(sizeof(ShapeSet) == 8, "the SHP header is 8 bytes on disk"); #pragma pack(pop) diff --git a/code/srfcache.cpp b/code/srfcache.cpp index 29ef9484b..9bf7cf18d 100644 --- a/code/srfcache.cpp +++ b/code/srfcache.cpp @@ -36,6 +36,7 @@ struct MSBitmap BITMAPFILEHEADER filehead; BITMAPINFO info; }; +static_assert(sizeof(MSBitmap) == 58, "the file header, info header and one colour occupy 58 bytes on disk"); #pragma pack(pop) diff --git a/code/voxel.h b/code/voxel.h index 08395d6e1..246509aa1 100644 --- a/code/voxel.h +++ b/code/voxel.h @@ -35,6 +35,7 @@ struct VPLHeaderStruct /// Unused int Unused; }; +static_assert(sizeof(VPLHeaderStruct) == 16, "the VPL header is 16 bytes on disk"); Matrix3D Get_Isometric_View_Matrix(void); void Init_Voxel_Matrices(void); diff --git a/code/voxel.hh b/code/voxel.hh index 3ccc745dd..b7f0cc817 100644 --- a/code/voxel.hh +++ b/code/voxel.hh @@ -82,6 +82,7 @@ struct VoxelHeaderStruct */ int DataSize; }; +static_assert(sizeof(VoxelHeaderStruct) == 32, "the VXL header before the remap bytes and palette is 32 bytes on disk"); /* @@ -105,6 +106,7 @@ struct VoxelLayerHeaderStruct int Unused1; unsigned char Unused2; }; +static_assert(sizeof(VoxelLayerHeaderStruct) == 28, "the VXL section header is 28 bytes on disk; the last byte is padded out to a word"); /* @@ -164,6 +166,7 @@ struct VoxelLayerInfoStruct */ unsigned char NormalType; }; +static_assert(sizeof(VoxelLayerInfoStruct) == 92, "the VXL section tailer is 92 bytes on disk"); /* @@ -189,3 +192,4 @@ struct VoxelAnimFileHeaderStruct */ int LayerCount; }; +static_assert(sizeof(VoxelAnimFileHeaderStruct) == 24, "the HVA header is 24 bytes on disk"); diff --git a/code/vqalib/loader.cpp b/code/vqalib/loader.cpp index 63200ec69..6c628ce80 100644 --- a/code/vqalib/loader.cpp +++ b/code/vqalib/loader.cpp @@ -306,6 +306,7 @@ struct VQASN2J { short index2; long predicted2; }; +static_assert(sizeof(VQASN2J) == 12, "the SN2J chunk is 12 bytes on disk"); #pragma pack(pop) @@ -3530,6 +3531,7 @@ long Load_SN2J(VQAHandleP *vqap, unsigned long iffsize) unsigned short wIndex2; unsigned int dwPredicted2; } data; + static_assert(sizeof(SNJ2Struct) == 12, "the SN2J chunk is 12 bytes on disk"); #pragma pack(pop) #if(VQAVOC_ON && VQAAUDIO_ON) diff --git a/code/vqalib/vqafile.h b/code/vqalib/vqafile.h index 21e6bd07b..16896bb1a 100644 --- a/code/vqalib/vqafile.h +++ b/code/vqalib/vqafile.h @@ -109,6 +109,7 @@ typedef struct _VQAHeader { */ unsigned long AudioPreload; } VQAHeader; +static_assert(sizeof(VQAHeader) == 42, "the VQHD chunk is 42 bytes on disk"); /* Version type. */ #define VQAHD_VER1 1 From 50d8a6fcf8e50a17b3f7b0a4dce02132f39eeaa0 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Thu, 10 Sep 2026 01:32:18 +0200 Subject: [PATCH 07/18] Take a passenger's direction from ObjectClass::Direction (#130) --- code/unit.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/unit.cpp b/code/unit.cpp index b7cfb86d3..9274d3060 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -4495,8 +4495,7 @@ FacingType UnitClass::Desired_Load_Dir(ObjectClass * passenger, Cell & moveto) c FacingType face = FACING_N; FacingType faceto; if (passenger != NULL) { - DirType direction = direction.Direction(Center_Coord(), passenger->Center_Coord()); - faceto = (FacingType)direction.As_Dir256(); + faceto = (FacingType)Direction(passenger).As_Dir256(); } else { faceto = (FacingType)(PrimaryFacing.Current().Right_180()).As_Dir256(); } From c604cf6c504c801ce17d455d70e43777236bedb2 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 03:11:06 +0300 Subject: [PATCH 08/18] Stage zone and subzone links in std containers --- code/map.cpp | 281 +++++++++++-------------------------------------- code/map.h | 23 ++-- code/mouse.cpp | 9 +- code/zone.hh | 27 +++-- 4 files changed, 94 insertions(+), 246 deletions(-) diff --git a/code/map.cpp b/code/map.cpp index ad65bac3f..9a7c06730 100644 --- a/code/map.cpp +++ b/code/map.cpp @@ -78,7 +78,6 @@ #include "dbgprint.h" #include "foot.h" #include "globals.h" -#include "hashtable.h" #include "house.h" #include "houstype.h" #include "incdec.h" @@ -209,7 +208,6 @@ FacingType BridgeSideFacings[BRIDGE_COUNT] = { }; -int SubzoneHash(unsigned int const & key); unsigned int Pick_Random_UInt(unsigned int start, unsigned int end); double Random_Fraction(void); @@ -246,22 +244,10 @@ MapClass::~MapClass(void) CellZones = NULL; } - if (ZoneAdjacency != NULL) { - delete ZoneAdjacency; - ZoneAdjacency = NULL; - } - if (CellSubzones != NULL) { delete [] CellSubzones; CellSubzones = NULL; } - - for (int i = 0; i < SUBZONE_COUNT; i++) { - if (SubzoneConnectionHashTable[i] != NULL) { - delete SubzoneConnectionHashTable[i]; - SubzoneConnectionHashTable[i] = NULL; - } - } } @@ -275,8 +261,7 @@ void MapClass::Serialize(SaveStreamClass & stream) { BASECLASS::Serialize(stream); - // ZoneAdjacency -- the zone graph. These tables are raw heap blocks that MouseClass::Load - // allocates and reads outside the archive. + // ZoneAdjacency -- scratch for the zone rebuild, which fills it again from the loaded terrain. // Zones stream.Serialize(ZoneCount); // ZoneConnections -- likewise part of the zone graph, read outside the archive. @@ -284,7 +269,7 @@ void MapClass::Serialize(SaveStreamClass & stream) stream.Serialize(CellZoneCount); // CellSubzones -- the subzone graph, grown again from the loaded terrain. // SubzoneTrackingEntryCount - // SubzoneConnectionHashTable + // SubzoneConnectionStaging // SubzoneTracking stream.Serialize(PendingBridgeCells); stream.Serialize(DirtyIceCells); @@ -526,14 +511,9 @@ void MapClass::One_Time(void) */ Alloc_Cells(); - if (ZoneAdjacency == NULL) { - ZoneAdjacency = new ZONE_PAIR_HASH_SET(20, 256, SubzoneHash); - } - int i; for (i = 0; i < ARRAY_SIZE(SubzoneTracking); i++) { SubzoneTracking[i].Clear(); - SubzoneConnectionHashTable[i] = new SUBZONE_CONNECTION_HASH_SET(20, 256, SubzoneHash); } for (i = 0; i < MZONE_COUNT; i++) { @@ -2781,26 +2761,13 @@ ObjectClass * MapClass::Close_Object(Coord const & coord) const /// -/// Packs a pair of zone numbers into a single key. -/// The zone adjacency table records which zones touch by storing pairs in this packed -/// form, so that a pair can be added and found as one value. +/// Stages a link between two subzones for the rebuild in progress to write out. +/// A rebuild stages the same pair many times and the first staging wins, so the cross block +/// flag is the one from the fill that reached the boundary first. /// -/// Returns with the two zone numbers packed into one key. -static unsigned Zone_Pack32(int zone1, int zone2) +static void Stage_Subzone_Link(SubzoneLinkStaging & staging, int subzone1, int subzone2, bool crossblock) { - return(zone2 | (zone1 << 16)); -} - - -/// -/// Packs a pair of zone numbers into a bucket index. -/// Use this routine to find which bucket of the zone adjacency table a pair of -/// neighboring zones is filed under. -/// -/// Returns with the bucket index for the pair of zones. -static unsigned Zone_Pack8(int zone1, int zone2) -{ - return(zone2 & 0xF | ((zone1 & 0xF) << 4)); + staging.try_emplace(ZonePair((unsigned short)subzone1, (unsigned short)subzone2), crossblock); } @@ -2836,7 +2803,7 @@ int MapClass::Zone_Reset(void) DynamicVectorClass vec; vec.Set_Growth_Step(300); - ZoneAdjacency->Clear(); + ZoneAdjacency.clear(); for (i = 0; i < MZONE_COUNT; i++) { if (Zones[i] != NULL) { @@ -2884,7 +2851,7 @@ int MapClass::Zone_Reset(void) if (to_zone < from_zone) { std::swap(to_zone, from_zone); } - ZoneAdjacency->Add_Object(ZONE_PAIR_HASH_SET::ObjectType(from_zone, to_zone)); + ZoneAdjacency.emplace((unsigned short)from_zone, (unsigned short)to_zone); } } } @@ -2894,20 +2861,9 @@ int MapClass::Zone_Reset(void) zone_degree[i] = 0; } - for (i = 0; i < 256; i++) { - ZONE_PAIR_HASH_SET::BucketType & bucket = ZoneAdjacency->Buckets[i]; - j = bucket.Count(); - if (j > 0) { - ZONE_PAIR_HASH_SET::ObjectType * obj = &bucket[0]; - do { - unsigned int value = obj->Value; - unsigned short zone1 = LOWORD(value); - unsigned short zone2 = HIWORD(value); - zone_degree[zone1]++; - zone_degree[zone2]++; - obj++; - } while (--j); - } + for (ZonePair const & pair : ZoneAdjacency) { + zone_degree[pair.second]++; + zone_degree[pair.first]++; } unsigned short ** zone_neighbors = new unsigned short *[ZoneCount]; @@ -2919,22 +2875,11 @@ int MapClass::Zone_Reset(void) zone_degree[i] = 0; } - for (i = 0; i < 256; i++) { - ZONE_PAIR_HASH_SET::BucketType & bucket = ZoneAdjacency->Buckets[i]; - j = bucket.Count(); - if (j > 0) { - ZONE_PAIR_HASH_SET::ObjectType * obj = &bucket[0]; - do { - unsigned int value = obj->Value; - unsigned short zone1 = LOWORD(value); - unsigned short zone2 = HIWORD(value); - zone_neighbors[zone1][zone_degree[zone1]] = zone2; - zone_neighbors[zone2][zone_degree[zone2]] = zone1; - zone_degree[zone1]++; - zone_degree[zone2]++; - obj++; - } while (--j); - } + for (ZonePair const & pair : ZoneAdjacency) { + zone_neighbors[pair.second][zone_degree[pair.second]] = pair.first; + zone_neighbors[pair.first][zone_degree[pair.first]] = pair.second; + zone_degree[pair.second]++; + zone_degree[pair.first]++; } unsigned char * zone_passability = new unsigned char[ZoneCount]; @@ -2993,19 +2938,6 @@ int MapClass::Zone_Reset(void) } -/// -/// Computes the hash of a packed pair of zone numbers. -/// The zone adjacency table and the subzone connection tables are all constructed with -/// this routine as their hash function. -/// -/// The packed pair of zone numbers to hash. -/// Returns with the bucket that the pair belongs in. -int SubzoneHash(unsigned int const & key) -{ - return(key & 0xF | ((key >> 12) & 0xF)); -} - - /*********************************************************************************************** * MapClass::Zone_Span -- Flood fills the specified zone from the cell origin. * * * @@ -3053,7 +2985,7 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) int begin_zone = begin->ZoneID; if (begin_zone != 0 && (abs(begin->Height - cell_height) < 2 || nopass) && begin_zone != LastAdjacentZone && begin_zone != (unsigned short)zone) { - ZoneAdjacency->Add_Object(ZONE_PAIR_HASH_SET::ObjectType(begin_zone, (unsigned short)zone)); + ZoneAdjacency.emplace((unsigned short)begin_zone, (unsigned short)zone); LastAdjacentZone = begin_zone; } @@ -3073,7 +3005,7 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) int end_zone = end->ZoneID; if (end_zone != 0 && (abs(end->Height - cell_height) < 2 || nopass) && end_zone != LastAdjacentZone && end_zone != (unsigned short)zone) { - ZoneAdjacency->Add_Object(ZONE_PAIR_HASH_SET::ObjectType(end_zone, (unsigned short)zone)); + ZoneAdjacency.emplace((unsigned short)end_zone, (unsigned short)zone); LastAdjacentZone = end_zone; } @@ -3113,7 +3045,7 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) } } else { if (zzone != (unsigned short)zone && zzone != LastAdjacentZone && (abs(fbegin->Height - adjacent->Height) < 2 || nopass)) { - ZoneAdjacency->Add_Object(ZONE_PAIR_HASH_SET::ObjectType(zzone, (unsigned short)zone)); + ZoneAdjacency.emplace((unsigned short)zzone, (unsigned short)zone); LastAdjacentZone = zzone; } fbegin++; @@ -3144,7 +3076,7 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) } } else { if (id != (unsigned short)zone && id != LastAdjacentZone && (abs(fbegin2->Height - adjacent->Height) < 2 || nopass)) { - ZoneAdjacency->Add_Object(ZONE_PAIR_HASH_SET::ObjectType(id, (unsigned short)zone)); + ZoneAdjacency.emplace((unsigned short)id, (unsigned short)zone); LastAdjacentZone = id; } fbegin2++; @@ -9739,17 +9671,11 @@ void MapClass::Shutdown(void) } } - if (ZoneAdjacency != NULL) { - delete ZoneAdjacency; - ZoneAdjacency = NULL; - } + ZoneAdjacency.clear(); for (index = 0; index < SUBZONE_COUNT; index++) { SubzoneTracking[index].Clear(); - if (SubzoneConnectionHashTable[index] != NULL) { - delete SubzoneConnectionHashTable[index]; - SubzoneConnectionHashTable[index] = NULL; - } + SubzoneConnectionStaging[index].clear(); } VeinholeMonsterClass::Reset(); @@ -10034,15 +9960,10 @@ void MapClass::Reset_All_Subzones(void) /// The subzone level to rebuild. void MapClass::Reset_Subzone(int subzone) { - /* - * Clear out every bucket of the connection hash set for this subzone level. - */ - SUBZONE_CONNECTION_HASH_SET * set = SubzoneConnectionHashTable[subzone]; + SubzoneConnectionStaging[subzone].clear(); + DynamicVectorClass * track = &SubzoneTracking[subzone]; CellSubzoneStruct * subend = &CellSubzones[CellZoneCount]; - for (int bucket = 0; bucket < set->NumBuckets; bucket++) { - set->Buckets[bucket].Clear(); - } /* * Reset the per-cell subzone identifiers for this level and refresh the @@ -10140,33 +10061,7 @@ void MapClass::Reset_Subzone(int subzone) */ Register_Subzone_Zone_Connections(subzone); - /* - * Walk every bucket of the connection hash set and register both endpoints - * of each recorded connection into the subzone tracking list. - */ - for (int bucket_index = 0; bucket_index < 256; bucket_index++) { - if (set->Buckets[bucket_index].Count() > 0) { - SUBZONE_CONNECTION_HASH_SET::ObjectType * object = &set->Buckets[bucket_index][0]; - for (int index = set->Buckets[bucket_index].Count(); index > 0; index--) { - unsigned packed = object->Value.SubzoneID; - WORD low = LOWORD(packed); - WORD high = HIWORD(packed); - bool costly = object->Value.IsCrossBlock; - - SubzoneConnectionStruct conn; - conn.SubzoneID = high; - conn.IsCrossBlock = costly; - (*track)[low].Connections.Add(conn); - - SubzoneConnectionStruct conn2; - conn2.SubzoneID = low; - conn2.IsCrossBlock = costly; - (*track)[high].Connections.Add(conn2); - - object++; - } - } - } + Register_Staged_Subzone_Connections(subzone); } @@ -10189,7 +10084,6 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz CellSubzoneStruct * begin = seed; CellSubzoneStruct * end = seed; - SUBZONE_CONNECTION_HASH_SET::ObjectType entry; int x = cell.X; int y = cell.Y; @@ -10202,7 +10096,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz int end_x = x; int ymax = bounds.Height + bounds.Y - 1; - SUBZONE_CONNECTION_HASH_SET * table = SubzoneConnectionHashTable[subzone_level]; + SubzoneLinkStaging & staging = SubzoneConnectionStaging[subzone_level]; int prev_height = seed->Height; /* @@ -10234,10 +10128,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz if (In_Local_Radar(probe, true)) { probe = Cell(x + 1, y); if (In_Local_Radar(probe, true)) { - int packed_pair = Zone_Pack32(begin_subzone, (unsigned short)subzone_id); - entry.Value.IsCrossBlock = false; - entry.Key = entry.Value.SubzoneID = packed_pair; - table->Add_Object(Zone_Pack8(begin_subzone, (unsigned short)subzone_id), entry); + Stage_Subzone_Link(staging, begin_subzone, (unsigned short)subzone_id, false); last_adjacent = begin_subzone; } } @@ -10269,10 +10160,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz if (In_Local_Radar(probe, true)) { probe = Cell(end_x - 1, y); if (In_Local_Radar(probe, true)) { - int packed_pair = Zone_Pack32(end_subzone, (unsigned short)subzone_id); - entry.Value.IsCrossBlock = false; - entry.Key = entry.Value.SubzoneID = packed_pair; - table->Add_Object(Zone_Pack8(end_subzone, (unsigned short)subzone_id), entry); + Stage_Subzone_Link(staging, end_subzone, (unsigned short)subzone_id, false); last_adjacent = end_subzone; } } @@ -10324,17 +10212,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz shadow_cell.Y = y - 1; shadow_cell.X = x; if (In_Local_Radar(shadow_cell, true)) { - int packed_pair = Zone_Pack32(shadow_subzone, (unsigned short)subzone_id); - if (x < xmin) { - entry.Value.IsCrossBlock = true; - } else { - entry.Value.IsCrossBlock = false; - if (x > xmax) { - entry.Value.IsCrossBlock = true; - } - } - entry.Key = entry.Value.SubzoneID = packed_pair; - table->Add_Object(Zone_Pack8(shadow_subzone, (unsigned short)subzone_id), entry); + Stage_Subzone_Link(staging, shadow_subzone, (unsigned short)subzone_id, x < xmin || x > xmax); last_adjacent = shadow_subzone; } } @@ -10393,17 +10271,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz shadow_cell.X = x; shadow_cell.Y = y + 1; if (In_Local_Radar(shadow_cell, true)) { - int packed_pair = Zone_Pack32(shadow_subzone, (unsigned short)subzone_id); - if (x < xmin) { - entry.Value.IsCrossBlock = true; - } else { - entry.Value.IsCrossBlock = false; - if (x > xmax) { - entry.Value.IsCrossBlock = true; - } - } - entry.Key = entry.Value.SubzoneID = packed_pair; - table->Add_Object(Zone_Pack8(shadow_subzone, (unsigned short)subzone_id), entry); + Stage_Subzone_Link(staging, shadow_subzone, (unsigned short)subzone_id, x < xmin || x > xmax); last_adjacent = shadow_subzone; } } @@ -10449,8 +10317,7 @@ void MapClass::Register_Subzone_Zone_Connections(int subzone) /// The subzone level whose staging set receives the links. void MapClass::Register_Zone_Connection_Entries(ZoneConnectionClass & connection, int index) { - SUBZONE_CONNECTION_HASH_SET::ObjectType connstr; - SUBZONE_CONNECTION_HASH_SET * set = SubzoneConnectionHashTable[index]; + SubzoneLinkStaging & staging = SubzoneConnectionStaging[index]; Cell from = connection.From; Cell to = connection.To; @@ -10493,36 +10360,47 @@ void MapClass::Register_Zone_Connection_Entries(ZoneConnectionClass & connection int from_zone = CellSubzones[Get_Cell_Subzone_Index(from)].SubzoneID[index]; int to_zone = CellSubzones[Get_Cell_Subzone_Index(to)].SubzoneID[index]; - int value = Zone_Pack32(from_zone, to_zone); - int bucket_index = Zone_Pack8(from_zone, to_zone); - connstr.Key = value; - connstr.Value = SubzoneConnectionStruct(value); - connstr.Value.IsCrossBlock = 0; - set->Add_Object(bucket_index, connstr); + Stage_Subzone_Link(staging, from_zone, to_zone, false); } { int from_zone = CellSubzones[Get_Cell_Subzone_Index(enter1)].SubzoneID[index]; int to_zone = CellSubzones[Get_Cell_Subzone_Index(newcell1)].SubzoneID[index]; - int value = Zone_Pack32(from_zone, to_zone); - int bucket_index = Zone_Pack8(from_zone, to_zone); - connstr.Key = value; - connstr.Value = SubzoneConnectionStruct(value); - connstr.Value.IsCrossBlock = 0; - set->Add_Object(bucket_index, connstr); + Stage_Subzone_Link(staging, from_zone, to_zone, false); } { int from_zone = CellSubzones[Get_Cell_Subzone_Index(enter2)].SubzoneID[index]; int to_zone = CellSubzones[Get_Cell_Subzone_Index(newcell2)].SubzoneID[index]; - int value = Zone_Pack32(from_zone, to_zone); - int bucket_index = Zone_Pack8(from_zone, to_zone); - connstr.Key = value; - connstr.Value = SubzoneConnectionStruct(value); - connstr.Value.IsCrossBlock = 0; - set->Add_Object(bucket_index, connstr); + Stage_Subzone_Link(staging, from_zone, to_zone, false); + } +} + + +/// +/// Writes the links staged for one subzone level into the two subzones each one joins. +/// Every link is recorded in both subzones, so the graph the route search walks is undirected. +/// The route search reads a subzone's neighbors in list order and settles a tie among equally +/// cheap blocks by which of them it reached first, so the order the links are written in is +/// the order it breaks those ties in. +/// +/// The subzone level whose staged links are to be written out. +void MapClass::Register_Staged_Subzone_Connections(int subzone) +{ + DynamicVectorClass & track = SubzoneTracking[subzone]; + + for (auto const & [link, crossblock] : SubzoneConnectionStaging[subzone]) { + SubzoneConnectionStruct conn; + conn.SubzoneID = link.first; + conn.IsCrossBlock = crossblock; + track[link.second].Connections.Add(conn); + + SubzoneConnectionStruct conn2; + conn2.SubzoneID = link.second; + conn2.IsCrossBlock = crossblock; + track[link.first].Connections.Add(conn2); } } @@ -10940,14 +10818,9 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) bounds.X = cell.X - cell.X % bounds.Width; bounds.Y = cell.Y - cell.Y % bounds.Height; - /* - * Clear out every bucket of the connection hash set for this level. - */ + SubzoneConnectionStaging[subzone].clear(); + DynamicVectorClass collected; - SUBZONE_CONNECTION_HASH_SET * set = SubzoneConnectionHashTable[subzone]; - for (int bucket = 0; bucket < set->NumBuckets; bucket++) { - set->Buckets[bucket].Clear(); - } DynamicVectorClass * track = &SubzoneTracking[subzone]; @@ -11055,33 +10928,7 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) } } - /* - * Walk every bucket of the connection hash set and register both endpoints - * of each recorded connection into the subzone tracking list. - */ - for (int bucket_index = 0; bucket_index < 256; bucket_index++) { - if (set->Buckets[bucket_index].Count() > 0) { - SUBZONE_CONNECTION_HASH_SET::ObjectType * object = &set->Buckets[bucket_index][0]; - for (int index = set->Buckets[bucket_index].Count(); index > 0; index--) { - unsigned packed = object->Value.SubzoneID; - WORD low = LOWORD(packed); - WORD high = HIWORD(packed); - bool costly = object->Value.IsCrossBlock; - - SubzoneConnectionStruct conn; - conn.SubzoneID = high; - conn.IsCrossBlock = costly; - (*track)[low].Connections.Add(conn); - - SubzoneConnectionStruct conn2; - conn2.SubzoneID = low; - conn2.IsCrossBlock = costly; - (*track)[high].Connections.Add(conn2); - - object++; - } - } - } + Register_Staged_Subzone_Connections(subzone); } /* diff --git a/code/map.h b/code/map.h index e43c8b26d..1776d86dc 100644 --- a/code/map.h +++ b/code/map.h @@ -54,8 +54,6 @@ class CellClass; class BuildingTypeClass; class FootClass; class SaveStreamClass; -template -class HashTableClass; class MapClass: public GScreenClass @@ -147,6 +145,7 @@ class MapClass: public GScreenClass int Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subzone_id, Rect const & bounds, Cell const & cell); void Register_Subzone_Zone_Connections(int subzone); void Register_Zone_Connection_Entries(ZoneConnectionClass & connection, int index); + void Register_Staged_Subzone_Connections(int subzone); Cell Get_Bridge_Zone_Connection_Cell(CellClass * cptr, bool isbridge); Cell Get_Zone_Connection_Destination(Cell const & cell, Cell const & reference); Cell Find_Bridge_Span_End_Cell(Cell const & cell, Cell const & reference); @@ -347,12 +346,13 @@ class MapClass: public GScreenClass virtual void Set_Local_Dimensions(Rect const & size); /* - * This is the set of base terrain zones that touch one another, keyed by a packed - * pair of zone IDs. It is filled while the zones are being flood filled, and gains a - * pair for every bridge and tunnel that is currently intact, so that the zones can - * then be unioned into the movement zones of each MZoneType. + * These are the pairs of base terrain zones that touch one another, filled while the + * zones are flood filled and gaining a pair for every intact bridge and tunnel, so + * that the zones can be unioned into the movement zones of each MZoneType. A pair is + * held as the site that found it wrote it, so one touching may appear as both (a,b) + * and (b,a). */ - ZONE_PAIR_HASH_SET * ZoneAdjacency; + ZonePairSet ZoneAdjacency; /* * This records the movement zones for this map. Cells share the same zone @@ -392,12 +392,12 @@ class MapClass: public GScreenClass /* * This is the subzone graph itself, one slot per level of coarseness. SubzoneTracking * holds a level's subzone records and SubzoneTrackingEntryCount how many of them are - * valid, while SubzoneConnectionHashTable is only scratch: a rebuild gathers adjacency - * pairs there so that duplicates fall out, then unpacks them into the records and - * clears it again. + * valid, while SubzoneConnectionStaging is only scratch: a rebuild clears it, gathers + * the level's adjacency pairs there so that duplicates fall out, then unpacks them + * into the records. What it holds after that is left until the next rebuild clears it. */ int SubzoneTrackingEntryCount[SUBZONE_COUNT]; - SUBZONE_CONNECTION_HASH_SET * SubzoneConnectionHashTable[SUBZONE_COUNT]; + SubzoneLinkStaging SubzoneConnectionStaging[SUBZONE_COUNT]; DynamicVectorClass SubzoneTracking[SUBZONE_COUNT]; /* @@ -555,5 +555,4 @@ class MapClass: public GScreenClass extern CellClass BlubCell; -int SubzoneHash(unsigned int const & key); extern int MZonePassability[MZONE_COUNT][PASSABLE_COUNT]; diff --git a/code/mouse.cpp b/code/mouse.cpp index ea7bdc390..2f94c3705 100644 --- a/code/mouse.cpp +++ b/code/mouse.cpp @@ -47,7 +47,6 @@ #include "builtype.h" #include "cell.h" #include "data.h" -#include "hashtable.h" #include "isotype.h" #include "mixfile.h" #include "overtype.h" @@ -416,8 +415,7 @@ HRESULT MouseClass::Load(IStream * stream) CellSubzones = NULL; delete CellZones; CellZones = NULL; - delete ZoneAdjacency; - ZoneAdjacency = NULL; + ZoneAdjacency.clear(); for (i = 0; i < SUBZONE_COUNT; i++) { SubzoneTracking[i].Clear(); @@ -429,8 +427,7 @@ HRESULT MouseClass::Load(IStream * stream) } for (i = 0; i < SUBZONE_COUNT; i++) { - delete SubzoneConnectionHashTable[i]; - SubzoneConnectionHashTable[i] = NULL; + SubzoneConnectionStaging[i].clear(); } Array.Clear(); @@ -469,13 +466,11 @@ HRESULT MouseClass::Load(IStream * stream) CellSubzones = new CellSubzoneStruct[CellZoneCount]; CellZones = new CellZoneStruct[CellZoneCount]; - ZoneAdjacency = new ZONE_PAIR_HASH_SET(20, 256, SubzoneHash); for (i = 0; i < SUBZONE_COUNT; i++) { int v = (1 << (i + 1)); SubzoneTracking[i].Clear(); SubzoneTracking[i].Set_Growth_Step((4 * PlayRect.Width * PlayRect.Height) / (v * v)); - SubzoneConnectionHashTable[i] = new SUBZONE_CONNECTION_HASH_SET(20, 256, SubzoneHash); } result = stream->Read(CellZones, sizeof(*CellZones) * CellZoneCount, NULL); diff --git a/code/zone.hh b/code/zone.hh index 177125280..ba37df08a 100644 --- a/code/zone.hh +++ b/code/zone.hh @@ -16,10 +16,11 @@ #include "coord.h" #include "vector.h" -#include "passblty.hh" +#include +#include +#include -template -class HashTableClass; +#include "passblty.hh" /********************************************************************** ** A base is broken up into several zones. This type enumerates the @@ -203,17 +204,13 @@ struct CellSubzoneStruct struct SubzoneConnectionStruct { SubzoneConnectionStruct(void) : SubzoneID(0), IsCrossBlock(false) {} - SubzoneConnectionStruct(int subzone_id) : SubzoneID(subzone_id), IsCrossBlock(false) {} SubzoneConnectionStruct(SubzoneConnectionStruct const &that) : SubzoneID(that.SubzoneID), IsCrossBlock(that.IsCrossBlock) {} bool operator==(const SubzoneConnectionStruct & that) const { return(SubzoneID == that.SubzoneID); } bool operator!=(const SubzoneConnectionStruct & that) const { return(SubzoneID != that.SubzoneID); } /* - * This is the subzone that the owning subzone connects to. While a connection is still - * staged in the SubzoneConnectionHashTable this holds both IDs packed together instead, as - * (neighbor << 16) | subzone, so that duplicate pairs fall out of the staging set before - * they are unpacked into the two subzones' adjacency lists. + * This is the subzone that the owning subzone connects to. */ int SubzoneID; @@ -272,5 +269,15 @@ struct SubzoneTrackingStruct int ThreatRegion; }; -typedef HashTableClass ZONE_PAIR_HASH_SET; -typedef HashTableClass SUBZONE_CONNECTION_HASH_SET; +/* + * A pair of ids, ordered as the site that staged it wrote them rather than smallest first, + * because the two staging containers below hold (a,b) and (b,a) as separate entries. + */ +using ZonePair = std::pair; + +using ZonePairSet = std::set; + +/* + * The subzone links a rebuild has staged, each mapped to whether it crosses a fill block. + */ +using SubzoneLinkStaging = std::map; From fce04341398bc81dc863d083f3a6ec75b3b978ad Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 03:22:49 +0300 Subject: [PATCH 09/18] Track radar blips per pixel --- code/hashtable.h | 253 ----------------------------------------------- code/radar.cpp | 187 ++++++++++++++++------------------- code/radar.h | 79 +++++++-------- 3 files changed, 123 insertions(+), 396 deletions(-) delete mode 100644 code/hashtable.h diff --git a/code/hashtable.h b/code/hashtable.h deleted file mode 100644 index 49c5869c2..000000000 --- a/code/hashtable.h +++ /dev/null @@ -1,253 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include "vector.h" - - -/* - * A single key/value entry stored within a hash table bucket. Equality and - * the "empty" test are keyed off the Key alone. - */ -template -struct HashObject { - HashObject(void) {} - HashObject(K const & key, V const & value) : Key(key), Value(value) {} - HashObject(HashObject const & that) : Key(that.Key), Value(that.Value) {} - - bool operator!=(const HashObject & that) const { return(Key != that.Key); } - bool operator==(const HashObject & that) const { return(Key == that.Key); } - - bool operator!() const { return(Key == 0); } - - /* - * This is the key the entry is filed under. It carries the entry's whole identity, - * so a bucket never holds two entries whose keys match. - */ - K Key; - - /* - * This is the value filed under the key. A table with nothing of its own to store - * packs everything it needs into the key and merely repeats it here. - */ - V Value; -}; - - -/* - * A chained (separate-chaining) hash table. It owns a fixed array of - * NumBuckets buckets, each bucket being a growable vector of key/value - * pairs. Collisions within a bucket are resolved by a linear scan. - * The same template backs every hash table in the game -- the map's - * subzone/zone connection tables and the radar object tracking table -- - * which differ only in their key/value types and in how the owning class - * derives a bucket index for a given key. Because of that, two styles of - * insertion coexist: - * * The caller computes the bucket index itself and passes it in - * (the subzone connection tables, whose keys are packed zone ids). - * * The table hashes the key directly via Key.Hash() and picks head or - * tail placement via Key.Use_Head() (the radar tracking table). - * Only the members actually used by a given instantiation are generated, - * so a key type need not provide Hash()/Use_Head() unless the key-hashed - * overloads are called for it. - * The owning class is a friend and also reaches into Buckets directly for - * iteration and some lookups. - */ -template -class HashTableClass -{ - public: - friend class RadarClass; - - typedef HashObject ObjectType; - typedef DynamicVectorClass BucketType; - typedef int (*HASH_FUNC)(const K &); - - public: - HashTableClass(int growth_step, int num_buckets, HASH_FUNC hash_func); - ~HashTableClass(void); - - /* - * Caller-hashed insertion. The packed key supplies its own bucket - * index, or the index is passed in explicitly. - */ - bool Add_Object(ObjectType const & object); - bool Add_Object(int bucket_index, ObjectType const & object); - - /* - * Key-hashed insertion: bucket index comes from Key.Hash() and the - * entry is placed at the head or tail based on Key.Use_Head(). - */ - bool Add_Object(ObjectType const & o, bool head); - - bool Get(const K & key, V & out); - bool Remove_Object(const K & key, const V & value); - - void Destroy(void); - void Clear(void); - - public: - /* - * The array of NumBuckets buckets. - */ - BucketType * Buckets; - - /* - * A pointer to the function for hashing an item's key. Nothing calls - * through it -- the bucket index is derived at each call site instead. - */ - HASH_FUNC HashFunction; - - /* - * The number of buckets in the hash table. - */ - int NumBuckets; - - /* - * When a bucket has insufficient room left, it will grow by the number - * of objects specified by this value. - */ - int GrowthStep; -}; - - -template -inline HashTableClass::HashTableClass(int growth_step, int num_buckets, HASH_FUNC hash_func) : - HashFunction(hash_func), - NumBuckets(num_buckets), - GrowthStep(growth_step) -{ - Buckets = new BucketType[num_buckets]; - for (int index = 0; index < NumBuckets; index++) { - Buckets[index].Set_Growth_Step(growth_step); - } -} - - -template -inline HashTableClass::~HashTableClass(void) -{ - Destroy(); -} - - -template -inline bool HashTableClass::Add_Object(ObjectType const & object) -{ - unsigned packed = object.Value | (object.Key << 16); - BucketType & bucket = Buckets[object.Value & 0xF | ((object.Key & 0xF) << 4)]; - - if (bucket.Count() > 0) { - ObjectType * obj = &bucket[0]; - for (int index = bucket.Count() - 1; index >= 0; index--) { - if (ObjectType(packed, packed) == *obj) { - return(false); - } - obj++; - } - } - - return(bucket.Add(ObjectType(packed, packed))); -} - - -template -inline bool HashTableClass::Add_Object(int bucket_index, ObjectType const & object) -{ - BucketType & bucket = Buckets[bucket_index]; - - if (bucket.Count() > 0) { - ObjectType * obj = &bucket[0]; - for (int index = bucket.Count() - 1; index >= 0; index--) { - if ((K &)object == (K &)*obj) { - - return(false); - } - obj++; - } - } - bucket.Add(object); - return(true); -} - - -template -inline bool HashTableClass::Add_Object(const ObjectType & o, bool head) -{ - BucketType & bucket = Buckets[o.Key.Hash()]; - if (bucket.Count() > 0) { - const ObjectType * oo = &bucket[0]; - - for (int index = 0; index < bucket.Count(); index++) { - if (oo[index] == o) { - return(false); - } - } - } - - if (o.Key.Use_Head()) { - bucket.Add_Head(o); - } else { - bucket.Add(o); - } - return(true); -} - - -template -inline bool HashTableClass::Get(const K & key, V & out) -{ - BucketType & bucket = Buckets[key.Hash()]; - int index = bucket.Count() - 1; - out = V(); - - if (index >= 0) { - ObjectType * oo = &bucket[0]; - while (true) { - V value = (V &)bucket[index].Key; - if ((K &)oo[index].Key == (K &)key) { - out = value; - return(true); - } - index--; - if (index < 0) { - break; - } - } - } - - return(false); -} - - -template -inline bool HashTableClass::Remove_Object(const K & key, const V & value) -{ - BucketType * bucket = &Buckets[key.Hash()]; - ObjectType a(key, value); - - return(bucket->Delete(a)); -} - - -template -inline void HashTableClass::Destroy(void) -{ - delete[] Buckets; -} - - -template -inline void HashTableClass::Clear(void) -{ - for (int index = 0; index < NumBuckets; index++) { - Buckets[index].Clear(); - } -} diff --git a/code/radar.cpp b/code/radar.cpp index 0077423de..03858b0e1 100644 --- a/code/radar.cpp +++ b/code/radar.cpp @@ -108,15 +108,6 @@ #include -/// -/// Should this tracked object go at the head of its hash bucket? -/// The radar draws only the first object it finds on any given pixel, so the local player's -/// own units are placed at the front of the bucket and win the blip. -/// -/// bool; Does the tracked object belong to the local player? -inline bool RadarTrackingStruct::Use_Head(void) const { return(Object->House == PlayerPtr); } - - RadarClass::RTacticalClass RadarClass::RadarButton; void const * RadarClass::RadarAnim = NULL; @@ -155,7 +146,6 @@ RadarClass::RadarClass(void) : RadarCellWidth(0), RadarCellHeight(0), CellRedrawRect(0,0,0,0), - RadarTrackingTable(0), PixelFlags(0), ZoomFactor(0), RadarScale(1), @@ -178,7 +168,7 @@ RadarClass::RadarClass(void) : /// -/// Destroys the radar map, releasing its surfaces and object tracking table. +/// Destroys the radar map, releasing its surfaces and dropping its object tracking. /// RadarClass::~RadarClass(void) { @@ -1341,7 +1331,7 @@ void RadarClass::Plot_Radar_Background(void) /// -/// Adds an object to the radar tracking table. +/// Adds an object to the radar tracking. /// This routine registers the object at a radar pixel so that it will be drawn as a blip on /// the next radar render. A unit that lands outside the radar surface is pulled back to the /// nearest edge pixel; a building in that position is simply not tracked. @@ -1369,12 +1359,9 @@ void RadarClass::Radar_Track(TechnoClass * techno, Point2D point) techno->RadarPos = point; } - RADAR_HASH_TABLE::ObjectType track; - track.Key.Position = point; - track.Key.Object = techno; - track.Value = techno; - - if (RadarTrackingTable->Add_Object(track, true)) { + // The radar draws only the head of a pixel's list, so the local player's own objects go + // there and win the blip. + if (RadarTracking.Track(point, techno, techno->House == PlayerPtr)) { Radar_Pixel(point); IsToRedraw = true; } @@ -1382,7 +1369,7 @@ void RadarClass::Radar_Track(TechnoClass * techno, Point2D point) /// -/// Removes an object from the radar tracking table. +/// Removes an object from the radar tracking. /// This routine is called when a tracked object moves off a radar pixel or leaves the game. /// The vacated pixel is flagged for redraw so that whatever lies beneath it shows through /// again. @@ -1390,10 +1377,7 @@ void RadarClass::Radar_Track(TechnoClass * techno, Point2D point) /// The radar pixel the object was being tracked at. void RadarClass::Radar_Untrack(TechnoClass * techno, Point2D point) { - RadarTrackingStruct track; - track.Object = techno; - track.Position = point; - if (RadarTrackingTable->Remove_Object(track, techno)) { + if (RadarTracking.Untrack(point, techno)) { Radar_Pixel(point); IsToRedraw = true; } @@ -1428,38 +1412,75 @@ Point2D RadarClass::Coord_To_Radar_Pixel(Coord const & coord, bool clip) } -int RadarTrackingStruct::Hash_Old(RadarTrackingStruct const & s) +bool RadarTrackingClass::Track(Point2D const & pixel, TechnoClass * object, bool head) +{ + std::vector & objects = Blips[std::pair(pixel.X, pixel.Y)]; + + if (std::find(objects.begin(), objects.end(), object) != objects.end()) { + return(false); + } + + if (head) { + objects.insert(objects.begin(), object); + } else { + objects.push_back(object); + } + return(true); +} + + +bool RadarTrackingClass::Untrack(Point2D const & pixel, TechnoClass * object) { - return((int)((uintptr_t)s.Object + 251 * s.Position.X)); + auto found = Blips.find(std::pair(pixel.X, pixel.Y)); + if (found == Blips.end()) { + return(false); + } + + std::vector & objects = found->second; + auto entry = std::find(objects.begin(), objects.end(), object); + if (entry == objects.end()) { + return(false); + } + + objects.erase(entry); + if (objects.empty()) { + Blips.erase(found); + } + return(true); } -int RadarTrackingStruct::Hash2(RadarTrackingStruct const & s) + +TechnoClass * RadarTrackingClass::First(Point2D const & pixel) const { - return(s.Position.X + 251 * s.Position.Y); + auto found = Blips.find(std::pair(pixel.X, pixel.Y)); + return(found == Blips.end() ? NULL : found->second.front()); +} + + +TechnoClass * RadarTrackingClass::Last(Point2D const & pixel) const +{ + auto found = Blips.find(std::pair(pixel.X, pixel.Y)); + return(found == Blips.end() ? NULL : found->second.back()); } /// -/// Creates the radar's object tracking table. +/// Empties the radar's object tracking. /// void RadarClass::Init_Radar(void) { - RadarTrackingTable = new RADAR_HASH_TABLE(10, 256, RadarTrackingStruct::Hash2); + RadarTracking.Clear(); } /// /// Resets the radar map back to a blank slate. -/// This routine throws the object tracking table away and rebuilds the radar image from the +/// This routine throws the object tracking away and rebuilds the radar image from the /// current local map bounds. Every object is marked as untracked so that it registers itself /// again on its next logic pass. /// void RadarClass::Reset_Radar(void) { - if (RadarTrackingTable != NULL) { - delete RadarTrackingTable; - } - Init_Radar(); Map.Set_Local_Dimensions(Map.LocalRect); Compute_Radar_Image(); @@ -1487,10 +1508,7 @@ void RadarClass::Clear_Radar(void) delete BackgroundColors; BackgroundColors = NULL; } - if (RadarTrackingTable != NULL) { - delete RadarTrackingTable; - RadarTrackingTable = NULL; - } + RadarTracking.Clear(); if (PixelFlags != NULL) { delete [] PixelFlags; PixelFlags = NULL; @@ -1500,7 +1518,7 @@ void RadarClass::Clear_Radar(void) /// /// Handles the radar repairs needed after a save game is loaded. -/// The radar's surfaces and object tracking table are never saved, so this routine releases +/// The radar's surfaces and object tracking are never saved, so this routine releases /// the ones the previous scenario left behind and builds fresh ones from the loaded map. /// Every object is marked as untracked so that it registers itself again as the game resumes. /// @@ -1555,22 +1573,7 @@ void RadarClass::Plot_Radar_Pixel(Point2D const & point) coord.Z = Map.Get_Height_GL(coord); bool shadow = (MainWindow && Map.Is_Shrouded(coord)); - RadarTrackingStruct track; - track.Position = point; - track.Object = 0; - - TechnoClass *tech = NULL; - - RADAR_HASH_TABLE::BucketType &bucket = RadarTrackingTable->Buckets[track.Hash()]; - int count = bucket.Count(); - - for (int index = 0; index < count; index++) { - TechnoClass *candidate = bucket[index].Key.Object; - if (bucket[index].Key == track) { - tech = candidate; - break; - } - } + TechnoClass *tech = RadarTracking.First(point); if (tech != NULL) { HouseClass *house = tech->House; @@ -1616,48 +1619,36 @@ void RadarClass::Render_Tracked_Objects(void) { memset(PixelFlags, 0, RadarSurface->Get_Width() * RadarSurface->Get_Height() / 8 + 1); - for (int b = 0; b < 256; b++) { - - RADAR_HASH_TABLE::BucketType &bucket = RadarTrackingTable->Buckets[b]; - - int count = bucket.Count(); - - RADAR_HASH_TABLE::ObjectType *ptr = &bucket[0]; - - for (int t = 0; t < count; t++) { - RadarTrackingStruct *track = &ptr[t].Key; - TechnoClass *tech = track->Object; - - int id = track->Position.X + track->Position.Y * RadarSurface->Get_Width(); - int i = id >> 3; - int bit = 1 << (id & 7); - - if ((PixelFlags[i] & bit) == 0) { - PixelFlags[i] |= bit; + RadarTracking.For_Each_Pixel([this](Point2D const & pixel, TechnoClass * tech) { + int id = pixel.X + pixel.Y * RadarSurface->Get_Width(); + int i = id >> 3; + int bit = 1 << (id & 7); - ColorScheme *scheme = ColorSchemes[tech->House->Scheme]; + if ((PixelFlags[i] & bit) == 0) { + PixelFlags[i] |= bit; - if (tech->RTTI == RTTI_INFANTRY) { - InfantryClass *inf = (InfantryClass *)tech; - if (inf->Class->IsDisguised) { - scheme = ColorSchemes[PlayerPtr->Scheme]; - } - } + ColorScheme *scheme = ColorSchemes[tech->House->Scheme]; - int color = scheme->Bright; - ConvertClass *drawer = scheme->Converter; - if (drawer->Bytes_Per_Pixel() == 1) { - unsigned char *translator = (unsigned char *)drawer->Get_Translate_Table(); - color = translator[color]; - } else { - unsigned short *translator = (unsigned short *)drawer->Get_Translate_Table(); - color = translator[color]; + if (tech->RTTI == RTTI_INFANTRY) { + InfantryClass *inf = (InfantryClass *)tech; + if (inf->Class->IsDisguised) { + scheme = ColorSchemes[PlayerPtr->Scheme]; } + } - RadarSurface->Put_Pixel(track->Position, color); + int color = scheme->Bright; + ConvertClass *drawer = scheme->Converter; + if (drawer->Bytes_Per_Pixel() == 1) { + unsigned char *translator = (unsigned char *)drawer->Get_Translate_Table(); + color = translator[color]; + } else { + unsigned short *translator = (unsigned short *)drawer->Get_Translate_Table(); + color = translator[color]; } + + RadarSurface->Put_Pixel(pixel, color); } - } + }); } @@ -1830,15 +1821,9 @@ void RadarClass::Resolve_Radar_Point(Point2D const & point, Cell & cell, ObjectC { Point2D pt = point - RadarRect.TopLeft; - RadarTrackingStruct track; - track.Object = NULL; - track.Position = pt; - - /* - * The lookup key carries no object, so Get matches on position alone (via the - * non-const, position-only RadarTrackingStruct::operator==). - */ - RadarTrackingTable->Get(track, (TechnoClass *&)object); + // A click takes the object at the tail of the pixel's list, which is the last one to have + // been tracked there that did not go to the head. + object = RadarTracking.Last(pt); if (object != NULL) { cell = object->Destination_Coord(); @@ -1851,7 +1836,7 @@ void RadarClass::Resolve_Radar_Point(Point2D const & point, Cell & cell, ObjectC /// /// Lists the members the radar map holds. /// Only the pending update lists and the state of the radar display itself travel. The radar -/// surfaces, the object tracking table and the picture geometry are all rebuilt from the +/// surfaces, the object tracking and the picture geometry are all rebuilt from the /// loaded map by Post_Load_Radar_Fixup. /// /// The stream carrying the members. @@ -1880,7 +1865,7 @@ void RadarClass::Serialize(SaveStreamClass & stream) // RadarCellWidth -- measured again while the radar background is resampled. // RadarCellHeight // CellRedrawRect - // RadarTrackingTable -- rebuilt by Post_Load_Radar_Fixup, which also marks every object + // RadarTracking -- rebuilt by Post_Load_Radar_Fixup, which also marks every object // untracked so that it registers itself again. stream.Serialize(PixelStack); diff --git a/code/radar.h b/code/radar.h index 0339fb0b0..7c0db29f3 100644 --- a/code/radar.h +++ b/code/radar.h @@ -34,10 +34,13 @@ #include "coord.h" #include "display.h" -#include "hashtable.h" #include "stimer.h" #include "timer.h" +#include +#include +#include + #include "bsize.hh" class DSurface; @@ -46,44 +49,39 @@ template class DynamicVectorClass; typedef DynamicVectorClass FOUNDATION_LIST; -struct RadarTrackingStruct { - - RadarTrackingStruct(TechnoClass * object = NULL, int x = 0, int y = 0) : Object(object), Position(x, y) {} - RadarTrackingStruct(RadarTrackingStruct const & that) : Object(that.Object), Position(that.Position) {} - - /* - * This is the object that appears as a blip at the tracked position. A building covers - * several radar pixels, so it is tracked once for every pixel of its radar foundation. - */ - TechnoClass * Object; - - /* - * This is the radar pixel that the object occupies. The tracking table is hashed on this - * alone, so the radar can find whichever object sits on a given pixel. - */ - Point2D Position; - - bool operator==(const RadarTrackingStruct & that) const { return(Object == that.Object && Position == that.Position); } - bool operator!=(const RadarTrackingStruct & that) const { return(Object != that.Object || Position != that.Position); } - - /* - * The non-const overloads compare Position only. A lookup key is built with - * Object == NULL and relies on these to match on position alone (see Get), - * while Add and Remove use the const (Object + Position) overloads above. - */ - bool operator==(RadarTrackingStruct & that) { return(Position == that.Position); } - bool operator!=(RadarTrackingStruct & that) { return(Position != that.Position); } - - int Hash(void) const { return((Position.X - 5 * Position.Y) & 0xFF); } - - bool Use_Head(void) const; +/* + * The objects that show up as blips on the radar, listed by the radar pixel each one occupies. + * A building covers several pixels and is tracked once for each of them, and several objects + * can stand on one pixel, so a pixel holds a list rather than a single object. The radar draws + * only the object at the head of that list, and a click on the radar takes the one at its tail. + */ +class RadarTrackingClass +{ + public: + // Places the object at the head of the pixel's list when head is set. One already + // tracked at that pixel keeps its place. + bool Track(Point2D const & pixel, TechnoClass * object, bool head); + bool Untrack(Point2D const & pixel, TechnoClass * object); + + // The object the radar draws at a pixel, and the one a click there resolves to. + TechnoClass * First(Point2D const & pixel) const; + TechnoClass * Last(Point2D const & pixel) const; + + void Clear(void) { Blips.clear(); } + + // Visits each occupied pixel once, with the object the radar draws on it. + template + void For_Each_Pixel(T visit) const + { + for (auto const & [pixel, objects] : Blips) { + visit(Point2D(pixel.first, pixel.second), objects.front()); + } + } - static int Hash_Old(RadarTrackingStruct const & s); - static int Hash2(RadarTrackingStruct const & s); + private: + std::map, std::vector> Blips; }; -typedef HashTableClass RADAR_HASH_TABLE; - class RadarClass: public DisplayClass { typedef DisplayClass BASECLASS; @@ -255,12 +253,9 @@ class RadarClass: public DisplayClass int RadarCellHeight; Rect CellRedrawRect; - /* - * This is the table of objects that show up as blips on the radar, keyed by the radar - * pixel each one occupies. It lets the radar draw its blips without walking every - * object in the game, and lets a click on the radar find the object underneath. - */ - RADAR_HASH_TABLE * RadarTrackingTable; + // Kept so that drawing the blips and resolving a radar click never walk every object + // in the game. + RadarTrackingClass RadarTracking; /* ** This is the list of radar pixels that need to be updated. Only a partial From 1a147670b696f55f7db8bbc6a2f9c4f28f987d3d Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 04:45:29 +0300 Subject: [PATCH 10/18] Carry zone and subzone ids on large maps --- code/_astar.h | 7 ++ code/astar.cpp | 86 ++++++++-------- code/astar.h | 12 ++- code/foot.cpp | 2 +- code/map.cpp | 140 +++++++++++++-------------- code/map.h | 4 +- code/mouse.cpp | 39 ++++---- code/zone.hh | 16 +-- manual/changes/large-map-zone-ids.md | 17 ++++ 9 files changed, 178 insertions(+), 145 deletions(-) create mode 100644 manual/changes/large-map-zone-ids.md diff --git a/code/_astar.h b/code/_astar.h index 805dae938..6d549c0ea 100644 --- a/code/_astar.h +++ b/code/_astar.h @@ -17,6 +17,13 @@ class AStarClass; +/* + * The most cells a path may run to. A buffer handed to Find_Path holds one more, because a + * finished path of length L is padded out to entry L. + */ +inline constexpr int PATH_LENGTH_MAX = 2000; + + /************************************************************************** ** Find_Path returns with a pointer to this structure. */ diff --git a/code/astar.cpp b/code/astar.cpp index 71cd05e55..535e7e76b 100644 --- a/code/astar.cpp +++ b/code/astar.cpp @@ -60,7 +60,7 @@ /* Define a couple of variables which are private to the module they are */ /* declared in. */ /*=========================================================================*/ -static unsigned int CellHeights[2000]; +static unsigned int CellHeights[PATH_LENGTH_MAX + 1]; unsigned int MapCellStride; @@ -412,7 +412,7 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F int zone_index = Map.Get_Cell_Zone_Index(neighbor_id); CellSubzoneStruct * subzones = Map.CellSubzones; - short subzone_id = subzones[zone_index].SubzoneID[SUBZONE_FINE]; + int subzone_id = subzones[zone_index].SubzoneID[SUBZONE_FINE]; if (fine_final_ids[subzone_id] != UniqueID && base_level && !neighbor_cell->AdjacentObjectCount && with_hs) { continue; } @@ -455,6 +455,14 @@ PathStruct * AStarClass::Find_Path_Regular(Cell const & from, Cell const & to, F continue; } + /* + * The destination is tested before a node is expanded, so a node at the + * limit can still finish a path; it just cannot extend one. + */ + if (working_node->PathLength >= PATH_LENGTH_MAX) { + continue; + } + RegularOpenNode new_node = Create_Node(working_node, working_to, to, movement_cost); if (!temp_node) { temp_node = new_node; @@ -673,7 +681,7 @@ AStarClass::AStarClass(void) : HierOnPath[i] = NULL; HierOpened[i] = NULL; HierCosts[i] = NULL; - HierBannedEdges[i].Clear(); + HierBannedEdges[i].clear(); memset(HierSubzonePath[i], 0, sizeof(HierSubzonePath[i])); HierSubzonePathCount[i] = 0; } @@ -698,38 +706,38 @@ AStarClass::~AStarClass(void) RegularNodes = NULL; if (RegularVisited != NULL) { - delete RegularVisited; + delete [] RegularVisited; RegularVisited = NULL; } if (RegularBridgeVisited != NULL) { - delete RegularBridgeVisited; + delete [] RegularBridgeVisited; RegularBridgeVisited = NULL; } if (RegularMovementCosts != NULL) { - delete RegularMovementCosts; + delete [] RegularMovementCosts; RegularMovementCosts = NULL; } if (RegularBridgeMovementCosts != NULL) { - delete RegularBridgeMovementCosts; + delete [] RegularBridgeMovementCosts; RegularBridgeMovementCosts = NULL; } for (int i = 0; i < 3; i++) { if (HierOnPath[i]) { - delete HierOnPath[i]; + delete [] HierOnPath[i]; HierOnPath[i] = NULL; } if (HierOpened[i]) { - delete HierOpened[i]; + delete [] HierOpened[i]; HierOpened[i] = NULL; } if (HierCosts[i]) { - delete HierCosts[i]; + delete [] HierCosts[i]; HierCosts[i] = NULL; } } @@ -813,28 +821,33 @@ PathStruct * AStarClass::Build_Final_Path(RegularOpenNode const & final_node, Fa void AStarClass::Update_Map_Dimensions(Rect const & dimensions) { if (RegularVisited != NULL) { - delete RegularVisited; + delete [] RegularVisited; RegularVisited = NULL; } if (RegularBridgeVisited != NULL) { - delete RegularBridgeVisited; + delete [] RegularBridgeVisited; RegularBridgeVisited = NULL; } if (RegularMovementCosts != NULL) { - delete RegularMovementCosts; + delete [] RegularMovementCosts; RegularMovementCosts = NULL; } if (RegularBridgeMovementCosts != NULL) { - delete RegularBridgeMovementCosts; + delete [] RegularBridgeMovementCosts; RegularBridgeMovementCosts = NULL; } MapCellStride = dimensions.Height + dimensions.Width + 1; int count = MapCellStride * MapCellStride; - RegularBridgeVisited = new int[count]; - RegularVisited = new int[count]; - RegularMovementCosts = new float[count]; - RegularBridgeMovementCosts = new float[count]; + + /* + * A search reads a cell's stamp before writing one, and only a wrap of UniqueID clears + * these tables, so they have to start at a value no search can own. + */ + RegularBridgeVisited = new int[count](); + RegularVisited = new int[count](); + RegularMovementCosts = new float[count](); + RegularBridgeMovementCosts = new float[count](); AStarFacingToOffset[FACING_N] = -MapCellStride; AStarFacingToOffset[FACING_NE] = 1 - MapCellStride; @@ -1643,7 +1656,7 @@ bool AStarClass::Find_Path_Hierarchical(Cell const & from, Cell const & to, MZon costs[start_subzone] = 0.0; std::optional best_node = HierQueue->Extract_Min(); - bool no_banned_edges = HierBannedEdges[subzone_level].Count() == 0; + bool no_banned_edges = HierBannedEdges[subzone_level].empty(); while (best_node) { int from_subzone = best_node->SubzoneID; @@ -1748,7 +1761,7 @@ PathStruct * AStarClass::Find_Path(Cell const & from, Cell const & to, FootClass Clear(); for (i = 0; i < ARRAY_SIZE(HierBannedEdges); i++) { - HierBannedEdges[i].Clear(); + HierBannedEdges[i].clear(); } Avoidance = avoidance; @@ -1866,13 +1879,13 @@ void AStarClass::Ban_Blocked_Subzone_Edges(FootClass const * foot) CellSubzoneStruct & subzone = Map.CellSubzones[Map.Get_Cell_Zone_Index(HierLastNodeCell)]; for (int subzone_level = 0; subzone_level < SUBZONE_COUNT; subzone_level++) { - unsigned short from_subzone = subzone.SubzoneID[subzone_level]; - DynamicVectorClass to_subzones; + int from_subzone = subzone.SubzoneID[subzone_level]; + DynamicVectorClass to_subzones; to_subzones.Clear(); if (Map.Build_Reachable_Subzones(&Map[HierLastNodeCell], subzone_level, to_subzones, foot)) { CellSubzoneStruct & last_subzone = Map.CellSubzones[Map.Get_Cell_Zone_Index(HierLastNodeCell)]; - unsigned int subzone_id = (unsigned short)last_subzone.SubzoneID[subzone_level]; + int subzone_id = last_subzone.SubzoneID[subzone_level]; Ban_Neighborhood_Subzone_Edges(subzone_id, subzone_level); continue; } @@ -1891,20 +1904,12 @@ void AStarClass::Ban_Blocked_Subzone_Edges(FootClass const * foot) /// /// The coarseness level the link belongs to. /// bool; Is the link banned? -bool AStarClass::Subzone_Edge_Banned(unsigned short subzone1, unsigned short subzone2, int subzone_level) +bool AStarClass::Subzone_Edge_Banned(int subzone1, int subzone2, int subzone_level) { if (subzone2 < subzone1) { std::swap(subzone1, subzone2); } - unsigned int edge = subzone2 | (subzone1 << 16); - - DynamicVectorClass & edges = HierBannedEdges[subzone_level]; - for (int i = edges.Count() - 1; i >= 0; i--) { - if (edges[i] == edge) { - return(true); - } - } - return(false); + return(HierBannedEdges[subzone_level].contains(std::pair(subzone1, subzone2))); } @@ -1914,14 +1919,13 @@ bool AStarClass::Subzone_Edge_Banned(unsigned short subzone1, unsigned short sub /// corridor that the regular search has already proven it cannot walk. /// /// The coarseness level the link belongs to. -void AStarClass::Ban_Subzone_Edge(unsigned int subzone1, unsigned int subzone2, int subzone_level) +void AStarClass::Ban_Subzone_Edge(int subzone1, int subzone2, int subzone_level) { if (subzone1 != subzone2) { if (subzone2 < subzone1) { std::swap(subzone2, subzone1); } - unsigned int edge = subzone2 | (subzone1 << 16); - HierBannedEdges[subzone_level].Add(edge); + HierBannedEdges[subzone_level].insert(std::pair(subzone1, subzone2)); } } @@ -1935,7 +1939,7 @@ void AStarClass::Ban_Subzone_Edge(unsigned int subzone1, unsigned int subzone2, /// /// The subzone the search became stuck in. /// The coarseness level to work at. -void AStarClass::Ban_Neighborhood_Subzone_Edges(unsigned int subzone, int subzone_level) +void AStarClass::Ban_Neighborhood_Subzone_Edges(int subzone, int subzone_level) { int i; int j; @@ -1957,8 +1961,8 @@ void AStarClass::Ban_Neighborhood_Subzone_Edges(unsigned int subzone, int subzon if (path_index == -1) { IsHSEnabled = false; } else { - unsigned short path_node; - unsigned short path_neighbor; + int path_node; + int path_neighbor; if (path_index == path_count - 1) { path_node = HierSubzonePath[subzone_level][path_index]; path_neighbor = HierSubzonePath[subzone_level][path_index - 1]; @@ -1973,7 +1977,7 @@ void AStarClass::Ban_Neighborhood_Subzone_Edges(unsigned int subzone, int subzon DynamicVectorClass & neighbor_conn = Map.SubzoneTracking[subzone_level][path_neighbor].Connections; for (j = node_conn.Count() - 1; j >= 0; j--) { - unsigned short common_subzone = node_conn[j].SubzoneID; + int common_subzone = node_conn[j].SubzoneID; if (common_subzone != path_neighbor) { for (i = neighbor_conn.Count() - 1; i >= 0; i--) { if (neighbor_conn[i].SubzoneID == common_subzone) { @@ -2006,7 +2010,7 @@ int AStarClass::Test_Cell_Walk(Cell const & from, Cell const & to, FootClass con Clear(); for (int i = 0; i < ARRAY_SIZE(HierBannedEdges); i++) { - HierBannedEdges[i].Clear(); + HierBannedEdges[i].clear(); } CellClass * from_ptr = &Map[from]; diff --git a/code/astar.h b/code/astar.h index 00b2b975a..90f337f4b 100644 --- a/code/astar.h +++ b/code/astar.h @@ -20,6 +20,8 @@ #include "vector.h" #include +#include +#include #include #include "facing.hh" @@ -160,9 +162,9 @@ class AStarClass */ bool Find_Path_Hierarchical(Cell const & from, Cell const & to, MZoneType mzone, FootClass const * foot); void Ban_Blocked_Subzone_Edges(FootClass const * foot); - bool Subzone_Edge_Banned(unsigned short subzone1, unsigned short subzone2, int subzone_level); - void Ban_Subzone_Edge(unsigned int subzone1, unsigned int subzone2, int subzone_level); - void Ban_Neighborhood_Subzone_Edges(unsigned int subzone, int subzone_level); + bool Subzone_Edge_Banned(int subzone1, int subzone2, int subzone_level); + void Ban_Subzone_Edge(int subzone1, int subzone2, int subzone_level); + void Ban_Neighborhood_Subzone_Edges(int subzone, int subzone_level); private: /* ----------------------------------------------------------------------------------- @@ -332,12 +334,12 @@ class AStarClass * per hierarchy level; the hierarchical search refuses to expand across * these when it retries after a regular pathfinding failure */ - DynamicVectorClass HierBannedEdges[SUBZONE_COUNT]; + std::set> HierBannedEdges[SUBZONE_COUNT]; /* * Ordered list of subzone IDs forming the hierarchical path per level */ - unsigned short HierSubzonePath[SUBZONE_COUNT][500]; + int HierSubzonePath[SUBZONE_COUNT][500]; /* * Number of valid entries in each hierarchical subzone path diff --git a/code/foot.cpp b/code/foot.cpp index d2b26c127..0d4349512 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -501,7 +501,7 @@ bool FootClass::Basic_Path(Cell cell, int path_offset, int avoidance) */ bool found = false; // Found a best path yet? PathStruct path1; - FacingType workpath[200*10]; // Staging area for path list. + FacingType workpath[PATH_LENGTH_MAX + 1]; // Staging area for path list. MoveType maxtype = MOVE_OK; path = Find_Path(cell, &workpath[0], ARRAY_SIZE(workpath), maxtype, path_offset, avoidance); diff --git a/code/map.cpp b/code/map.cpp index 9a7c06730..7c824e39b 100644 --- a/code/map.cpp +++ b/code/map.cpp @@ -108,6 +108,7 @@ #include #include #include +#include Cell const MapClass::RadiusOffset[] = { /* 0 */ Cell(0,0), @@ -2767,7 +2768,7 @@ ObjectClass * MapClass::Close_Object(Coord const & coord) const /// static void Stage_Subzone_Link(SubzoneLinkStaging & staging, int subzone1, int subzone2, bool crossblock) { - staging.try_emplace(ZonePair((unsigned short)subzone1, (unsigned short)subzone2), crossblock); + staging.try_emplace(ZonePair(subzone1, subzone2), crossblock); } @@ -2807,7 +2808,7 @@ int MapClass::Zone_Reset(void) for (i = 0; i < MZONE_COUNT; i++) { if (Zones[i] != NULL) { - delete Zones[i]; + delete [] Zones[i]; Zones[i] = NULL; } } @@ -2829,7 +2830,7 @@ int MapClass::Zone_Reset(void) LastAdjacentZone = 0; int span = Zone_Span(czone, zone, skip); if (span > bestspan) { - bestzone = (unsigned short)zone; + bestzone = zone; bestspan = span; } vec.Add(pass); @@ -2840,7 +2841,7 @@ int MapClass::Zone_Reset(void) } } - ZoneCount = (unsigned short)zone; + ZoneCount = zone; for (i = ZoneConnections.Count() - 1; i >= 0; i--) { ZoneConnectionClass * connection = &ZoneConnections[i]; @@ -2851,24 +2852,21 @@ int MapClass::Zone_Reset(void) if (to_zone < from_zone) { std::swap(to_zone, from_zone); } - ZoneAdjacency.emplace((unsigned short)from_zone, (unsigned short)to_zone); + ZoneAdjacency.emplace(from_zone, to_zone); } } } - unsigned short * zone_degree = new unsigned short[ZoneCount]; - for (i = 0; i < ZoneCount; i++) { - zone_degree[i] = 0; - } + std::vector zone_degree(ZoneCount, 0); for (ZonePair const & pair : ZoneAdjacency) { zone_degree[pair.second]++; zone_degree[pair.first]++; } - unsigned short ** zone_neighbors = new unsigned short *[ZoneCount]; + std::vector> zone_neighbors(ZoneCount); for (i = 0; i < ZoneCount; i++) { - zone_neighbors[i] = new unsigned short[zone_degree[i]]; + zone_neighbors[i].resize(zone_degree[i]); } for (i = 0; i < ZoneCount; i++) { @@ -2882,18 +2880,18 @@ int MapClass::Zone_Reset(void) zone_degree[pair.first]++; } - unsigned char * zone_passability = new unsigned char[ZoneCount]; + std::vector zone_passability(ZoneCount); for (i = 0; i < ZoneCount; i++) { zone_passability[i] = vec[i]; } - unsigned short * stack = new unsigned short[ZoneCount]; + std::vector stack(ZoneCount); for (MZoneType mzone = MZONE_FIRST; mzone < MZONE_COUNT; mzone++) { int next_movement_zone = 2; int * table = MZonePassability[mzone]; - unsigned short * nzone = new unsigned short[ZoneCount]; + int * nzone = new int[ZoneCount]; Zones[mzone] = nzone; for (j = 0; j < ZoneCount; j++) { @@ -2908,7 +2906,7 @@ int MapClass::Zone_Reset(void) int passable = table[zone_passability[zone_index]]; while (stackcount) { int current = stack[--stackcount]; - unsigned short * neighbors = zone_neighbors[current]; + std::vector const & neighbors = zone_neighbors[current]; int degree = zone_degree[current]; for (k = degree - 1; k >= 0; k--) { int neighbor = neighbors[k]; @@ -2922,18 +2920,13 @@ int MapClass::Zone_Reset(void) } } - nzone[0] = -1; - } - - for (i = 0 ; i < ZoneCount; i++) { - delete zone_neighbors[i]; + /* + * Ground off the playfield lands here. Callers read a zone of -1 as "any zone will + * do", so this must not be -1. + */ + nzone[0] = 0xFFFF; } - delete [] zone_passability; - delete [] zone_neighbors; - delete [] zone_degree; - delete [] stack; - return(Zones[0][bestzone]); } @@ -2984,8 +2977,8 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) } int begin_zone = begin->ZoneID; - if (begin_zone != 0 && (abs(begin->Height - cell_height) < 2 || nopass) && begin_zone != LastAdjacentZone && begin_zone != (unsigned short)zone) { - ZoneAdjacency.emplace((unsigned short)begin_zone, (unsigned short)zone); + if (begin_zone != 0 && (abs(begin->Height - cell_height) < 2 || nopass) && begin_zone != LastAdjacentZone && begin_zone != zone) { + ZoneAdjacency.emplace(begin_zone, zone); LastAdjacentZone = begin_zone; } @@ -3004,8 +2997,8 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) } int end_zone = end->ZoneID; - if (end_zone != 0 && (abs(end->Height - cell_height) < 2 || nopass) && end_zone != LastAdjacentZone && end_zone != (unsigned short)zone) { - ZoneAdjacency.emplace((unsigned short)end_zone, (unsigned short)zone); + if (end_zone != 0 && (abs(end->Height - cell_height) < 2 || nopass) && end_zone != LastAdjacentZone && end_zone != zone) { + ZoneAdjacency.emplace(end_zone, zone); LastAdjacentZone = end_zone; } @@ -3044,8 +3037,8 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) fbegin++; } } else { - if (zzone != (unsigned short)zone && zzone != LastAdjacentZone && (abs(fbegin->Height - adjacent->Height) < 2 || nopass)) { - ZoneAdjacency.emplace((unsigned short)zzone, (unsigned short)zone); + if (zzone != zone && zzone != LastAdjacentZone && (abs(fbegin->Height - adjacent->Height) < 2 || nopass)) { + ZoneAdjacency.emplace(zzone, zone); LastAdjacentZone = zzone; } fbegin++; @@ -3075,8 +3068,8 @@ int MapClass::Zone_Span(CellZoneStruct * data, int zone, int & skip) fbegin2++; } } else { - if (id != (unsigned short)zone && id != LastAdjacentZone && (abs(fbegin2->Height - adjacent->Height) < 2 || nopass)) { - ZoneAdjacency.emplace((unsigned short)id, (unsigned short)zone); + if (id != zone && id != LastAdjacentZone && (abs(fbegin2->Height - adjacent->Height) < 2 || nopass)) { + ZoneAdjacency.emplace(id, zone); LastAdjacentZone = id; } fbegin2++; @@ -9666,7 +9659,7 @@ void MapClass::Shutdown(void) for (index = 0; index < MZONE_COUNT; index++) { if (Zones[index] != NULL) { - delete Zones[index]; + delete [] Zones[index]; Zones[index] = NULL; } } @@ -10030,8 +10023,8 @@ void MapClass::Reset_Subzone(int subzone) track->Add(SubzoneTrackingStruct()); - SubzoneTrackingStruct * added = &(*track)[(unsigned short)entry_count]; - added->ParentSubzoneID = (unsigned short)parent; + SubzoneTrackingStruct * added = &(*track)[entry_count]; + added->ParentSubzoneID = parent; added->Passability = (PassabilityType)passability; added->Connections.Set_Growth_Step(16); added->ThreatRegion = (short)X / REGION_WIDTH + MAP_REGION_WIDTH * ((short)Y / REGION_HEIGHT) + (MAP_REGION_WIDTH + 1); @@ -10054,7 +10047,7 @@ void MapClass::Reset_Subzone(int subzone) } } - SubzoneTrackingEntryCount[subzone] = (unsigned short)entry_count; + SubzoneTrackingEntryCount[subzone] = entry_count; /* * Re-register the zone-to-zone connections for this subzone level. @@ -10110,7 +10103,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz if (abs(begin->Height - prev_height) >= 2) { break; } - begin->SubzoneID[subzone_level] = (short)subzone_id; + begin->SubzoneID[subzone_level] = subzone_id; prev_height = begin->Height; int prev_zone = begin[-1].ZoneID; begin--; @@ -10128,7 +10121,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz if (In_Local_Radar(probe, true)) { probe = Cell(x + 1, y); if (In_Local_Radar(probe, true)) { - Stage_Subzone_Link(staging, begin_subzone, (unsigned short)subzone_id, false); + Stage_Subzone_Link(staging, begin_subzone, subzone_id, false); last_adjacent = begin_subzone; } } @@ -10146,7 +10139,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz if (abs(end->Height - prev_height) >= 2) { break; } - end->SubzoneID[subzone_level] = (short)subzone_id; + end->SubzoneID[subzone_level] = subzone_id; prev_height = end->Height; end++; end_x++; @@ -10160,7 +10153,7 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz if (In_Local_Radar(probe, true)) { probe = Cell(end_x - 1, y); if (In_Local_Radar(probe, true)) { - Stage_Subzone_Link(staging, end_subzone, (unsigned short)subzone_id, false); + Stage_Subzone_Link(staging, end_subzone, subzone_id, false); last_adjacent = end_subzone; } } @@ -10205,14 +10198,14 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz } if (shadow_subzone != 0 || y <= ymin || x < xmin || x > xmax) { - if (shadow_subzone != (unsigned short)subzone_id && shadow_subzone != last_adjacent) { + if (shadow_subzone != subzone_id && shadow_subzone != last_adjacent) { if (shadow_subzone != 0) { if (abs(span_ptr->Height - above->Height) < 2 && In_Local_Radar(span_cell, true)) { Cell shadow_cell; shadow_cell.Y = y - 1; shadow_cell.X = x; if (In_Local_Radar(shadow_cell, true)) { - Stage_Subzone_Link(staging, shadow_subzone, (unsigned short)subzone_id, x < xmin || x > xmax); + Stage_Subzone_Link(staging, shadow_subzone, subzone_id, x < xmin || x > xmax); last_adjacent = shadow_subzone; } } @@ -10264,14 +10257,14 @@ int MapClass::Subzone_Span(CellSubzoneStruct * seed, int subzone_level, int subz } if (shadow_subzone != 0 || y >= ymax || x < xmin || x > xmax) { - if (shadow_subzone != (unsigned short)subzone_id && shadow_subzone != last_adjacent) { + if (shadow_subzone != subzone_id && shadow_subzone != last_adjacent) { if (shadow_subzone != 0) { if (abs(below->Height - span_ptr->Height) < 2 && In_Local_Radar(span_cell, true)) { Cell shadow_cell; shadow_cell.X = x; shadow_cell.Y = y + 1; if (In_Local_Radar(shadow_cell, true)) { - Stage_Subzone_Link(staging, shadow_subzone, (unsigned short)subzone_id, x < xmin || x > xmax); + Stage_Subzone_Link(staging, shadow_subzone, subzone_id, x < xmin || x > xmax); last_adjacent = shadow_subzone; } } @@ -10661,7 +10654,7 @@ Cell MapClass::Find_Bridge_End_Cell_For_Subzone(Cell const & cell, int subzone_l /// The unit whose ability to enter a cell decides what counts as /// blocked. /// bool; Is part of the subzone itself cut off from the starting cell? -bool MapClass::Build_Reachable_Subzones(CellClass * cptr, int subzone_level, DynamicVectorClass const & connections, FootClass const * foot) +bool MapClass::Build_Reachable_Subzones(CellClass * cptr, int subzone_level, DynamicVectorClass & connections, FootClass const * foot) { /* * The flood is bounded by one coarse block, so the scratch it needs is a fixed size and @@ -10674,7 +10667,7 @@ bool MapClass::Build_Reachable_Subzones(CellClass * cptr, int subzone_level, Dyn int dim = 1 << (subzone_level + 1); - DynamicVectorClass list; + DynamicVectorClass list; list.Clear(); if (dim > 0) { @@ -10683,9 +10676,9 @@ bool MapClass::Build_Reachable_Subzones(CellClass * cptr, int subzone_level, Dyn } } - unsigned short other_subzone = 0; + int other_subzone = 0; int count = 1; - unsigned short start_subzone = CellSubzones[Get_Cell_Subzone_Index(cptr->CellID)].SubzoneID[subzone_level]; + int start_subzone = CellSubzones[Get_Cell_Subzone_Index(cptr->CellID)].SubzoneID[subzone_level]; _subzone_flood_stack[0] = cptr; _subzone_flood_visited[(dim - 1) & cptr->CellID.X][(dim - 1) & cptr->CellID.Y] = 1; @@ -10703,7 +10696,7 @@ bool MapClass::Build_Reachable_Subzones(CellClass * cptr, int subzone_level, Dyn CellClass * adj = &cur->Adjacent_Cell(facing); int ax = (dim - 1) & adj->CellID.X; int ay = (dim - 1) & adj->CellID.Y; - unsigned short adj_subzone = CellSubzones[Get_Cell_Subzone_Index(adj->CellID)].SubzoneID[subzone_level]; + int adj_subzone = CellSubzones[Get_Cell_Subzone_Index(adj->CellID)].SubzoneID[subzone_level]; if (foot->Can_Enter_Cell(adj, facing, adj->Height, NULL, true) == MOVE_OK || mzone_table[adj->Passability] != TRAVERSAL_PASSABLE) { @@ -10755,7 +10748,7 @@ bool MapClass::Build_Reachable_Subzones(CellClass * cptr, int subzone_level, Dyn Cell c = cptr->CellID + Cell(bx, by); if (In_Local_Radar(c, true)) { - if (start_subzone == (unsigned short)CellSubzones[Get_Cell_Subzone_Index(c)].SubzoneID[subzone_level] && !_subzone_flood_visited[row][col]) { + if (start_subzone == CellSubzones[Get_Cell_Subzone_Index(c)].SubzoneID[subzone_level] && !_subzone_flood_visited[row][col]) { return(true); } } @@ -10777,7 +10770,7 @@ bool MapClass::Build_Reachable_Subzones(CellClass * cptr, int subzone_level, Dyn } } if (j == -1) { - ((DynamicVectorClass &)connections).Add(sub); + connections.Add(sub); } conn--; @@ -10818,9 +10811,16 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) bounds.X = cell.X - cell.X % bounds.Width; bounds.Y = cell.Y - cell.Y % bounds.Height; + /* + * The window is aligned down to the block size, so its far edge can reach past + * the last row and column the zone tables hold. + */ + int xend = std::min(bounds.X + bounds.Width, PlayRect.Width + PlayRect.Height + 1); + int yend = std::min(bounds.Y + bounds.Height, PlayRect.Width + PlayRect.Height + 1); + SubzoneConnectionStaging[subzone].clear(); - DynamicVectorClass collected; + DynamicVectorClass collected; DynamicVectorClass * track = &SubzoneTracking[subzone]; @@ -10828,10 +10828,10 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) * Collect the distinct subzone ids that currently occupy the window and * clear each cell's subzone id for this level (refreshing its zone id). */ - for (y = bounds.Y; y < bounds.Y + bounds.Height; y++) { - for (x = bounds.X; x < bounds.Width + bounds.X; x++) { - CellSubzoneStruct * subptr = &CellSubzones[(short)x + (short)y * (PlayRect.Width + PlayRect.Height + 1)]; - unsigned short subid = subptr->SubzoneID[subzone]; + for (y = bounds.Y; y < yend; y++) { + for (x = bounds.X; x < xend; x++) { + CellSubzoneStruct * subptr = &CellSubzones[Get_Cell_Zone_Index(Cell(x, y))]; + int subid = subptr->SubzoneID[subzone]; if (subid != 0) { int found; for (found = collected.Count() - 1; found >= 0; found--) { @@ -10844,7 +10844,7 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) } } subptr->SubzoneID[subzone] = 0; - subptr->ZoneID = CellZones[(short)x + (short)y * (PlayRect.Width + PlayRect.Height + 1)].ZoneID; + subptr->ZoneID = CellZones[Get_Cell_Zone_Index(Cell(x, y))].ZoneID; } } @@ -10854,7 +10854,7 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) * then clear its own connection list. */ for (int idx = collected.Count() - 1; idx >= 0; idx--) { - unsigned short subid = collected[idx]; + int subid = collected[idx]; SubzoneTrackingStruct * entry = &SubzoneTracking[subzone][subid]; for (int i = entry->Connections.Count() - 1; i >= 0; i--) { SubzoneTrackingStruct * neighbor = &SubzoneTracking[subzone][entry->Connections[i].SubzoneID]; @@ -10874,16 +10874,16 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) * window, recording a tracking entry for each new subzone. */ int entry_count = SubzoneTrackingEntryCount[subzone]; - for (y = bounds.Y; y < bounds.Y + bounds.Height; y++) { - for (x = bounds.X; x < bounds.Width + bounds.X; x++) { + for (y = bounds.Y; y < yend; y++) { + for (x = bounds.X; x < xend; x++) { Cell cella; cella.X = x; cella.Y = y; if (In_Local_Radar(cella)) { - CellSubzoneStruct * subptr = &CellSubzones[(short)y * (PlayRect.Width + PlayRect.Height + 1) + (short)x]; - int passability = CellZones[(short)y * (PlayRect.Width + PlayRect.Height + 1) + (short)x].Passability; + CellSubzoneStruct * subptr = &CellSubzones[Get_Cell_Zone_Index(Cell(x, y))]; + int passability = CellZones[Get_Cell_Zone_Index(Cell(x, y))].Passability; if (passability != PASSABLE_OUTSIDE && subptr->SubzoneID[subzone] == 0) { trycell.Y = y; @@ -10897,16 +10897,12 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) if (subzone != 2) { parent = subptr->SubzoneID[subzone + 1]; } - added->ParentSubzoneID = (unsigned short)parent; + added->ParentSubzoneID = parent; added->Passability = (PassabilityType)passability; added->Connections.Set_Growth_Step(16); added->ThreatRegion = (short)x / REGION_WIDTH + MAP_REGION_WIDTH * ((short)y / REGION_HEIGHT) + (MAP_REGION_WIDTH + 1); entry_count++; - if (entry_count == 0) { - Reset_All_Subzones(); - return; - } } } } @@ -10937,12 +10933,14 @@ void MapClass::Update_Cell_Subzones(Cell const & cell) */ int basex = cell.X - cell.X % 8; int basey = cell.Y - cell.Y % 8; - for (y = basey; y < basey + 8; y++) { - for (x = basex; x < basex + 8; x++) { + int parent_xend = std::min(basex + 8, PlayRect.Width + PlayRect.Height + 1); + int parent_yend = std::min(basey + 8, PlayRect.Width + PlayRect.Height + 1); + for (y = basey; y < parent_yend; y++) { + for (x = basex; x < parent_xend; x++) { trycell.X = x; trycell.Y = y; if (In_Local_Radar(trycell)) { - CellSubzoneStruct * subptr = &CellSubzones[(short)x + (short)y * (PlayRect.Width + PlayRect.Height + 1)]; + CellSubzoneStruct * subptr = &CellSubzones[Get_Cell_Zone_Index(Cell(x, y))]; for (int level = 0; level < 2; level++) { SubzoneTracking[level][subptr->SubzoneID[level]].ParentSubzoneID = subptr->SubzoneID[level + 1]; } diff --git a/code/map.h b/code/map.h index 1776d86dc..af96ee643 100644 --- a/code/map.h +++ b/code/map.h @@ -150,7 +150,7 @@ class MapClass: public GScreenClass Cell Get_Zone_Connection_Destination(Cell const & cell, Cell const & reference); Cell Find_Bridge_Span_End_Cell(Cell const & cell, Cell const & reference); Cell Find_Bridge_End_Cell_For_Subzone(Cell const & cell, int subzone_level, int subzone_id); - bool Build_Reachable_Subzones(CellClass * cptr, int subzone_level, DynamicVectorClass const & connections, FootClass const * foot); + bool Build_Reachable_Subzones(CellClass * cptr, int subzone_level, DynamicVectorClass & connections, FootClass const * foot); void Update_Cell_Subzones(Cell const & cell); void Register_Subzone_Connection(ZoneConnectionClass * connection); void Unregister_Subzone_Connection(ZoneConnectionClass * connection); @@ -362,7 +362,7 @@ class MapClass: public GScreenClass * number, so two cells are mutually reachable by a given movement type only * when their zones map to the same number. */ - unsigned short * Zones[MZONE_COUNT]; + int * Zones[MZONE_COUNT]; /* * This is the number of base terrain zones the last rebuild produced, and thus the diff --git a/code/mouse.cpp b/code/mouse.cpp index 2f94c3705..32a5718ce 100644 --- a/code/mouse.cpp +++ b/code/mouse.cpp @@ -411,18 +411,19 @@ HRESULT MouseClass::Load(IStream * stream) */ Free_Cells(); - delete CellSubzones; + delete [] CellSubzones; CellSubzones = NULL; - delete CellZones; + delete [] CellZones; CellZones = NULL; ZoneAdjacency.clear(); for (i = 0; i < SUBZONE_COUNT; i++) { SubzoneTracking[i].Clear(); + SubzoneTrackingEntryCount[i] = 0; } for (i = 0; i < MZONE_COUNT; i++) { - delete Zones[i]; + delete [] Zones[i]; Zones[i] = NULL; } @@ -450,40 +451,40 @@ HRESULT MouseClass::Load(IStream * stream) */ Init_Cells(); - CellSubzones = NULL; - CellZones = NULL; - Set_Map_Dimensions(PlayRect, 1, 0, false); - if (CellSubzones) { - delete CellSubzones; - CellSubzones = NULL; - } - if (CellZones) { - delete CellZones; - CellZones = NULL; - } - CellSubzones = new CellSubzoneStruct[CellZoneCount]; CellZones = new CellZoneStruct[CellZoneCount]; for (i = 0; i < SUBZONE_COUNT; i++) { int v = (1 << (i + 1)); SubzoneTracking[i].Clear(); + SubzoneTrackingEntryCount[i] = 0; SubzoneTracking[i].Set_Growth_Step((4 * PlayRect.Width * PlayRect.Height) / (v * v)); } - result = stream->Read(CellZones, sizeof(*CellZones) * CellZoneCount, NULL); + /* + * These blocks are read raw, so a file whose records are a different size would drag + * the rest of the stream out of step. A short read reports S_FALSE, not a failure. + */ + ULONG readcount = 0; + result = stream->Read(CellZones, sizeof(*CellZones) * CellZoneCount, &readcount); if (FAILED(result)) { return(result); } + if (readcount != sizeof(*CellZones) * CellZoneCount) { + return(E_FAIL); + } for (i = 0; i < MZONE_COUNT; i++) { - Zones[i] = new unsigned short[ZoneCount]; - result = stream->Read(Zones[i], sizeof(unsigned short) * ZoneCount, NULL); + Zones[i] = new int[ZoneCount]; + result = stream->Read(Zones[i], sizeof(*Zones[i]) * ZoneCount, &readcount); if (FAILED(result)) { return(result); } + if (readcount != sizeof(*Zones[i]) * ZoneCount) { + return(E_FAIL); + } } savestream.Serialize(ZoneConnections); @@ -559,7 +560,7 @@ HRESULT MouseClass::Save(IStream * stream) } for (i = 0; i < MZONE_COUNT; i++) { - result = stream->Write(Zones[i], sizeof(unsigned short) * ZoneCount, NULL); + result = stream->Write(Zones[i], sizeof(*Zones[i]) * ZoneCount, NULL); if (FAILED(result)) { return(result); } diff --git a/code/zone.hh b/code/zone.hh index ba37df08a..08aecf77d 100644 --- a/code/zone.hh +++ b/code/zone.hh @@ -160,13 +160,17 @@ struct CellZoneStruct */ unsigned char Height; + // The alignment gap ahead of ZoneID, named and cleared because a save carries this + // struct as raw bytes. + unsigned short Padding; + /* * This is the base terrain zone the cell was flood filled into, or 0 for a cell that * lies outside the playfield. It indexes the Zones layers to give the movement zone. */ - unsigned short ZoneID; + int ZoneID; - CellZoneStruct(void) : Passability(PASSABLE_OUTSIDE), Height(0) {} + CellZoneStruct(void) : Passability(PASSABLE_OUTSIDE), Height(0), Padding(0), ZoneID(0) {} }; @@ -179,13 +183,13 @@ struct CellSubzoneStruct * This is the subzone the cell belongs to at each level of pathfinding coarseness * (SubzoneLevelType), or 0 where the cell has none at that level. */ - signed short SubzoneID[SUBZONE_COUNT]; + int SubzoneID[SUBZONE_COUNT]; /* * This is the base terrain zone of the cell, cached from CellZones on each rebuild. A * subzone never spans two zones, so the fill spreads only while this value matches. */ - signed short ZoneID; + int ZoneID; /* * This is the ground level height of the cell, cached alongside its zone. A subzone @@ -251,7 +255,7 @@ struct SubzoneTrackingStruct * expand a subzone whose parent lay on the route the coarser level settled on, which is * what keeps a long path cheap to find. */ - unsigned short ParentSubzoneID; + int ParentSubzoneID; /* * This is the passability shared by all of this subzone's cells -- a subzone never spans @@ -273,7 +277,7 @@ struct SubzoneTrackingStruct * A pair of ids, ordered as the site that staged it wrote them rather than smallest first, * because the two staging containers below hold (a,b) and (b,a) as separate entries. */ -using ZonePair = std::pair; +using ZonePair = std::pair; using ZonePairSet = std::set; diff --git a/manual/changes/large-map-zone-ids.md b/manual/changes/large-map-zone-ids.md new file mode 100644 index 000000000..7d9955c51 --- /dev/null +++ b/manual/changes/large-map-zone-ids.md @@ -0,0 +1,17 @@ +--- +title: Carry zone and subzone ids on large maps +category: fix +release: 0.2.0 +targets: +- type: system + id: route-search + effect: changed +- type: format + id: save-games + effect: changed +credit: [ZivDero] +--- + +Terrain zone and subzone identifiers are held as full integers rather than 16-bit values, so a map large enough to produce more than 32767 subzones no longer indexes the route search's tables with a negative number. A terrain change near the bottom or right edge of the playfield also no longer clears subzone identifiers past the end of the zone tables, and a route longer than 2000 cells is now abandoned instead of overrunning the move list it is written into. + +The map record of a save game grew with those identifiers. A save written by an earlier development snapshot of this release cycle is refused when that record is read. From 1f360a70d6d1e19d46c2b10dd934873324a8d703 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 05:14:17 +0300 Subject: [PATCH 11/18] Group the test harnesses in a solution folder --- CMakeLists.txt | 4 ++++ tests/CMakeLists.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d4cdc1ae..2301d1e11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,10 @@ option(OPENTS_OFFICIAL_BUILD "Build as an official release of the declared versi option(OPENTS_EXPERIMENTAL_CLANG_CL "Build with clang-cl using the MSVC ABI" OFF) option(OPENTS_EXPERIMENTAL_X64 "Configure an unsupported 64-bit Windows build" OFF) +# Lets a target's FOLDER place it in a Visual Studio solution folder. The bgfx submodule sets +# this as well, so declare it here rather than inherit it from a vendored build. +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 63027f2d9..4b335c072 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,7 @@ function(opents_add_test target) add_executable(${target} ${sources}) target_compile_features(${target} PRIVATE cxx_std_20) + set_target_properties(${target} PROPERTIES FOLDER "Tests") target_include_directories(${target} PRIVATE "${CMAKE_SOURCE_DIR}/code" From f6a3a1c65987cb0827550b74c144dd8f6e105fde Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 05:30:34 +0300 Subject: [PATCH 12/18] Group the vendored libraries in a solution folder --- thirdparty/CMakeLists.txt | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 76dd59ac8..64f91576d 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -101,3 +101,32 @@ target_include_directories(lzo PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/lzo/include" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/lzo/src" ) + + +# +# --------------------------------------------------------- +# Solution layout +# --------------------------------------------------------- +# + +# Files a directory's targets, and those of every directory below it, under one solution +# folder. A target that already names a folder keeps it as a subfolder of that one, so the +# grouping bgfx gives its own targets survives. +function(opents_set_solution_folder directory folder) + get_property(targets DIRECTORY "${directory}" PROPERTY BUILDSYSTEM_TARGETS) + foreach(target IN LISTS targets) + get_target_property(current ${target} FOLDER) + if(current) + set_target_properties(${target} PROPERTIES FOLDER "${folder}/${current}") + else() + set_target_properties(${target} PROPERTIES FOLDER "${folder}") + endif() + endforeach() + + get_property(subdirectories DIRECTORY "${directory}" PROPERTY SUBDIRECTORIES) + foreach(subdirectory IN LISTS subdirectories) + opents_set_solution_folder("${subdirectory}" "${folder}") + endforeach() +endfunction() + +opents_set_solution_folder("${CMAKE_CURRENT_SOURCE_DIR}" "Thirdparty") From 65b3cd53e934b1099cd7c042ee6a553adc178df5 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 06:08:37 +0300 Subject: [PATCH 13/18] State the minimum supported Windows version --- README.md | 10 +++++----- manual/changes/utf8-text.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 44e6bacc4..5b96a36c6 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,13 @@ endorsed by Electronic Arts. 2. Extract the release zip into the Tiberian Sun game directory. 3. Run `Game.exe`. +OpenTS supports Windows 10 version 1903 (build 18362) and newer. Earlier +Windows versions are untested and unsupported. Wine may work, but there is no +supported native Linux build. + OpenTS supplies the engine, not the game data: the installation above provides the original assets. There is no installer, and no extra runtime -library or launch argument is required. Windows is supported; Wine may work, -but there is no supported native Linux build. The engine asks Windows for the -UTF-8 code page, which needs Windows 10 version 1903 or newer. Older Windows -keeps its own code page, so game text still shows, but a path or file name -holding a character that code page lacks may fail. +library or launch argument is required. ## Documentation diff --git a/manual/changes/utf8-text.md b/manual/changes/utf8-text.md index adaa15dff..290515f6e 100644 --- a/manual/changes/utf8-text.md +++ b/manual/changes/utf8-text.md @@ -14,4 +14,4 @@ Game text, INI files, typed input, player names and chat are UTF-8. The shipped An INI file that is not valid UTF-8 is read as Windows-1252, so existing maps and mods keep their accented names and a file whose digest was written in that code page still verifies. Saving writes UTF-8 without a byte order mark. -Player names now hold 64 bytes and chat lines 224, where an accented Latin character takes two bytes. The UTF-8 code page needs Windows 10 version 1903 or newer; older Windows keeps its own code page, where the game's text still renders. +Player names now hold 64 bytes and chat lines 224, where an accented Latin character takes two bytes. The UTF-8 code page needs Windows 10 version 1903, which is now the minimum Windows version OpenTS supports. From 44f3cf94885cf93f4626927513986ea2d1e688d2 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 06:22:44 +0300 Subject: [PATCH 14/18] Read every bootstrap palette through one helper --- code/init.cpp | 99 ++++++++++++++++++--------------------------------- 1 file changed, 34 insertions(+), 65 deletions(-) diff --git a/code/init.cpp b/code/init.cpp index fe9a7cb8a..1789693cc 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -2340,6 +2340,31 @@ static void Init_Patch_Mixfiles(void) } +/// +/// Reads a palette out of the mounted archives and expands it to the game's colour range. +/// +/// The palette to fill, left unchanged if the file is not there. +/// The palette file to read. +static void Read_Palette(PaletteClass & palette, char const * name) +{ + void const * data = MFCD::Retrieve(name); + + if (data == NULL) { + DebugString("%s not found; leaving that palette unchanged.\n", name); + return; + } + + memmove(&palette[0], data, sizeof(palette)); + + for (int index = 0; index < PaletteClass::COLOR_COUNT; index++) { + palette[index] = RGBClass( + (unsigned char)(palette[index].Get_Red()<<2), + (unsigned char)(palette[index].Get_Green()<<2), + (unsigned char)(palette[index].Get_Blue()<<2)); + } +} + + /*********************************************************************************************** * Init_Bootstrap_Mixfiles -- Registers and caches any mixfiles needed for bootstrapping. * * * @@ -2546,8 +2571,6 @@ static bool Init_Secondary_Mixfiles(void) *=============================================================================================*/ static bool Bootstrap(void) { - int index; - /* ** Process the message loop until we are in focus. We need to be in focus to read pixels from ** the screen. @@ -2596,39 +2619,18 @@ static bool Bootstrap(void) /* * House specific scheme palette initialization. */ - memmove((unsigned char *)&SchemePalette[0], (void *)MFCD::Retrieve("UNITSNO.PAL"), sizeof(SchemePalette)); - - for (index = 0; index < 256; index++) { - SchemePalette[index] = RGBClass( - (unsigned char)(SchemePalette[index].Get_Red()<<2), - (unsigned char)(SchemePalette[index].Get_Green()<<2), - (unsigned char)(SchemePalette[index].Get_Blue()<<2)); - } + Read_Palette(SchemePalette, "UNITSNO.PAL"); /* ** Default palette initialization. */ - memmove((unsigned char *)&GamePalette[0], (void *)MFCD::Retrieve("TEMPERAT.PAL"), sizeof(GamePalette)); - - for (index = 0; index < 256; index++) { - GamePalette[index] = RGBClass( - (unsigned char)(GamePalette[index].Get_Red()<<2), - (unsigned char)(GamePalette[index].Get_Green()<<2), - (unsigned char)(GamePalette[index].Get_Blue()<<2)); - } + Read_Palette(GamePalette, "TEMPERAT.PAL"); OriginalPalette = GamePalette; CCPalette = GamePalette; WhitePalette[0] = BlackPalette[0]; - memmove((unsigned char *)&WaypointPalette[0], (void *)MFCD::Retrieve("WAYPOINT.PAL"), sizeof(WaypointPalette)); - - for (index = 0; index < 256; index++) { - WaypointPalette[index] = RGBClass( - (unsigned char)(WaypointPalette[index].Get_Red()<<2), - (unsigned char)(WaypointPalette[index].Get_Green()<<2), - (unsigned char)(WaypointPalette[index].Get_Blue()<<2)); - } + Read_Palette(WaypointPalette, "WAYPOINT.PAL"); /* * Voxel system initialization. @@ -2654,54 +2656,21 @@ static bool Bootstrap(void) */ TerrainDrawer = new ConvertClass(GamePalette, GamePalette, *VisibleSurface, NUM_INTENSITY_LEVELS); - PaletteClass pal; - - memmove((unsigned char *)&pal[0], (void *)MFCD::Retrieve("ANIM.PAL"), sizeof(pal)); - for (index = 0; index < 256; index++) { - pal[index] = RGBClass( - (unsigned char)(pal[index].Get_Red()<<2), - (unsigned char)(pal[index].Get_Green()<<2), - (unsigned char)(pal[index].Get_Blue()<<2)); - } + PaletteClass pal = BlackPalette; + Read_Palette(pal, "ANIM.PAL"); AnimDrawer = new ConvertClass(pal, GamePalette, *VisibleSurface, NUM_INTENSITY_LEVELS); - memmove((unsigned char *)&pal[0], (void *)MFCD::Retrieve("PALETTE.PAL"), sizeof(pal)); - for (index = 0; index < 256; index++) { - pal[index] = RGBClass( - (unsigned char)(pal[index].Get_Red()<<2), - (unsigned char)(pal[index].Get_Green()<<2), - (unsigned char)(pal[index].Get_Blue()<<2)); - } - + Read_Palette(pal, "PALETTE.PAL"); NormalDrawer = new ConvertClass(pal, GamePalette, *VisibleSurface, NUM_INTENSITY_LEVELS); - memmove((unsigned char *)&pal[0], (void *)MFCD::Retrieve("UNITSNO.PAL"), sizeof(pal)); - for (index = 0; index < 256; index++) { - pal[index] = RGBClass( - (unsigned char)(pal[index].Get_Red()<<2), - (unsigned char)(pal[index].Get_Green()<<2), - (unsigned char)(pal[index].Get_Blue()<<2)); - } - + Read_Palette(pal, "UNITSNO.PAL"); VoxelDrawer = new ConvertClass(pal, GamePalette, *VisibleSurface, NUM_INTENSITY_LEVELS); - memmove((unsigned char *)&pal[0], (void *)MFCD::Retrieve("CAMEO.PAL"), sizeof(pal)); - for (index = 0; index < 256; index++) { - pal[index] = RGBClass( - (unsigned char)(pal[index].Get_Red()<<2), - (unsigned char)(pal[index].Get_Green()<<2), - (unsigned char)(pal[index].Get_Blue()<<2)); - } + Read_Palette(pal, "CAMEO.PAL"); CameoDrawer = new ConvertClass(pal, GamePalette, *VisibleSurface, NUM_INTENSITY_LEVELS); - memmove((unsigned char *)&pal[0], (void *)MFCD::Retrieve("MOUSEPAL.PAL"), sizeof(pal)); - for (index = 0; index < 256; index++) { - pal[index] = RGBClass( - (unsigned char)(pal[index].Get_Red()<<2), - (unsigned char)(pal[index].Get_Green()<<2), - (unsigned char)(pal[index].Get_Blue()<<2)); - } + Read_Palette(pal, "MOUSEPAL.PAL"); MouseDrawer = new ConvertClass(pal, GamePalette, *VisibleSurface); TiberiumDrawer = VoxelDrawer; From 6b5508f212a1149ad6f13dc1573ab151e50c7d30 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 06:22:56 +0300 Subject: [PATCH 15/18] Name the deployment's own files in OPENTS.INI --- code/addon.cpp | 4 +- code/deploymentconfig.cpp | 19 ++++ code/deploymentconfig.h | 27 ++++++ code/init.cpp | 58 +++++++------ code/options.cpp | 6 +- code/rules.cpp | 12 +-- code/saveload.cpp | 4 +- code/session.cpp | 4 +- code/startup.cpp | 2 +- code/sun.h | 1 - manual/changes/deployment-file-names.md | 32 +++++++ manual/content/formats/opents-ini.md | 46 +++++++++- manual/content/formats/sound-ini.md | 1 + manual/content/formats/theme-ini.md | 1 + manual/content/formats/tutorial-ini.md | 2 + manual/content/using/configuration-files.md | 2 +- manual/data/ini-read-exclusions.yaml | 4 +- .../documentation-source-contract.test.mjs | 52 +++++++++++ .../deploymentconfigcontract.cpp | 87 +++++++++++++++++++ 19 files changed, 320 insertions(+), 44 deletions(-) create mode 100644 manual/changes/deployment-file-names.md diff --git a/code/addon.cpp b/code/addon.cpp index 4a97f3f84..50764ffca 100644 --- a/code/addon.cpp +++ b/code/addon.cpp @@ -11,8 +11,10 @@ #include "addon.h" +#include "_deploymentconfig.h" #include "ccfile.h" #include "data.h" +#include "deploymentconfig.h" #include "init.h" #include "language/language.h" #include "ownrdraw.h" @@ -136,7 +138,7 @@ void Detect_Addons(void) AvailableAddOns = (1 << ADDON_BASE_GAME); ActiveAddOns = (1 << ADDON_BASE_GAME); - if (CCFileClass("FIRESTRM.INI").Is_Available() == true) { + if (CCFileClass(DeploymentConfig.RulesExpansionFile.c_str()).Is_Available() == true) { AvailableAddOns |= (1 << ADDON_FIRESTORM); } } diff --git a/code/deploymentconfig.cpp b/code/deploymentconfig.cpp index 614bb5af5..26301f981 100644 --- a/code/deploymentconfig.cpp +++ b/code/deploymentconfig.cpp @@ -27,6 +27,25 @@ void DeploymentConfigClass::Read_INI(INIClass const & ini) { SearchPaths = ini.Get_String("Paths", "SearchPaths", SearchPaths.c_str()); CarryScenarioFile = ini.Get_Bool("Saves", "CarryScenarioFile", CarryScenarioFile); + RulesFile = ini.Get_String("Files", "Rules", RulesFile.c_str()); + RulesExpansionFile = ini.Get_String("Files", "RulesExpansion", RulesExpansionFile.c_str()); + ArtFile = ini.Get_String("Files", "Art", ArtFile.c_str()); + ArtExpansionFile = ini.Get_String("Files", "ArtExpansion", ArtExpansionFile.c_str()); + AIFile = ini.Get_String("Files", "AI", AIFile.c_str()); + AIExpansionFile = ini.Get_String("Files", "AIExpansion", AIExpansionFile.c_str()); + SoundFile = ini.Get_String("Files", "Sound", SoundFile.c_str()); + SoundExpansionFile = ini.Get_String("Files", "SoundExpansion", SoundExpansionFile.c_str()); + ThemeFile = ini.Get_String("Files", "Theme", ThemeFile.c_str()); + ThemeExpansionFile = ini.Get_String("Files", "ThemeExpansion", ThemeExpansionFile.c_str()); + BattleFile = ini.Get_String("Files", "Battle", BattleFile.c_str()); + BattleExpansionFile = ini.Get_String("Files", "BattleExpansion", BattleExpansionFile.c_str()); + LanguageRulesFile = ini.Get_String("Files", "LanguageRules", LanguageRulesFile.c_str()); + LanguageRulesExpansionFile = ini.Get_String("Files", "LanguageRulesExpansion", LanguageRulesExpansionFile.c_str()); + TutorialFile = ini.Get_String("Files", "Tutorial", TutorialFile.c_str()); + UIFile = ini.Get_String("Files", "UI", UIFile.c_str()); + SettingsFile = ini.Get_String("Files", "Settings", SettingsFile.c_str()); + SchemePaletteFile = ini.Get_String("Palettes", "Scheme", SchemePaletteFile.c_str()); + GamePaletteFile = ini.Get_String("Palettes", "Game", GamePaletteFile.c_str()); } diff --git a/code/deploymentconfig.h b/code/deploymentconfig.h index 6d383270c..8836a3e96 100644 --- a/code/deploymentconfig.h +++ b/code/deploymentconfig.h @@ -26,6 +26,33 @@ class DeploymentConfigClass // Whether a save carries the scenario file it was played from, which enlarges a save by half again. bool CarryScenarioFile = false; + // The files the game reads its rules, artwork and text from. Whether the expansion is + // installed at all is decided by looking for the rules expansion. + std::string RulesFile = "RULES.INI"; + std::string RulesExpansionFile = "FIRESTRM.INI"; + std::string ArtFile = "ART.INI"; + std::string ArtExpansionFile = "ARTFS.INI"; + std::string AIFile = "AI.INI"; + std::string AIExpansionFile = "AIFS.INI"; + std::string SoundFile = "SOUND.INI"; + std::string SoundExpansionFile = "SOUND01.INI"; + std::string ThemeFile = "THEME.INI"; + std::string ThemeExpansionFile = "THEME01.INI"; + std::string BattleFile = "BATTLE.INI"; + std::string BattleExpansionFile = "BATTLEFS.INI"; + std::string LanguageRulesFile = "LANGRULE.INI"; + std::string LanguageRulesExpansionFile = "LANGFS.INI"; + std::string TutorialFile = "TUTORIAL.INI"; + std::string UIFile = "UI.INI"; + + // The file a player's own settings are read from and written back to. + std::string SettingsFile = "SUN.INI"; + + // The palettes in force until a theater is loaded, which the theater roster cannot name + // because they are read before the rules that declare it. + std::string SchemePaletteFile = "UNITSNO.PAL"; + std::string GamePaletteFile = "TEMPERAT.PAL"; + void Read_INI(INIClass const & ini); /* diff --git a/code/init.cpp b/code/init.cpp index 1789693cc..24e17729b 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -61,6 +61,7 @@ #include "_bench.h" #include "_command.h" #include "_convert.h" +#include "_deploymentconfig.h" #include "_font.h" #include "_keyboar.h" #include "_logic.h" @@ -102,6 +103,7 @@ #include "conquer.h" #include "data.h" #include "dbgprint.h" +#include "deploymentconfig.h" #include "dialog.h" #include "audio/audioengine.h" #include "dsurface.h" @@ -478,19 +480,19 @@ int Init_Game(int , char * []) return(-1); } - DebugString("Reading UI.INI\n"); - if (!UIControls.Read_INI_File("UI.INI", true)) { - DebugString("UI.INI not found, using the defaults.\n"); + DebugString("Reading %s\n", DeploymentConfig.UIFile.c_str()); + if (!UIControls.Read_INI_File(DeploymentConfig.UIFile.c_str(), true)) { + DebugString("%s not found, using the defaults.\n", DeploymentConfig.UIFile.c_str()); } /* ** */ - DebugString("Reading SOUND.INI\n"); + DebugString("Reading %s\n", DeploymentConfig.SoundFile.c_str()); CCINIClass voc_ini; - if (!Read_INI_And_Expansion(voc_ini, "SOUND.INI", "SOUND01.INI")) { - DebugString("Failed to read SOUND.INI or SOUND01.INI!\n"); + if (!Read_INI_And_Expansion(voc_ini, DeploymentConfig.SoundFile.c_str(), DeploymentConfig.SoundExpansionFile.c_str())) { + DebugString("Failed to read %s or %s!\n", DeploymentConfig.SoundFile.c_str(), DeploymentConfig.SoundExpansionFile.c_str()); return(-1); } @@ -513,11 +515,11 @@ int Init_Game(int , char * []) // A score's Side= names a side the rules declare, so the roster is built before the scores are read. Prepare_Side_Roster(); - DebugString("Reading THEME.INI\n"); + DebugString("Reading %s\n", DeploymentConfig.ThemeFile.c_str()); CCINIClass theme_ini; - if (!Read_INI_And_Expansion(theme_ini, "THEME.INI", "THEME01.INI")) { - DebugString("Failed to read THEME.INI or THEME01.INI!\n"); + if (!Read_INI_And_Expansion(theme_ini, DeploymentConfig.ThemeFile.c_str(), DeploymentConfig.ThemeExpansionFile.c_str())) { + DebugString("Failed to read %s or %s!\n", DeploymentConfig.ThemeFile.c_str(), DeploymentConfig.ThemeExpansionFile.c_str()); return(-1); } @@ -637,7 +639,7 @@ void Init_Campaigns(void) CCINIClass * ini = new CCINIClass; ini->Load(file, false); - if (stricmp(name.c_str(), "BATTLE.INI") == 0) { + if (stricmp(name.c_str(), DeploymentConfig.BattleFile.c_str()) == 0) { found = true; } @@ -646,7 +648,7 @@ void Init_Campaigns(void) } if (!found) { - CCFileClass file("BATTLE.INI"); + CCFileClass file(DeploymentConfig.BattleFile.c_str()); CCINIClass * ini = new CCINIClass; if (ini != NULL) { @@ -656,7 +658,7 @@ void Init_Campaigns(void) } } - CCFileClass file("BATTLEFS.INI"); + CCFileClass file(DeploymentConfig.BattleExpansionFile.c_str()); if (file.Is_Available() == true) { CCINIClass * ini = new CCINIClass; @@ -912,7 +914,7 @@ static bool Init_Rules(void) rule->Load(file, false); - if (stricmp(name.c_str(), "RULES.INI") == 0) { + if (stricmp(name.c_str(), DeploymentConfig.RulesFile.c_str()) == 0) { found = true; Rules.Add_Head(rule); } else { @@ -921,7 +923,7 @@ static bool Init_Rules(void) } if (!found) { - CCFileClass file("RULES.INI"); + CCFileClass file(DeploymentConfig.RulesFile.c_str()); CCINIClass * rule = new CCINIClass; rule->Load(file, false); Rules.Add_Head(rule); @@ -933,26 +935,26 @@ static bool Init_Rules(void) return(false); } - CCFileClass art_file("ART.INI"); + CCFileClass art_file(DeploymentConfig.ArtFile.c_str()); if (!ArtINI.Load(art_file, false)) { - DebugString("Failed to load ART.INI!\n"); + DebugString("Failed to load %s!\n", DeploymentConfig.ArtFile.c_str()); return(false); } CCINIClass art_ini; - CCFileClass art_fs_file("ARTFS.INI"); + CCFileClass art_fs_file(DeploymentConfig.ArtExpansionFile.c_str()); if (art_fs_file.Is_Available() == true) { art_ini.Load(art_fs_file, false); } if (Addon_Installed(ADDON_FIRESTORM)) { - CCFileClass rules_fs_file("FIRESTRM.INI"); + CCFileClass rules_fs_file(DeploymentConfig.RulesExpansionFile.c_str()); if (rules_fs_file.Is_Available() == true) { CCINIClass rule_fs; if (!FSRuleINI.Load(rules_fs_file, false)) { - DebugString("Failed to load FIRESTRM.INI!\n"); + DebugString("Failed to load %s!\n", DeploymentConfig.RulesExpansionFile.c_str()); return(false); } } @@ -989,7 +991,7 @@ static bool Init_Rules(void) Session.Options.AIPlayers = 0; Session.Options.AIDifficulty = DIFF_NORMAL; - CCFileClass lang_file("LANGRULE.INI"); + CCFileClass lang_file(DeploymentConfig.LanguageRulesFile.c_str()); if (lang_file.Is_Available() == true) { CCINIClass lang_ini; @@ -1007,15 +1009,15 @@ static bool Init_Rules(void) } } - CCFileClass ai_file("AI.INI"); + CCFileClass ai_file(DeploymentConfig.AIFile.c_str()); AIINI.Load(ai_file, true); if (Addon_Installed(ADDON_FIRESTORM)) { - CCFileClass ai_fs_file("AIFS.INI"); + CCFileClass ai_fs_file(DeploymentConfig.AIExpansionFile.c_str()); if (ai_fs_file.Is_Available() == true) { CCINIClass ai_fs_ini; if (!FSAIINI.Load(ai_fs_file, false)) { - DebugString("Failed to load AIFS.INI!\n"); + DebugString("Failed to load %s!\n", DeploymentConfig.AIExpansionFile.c_str()); return(false); } } @@ -2619,12 +2621,12 @@ static bool Bootstrap(void) /* * House specific scheme palette initialization. */ - Read_Palette(SchemePalette, "UNITSNO.PAL"); + Read_Palette(SchemePalette, DeploymentConfig.SchemePaletteFile.c_str()); /* ** Default palette initialization. */ - Read_Palette(GamePalette, "TEMPERAT.PAL"); + Read_Palette(GamePalette, DeploymentConfig.GamePaletteFile.c_str()); OriginalPalette = GamePalette; CCPalette = GamePalette; @@ -2664,7 +2666,7 @@ static bool Bootstrap(void) Read_Palette(pal, "PALETTE.PAL"); NormalDrawer = new ConvertClass(pal, GamePalette, *VisibleSurface, NUM_INTENSITY_LEVELS); - Read_Palette(pal, "UNITSNO.PAL"); + Read_Palette(pal, DeploymentConfig.SchemePaletteFile.c_str()); VoxelDrawer = new ConvertClass(pal, GamePalette, *VisibleSurface, NUM_INTENSITY_LEVELS); Read_Palette(pal, "CAMEO.PAL"); @@ -2770,7 +2772,7 @@ static bool Init_Bulk_Data(void) ** Fetch the tutorial message data. */ INIClass ini; - CCFileClass file("TUTORIAL.INI"); + CCFileClass file(DeploymentConfig.TutorialFile.c_str()); ini.Load(file); TutorialText.Read_Base(ini); @@ -6388,7 +6390,7 @@ bool Prep_For_Side(SideType side) } // A side archive may carry its own copy of the file. - UIControls.Read_INI_File("UI.INI", true); + UIControls.Read_INI_File(DeploymentConfig.UIFile.c_str(), true); Map.Init_For_House(); diff --git a/code/options.cpp b/code/options.cpp index 74ce00f6a..00d68720d 100644 --- a/code/options.cpp +++ b/code/options.cpp @@ -61,6 +61,7 @@ #include "options.h" #include "_command.h" +#include "_deploymentconfig.h" #include "_map.h" #include "_rules.h" #include "audio/audioengine.h" @@ -68,6 +69,7 @@ #include "ccrand.h" #include "command.h" #include "dbgprint.h" +#include "deploymentconfig.h" #include "dsurface.h" #include "globals.h" #include "init.h" @@ -356,7 +358,7 @@ static char const * Scale_Mode_Name(int mode) *=============================================================================================*/ void OptionsClass::Load_Settings(void) { - DebugString("--------- Loading SUN.INI settings ---------------\n"); + DebugString("--------- Loading %s settings ---------------\n", DeploymentConfig.SettingsFile.c_str()); /* ** Read in the Options values @@ -456,7 +458,7 @@ void OptionsClass::Load_Settings(void) *=============================================================================================*/ void OptionsClass::Save_Settings (void) { - CCFileClass file(CONFIG_FILE_NAME); + CCFileClass file(DeploymentConfig.SettingsFile.c_str()); DebugString("Saving game settings\n"); diff --git a/code/rules.cpp b/code/rules.cpp index 0721f0374..2979154b7 100644 --- a/code/rules.cpp +++ b/code/rules.cpp @@ -51,6 +51,7 @@ #include "rules.h" #include "_bench.h" +#include "_deploymentconfig.h" #include "_palette.h" #include "_rules.h" #include "_warhead.h" @@ -66,6 +67,7 @@ #include "conquer.h" #include "convert.h" #include "dbgprint.h" +#include "deploymentconfig.h" #include "findmake.h" #include "globals.h" #include "house.h" @@ -691,7 +693,7 @@ void RulesClass::Initialize(CCINIClass const & ini) Load_Art_INI(); if (Addon_Enabled(ADDON_FIRESTORM) == true) { - CCFileClass artfsfile("ARTFS.INI"); + CCFileClass artfsfile(DeploymentConfig.ArtExpansionFile.c_str()); if (artfsfile.Is_Available() == true) { ArtINI.Load(artfsfile, false); } @@ -700,7 +702,7 @@ void RulesClass::Initialize(CCINIClass const & ini) Heap_Maximums(ini); Addition(ini); - CCFileClass langfile("LANGRULE.INI"); + CCFileClass langfile(DeploymentConfig.LanguageRulesFile.c_str()); if (langfile.Is_Available() == true) { CCINIClass langini; if (langini.Load(langfile, true) > 1) { @@ -714,7 +716,7 @@ void RulesClass::Initialize(CCINIClass const & ini) Addition(FSRuleINI); } - CCFileClass langfsfile("LANGFS.INI"); + CCFileClass langfsfile(DeploymentConfig.LanguageRulesExpansionFile.c_str()); if (langfsfile.Is_Available() == true) { CCINIClass langfsini; langfsini.Load(langfsfile, false); @@ -2996,7 +2998,7 @@ int RulesClass::Get_Art_Unique_ID(void) { int id = ArtINI.Get_Unique_ID(); if (Addon_Enabled(ADDON_FIRESTORM) == true) { - CCFileClass artfs("ARTFS.INI"); + CCFileClass artfs(DeploymentConfig.ArtExpansionFile.c_str()); if (artfs.Is_Available() == true) { CCINIClass artfsini; artfsini.Load(artfs, false); @@ -3033,6 +3035,6 @@ int RulesClass::Get_AI_Unique_ID(void) void RulesClass::Load_Art_INI(void) { ArtINI.Clear(); - CCFileClass art("ART.INI"); + CCFileClass art(DeploymentConfig.ArtFile.c_str()); ArtINI.Load(art, false); } diff --git a/code/saveload.cpp b/code/saveload.cpp index 22633874a..1353bf15b 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -47,6 +47,7 @@ #include "saveload.h" +#include "_deploymentconfig.h" #include "_logic.h" #include "_map.h" #include "_rect.h" @@ -69,6 +70,7 @@ #include "bullettype.h" #include "data.h" #include "dbgprint.h" +#include "deploymentconfig.h" #include "empulse.h" #include "enviro.h" #include "factory.h" @@ -670,7 +672,7 @@ static bool Get_All(IStream *stream, bool save_net) RulesClass::Load_Art_INI(); if (Addon_Enabled(ADDON_FIRESTORM) == true) { - CCFileClass artfs("ARTFS.INI"); + CCFileClass artfs(DeploymentConfig.ArtExpansionFile.c_str()); if (artfs.Is_Available() == true) { ArtINI.Load(artfs, false); } diff --git a/code/session.cpp b/code/session.cpp index c15304290..3164bb476 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -48,6 +48,7 @@ #include "session.h" +#include "_deploymentconfig.h" #include "_keyboar.h" #include "_map.h" #include "_rules.h" @@ -56,6 +57,7 @@ #include "conquer.h" #include "data.h" #include "dbgprint.h" +#include "deploymentconfig.h" #include "gamedirs.h" // for Search_Files. #include "globals.h" #include "ipxmgr.h" @@ -639,7 +641,7 @@ bool SessionClass::Log_To_File(FILE *out) *=========================================================================*/ void SessionClass::Write_MultiPlayer_Settings(void) { - CDFileClass file(CONFIG_FILE_NAME); + CDFileClass file(DeploymentConfig.SettingsFile.c_str()); { // Save the player's last-used Handle & Color ConfigINI.Put_Int("MultiPlayer", "Color", (int)PrefColor); diff --git a/code/startup.cpp b/code/startup.cpp index ced0e7089..43ab863af 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -571,7 +571,7 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho // anywhere for a player's files to go. Naming it again settles it where it belongs. Session.RecordFile.Set_Name("RECORD.BIN"); - CDFileClass *cfile = new CDFileClass(CONFIG_FILE_NAME); + CDFileClass *cfile = new CDFileClass(DeploymentConfig.SettingsFile.c_str()); ConfigINI.Load(*cfile, false); Options.ScreenWidth = ConfigINI.Get_Int("Video", "ScreenWidth", Options.ScreenWidth); diff --git a/code/sun.h b/code/sun.h index 1364b5222..ac6a07811 100644 --- a/code/sun.h +++ b/code/sun.h @@ -66,7 +66,6 @@ ** Filenames of the data files it can create at run time. */ #define FAME_FILE_NAME "HALLFAME.DAT" -#define CONFIG_FILE_NAME "SUN.INI" /********************************************************************** diff --git a/manual/changes/deployment-file-names.md b/manual/changes/deployment-file-names.md new file mode 100644 index 000000000..fc8a04bdd --- /dev/null +++ b/manual/changes/deployment-file-names.md @@ -0,0 +1,32 @@ +--- +title: Name the deployment's own files in OPENTS.INI +category: feature +release: 0.2.0 +targets: +- type: format + id: opents-ini + effect: changed +- type: format + id: sound-ini + effect: changed +- type: format + id: theme-ini + effect: changed +- type: format + id: tutorial-ini + effect: changed +- type: format + id: ui-ini + effect: changed +credit: +- ZivDero +--- + +A deployment names its own game data files in `OPENTS.INI`: rules, artwork, AI, sound, +music, campaigns, translated rules, tutorial text and interface, the expansion copy of each, +the file a player's settings are written back to, and the two palettes the game starts with. +A name it does not write keeps the one Tiberian Sun uses. The expansion rules file it names +is also what the game looks for to decide the expansion is installed. + +A palette file the game cannot find leaves that palette unchanged. It stopped the game +before. diff --git a/manual/content/formats/opents-ini.md b/manual/content/formats/opents-ini.md index a52af0701..64ae82b3a 100644 --- a/manual/content/formats/opents-ini.md +++ b/manual/content/formats/opents-ini.md @@ -1,11 +1,12 @@ --- format_id: opents-ini title: OPENTS.INI -summary: Names the folders a deployment keeps its game files sorted into, and what its saves carry. +summary: Names the folders a deployment keeps its game files sorted into, the files it reads them from, and what its saves carry. kind: file source_files: - code/deploymentconfig.cpp - code/gamedirs.cpp + - code/init.cpp filenames: - OPENTS.INI related: @@ -30,6 +31,49 @@ Without the file, and without the key, the game behaves as though `SearchPaths=I The game's own directory is examined before any listed folder, so naming it adds nothing. Naming only it, as `SearchPaths=.`, is how a deployment asks for no other folder to be searched. An empty `SearchPaths=` does not do this: the file reader passes over an entry with nothing after the equals sign, leaving the default in force. +## The files it reads + +```ini title="OPENTS.INI" +[Files] +Rules=RULES.INI +RulesExpansion=FIRESTRM.INI +Art=ART.INI +ArtExpansion=ARTFS.INI +AI=AI.INI +AIExpansion=AIFS.INI +Sound=SOUND.INI +SoundExpansion=SOUND01.INI +Theme=THEME.INI +ThemeExpansion=THEME01.INI +Battle=BATTLE.INI +BattleExpansion=BATTLEFS.INI +LanguageRules=LANGRULE.INI +LanguageRulesExpansion=LANGFS.INI +Tutorial=TUTORIAL.INI +UI=UI.INI +Settings=SUN.INI +``` + +Each key names one file, and an unwritten key keeps the name above. `Rules` names the rules, `Art` the artwork, `AI` the computer player's data, `Sound` the [sound registry](/formats/sound-ini/), `Theme` the [music registry](/formats/theme-ini/), `Battle` the campaign list, `LanguageRules` the translated rules read over the rest, `Tutorial` the [numbered text lines](/formats/tutorial-ini/), `UI` the [interface settings](/formats/ui-ini/), and `Settings` the file a player's own options are written back to. + +The seven `Expansion` keys name the expansion's copy of a file, which is read over the base one. `RulesExpansion` also decides whether the expansion is installed: the game looks for that file and nothing else, so renaming it moves the test. + +Renaming a file does not move it. Every name here is searched for in the order the section below gives, the same as any other file the game opens. + +Rules and campaign files are also gathered by wildcard, as `RULE*.INI` and `BATTLE*.INI`, and those two patterns are fixed. A rules file named outside the pattern is read anyway and is the one the game starts from; where the search turns up others as well, the game asks which set to play with, as it does for a stock installation. Campaign files add to one another, so one named outside the pattern is read alongside those inside it. + +## The palettes it starts with + +```ini title="OPENTS.INI" +[Palettes] +Scheme=UNITSNO.PAL +Game=TEMPERAT.PAL +``` + +`Scheme` names the palette the player colors are built against, and `Game` the one the game is drawn through. Both stand only until a scenario loads its [theater](/formats/theater-control/), which replaces them with the palettes the theater's `Root=` and `Suffix=` name. They are settings of this file because nothing has declared a theater yet when they are read. + +A palette file the deployment does not ship leaves that palette unchanged, and the name goes to the debug log. The game starts either way. + ## What a save carries ```ini title="OPENTS.INI" diff --git a/manual/content/formats/sound-ini.md b/manual/content/formats/sound-ini.md index 4c8813926..529313315 100644 --- a/manual/content/formats/sound-ini.md +++ b/manual/content/formats/sound-ini.md @@ -13,6 +13,7 @@ key_scopes: source: sound related: - { type: format, id: aud } + - { type: format, id: opents-ini } source_files: - code/init.cpp - code/vocini.cpp diff --git a/manual/content/formats/theme-ini.md b/manual/content/formats/theme-ini.md index 79e0bbb38..2e0a25a6d 100644 --- a/manual/content/formats/theme-ini.md +++ b/manual/content/formats/theme-ini.md @@ -13,6 +13,7 @@ key_scopes: source: theme related: - { type: format, id: aud } + - { type: format, id: opents-ini } source_files: - code/init.cpp - code/theme.cpp diff --git a/manual/content/formats/tutorial-ini.md b/manual/content/formats/tutorial-ini.md index 8f95bbaaa..71d0db897 100644 --- a/manual/content/formats/tutorial-ini.md +++ b/manual/content/formats/tutorial-ini.md @@ -13,6 +13,8 @@ related: id: TACTION_TEXT_TRIGGER - type: key id: MessageDelay +- type: format + id: opents-ini filenames: - TUTORIAL.INI --- diff --git a/manual/content/using/configuration-files.md b/manual/content/using/configuration-files.md index 830e86789..bb8e271d5 100644 --- a/manual/content/using/configuration-files.md +++ b/manual/content/using/configuration-files.md @@ -21,6 +21,6 @@ Configuration is documented in five areas rather than one. The table names what | [Command line options](/using/command-line/) | Options accepted on the OpenTS command line | | [Mapping](/mapping/) | Scenario sections, triggers, TeamTypes, TaskForces, Scripts, and AI triggers | -`SUN.INI` stores local player options. `KEYBOARD.INI` maps command names to keys. [`UI.INI`](/formats/ui-ini/), which a mod or deployment may ship, styles the order lines. Rules, art, sound, theme, and scenario files supply game and mod data. +`SUN.INI` stores local player options. `KEYBOARD.INI` maps command names to keys. [`UI.INI`](/formats/ui-ini/), which a mod or deployment may ship, styles the order lines. Rules, art, sound, theme, and scenario files supply game and mod data. A deployment that keeps these under other names writes them in its [`OPENTS.INI`](/formats/opents-ini/#the-files-it-reads). The engine loads its configuration files in a defined order. Use the relevant Format page when file selection, layering, registration, or section identity affects the result. diff --git a/manual/data/ini-read-exclusions.yaml b/manual/data/ini-read-exclusions.yaml index 298d7cbcd..5f9a5a7ee 100644 --- a/manual/data/ini-read-exclusions.yaml +++ b/manual/data/ini-read-exclusions.yaml @@ -133,9 +133,9 @@ site_exclusions: - path: code/deploymentconfig.cpp function: DeploymentConfigClass::Read_INI - keys: [SearchPaths, CarryScenarioFile] + keys: [SearchPaths, CarryScenarioFile, Rules, RulesExpansion, Art, ArtExpansion, AI, AIExpansion, Sound, SoundExpansion, Theme, ThemeExpansion, Battle, BattleExpansion, LanguageRules, LanguageRulesExpansion, Tutorial, UI, Settings, Scheme, Game] classification: excluded - reason: Both are settings of the deployment's own file, documented as that format rather than as game data. + reason: Each is a setting of the deployment's own file, documented as that format rather than as game data. - path: code/spawnerconfig.cpp function: SpawnerConfigClass::Read_Slots diff --git a/manual/site/tests/documentation-source-contract.test.mjs b/manual/site/tests/documentation-source-contract.test.mjs index d45716bbd..817e341ba 100644 --- a/manual/site/tests/documentation-source-contract.test.mjs +++ b/manual/site/tests/documentation-source-contract.test.mjs @@ -955,3 +955,55 @@ test('New theater artwork is renamed by image letter, not by a prefix list', () 'the shape fetch calls the convention rather than repeating it', ); }); + +test('The deployment names the files the game reads', () => { + const config = functionBody( + source('code/deploymentconfig.cpp'), + 'void DeploymentConfigClass::Read_INI(INIClass const & ini)', + ); + + for (const [key, member] of [ + ['Rules', 'RulesFile'], + ['RulesExpansion', 'RulesExpansionFile'], + ['Art', 'ArtFile'], + ['Settings', 'SettingsFile'], + ]) { + assert.match( + config, + new RegExp(`${member} = ini\\.Get_String\\("Files", "${key}", ${member}\\.c_str\\(\\)\\);`), + `the deployment names its ${key} file`, + ); + } + + assert.match( + config, + /SchemePaletteFile = ini\.Get_String\("Palettes", "Scheme", SchemePaletteFile\.c_str\(\)\);/, + 'and the palette it starts from', + ); + + const init = source('code/init.cpp'); + + assert.match( + init, + /stricmp\(name\.c_str\(\), DeploymentConfig\.RulesFile\.c_str\(\)\) == 0/, + 'the wildcard search knows the rules file by the name the deployment gives it', + ); + + assert.match( + init, + /CCFileClass file\(DeploymentConfig\.RulesFile\.c_str\(\)\);/, + 'and the file it falls back on is that same one', + ); + + assert.match( + init, + /Read_Palette\(SchemePalette, DeploymentConfig\.SchemePaletteFile\.c_str\(\)\);/, + 'the palettes are read through the names it gives', + ); + + assert.match( + functionBody(source('code/addon.cpp'), 'void Detect_Addons(void)'), + /CCFileClass\(DeploymentConfig\.RulesExpansionFile\.c_str\(\)\)\.Is_Available\(\)/, + 'the expansion is looked for under the name the deployment gives it', + ); +}); diff --git a/tests/deploymentconfig/deploymentconfigcontract.cpp b/tests/deploymentconfig/deploymentconfigcontract.cpp index 14031ffe2..592725d4b 100644 --- a/tests/deploymentconfig/deploymentconfigcontract.cpp +++ b/tests/deploymentconfig/deploymentconfigcontract.cpp @@ -211,6 +211,90 @@ void Test_Carry_Scenario_File(void) Check(!config.CarryScenarioFile, "with the file gone it is off"); } + +void Test_File_Names(void) +{ + DeploymentConfigClass config; + + Check(config.RulesFile == "RULES.INI", "with no file the rules come from RULES.INI"); + Check(config.SettingsFile == "SUN.INI", "and a player's settings from SUN.INI"); + + Write_File(Root + "\\OPENTS.INI", "[Paths]\nSearchPaths=Data\n"); + config.Read_File(""); + Check(config.ArtFile == "ART.INI", "a file that names none of them leaves the defaults"); + + Write_File(Root + "\\OPENTS.INI", + "[Files]\nRules=dtarules.ini\nArt=dtaart.ini\nAI=dtaai.ini\nSound=dtasound.ini\n" + "Theme=dtatheme.ini\nBattle=dtabattle.ini\nLanguageRules=dtalang.ini\n" + "Tutorial=dtatutorial.ini\nUI=dtaui.ini\nSettings=Settings.ini\n"); + config.Read_File(""); + Check(config.RulesFile == "dtarules.ini", "a name it writes for the rules is taken"); + Check(config.ArtFile == "dtaart.ini", "and for the artwork"); + Check(config.AIFile == "dtaai.ini", "and for the AI"); + Check(config.SoundFile == "dtasound.ini", "and for the sounds"); + Check(config.ThemeFile == "dtatheme.ini", "and for the music"); + Check(config.BattleFile == "dtabattle.ini", "and for the campaigns"); + Check(config.LanguageRulesFile == "dtalang.ini", "and for the translated rules"); + Check(config.TutorialFile == "dtatutorial.ini", "and for the tutorial text"); + Check(config.UIFile == "dtaui.ini", "and for the interface"); + Check(config.SettingsFile == "Settings.ini", "and for a player's settings"); + Check(config.ArtExpansionFile == "ARTFS.INI", "an expansion file it leaves alone keeps its name"); + + Remove_File(Root + "\\OPENTS.INI"); + config.Read_File(""); + Check(config.RulesFile == "RULES.INI", "with the file gone the names return to the defaults"); + Check(config.SettingsFile == "SUN.INI", "every one of them"); +} + + +void Test_Expansion_File_Names(void) +{ + DeploymentConfigClass config; + + Check(config.RulesExpansionFile == "FIRESTRM.INI", "with no file the expansion rules are FIRESTRM.INI"); + + Write_File(Root + "\\OPENTS.INI", + "[Files]\nRulesExpansion=fsrules.ini\nArtExpansion=fsart.ini\nAIExpansion=fsai.ini\n" + "SoundExpansion=fssound.ini\nThemeExpansion=fstheme.ini\nBattleExpansion=fsbattle.ini\n" + "LanguageRulesExpansion=fslang.ini\n"); + config.Read_File(""); + Check(config.RulesExpansionFile == "fsrules.ini", "a name it writes for the expansion rules is taken"); + Check(config.ArtExpansionFile == "fsart.ini", "and for the expansion artwork"); + Check(config.AIExpansionFile == "fsai.ini", "and for the expansion AI"); + Check(config.SoundExpansionFile == "fssound.ini", "and for the expansion sounds"); + Check(config.ThemeExpansionFile == "fstheme.ini", "and for the expansion music"); + Check(config.BattleExpansionFile == "fsbattle.ini", "and for the expansion campaigns"); + Check(config.LanguageRulesExpansionFile == "fslang.ini", "and for the translated expansion rules"); + Check(config.RulesFile == "RULES.INI", "while the base files stand where it names none of them"); + + Remove_File(Root + "\\OPENTS.INI"); + config.Read_File(""); + Check(config.RulesExpansionFile == "FIRESTRM.INI", "with the file gone they return to the defaults"); +} + + +void Test_Palette_Names(void) +{ + DeploymentConfigClass config; + + Check(config.SchemePaletteFile == "UNITSNO.PAL", "with no file the scheme palette is UNITSNO.PAL"); + Check(config.GamePaletteFile == "TEMPERAT.PAL", "and the starting palette TEMPERAT.PAL"); + + Write_File(Root + "\\OPENTS.INI", "[Palettes]\nScheme=UNITTEM.PAL\nGame=DESERT.PAL\n"); + config.Read_File(""); + Check(config.SchemePaletteFile == "UNITTEM.PAL", "the names it writes are taken"); + Check(config.GamePaletteFile == "DESERT.PAL", "both of them"); + + Write_File(Root + "\\OPENTS.INI", "[Palettes]\nScheme=UNITTEM.PAL\n"); + config.Read_File(""); + Check(config.GamePaletteFile == "TEMPERAT.PAL", "and one named alone leaves the other at its default"); + + Remove_File(Root + "\\OPENTS.INI"); + config.Read_File(""); + Check(config.SchemePaletteFile == "UNITSNO.PAL", "with the file gone both return to the defaults"); + Check(config.GamePaletteFile == "TEMPERAT.PAL", "as they stand in Tiberian Sun"); +} + } @@ -231,6 +315,9 @@ int main(void) Test_The_Directory_Named(); Test_A_Read_Starts_Over(); Test_Carry_Scenario_File(); + Test_File_Names(); + Test_Expansion_File_Names(); + Test_Palette_Names(); Remove_Root(); From f3f939f3b5e608741c1b1a9e149a202f081f2df0 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Thu, 10 Sep 2026 07:18:44 +0300 Subject: [PATCH 16/18] Fix typos in debug strings --- code/init.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/init.cpp b/code/init.cpp index 24e17729b..d99c3b88c 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -6365,7 +6365,7 @@ bool Prep_For_Side(SideType side) } sprintf(name, "SIDENC%02d.MIX", id); - DebugString(" Initilizing %s\n", name); + DebugString(" Initializing %s\n", name); if (CCFileClass(name).Is_Available()) { SideNCMix = new MFCD(name, &FastKey); @@ -6379,7 +6379,7 @@ bool Prep_For_Side(SideType side) sprintf(name, "E%02dSCD%02d.MIX", Get_Required_Addon(), id); } - DebugString(" Initilizing %s\n", name); + DebugString(" Initializing %s\n", name); if (CCFileClass(name).Is_Available()) { SideCDMix = new MFCD(name, &FastKey); } @@ -6443,7 +6443,7 @@ bool Prep_Speech_For_Side(SideType side) } sprintf(name, "SPEECH%02d.MIX", id); - DebugString(" Initilizing %s\n", name); + DebugString(" Initializing %s\n", name); if (CCFileClass(name).Is_Available()) { SpeechMix = new MFCD(name, &FastKey); } From 945e858458e58408157790f69314cacced841ac3 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Thu, 10 Sep 2026 08:08:35 +0200 Subject: [PATCH 17/18] Replace the compound-file save container and remove COM from the object model (#140) Co-authored-by: ZivDero --- code/CMakeLists.txt | 41 +- code/abstract.cpp | 162 +--- code/abstract.h | 33 +- code/aircraft.cpp | 75 +- code/aircraft.h | 17 +- code/airctype.cpp | 14 +- code/airctype.h | 2 +- code/aitrig.cpp | 13 +- code/aitrig.h | 2 +- code/alphashp.cpp | 14 +- code/alphashp.h | 2 +- code/anim.cpp | 16 +- code/anim.h | 2 +- code/animtype.cpp | 14 +- code/animtype.h | 2 +- code/base.cpp | 25 - code/base.h | 3 - code/blight.cpp | 13 +- code/blight.h | 2 +- code/blowfish.cpp | 28 - code/blowfish.h | 16 - code/brain.cpp | 52 +- code/brain.h | 8 +- code/building.cpp | 47 +- code/building.h | 4 +- code/builtype.cpp | 14 +- code/builtype.h | 2 +- code/bullet.cpp | 54 +- code/bullet.h | 4 +- code/bullettype.cpp | 11 +- code/bullettype.h | 2 +- code/campaign.cpp | 14 +- code/campaign.h | 2 +- code/cell.cpp | 27 +- code/cell.h | 2 +- code/classfactory.cpp | 67 ++ code/classfactory.h | 113 +-- code/classid.h | 35 + code/classids.cpp | 82 ++ code/classids.h | 85 ++ code/crc.cpp | 2 +- code/crc.h | 2 +- code/cstream.cpp | 540 ----------- code/cstream.h | 120 --- code/display.cpp | 18 +- code/display.h | 4 +- code/drive.cpp | 182 +--- code/drive.h | 68 +- code/droppod.cpp | 138 +-- code/droppod.h | 32 +- code/empulse.cpp | 14 +- code/empulse.h | 2 +- code/enviro.cpp | 16 +- code/enviro.h | 5 +- code/factory.cpp | 13 +- code/factory.h | 2 +- code/fly.cpp | 69 +- code/fly.h | 38 +- code/fog.cpp | 14 +- code/fog.h | 2 +- code/foot.cpp | 64 +- code/foot.h | 3 +- code/globals.cpp | 15 +- code/house.cpp | 51 +- code/house.h | 8 +- code/houstype.cpp | 80 +- code/houstype.h | 6 +- code/hover.cpp | 50 +- code/hover.h | 38 +- code/iblockci.h | 27 - code/iblockci_i.c | 52 -- code/iblowfish.h | 17 - code/iblowfish_i.c | 55 -- code/iflyctrl.h | 23 +- code/iflyctrl_i.c | 52 -- code/ilinkstm.h | 29 - code/iloco.h | 112 ++- code/iloco_i.c | 52 -- code/ilocos.h | 27 - code/ilocos_i.c | 83 -- code/infantry.cpp | 62 +- code/infantry.h | 4 +- code/infatype.cpp | 13 +- code/infatype.h | 2 +- code/ini.cpp | 101 +- code/ini.h | 6 +- code/init.cpp | 2 +- code/ion.cpp | 16 +- code/ion.h | 7 +- code/ipiggy.h | 37 +- code/ipiggy_i.c | 52 -- code/isotile.cpp | 14 +- code/isotile.h | 3 +- code/isotype.cpp | 12 +- code/isotype.h | 2 +- code/isun.h | 80 -- code/isun_i.c | 238 ----- code/jumpjet.cpp | 32 +- code/jumpjet.h | 22 +- code/layer.cpp | 28 +- code/layer.h | 4 +- code/levitate.cpp | 23 +- code/levitate.h | 18 +- code/light.cpp | 12 +- code/light.h | 2 +- code/loco.cpp | 252 ++--- code/loco.h | 140 +-- code/logic.cpp | 6 - code/map.cpp | 30 + code/map.h | 1 + code/mech.cpp | 35 +- code/mech.h | 26 +- code/mouse.cpp | 130 ++- code/mouse.h | 4 +- code/overlay.cpp | 1 - code/overlay.h | 4 +- code/overtype.cpp | 13 +- code/overtype.h | 2 +- code/particle.cpp | 20 +- code/particle.h | 4 +- code/partsys.cpp | 14 +- code/partsys.h | 2 +- code/persist.h | 30 + code/psystype.cpp | 14 +- code/psystype.h | 2 +- code/ptype.cpp | 14 +- code/ptype.h | 2 +- code/reinf.cpp | 4 +- code/revent.cpp | 31 +- code/revent.h | 5 +- code/rules.cpp | 12 +- code/rules.h | 4 +- code/savefile.cpp | 539 +++++++++++ code/savefile.h | 79 ++ code/saveload.cpp | 616 +++++++------ code/saveload.h | 39 +- code/savestream.cpp | 82 +- code/savestream.h | 159 +++- code/savever.cpp | 860 +----------------- code/savever.h | 48 +- code/scenario.cpp | 18 +- code/scenario.h | 4 +- code/script.cpp | 27 +- code/script.h | 4 +- code/session.cpp | 22 +- code/session.h | 4 +- code/side.cpp | 14 +- code/side.h | 2 +- code/smudge.cpp | 14 +- code/smudge.h | 2 +- code/smudtype.cpp | 13 +- code/smudtype.h | 2 +- code/startup.cpp | 222 ++--- code/sun.h | 4 +- code/super.cpp | 11 +- code/super.h | 2 +- code/suprtype.cpp | 13 +- code/suprtype.h | 2 +- code/swizzle.cpp | 13 + code/swizzle.h | 11 + code/tactical.cpp | 13 +- code/tactical.h | 2 +- code/taction.cpp | 14 +- code/taction.h | 2 +- code/tag.cpp | 12 +- code/tag.h | 2 +- code/tagtype.cpp | 14 +- code/tagtype.h | 2 +- code/taskforc.cpp | 13 +- code/taskforc.h | 2 +- code/team.cpp | 14 +- code/team.h | 2 +- code/teamtype.cpp | 14 +- code/teamtype.h | 2 +- code/techno.cpp | 4 +- code/techtype.cpp | 6 +- code/techtype.h | 3 +- code/teleport.cpp | 29 +- code/teleport.h | 16 +- code/terrain.cpp | 18 +- code/terrain.h | 4 +- code/terrtype.cpp | 14 +- code/terrtype.h | 2 +- code/tevent.cpp | 14 +- code/tevent.h | 2 +- code/tiberium.cpp | 17 +- code/tiberium.h | 4 +- code/tracker.cpp | 14 +- code/trigger.cpp | 14 +- code/trigger.h | 2 +- code/trigtype.cpp | 14 +- code/trigtype.h | 2 +- code/tube.cpp | 14 +- code/tube.h | 2 +- code/tunnel.cpp | 45 +- code/tunnel.h | 34 +- code/typelist.h | 1 - code/unit.cpp | 48 +- code/unit.h | 4 +- code/unittype.cpp | 11 +- code/unittype.h | 2 +- code/vanim.cpp | 14 +- code/vanim.h | 2 +- code/vanimtype.cpp | 14 +- code/vanimtype.h | 2 +- code/vector.h | 6 +- code/vein.cpp | 53 +- code/vein.h | 6 +- code/walk.cpp | 139 +-- code/walk.h | 50 +- code/warhead.cpp | 14 +- code/warhead.h | 2 +- code/wave.cpp | 14 +- code/wave.h | 2 +- code/waypoint.cpp | 14 +- code/waypoint.h | 2 +- code/weapon.cpp | 14 +- code/weapon.h | 2 +- docs/README.md | 2 + docs/SAVE-FORMAT.md | 178 ++++ manual/content/formats/save-games.md | 23 +- manual/content/internals/class-hierarchy.md | 2 +- manual/content/internals/locomotion.md | 16 +- manual/data/ini-keys.yaml | 2 +- manual/site/scripts/check-render.mjs | 2 +- .../documentation-source-contract.test.mjs | 12 +- manual/tools/extract_engine.py | 4 +- tests/CMakeLists.txt | 2 +- tests/cstream/CMakeLists.txt | 10 - tests/cstream/cstreamcontract.cpp | 148 --- tests/save/CMakeLists.txt | 14 + tests/save/savetest.cpp | 485 ++++++++++ 232 files changed, 3591 insertions(+), 5773 deletions(-) create mode 100644 code/classfactory.cpp create mode 100644 code/classid.h create mode 100644 code/classids.cpp create mode 100644 code/classids.h delete mode 100644 code/cstream.cpp delete mode 100644 code/cstream.h delete mode 100644 code/iblockci.h delete mode 100644 code/iblockci_i.c delete mode 100644 code/iblowfish.h delete mode 100644 code/iblowfish_i.c delete mode 100644 code/iflyctrl_i.c delete mode 100644 code/ilinkstm.h delete mode 100644 code/iloco_i.c delete mode 100644 code/ilocos.h delete mode 100644 code/ilocos_i.c delete mode 100644 code/ipiggy_i.c delete mode 100644 code/isun.h delete mode 100644 code/isun_i.c create mode 100644 code/persist.h create mode 100644 code/savefile.cpp create mode 100644 code/savefile.h create mode 100644 docs/SAVE-FORMAT.md delete mode 100644 tests/cstream/CMakeLists.txt delete mode 100644 tests/cstream/cstreamcontract.cpp create mode 100644 tests/save/CMakeLists.txt create mode 100644 tests/save/savetest.cpp diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 137d06855..8966dc5b6 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -177,11 +177,6 @@ target_compile_definitions(OpenTS PRIVATE WIN32 _WINDOWS NOMINMAX - - # Compiles Blowfish into the binary instead of reaching it through the COM object in - # blowfish.dll. It decides the layout of BlowfishEngine, so every translation unit has - # to agree on it and it belongs on the compile line rather than in a header. - NO_BLOWFISH_DLL ) # @@ -217,7 +212,7 @@ target_link_libraries(OpenTS PRIVATE winmm ws2_32 kernel32 user32 gdi32 winspool comdlg32 advapi32 shell32 - ole32 oleaut32 uuid odbc32 odbccp32 + odbc32 odbccp32 ) if(MSVC) @@ -272,37 +267,13 @@ foreach(f ${OPENTS_SRC}) endif() endforeach() -# List of all interface filenames (headers + C stubs) -set(INTERFACE_FILES - iblockci.h iblockci_i.c - iblowfish.h iblowfish_i.c - iflyctrl.h iflyctrl_i.c - ilinkstm.h - iloco.h iloco_i.c - ilocos.h ilocos_i.c - ipiggy.h ipiggy_i.c - isun.h isun_i.c +# The interface headers the locomotors are written against. +source_group("Interface Files" FILES + "${CMAKE_CURRENT_SOURCE_DIR}/iflyctrl.h" + "${CMAKE_CURRENT_SOURCE_DIR}/iloco.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ipiggy.h" ) -# Convert to full paths relative to source dir -set(FULL_INTERFACE_FILES "") -foreach(f IN LISTS INTERFACE_FILES) - list(APPEND FULL_INTERFACE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${f}") -endforeach() - -# Add them to the "Interface Files" group and exclude *_i.c from build -foreach(f IN LISTS FULL_INTERFACE_FILES) - - # Put into VS filter - source_group("Interface Files" FILES "${f}") - - # Exclude *_i.c from build, but DO NOT mark headers as header-only - if(f MATCHES "_i\\.c$") - set_source_files_properties("${f}" PROPERTIES HEADER_FILE_ONLY TRUE) - endif() - -endforeach() - # General source files source_group("Source Files" REGULAR_EXPRESSION ".*\\.(c|cpp)$") source_group("Header Files" REGULAR_EXPRESSION ".*\\.(h|hpp)$") diff --git a/code/abstract.cpp b/code/abstract.cpp index 355435def..72bdafea2 100644 --- a/code/abstract.cpp +++ b/code/abstract.cpp @@ -58,7 +58,6 @@ /// AbstractClass::AbstractClass(void) : ID(-1), - RefCount(0), Dirty(false) { } @@ -107,75 +106,13 @@ void AbstractClass::Create_ID(void) } -/// -/// Fetches a COM interface pointer from this object. -/// This is the IUnknown implementation shared by every game object. Abstract -/// objects expose IUnknown, IPersistStream and IPersist; the save game system -/// reaches the whole object hierarchy through them. -/// -/// The identifier of the interface being asked for. -/// Receives the interface pointer, or NULL when the -/// interface is not supported. -/// -/// Returns with S_OK when the interface was supplied. Otherwise E_NOINTERFACE is -/// returned for an unsupported interface, or E_POINTER when no output pointer was given. -/// -HRESULT STDMETHODCALLTYPE AbstractClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - - if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(IPersistStream *)this; - } - if (riid == IID_IPersistStream) { - *ppvObject = (IPersistStream *)this; - } - if (riid == IID_IPersist) { - *ppvObject = (IPersist *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - - AddRef(); - return(S_OK); -} - - -/// -/// Satisfies the IUnknown reference count contract. -/// The game owns its objects outright and they outlive any interface pointer -/// handed out, so nothing is actually counted. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE AbstractClass::AddRef(void) -{ - return(1); -} - - -/// -/// Satisfies the IUnknown release contract. -/// Releasing an interface never destroys a game object -- see AddRef. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE AbstractClass::Release(void) -{ - return(1); -} - - /// /// Writes this object to the save stream. /// /// The stream to write to. /// Should the object be marked clean once it has been written? -/// Returns with S_OK when the object was written, otherwise a failure code. -HRESULT STDMETHODCALLTYPE AbstractClass::Save(IStream * stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool AbstractClass::Save(SaveStreamClass & stream, bool cleardirty) { return(Save_Members(stream, cleardirty)); } @@ -185,8 +122,8 @@ HRESULT STDMETHODCALLTYPE AbstractClass::Save(IStream * stream, BOOL cleardirty) /// Reads this object back from the save stream. /// /// The stream to read from. -/// Returns with S_OK when the object was read, otherwise a failure code. -HRESULT STDMETHODCALLTYPE AbstractClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool AbstractClass::Load(SaveStreamClass & stream) { return(Load_Members(stream)); } @@ -199,28 +136,16 @@ HRESULT STDMETHODCALLTYPE AbstractClass::Load(IStream * stream) /// /// The stream to write to. /// Should the object be marked clean once it has been written? -/// Returns with S_OK when the record was written, otherwise a failure code. -HRESULT AbstractClass::Save_Members(IStream * stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool AbstractClass::Save_Members(SaveStreamClass & stream, bool cleardirty) { - if (stream == NULL) { - return(E_POINTER); - } - SwizzleIDType id = Swizzler.ID_Of(this); - - HRESULT result = stream->Write(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); + stream.Serialize(id); + Serialize(stream); + if (!stream.Was_Error() && cleardirty) { + Dirty = false; } - - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - - if (SUCCEEDED(savestream.Result()) && cleardirty) { - Dirty = false; - } - - return(savestream.Result()); + return(!stream.Was_Error()); } @@ -230,31 +155,24 @@ HRESULT AbstractClass::Save_Members(IStream * stream, BOOL cleardirty) /// save game can be remapped onto this object, and the members follow. /// /// The stream to read from. -/// Returns with S_OK when the record was read, otherwise a failure code. -HRESULT AbstractClass::Load_Members(IStream * stream) +/// bool; Was the record read whole? +bool AbstractClass::Load_Members(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); + SwizzleIDType id = 0; + stream.Serialize(id); + if (stream.Was_Error()) { + return(false); } - - SwizzleIDType id; - - HRESULT result = stream->Read(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); - } - Swizzle_Here_I_Am(id, this); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context(typeid(*this).name(), id); - Serialize(savestream); + // A nested record borrows the stream, so the owner's context is put back afterwards. + char const * const outertype = stream.Context_Type(); + SwizzleIDType const outerid = stream.Context_ID(); + stream.Set_Context(typeid(*this).name(), id); + Serialize(stream); + stream.Set_Context(outertype, outerid); - if (SUCCEEDED(savestream.Result())) { - Post_Load(); - } - - return(savestream.Result()); + return(!stream.Was_Error()); } @@ -274,25 +192,10 @@ void AbstractClass::Post_Load(void) void AbstractClass::Serialize(SaveStreamClass & stream) { stream.Serialize(ID); - // RefCount -- belongs to the running session rather than the record. stream.Serialize(Dirty); } -/// -/// Fetches the number of bytes that Save will write. -/// A record is as long as the members a class names, so the count is not known before -/// the members have been written. Nothing in the game asks for it, so rather than -/// walk the object twice this reports that the size cannot be supplied. -/// -/// Receives the maximum size, in bytes. -/// Returns with E_NOTIMPL. -HRESULT STDMETHODCALLTYPE AbstractClass::GetSizeMax(ULARGE_INTEGER *pcbSize) -{ - return(E_NOTIMPL); -} - - /// /// Folds this object's state into a running CRC. /// The multiplayer sync check walks every object each frame and accumulates its @@ -335,23 +238,6 @@ bool AbstractClass::Is_Techno(void) const } -/// -/// Determines if this object has changed since it was last saved. -/// -/// Returns with S_OK when the object is dirty, or S_FALSE when it is not. -HRESULT AbstractClass::IsDirty(void) -{ - /* - * Per IPersistStream::IsDirty specifications this method returns S_OK to indicate that the object has changed. - * Otherwise, it returns S_FALSE. - */ - if (Dirty) { - return(S_OK); - } - return(S_FALSE); -} - - /// /// Resets this object to its start of scenario state. /// The bare abstract object carries no scenario state, so there is nothing to do. diff --git a/code/abstract.h b/code/abstract.h index 8e9278fe0..46d3ebf59 100644 --- a/code/abstract.h +++ b/code/abstract.h @@ -39,7 +39,7 @@ #include "house.hh" #include "rtti.hh" -#include +#include "persist.h" class AbstractTypeClass; class CRCEngine; @@ -62,7 +62,7 @@ class MonoClass; ** This class is the base class for all game objects that have an existence on the ** battlefield. */ -class AbstractClass : public IPersistStream +class AbstractClass : public IPersistent { public: @@ -74,8 +74,8 @@ class AbstractClass : public IPersistStream * the members are read -- dropping a registration keyed by the identity the read * is about to replace, say. */ - HRESULT Save_Members(IStream * stream, BOOL cleardirty); - HRESULT Load_Members(IStream * stream); + bool Save_Members(SaveStreamClass & stream, bool cleardirty); + bool Load_Members(SaveStreamClass & stream); public: @@ -87,16 +87,9 @@ class AbstractClass : public IPersistStream __declspec(property(get = Fetch_RTTI)) RTTIType RTTI; int ID; - /* - * This is the count of outstanding COM references to this object. Only projectiles - * are genuinely reference counted -- everything else answers 1 to AddRef and to - * Release -- so elsewhere it merely rides along, preserved by hand across a load. - */ - LONG RefCount; - /* * If this object has changed since it was last written out, then this flag will be - * true. Save clears it on request and IsDirty reports it, as IPersistStream asks. + * true. Save clears it on request. */ bool Dirty; @@ -106,14 +99,9 @@ class AbstractClass : public IPersistStream AbstractClass(void); virtual ~AbstractClass(void); - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; - virtual HRESULT STDMETHODCALLTYPE IsDirty(void) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream, BOOL cleardirty) override; - virtual HRESULT STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER *pcbSize) override; + virtual bool Load(SaveStreamClass & stream) override; + virtual bool Save(SaveStreamClass & stream, bool cleardirty) override; virtual int What_Am_I(void) const; virtual int Fetch_ID(void) const; @@ -122,7 +110,6 @@ class AbstractClass : public IPersistStream AbstractClass & operator = (const AbstractClass & that) { ID = that.ID; - RefCount = that.RefCount; Dirty = that.Dirty; return(*this); } @@ -137,9 +124,9 @@ class AbstractClass : public IPersistStream /* * Restores whatever the record could not carry -- artwork fetched by name, tables * shared with other objects, registrations that depend on the loaded identity. - * Load_Members calls this once the members are in place, so a base class fixup - * runs even when the load was entered through a derived class. An implementation - * chains to its base first and never touches the stream. + * Load_Object calls this once the record has been checked, so an object never takes + * its place in the map or a side table while its record is still in doubt. An + * implementation chains to its base first and never touches the stream. */ virtual void Post_Load(void); diff --git a/code/aircraft.cpp b/code/aircraft.cpp index d4314b432..b234f90b1 100644 --- a/code/aircraft.cpp +++ b/code/aircraft.cpp @@ -89,7 +89,6 @@ * _Counts_As_Civ_Evac -- Is the specified object a candidate for civilian evac logic? * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "aircraft.h" @@ -221,7 +220,7 @@ AircraftClass::AircraftClass(AircraftTypeClass const * type, HouseClass * house) Create_ID(); if (Class != NULL) { - Locomotion.CreateInstance(Class->Locomotor); + Locomotion = Create_Locomotor(Class->Locomotor); Locomotion->Link_To_Object(this); } @@ -266,48 +265,6 @@ void AircraftClass::Init(void) } -/// -/// Fetches the requested interface from this aircraft. -/// Aircraft add the fly control interface to the set that every game object supports, so -/// that the flying locomotor can interrogate them about how they wish to be flown. -/// -/// The identifier of the interface being asked for. -/// Pointer to the pointer to fill in with the interface. -/// Returns with S_OK if the interface was supplied. -HRESULT STDMETHODCALLTYPE AircraftClass::QueryInterface(struct _GUID const &guid, void **ppv) -{ - HRESULT res = BASECLASS::QueryInterface(guid, ppv); - if (FAILED(res)) { - if (guid == IID_IFlyControl) { - *ppv = (IFlyControl *)(this); - } - res = S_OK; - AddRef(); - } - return(res); -} - - -/// -/// Adds a reference to this aircraft. -/// -/// Returns with the new number of references outstanding. -ULONG STDMETHODCALLTYPE AircraftClass::AddRef(void) -{ - return(BASECLASS::AddRef()); -} - - -/// -/// Releases a reference to this aircraft. -/// -/// Returns with the number of references still outstanding. -ULONG STDMETHODCALLTYPE AircraftClass::Release(void) -{ - return(BASECLASS::Release()); -} - - /*********************************************************************************************** * AircraftClass::Unlimbo -- Removes an aircraft from the limbo state. * * * @@ -1397,8 +1354,7 @@ void AircraftClass::Drop_Off_Cargo(void) unit->IsOnBridge = false; } - unit->Locomotion.Release(); - unit->Locomotion = ILocomotionPtr(unit->TClass->Locomotor); + unit->Locomotion = Create_Locomotor(unit->TClass->Locomotor); unit->Locomotion->Link_To_Object(unit); if (!unit->Unlimbo(coord)) { @@ -3932,8 +3888,8 @@ void AircraftClass::Read_INI(CCINIClass const & ini) /// again once that identity has arrived. /// /// The stream to read this object from. -/// Returns with S_OK if the aircraft was loaded successfully. -HRESULT STDMETHODCALLTYPE AircraftClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool AircraftClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); @@ -4029,7 +3985,7 @@ void AircraftClass::Detach(AbstractClass const * target, bool all) /// can pick up or set down its cargo. /// /// Returns with the height above ground level to settle at. -LONG STDMETHODCALLTYPE AircraftClass::Landing_Altitude(void) +LONG AircraftClass::Landing_Altitude(void) { if (Class->IsCarryall && !Cargo.Is_Something_Attached() && In_Radio_Contact()) { BuildingClass * bptr = (BuildingClass *)Contact_With_Whom(); @@ -4057,7 +4013,7 @@ LONG STDMETHODCALLTYPE AircraftClass::Landing_Altitude(void) /// while loaded, or settles into the default parked pose. /// /// Returns with the facing to land at. -LONG STDMETHODCALLTYPE AircraftClass::Landing_Direction(void) +LONG AircraftClass::Landing_Direction(void) { TechnoClass * tptr = Contact_With_Whom(); if (tptr != NULL) { @@ -4076,7 +4032,7 @@ LONG STDMETHODCALLTYPE AircraftClass::Landing_Direction(void) /// empty one. /// /// Returns with true if there is cargo aboard this aircraft. -BOOL STDMETHODCALLTYPE AircraftClass::Is_Loaded(void) +BOOL AircraftClass::Is_Loaded(void) { return(Cargo.Is_Something_Attached()); } @@ -4088,7 +4044,7 @@ BOOL STDMETHODCALLTYPE AircraftClass::Is_Loaded(void) /// from a hover. Only a visible and unguided projectile is suited to strafing. /// /// Returns with true if the aircraft should make strafing attack runs. -LONG STDMETHODCALLTYPE AircraftClass::Is_Strafe(void) +LONG AircraftClass::Is_Strafe(void) { const WeaponDataStruct * data = Get_Class_Weapon_Data(0); if (data == NULL) { @@ -4114,7 +4070,7 @@ LONG STDMETHODCALLTYPE AircraftClass::Is_Strafe(void) /// to an attack run. /// /// Returns with true if the aircraft must hold its present heading. -LONG STDMETHODCALLTYPE AircraftClass::Is_Locked(void) +LONG AircraftClass::Is_Locked(void) { return(IsLockedStraight); } @@ -4233,18 +4189,9 @@ RTTIType AircraftClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence support. The save/load machinery uses the -/// class identifier to recreate an object of the correct type when a game is restored. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE AircraftClass::GetClassID(CLSID * retval) +ClassID AircraftClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AircraftClass; - return(S_OK); + return(ClassID_AircraftClass); } diff --git a/code/aircraft.h b/code/aircraft.h index 73a4baa03..591ddd970 100644 --- a/code/aircraft.h +++ b/code/aircraft.h @@ -59,23 +59,20 @@ class AircraftClass : public FootClass, public IFlyControl AircraftClass(AircraftTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~AircraftClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual ClassID Class_ID(void) const override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; /* * IFlyControl methods. */ - virtual LONG STDMETHODCALLTYPE Landing_Altitude(void) override; - virtual LONG STDMETHODCALLTYPE Landing_Direction(void) override; - virtual BOOL STDMETHODCALLTYPE Is_Loaded(void) override; - virtual LONG STDMETHODCALLTYPE Is_Strafe(void) override; - virtual LONG STDMETHODCALLTYPE Is_Locked(void) override; + virtual LONG Landing_Altitude(void) override; + virtual LONG Landing_Direction(void) override; + virtual BOOL Is_Loaded(void) override; + virtual LONG Is_Strafe(void) override; + virtual LONG Is_Locked(void) override; virtual void Init(void) override; virtual void Detach(AbstractClass const * target, bool all = true) override; diff --git a/code/airctype.cpp b/code/airctype.cpp index c86250758..6d3453a59 100644 --- a/code/airctype.cpp +++ b/code/airctype.cpp @@ -45,7 +45,6 @@ * AircraftTypeClass::operator new -- Allocates an aircraft type object from special pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "airctype.h" @@ -327,18 +326,9 @@ void AircraftTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of the aircraft type. -/// The save game machinery asks each object for this identifier so that it can create an -/// object of the right class again when the game is loaded. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if there was nowhere to put the answer. -HRESULT STDMETHODCALLTYPE AircraftTypeClass::GetClassID(CLSID * retval) +ClassID AircraftTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AircraftTypeClass; - return(S_OK); + return(ClassID_AircraftTypeClass); } diff --git a/code/airctype.h b/code/airctype.h index 8f462fbf2..913c8e1a8 100644 --- a/code/airctype.h +++ b/code/airctype.h @@ -57,7 +57,7 @@ class AircraftTypeClass : public TechnoTypeClass AircraftTypeClass(char const * ininame = NULL); virtual ~AircraftTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/aitrig.cpp b/code/aitrig.cpp index 8b0f19074..cfd2391dc 100644 --- a/code/aitrig.cpp +++ b/code/aitrig.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "aitrig.h" @@ -92,17 +91,9 @@ AITriggerTypeClass::~AITriggerTypeClass(void) } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this identifier to work out which class to build when the -/// object is read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE AITriggerTypeClass::GetClassID(CLSID * retval) +ClassID AITriggerTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AITriggerTypeClass; - return(S_OK); + return(ClassID_AITriggerTypeClass); } diff --git a/code/aitrig.h b/code/aitrig.h index 6d141b801..62dd872b1 100644 --- a/code/aitrig.h +++ b/code/aitrig.h @@ -60,7 +60,7 @@ class AITriggerTypeClass : public AbstractTypeClass AITriggerTypeClass(const char *name = NULL); ~AITriggerTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static AITriggerTypeClass * Find_Or_Make(char const * ininame); diff --git a/code/alphashp.cpp b/code/alphashp.cpp index b7cc98727..dcd3b66ec 100644 --- a/code/alphashp.cpp +++ b/code/alphashp.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "alphashp.h" @@ -93,18 +92,9 @@ AlphaShapeClass::~AlphaShapeClass(void) } -/// -/// Fetches the class identifier used to persist this object. -/// The save system writes this identifier ahead of the object data so that the loader -/// knows what kind of object to reconstruct. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE AlphaShapeClass::GetClassID(CLSID * retval) +ClassID AlphaShapeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AlphaShapeClass; - return(S_OK); + return(ClassID_AlphaShapeClass); } diff --git a/code/alphashp.h b/code/alphashp.h index fc891a87d..04a67befa 100644 --- a/code/alphashp.h +++ b/code/alphashp.h @@ -34,7 +34,7 @@ class AlphaShapeClass : public AbstractClass AlphaShapeClass(void); ~AlphaShapeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/anim.cpp b/code/anim.cpp index 62116e0ce..a8e78ca84 100644 --- a/code/anim.cpp +++ b/code/anim.cpp @@ -50,7 +50,6 @@ * Shorten_Attached_Anims -- Reduces attached animation durations. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "anim.h" @@ -245,7 +244,7 @@ AnimClass::AnimClass(AnimTypeClass const * type, Coord const & coord, int timede /// /// Constructs a blank animation object. /// This constructor serves the load system, which creates an empty animation through the -/// class factory and then fills it in from the save game. The animation joins the master +/// class table and then fills it in from the save game. The animation joins the master /// animation list but has no type and is nowhere on the map. /// AnimClass::AnimClass(void) : @@ -1729,18 +1728,9 @@ void AnimClass::Post_Load_Game(void) } -/// -/// Fetches the persistent class identifier for animation objects. -/// The save and load machinery uses this identifier to recreate the right kind of object -/// when a game is restored. -/// -/// Pointer to the location to store the class identifier at. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE AnimClass::GetClassID(CLSID * retval) +ClassID AnimClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AnimClass; - return(S_OK); + return(ClassID_AnimClass); } diff --git a/code/anim.h b/code/anim.h index 8be08019f..8cf2c763a 100644 --- a/code/anim.h +++ b/code/anim.h @@ -64,7 +64,7 @@ class AnimClass : public ObjectClass, public StageClass AnimClass(void); virtual ~AnimClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/animtype.cpp b/code/animtype.cpp index 95ed94f4d..8e4d29d6d 100644 --- a/code/animtype.cpp +++ b/code/animtype.cpp @@ -38,7 +38,6 @@ * AnimTypeClass::operator delete -- Returns an anim type class object back to the pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "animtype.h" @@ -577,18 +576,9 @@ void AnimTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the persistent class identifier of this object. -/// This routine is used by the save game machinery to recognize an animation type when it -/// comes back off the stream. -/// -/// Pointer to the place to store the class identifier. -/// Returns with S_OK, or E_POINTER if there was nowhere to store the answer. -HRESULT STDMETHODCALLTYPE AnimTypeClass::GetClassID(CLSID * retval) +ClassID AnimTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AnimTypeClass; - return(S_OK); + return(ClassID_AnimTypeClass); } diff --git a/code/animtype.h b/code/animtype.h index 11d63eed9..1b2432bf8 100644 --- a/code/animtype.h +++ b/code/animtype.h @@ -430,7 +430,7 @@ class AnimTypeClass : public ObjectTypeClass AnimTypeClass(char const * ininame = NULL); virtual ~AnimTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/base.cpp b/code/base.cpp index b4344eb5a..c3d566cbb 100644 --- a/code/base.cpp +++ b/code/base.cpp @@ -547,31 +547,6 @@ void BaseClass::Write_INI(CCINIClass & ini, char const * hname) /// Reads the base back in from a save game. /// /// Returns with the result reported by the stream read. -HRESULT STDMETHODCALLTYPE BaseClass::Load(IStream *stream) -{ - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("BaseClass"); - Serialize(savestream); - return(savestream.Result()); -} - - -/// -/// Writes the base out to a save game. -/// -/// Returns with the result reported by the stream write. -HRESULT STDMETHODCALLTYPE BaseClass::Save(IStream * stream) -{ - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(savestream.Result()); -} - - -/// -/// Lists the members the base plan holds. -/// -/// The stream carrying the members. void BaseClass::Serialize(SaveStreamClass & stream) { stream.Serialize(Nodes); diff --git a/code/base.h b/code/base.h index 352064766..7c8a0ed0b 100644 --- a/code/base.h +++ b/code/base.h @@ -37,7 +37,6 @@ #include "house.hh" #include "struct.hh" -#include class CCINIClass; @@ -103,8 +102,6 @@ class BaseClass */ void Read_INI(CCINIClass const & ini, char const * hname); void Write_INI(CCINIClass & ini, char const * hname); - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream); - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream); void Serialize(SaveStreamClass & stream); virtual void Compute_CRC(CRCEngine &) const; diff --git a/code/blight.cpp b/code/blight.cpp index 7850a58b5..a45f459db 100644 --- a/code/blight.cpp +++ b/code/blight.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "blight.h" @@ -280,17 +279,9 @@ void BuildingLightClass::AI(void) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game code so that an object of the right kind can -/// be created when the game is loaded back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BuildingLightClass::GetClassID(CLSID * retval) +ClassID BuildingLightClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BuildingLightClass; - return(S_OK); + return(ClassID_BuildingLightClass); } diff --git a/code/blight.h b/code/blight.h index c12e01e09..5a5ba3731 100644 --- a/code/blight.h +++ b/code/blight.h @@ -25,7 +25,7 @@ class BuildingLightClass : public ObjectClass BuildingLightClass(TechnoClass * owner = NULL); virtual ~BuildingLightClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/blowfish.cpp b/code/blowfish.cpp index cc9f746f1..91ec2c67e 100644 --- a/code/blowfish.cpp +++ b/code/blowfish.cpp @@ -39,15 +39,11 @@ #include "blowfish.h" -#ifndef NO_BLOWFISH_DLL -#include "iblowfish.h" -#endif #include -#ifdef NO_BLOWFISH_DLL /* ** Byte order controlled long integer. This integer is constructed ** so that character 0 (C0) is the most significant byte of the @@ -63,7 +59,6 @@ typedef union { unsigned char C0; } Char; } Int; -#endif /// @@ -73,11 +68,7 @@ typedef union { /// /// You must submit the key before calling the encrypt or decrypt routines. BlowfishEngine::BlowfishEngine(void) : -#ifndef NO_BLOWFISH_DLL - BlockCypher(CLSID_BlowfishObject) -#else IsKeyed(false) -#endif { } @@ -99,11 +90,9 @@ BlowfishEngine::BlowfishEngine(void) : *=============================================================================================*/ BlowfishEngine::~BlowfishEngine(void) { -#ifdef NO_BLOWFISH_DLL if (IsKeyed) { Submit_Key(NULL, 0); } -#endif } @@ -133,10 +122,6 @@ BlowfishEngine::~BlowfishEngine(void) *=============================================================================================*/ void BlowfishEngine::Submit_Key(void const * key, int length) { -#ifndef NO_BLOWFISH_DLL - BlockCypher->Set_Key(length, key); - return; -#else assert(length <= MAX_KEY_LENGTH); /* @@ -210,7 +195,6 @@ void BlowfishEngine::Submit_Key(void const * key, int length) } IsKeyed = true; -#endif } @@ -238,10 +222,6 @@ void BlowfishEngine::Submit_Key(void const * key, int length) *=============================================================================================*/ int BlowfishEngine::Encrypt(void const * plaintext, int length, void * cyphertext) { -#ifndef NO_BLOWFISH_DLL - BlockCypher->Encrypt(length, plaintext, cyphertext); - return(length); -#else if (plaintext == 0 || length == 0) { return(0); } @@ -281,7 +261,6 @@ int BlowfishEngine::Encrypt(void const * plaintext, int length, void * cyphertex memmove(cyphertext, plaintext, length); } return(length); -#endif } @@ -309,10 +288,6 @@ int BlowfishEngine::Encrypt(void const * plaintext, int length, void * cyphertex *=============================================================================================*/ int BlowfishEngine::Decrypt(void const * cyphertext, int length, void * plaintext) { -#ifndef NO_BLOWFISH_DLL - BlockCypher->Decrypt(length, cyphertext, plaintext); - return(length); -#else if (cyphertext == 0 || length == 0) { return(0); } @@ -352,11 +327,9 @@ int BlowfishEngine::Decrypt(void const * cyphertext, int length, void * plaintex memmove(plaintext, cyphertext, length); } return(length); -#endif } -#ifdef NO_BLOWFISH_DLL /*********************************************************************************************** * BlowfishEngine::Process_Block -- Process a block of data using Blowfish algorithm. * * * @@ -631,4 +604,3 @@ unsigned int const BlowfishEngine::S_Init[4][UCHAR_MAX+1] = { 0x90D4F869U,0xA65CDEA0U,0x3F09252DU,0xC208E69FU,0xB74E6132U,0xCE77E25BU,0x578FDFE3U,0x3AC372E6U } }; -#endif diff --git a/code/blowfish.h b/code/blowfish.h index 3cbc2d7c6..41d2e7e2b 100644 --- a/code/blowfish.h +++ b/code/blowfish.h @@ -33,14 +33,7 @@ #include "win.h" -/// Names and comments from TLBs - #include -#ifndef NO_BLOWFISH_DLL -#include "iblockci.h" -#include -_COM_SMARTPTR_TYPEDEF(IBlockCipher, __uuidof(IBlockCipher)); -#endif /* ** This engine will process data blocks by encryption and decryption. @@ -70,14 +63,6 @@ class BlowfishEngine { enum {MAX_KEY_LENGTH=56}; private: -#ifndef NO_BLOWFISH_DLL - /* - * This points to the block cipher object that performs the actual key setup and - * block processing. Where the cipher is available as a component, this engine is - * only a convenience wrapper around it and keeps no tables of its own. - */ - IBlockCipherPtr BlockCypher; -#else bool IsKeyed; void Sub_Key_Encrypt(unsigned int & left, unsigned int & right); @@ -107,5 +92,4 @@ class BlowfishEngine { ** S-Box tables (four). */ unsigned int bf_S[4][UCHAR_MAX+1]; -#endif }; diff --git a/code/brain.cpp b/code/brain.cpp index b09549d86..ef7aae44c 100644 --- a/code/brain.cpp +++ b/code/brain.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "brain.h" @@ -48,18 +47,9 @@ NeuronClass::~NeuronClass(void) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game system so that it knows what kind of object to -/// construct when the stream is read back in. -/// -/// Pointer to the place to store the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE NeuronClass::GetClassID(CLSID * retval) +ClassID NeuronClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_NeuronClass; - return(S_OK); + return(ClassID_NeuronClass); } @@ -155,19 +145,12 @@ bool BrainClass::Add_Neuron(NeuronClass *neuron) /// Saves this brain to the save game stream. /// /// Should the neurons be marked clean once they are written? -/// -/// Returns with S_OK when the brain was written, E_POINTER when no stream was supplied, -/// or the stream's own failure code. -/// -HRESULT BrainClass::Save(IStream * stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool BrainClass::Save(SaveStreamClass & stream, bool cleardirty) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream, cleardirty); - return(savestream.Result()); + Serialize(stream, cleardirty); + return(!stream.Was_Error()); } @@ -176,20 +159,13 @@ HRESULT BrainClass::Save(IStream * stream, BOOL cleardirty) /// Whatever neurons the brain was holding are destroyed first, so the stream's neurons /// entirely replace them. /// -/// -/// Returns with S_OK when the brain was read, E_POINTER when no stream was supplied, or -/// the stream's own failure code. -/// -HRESULT BrainClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool BrainClass::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("BrainClass"); - Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("BrainClass"); + Serialize(stream); + return(!stream.Was_Error()); } @@ -200,7 +176,7 @@ HRESULT BrainClass::Load(IStream * stream) /// /// The stream carrying the members. /// Should the neurons be marked clean once they are written? -void BrainClass::Serialize(SaveStreamClass & stream, BOOL cleardirty) +void BrainClass::Serialize(SaveStreamClass & stream, bool cleardirty) { int count = Neurons.Count(); stream.Serialize(count); @@ -212,10 +188,10 @@ void BrainClass::Serialize(SaveStreamClass & stream, BOOL cleardirty) for (int i = 0; i < count && !stream.Was_Error(); i++) { if (stream.Is_Loading()) { NeuronClass * neuron = new NeuronClass; - neuron->Load(stream.Get_Stream()); + neuron->Load(stream); Add_Neuron(neuron); } else { - Neurons[i]->Save(stream.Get_Stream(), cleardirty); + Neurons[i]->Save(stream, cleardirty); } } // MinCount -- the limits a brain was prepared with rather than anything it accumulated. diff --git a/code/brain.h b/code/brain.h index 9287f630c..198a18a5f 100644 --- a/code/brain.h +++ b/code/brain.h @@ -24,7 +24,7 @@ class NeuronClass : public AbstractClass NeuronClass(void); virtual ~NeuronClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual RTTIType Fetch_RTTI(void) const override { return(RTTI_NEURON); } @@ -62,10 +62,10 @@ class BrainClass void Init(int min, int max); bool Add_Neuron(NeuronClass *neuron); - HRESULT Load(IStream * stream); - HRESULT Save(IStream * stream, BOOL cleardirty); + bool Load(SaveStreamClass & stream); + bool Save(SaveStreamClass & stream, bool cleardirty); - void Serialize(SaveStreamClass & stream, BOOL cleardirty = FALSE); + void Serialize(SaveStreamClass & stream, bool cleardirty = false); private: /* diff --git a/code/building.cpp b/code/building.cpp index 4e9c1ee30..55e5d25a1 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -102,7 +102,6 @@ * BuildingClass::~BuildingClass -- Destructor for building type objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "building.h" @@ -124,6 +123,7 @@ #include "bullettype.h" #include "ccrand.h" #include "cell.h" +#include "classids.h" #include "combat.h" #include "conquer.h" #include "dbgprint.h" @@ -138,7 +138,6 @@ #include "house.h" #include "houstype.h" #include "iloco.h" -#include "ilocos.h" #include "incdec.h" #include "infantry.h" #include "infatype.h" @@ -5533,10 +5532,8 @@ int BuildingClass::Do_MISSION_REPAIR(void) ** distance check. Fixed-wing aircraft are very inaccurate with ** their landings. */ - IPersistPtr persist(tech->Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); - bool hover = (clsid == CLSID_HoverLocomotion) != 0; + ClassID const clsid = Locomotion_Class_ID(tech->Locomotion.get()); + bool hover = (clsid == ClassID_HoverLocomotion) != 0; if (hover) { distance = 0x96; } @@ -5993,7 +5990,7 @@ int BuildingClass::Do_MISSION_MISSILE(void) Status = DONE; return(1); } else { - bullet->Release(); + delete bullet; Begin_Mode(BSTATE_IDLE); // keep the door closed. Assign_Mission(MISSION_GUARD); return(4 * TICKS_PER_SECOND); @@ -6253,27 +6250,25 @@ int BuildingClass::Do_MISSION_UNLOAD(void) if (unit) { unit->Assign_Mission(MISSION_MOVE); - IPersistPtr persist(unit->Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + ClassID const clsid = Locomotion_Class_ID(unit->Locomotion.get()); - if (clsid == CLSID_TunnelLocomotion) { - IPiggybackPtr piggy(unit->Locomotion); + if (clsid == ClassID_TunnelLocomotion) { + IPiggyback * piggy = Piggyback_Of(unit->Locomotion.get()); if (piggy != NULL && piggy->Is_Piggybacking()) { - piggy->End_Piggyback(&unit->Locomotion); + unit->Locomotion = piggy->End_Piggyback(); } - ILocomotionPtr walk(CLSID_DriveLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_DriveLocomotion); walk->Link_To_Object(unit); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { piggy->Begin_Piggyback(unit->Locomotion); - unit->Locomotion = walk; + unit->Locomotion = std::move(walk); unit->Locomotion->Force_Track(DriveLocomotionClass::OUT_OF_WEAPON_FACTORY, coord); } else { int damage = unit->Strength; unit->Take_Damage(damage, 0, Rule->C4Warhead, NULL, true); } - } else if (clsid != CLSID_DriveLocomotion) { + } else if (clsid != ClassID_DriveLocomotion) { unit->Assign_Destination(&Map[Get_Cell() + Cell(3, 1)]); } else { Coord cs; @@ -8845,9 +8840,8 @@ void BuildingClass::Clear_Occupy_Bit(Coord const & coord) /// since the one it is about to be given is the one it was saved with. Post_Load enters it /// again once that identity has arrived. /// -/// Returns with S_OK if the building was read, or the failure code from the -/// underlying stream. -HRESULT STDMETHODCALLTYPE BuildingClass::Load(IStream *stream) +/// bool; Was the record read whole? +bool BuildingClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); @@ -10348,18 +10342,9 @@ void BuildingClass::Discharge_Turret(void) } -/// -/// Fetches the persistent class identifier for this building. -/// This routine is part of the persistence support. The save code writes this identifier -/// ahead of the object so that the loader knows what kind of object to create. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BuildingClass::GetClassID(CLSID * retval) +ClassID BuildingClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BuildingClass; - return(S_OK); + return(ClassID_BuildingClass); } diff --git a/code/building.h b/code/building.h index fd605bf5f..3dbbd841e 100644 --- a/code/building.h +++ b/code/building.h @@ -357,8 +357,8 @@ class BuildingClass : public TechnoClass BuildingClass(BuildingTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~BuildingClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual ClassID Class_ID(void) const override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/builtype.cpp b/code/builtype.cpp index 72fdc7395..478a06b25 100644 --- a/code/builtype.cpp +++ b/code/builtype.cpp @@ -55,7 +55,6 @@ * BuildingTypeClass::operator new -- Allocates a building type object from the special heap.* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "builtype.h" @@ -1942,18 +1941,9 @@ void BuildingTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game system so that it knows what kind of object to -/// construct when the stream is read back in. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BuildingTypeClass::GetClassID(CLSID * retval) +ClassID BuildingTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BuildingTypeClass; - return(S_OK); + return(ClassID_BuildingTypeClass); } diff --git a/code/builtype.h b/code/builtype.h index 00c63bcd9..4019ff5ad 100644 --- a/code/builtype.h +++ b/code/builtype.h @@ -855,7 +855,7 @@ class BuildingTypeClass : public TechnoTypeClass BuildingTypeClass(char const * ininame = NULL); virtual ~BuildingTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/bullet.cpp b/code/bullet.cpp index fd5ef3660..40c62322f 100644 --- a/code/bullet.cpp +++ b/code/bullet.cpp @@ -47,7 +47,6 @@ * BulletClass::~BulletClass -- Destructor for bullet objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "bullet.h" @@ -95,7 +94,6 @@ #include -extern ULONG COMRefCount; /*********************************************************************************************** @@ -1461,35 +1459,6 @@ void BulletClass::Serialize(SaveStreamClass & stream) } -/// -/// Takes out a reference on this projectile. -/// This is the IUnknown implementation used by the COM machinery that owns projectiles. -/// -/// Returns with the number of references now outstanding. -ULONG STDMETHODCALLTYPE BulletClass::AddRef(void) -{ - COMRefCount++; - return(InterlockedIncrement(&RefCount)); -} - - -/// -/// Drops a reference to this projectile. -/// This is the IUnknown implementation. The projectile deletes itself when the last -/// reference to it is released. -/// -/// Returns with the number of references still outstanding. -ULONG STDMETHODCALLTYPE BulletClass::Release(void) -{ - COMRefCount--; - ULONG count = InterlockedDecrement(&RefCount); - if (count == 0) { - delete this; - } - return(count); -} - - /// /// Can this projectile steer toward its target? /// The flight logic calls this routine to decide whether the projectile should be turned @@ -1507,9 +1476,7 @@ bool BulletClass::Is_Homing(void) const /// /// Creates a projectile and fills in the data for the shot. -/// This routine is used by the weapon firing code in place of a bare new -- projectiles are -/// COM objects, so the instance must come from the class factory. The projectile is inert -/// until it is unlimboed with a starting position and velocity. +/// The projectile is inert until it is unlimboed with a starting position and velocity. /// /// The object that fired the shot. It receives credit for any kill. /// The damage the projectile will inflict when it detonates. @@ -1518,12 +1485,7 @@ bool BulletClass::Is_Homing(void) const /// made. BulletClass * Create_Bullet(BulletTypeClass const *type, AbstractClass *target, TechnoClass *payback, int strength, WarheadTypeClass const *warhead, int max_speed, int range, bool bright) { - LPVOID unk = NULL; - if (FAILED(CoCreateInstance(CLSID_BulletClass, NULL, CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER|CLSCTX_LOCAL_SERVER, IID_IUnknown, &unk))) { - return(NULL); - } - - BulletClass * bullet = (BulletClass *)unk; + BulletClass * bullet = new BulletClass; bullet->Set_Bullet_Data(type, target, payback, strength, warhead, max_speed, range, bright); return(bullet); } @@ -1570,17 +1532,9 @@ RTTIType BulletClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier for this projectile. -/// This is the IPersist implementation the save and load machinery uses to recognize which -/// kind of object it is about to read back from the stream. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BulletClass::GetClassID(CLSID * retval) +ClassID BulletClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BulletClass; - return(S_OK); + return(ClassID_BulletClass); } diff --git a/code/bullet.h b/code/bullet.h index a51519ad8..4f2f2aa2a 100644 --- a/code/bullet.h +++ b/code/bullet.h @@ -52,8 +52,6 @@ class BulletClass : public ObjectClass typedef ObjectClass BASECLASS; public: - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; public: @@ -78,7 +76,7 @@ class BulletClass : public ObjectClass BulletClass(void); virtual ~BulletClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/bullettype.cpp b/code/bullettype.cpp index 4fd298dcc..0c19cd2db 100644 --- a/code/bullettype.cpp +++ b/code/bullettype.cpp @@ -38,7 +38,6 @@ * BulletTypeClass::operator new -- Allocates a bullet type object from the special heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "bullettype.h" @@ -356,15 +355,9 @@ void BulletTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier that the save game code stores for this object. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BulletTypeClass::GetClassID(CLSID * retval) +ClassID BulletTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BulletTypeClass; - return(S_OK); + return(ClassID_BulletTypeClass); } diff --git a/code/bullettype.h b/code/bullettype.h index 9a8c96831..ecd3ceaec 100644 --- a/code/bullettype.h +++ b/code/bullettype.h @@ -237,7 +237,7 @@ class BulletTypeClass : public ObjectTypeClass BulletTypeClass(char const * name = NULL); virtual ~BulletTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/campaign.cpp b/code/campaign.cpp index 2731a716a..8ac83dc87 100644 --- a/code/campaign.cpp +++ b/code/campaign.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "campaign.h" @@ -126,18 +125,9 @@ void Read_Battle_INI(CCINIClass const & ini) } -/// -/// Fetches the class identifier of this object. -/// This routine is required of every persistent object so that the save game loader -/// can recognize what to construct when the object is read back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE CampaignClass::GetClassID(CLSID * retval) +ClassID CampaignClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_CampaignClass; - return(S_OK); + return(ClassID_CampaignClass); } diff --git a/code/campaign.h b/code/campaign.h index a59c4da89..585345e8d 100644 --- a/code/campaign.h +++ b/code/campaign.h @@ -23,7 +23,7 @@ class CampaignClass : public AbstractTypeClass CampaignClass(char const * name = NULL); virtual ~CampaignClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/cell.cpp b/code/cell.cpp index eca014b12..6218a1685 100644 --- a/code/cell.cpp +++ b/code/cell.cpp @@ -74,7 +74,6 @@ * CellClass::Wall_Update -- Updates the imagery for wall objects in cell. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "cell.h" @@ -4366,6 +4365,13 @@ void CellClass::Serialize(SaveStreamClass & stream) stream.Serialize(CellID); + // Post_Load installs the cell in the array slot this coordinate names, so a coordinate + // that names none is refused here, while the record can still be thrown away whole. + if (stream.Is_Loading() && Map.Cell_Slot(CellID) < 0) { + stream.Fail(); + return; + } + /* * The snapshot list is built only once something standing here has been fogged over, * so whether the cell has one at all travels ahead of its contents. @@ -4469,7 +4475,11 @@ void CellClass::Post_Load(void) { BASECLASS::Post_Load(); - int id = CellID.X + (CellID.Y << 9); + int id = Map.Cell_Slot(CellID); + if (id < 0) { + return; + } + if (Map.Array[id] != NULL) { delete Map.Array[id]; Map.Array[id] = NULL; @@ -5168,18 +5178,9 @@ void CellClass::Detach(AbstractClass const * target) } -/// -/// Fetches the class identifier of this object. -/// This is the persistence requirement that lets the save system recognize a cell when a -/// saved game is read back in. -/// -/// Pointer to the location to store the class identifier in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE CellClass::GetClassID(CLSID * retval) +ClassID CellClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_CellClass; - return(S_OK); + return(ClassID_CellClass); } diff --git a/code/cell.h b/code/cell.h index 790b5b7dc..c5bda32e7 100644 --- a/code/cell.h +++ b/code/cell.h @@ -517,7 +517,7 @@ class CellClass : public AbstractClass CellClass(void); virtual ~CellClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/classfactory.cpp b/code/classfactory.cpp new file mode 100644 index 000000000..bf3c87adb --- /dev/null +++ b/code/classfactory.cpp @@ -0,0 +1,67 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "always.h" + +#include "classfactory.h" +#include "dbgprint.h" + +#include + +namespace { + +struct ClassEntryType { + ClassID Class; + ClassCreatorType Creator; +}; + +std::vector Classes; + +} // namespace + + +// A later registration of the same identifier wins, as the last class object +// published did before. +void Register_Class(ClassID const & classid, ClassCreatorType creator) +{ + for (ClassEntryType & entry : Classes) { + if (entry.Class == classid) { + entry.Creator = creator; + return; + } + } + Classes.push_back({ classid, creator }); +} + + +void Unregister_Classes(void) +{ + Classes.clear(); +} + + +/// +/// Creates a new object of the registered class named by the identifier. +/// +/// The object, owned by the caller, or nothing with a debug line naming the +/// identifier when no class was registered for it. +std::unique_ptr Create_Object(ClassID const & classid) +{ + for (ClassEntryType const & entry : Classes) { + if (entry.Class == classid) { + return(entry.Creator()); + } + } + + DebugString("No class is registered for {%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n", + (unsigned long)classid.Data1, (unsigned int)classid.Data2, (unsigned int)classid.Data3, + classid.Data4[0], classid.Data4[1], classid.Data4[2], classid.Data4[3], + classid.Data4[4], classid.Data4[5], classid.Data4[6], classid.Data4[7]); + return(nullptr); +} diff --git a/code/classfactory.h b/code/classfactory.h index f11d77047..cebba65c9 100644 --- a/code/classfactory.h +++ b/code/classfactory.h @@ -9,113 +9,20 @@ #pragma once -template -class TClassFactory : public IClassFactory -{ - public: - TClassFactory(void); - - STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj); - STDMETHOD_(ULONG, AddRef)(void); - STDMETHOD_(ULONG, Release)(void); - - STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppbObj); - STDMETHOD(LockServer)(BOOL fLock); - - private: - /* - * This is the number of outstanding references to this factory, counting both the - * interface pointers handed out and any server locks taken. The factory deletes - * itself once the count falls back to zero. - */ - LONG RefCount; -}; - - -template -TClassFactory::TClassFactory(void) : - RefCount(0) -{ -} - - -template -STDMETHODIMP TClassFactory::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == NULL) { - return(E_POINTER); - } - - *ppvObj = NULL; - - if (riid == IID_IUnknown) { - *ppvObj = (void *)((IClassFactory *)this); - } else if (riid == IID_IClassFactory) { - *ppvObj = (void *)((IClassFactory *)this); - } - - if (*ppvObj == NULL) { - return(E_NOINTERFACE); - } - - ((IClassFactory *)this)->AddRef(); - - return(S_OK); -} - - -template -ULONG TClassFactory::AddRef(void) -{ - return(InterlockedIncrement(&RefCount)); -} +#include "persist.h" +#include -template -ULONG TClassFactory::Release(void) -{ - int count = InterlockedDecrement(&RefCount); - if (count == 0) { - delete this; - } - - return(count); -} - - -template -STDMETHODIMP TClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObj) -{ - if (ppvObj == NULL) { - return(E_INVALIDARG); - } - - *ppvObj = NULL; - if (pUnkOuter != NULL) { - return(CLASS_E_NOAGGREGATION); - } - - T *obj = new T(); - if (obj == NULL) { - return(E_OUTOFMEMORY); - } - - HRESULT hr = obj->QueryInterface(riid, ppvObj); - if (FAILED(hr)) { - delete obj; - } - - return(hr); -} +// The classes a saved game or a unit type can name by class identifier. Startup +// registers each one; nothing is created for an identifier nobody registered. +using ClassCreatorType = std::unique_ptr (*)(void); +void Register_Class(ClassID const & classid, ClassCreatorType creator); +void Unregister_Classes(void); +std::unique_ptr Create_Object(ClassID const & classid); template -HRESULT STDMETHODCALLTYPE TClassFactory::LockServer(BOOL fLock) +void Register_Class(ClassID const & classid) { - if (fLock) { - RefCount++; - } else { - RefCount--; - } - return(S_OK); + Register_Class(classid, []() -> std::unique_ptr { return(std::make_unique()); }); } diff --git a/code/classid.h b/code/classid.h new file mode 100644 index 000000000..42474d286 --- /dev/null +++ b/code/classid.h @@ -0,0 +1,35 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include + +// The identity a persistent class is saved and named by. The sixteen bytes are those of +// the COM class identifier the class once registered, kept as they are because saved +// games and the Locomotor= key carry them. +struct ClassID +{ + unsigned int Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; +}; + +static_assert(sizeof(ClassID) == 16, "a class identifier is sixteen bytes on disk"); + +inline bool operator==(ClassID const & left, ClassID const & right) +{ + return(std::memcmp(&left, &right, sizeof(ClassID)) == 0); +} + +inline bool operator!=(ClassID const & left, ClassID const & right) +{ + return(!(left == right)); +} diff --git a/code/classids.cpp b/code/classids.cpp new file mode 100644 index 000000000..0b88b27d7 --- /dev/null +++ b/code/classids.cpp @@ -0,0 +1,82 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "classids.h" + +ClassID const ClassID_HouseClass = {0xD9D4A910,0x87C6,0x11D1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_SuperWeaponTypeClass = {0x0CF2BCE7,0x36E4,0x11D2,{0xB8,0xD8,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_SuperWeaponClass = {0xD7F754C6,0x391C,0x11D2,{0x9B,0x64,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; +ClassID const ClassID_UnitTypeClass = {0xDCBD42EA,0x0546,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_InfantryTypeClass = {0xAE8B33D8,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_AircraftTypeClass = {0xAE8B33D9,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_BuildingTypeClass = {0xAE8B33DB,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_BulletTypeClass = {0x5AF2CE77,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TerrainTypeClass = {0x5AF2CE7B,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_IsometricTileTypeClass = {0x5AF2CE7A,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_OverlayTypeClass = {0x5AF2CE79,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_SmudgeTypeClass = {0x5AF2CE78,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_AnimTypeClass = {0xAE8B33DA,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_HouseTypeClass = {0x1DD43928,0x046B,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_IsometricTileClass = {0x0E272DC0,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_VoxelAnimClass = {0x0E272DC1,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_AircraftClass = {0x0E272DC2,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_AnimClass = {0x0E272DC3,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_InfantryClass = {0x0E272DC4,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_SmudgeClass = {0x0E272DC5,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_BuildingClass = {0x0E272DC6,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_OverlayClass = {0x0E272DC7,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleSystemClass = {0x0E272DC8,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleSystemTypeClass = {0x703E044A,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_BulletClass = {0x0E272DC9,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_UnitClass = {0x0E272DCA,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleClass = {0x0E272DCC,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleTypeClass = {0x703E044B,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WaveClass = {0x0E272DCD,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_BuildingLightClass = {0x54822258,0xD8A8,0x11D1,{0xB4,0x62,0x00,0x60,0x97,0xC6,0xA9,0x79}}; +ClassID const ClassID_TerrainClass = {0x0E272DCE,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TubeClass = {0x0B4CA41C,0xB3A7,0x11D1,{0xB4,0x57,0x00,0x60,0x97,0xC6,0xA9,0x79}}; +ClassID const ClassID_TeamClass = {0x0E272DCF,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TaskForceClass = {0x61DE341E,0x0774,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TeamTypeClass = {0xD1DBA64E,0x0778,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_VoxelAnimTypeClass = {0x2EBB6D66,0x0D4D,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_ScriptClass = {0x42F3A646,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_ScriptTypeClass = {0x42F3A647,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TagClass = {0x54F6E432,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TagTypeClass = {0x54F6E433,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TriggerClass = {0xC02D1590,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TriggerTypeClass = {0xC02D1591,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_ActionClass = {0x4F0EC392,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_EventClass = {0x4F0EC393,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_FactoryClass = {0x34ECD9A8,0x0AB0,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WeaponTypeClass = {0x9FD219CA,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WarheadTypeClass = {0xA8C54DA4,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WaypointPath = {0xF73125BA,0x1054,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_LightSource = {0x6F9C48F0,0x1207,0x11D2,{0x81,0x74,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_CampaignClass = {0xFFDAC848,0x1517,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_SideClass = {0xC53DD372,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TiberiumClass = {0xC53DD373,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_CellClass = {0xC1BF99CE,0x1A8C,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_EMPulseClass = {0xB825CB22,0x200E,0x11D2,{0x9F,0xA9,0x00,0x60,0x08,0x9A,0xD4,0x58}}; +ClassID const ClassID_TacticalMapClass = {0xCF56B38A,0x240D,0x11D2,{0x81,0x7C,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_AITriggerTypeClass = {0xBA093524,0x4CF4,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; +ClassID const ClassID_AITriggerClass = {0x03C4CE76,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; +ClassID const ClassID_NeuronClass = {0x241AB316,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; +ClassID const ClassID_FoggedObjectClass = {0x1C470B0E,0x69D7,0x11D2,{0xB8,0xF2,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_AlphaShapeClass = {0x623C7584,0x74E7,0x11D2,{0xB8,0xF5,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_VeinholeMonsterClass = {0x5192D06A,0xC632,0x11D2,{0xB9,0x0B,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_DriveLocomotion = {0x4A582741,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_HoverLocomotion = {0x4A582742,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TunnelLocomotion = {0x4A582743,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_WalkLocomotion = {0x4A582744,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_BallisticLocomotion = {0x4A582745,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_FlyerLocomotion = {0x4A582746,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TeleportLocomotion = {0x4A582747,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_MechLocomotion = {0x55D141B8,0xDB94,0x11D1,{0xAC,0x98,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_JumpjetLocomotion = {0x92612C46,0xF71F,0x11D1,{0xAC,0x9F,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_LevitateLocomotion = {0x3DC0B295,0x6546,0x11D3,{0x80,0xB0,0x00,0x90,0x27,0x92,0x49,0x4C}}; diff --git a/code/classids.h b/code/classids.h new file mode 100644 index 000000000..e86803bed --- /dev/null +++ b/code/classids.h @@ -0,0 +1,85 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include "classid.h" + +// The identifiers of every class a saved game or a Locomotor= key can name. +extern ClassID const ClassID_HouseClass; +extern ClassID const ClassID_SuperWeaponTypeClass; +extern ClassID const ClassID_SuperWeaponClass; +extern ClassID const ClassID_UnitTypeClass; +extern ClassID const ClassID_InfantryTypeClass; +extern ClassID const ClassID_AircraftTypeClass; +extern ClassID const ClassID_BuildingTypeClass; +extern ClassID const ClassID_BulletTypeClass; +extern ClassID const ClassID_TerrainTypeClass; +extern ClassID const ClassID_IsometricTileTypeClass; +extern ClassID const ClassID_OverlayTypeClass; +extern ClassID const ClassID_SmudgeTypeClass; +extern ClassID const ClassID_AnimTypeClass; +extern ClassID const ClassID_HouseTypeClass; +extern ClassID const ClassID_IsometricTileClass; +extern ClassID const ClassID_VoxelAnimClass; +extern ClassID const ClassID_AircraftClass; +extern ClassID const ClassID_AnimClass; +extern ClassID const ClassID_InfantryClass; +extern ClassID const ClassID_SmudgeClass; +extern ClassID const ClassID_BuildingClass; +extern ClassID const ClassID_OverlayClass; +extern ClassID const ClassID_ParticleSystemClass; +extern ClassID const ClassID_ParticleSystemTypeClass; +extern ClassID const ClassID_BulletClass; +extern ClassID const ClassID_UnitClass; +extern ClassID const ClassID_ParticleClass; +extern ClassID const ClassID_ParticleTypeClass; +extern ClassID const ClassID_WaveClass; +extern ClassID const ClassID_BuildingLightClass; +extern ClassID const ClassID_TerrainClass; +extern ClassID const ClassID_TubeClass; +extern ClassID const ClassID_TeamClass; +extern ClassID const ClassID_TaskForceClass; +extern ClassID const ClassID_TeamTypeClass; +extern ClassID const ClassID_VoxelAnimTypeClass; +extern ClassID const ClassID_ScriptClass; +extern ClassID const ClassID_ScriptTypeClass; +extern ClassID const ClassID_TagClass; +extern ClassID const ClassID_TagTypeClass; +extern ClassID const ClassID_TriggerClass; +extern ClassID const ClassID_TriggerTypeClass; +extern ClassID const ClassID_ActionClass; +extern ClassID const ClassID_EventClass; +extern ClassID const ClassID_FactoryClass; +extern ClassID const ClassID_WeaponTypeClass; +extern ClassID const ClassID_WarheadTypeClass; +extern ClassID const ClassID_WaypointPath; +extern ClassID const ClassID_LightSource; +extern ClassID const ClassID_CampaignClass; +extern ClassID const ClassID_SideClass; +extern ClassID const ClassID_TiberiumClass; +extern ClassID const ClassID_CellClass; +extern ClassID const ClassID_EMPulseClass; +extern ClassID const ClassID_TacticalMapClass; +extern ClassID const ClassID_AITriggerTypeClass; +extern ClassID const ClassID_AITriggerClass; +extern ClassID const ClassID_NeuronClass; +extern ClassID const ClassID_FoggedObjectClass; +extern ClassID const ClassID_AlphaShapeClass; +extern ClassID const ClassID_VeinholeMonsterClass; +extern ClassID const ClassID_DriveLocomotion; +extern ClassID const ClassID_HoverLocomotion; +extern ClassID const ClassID_TunnelLocomotion; +extern ClassID const ClassID_WalkLocomotion; +extern ClassID const ClassID_BallisticLocomotion; +extern ClassID const ClassID_FlyerLocomotion; +extern ClassID const ClassID_TeleportLocomotion; +extern ClassID const ClassID_MechLocomotion; +extern ClassID const ClassID_JumpjetLocomotion; +extern ClassID const ClassID_LevitateLocomotion; diff --git a/code/crc.cpp b/code/crc.cpp index 60238085d..bd7f3a4eb 100644 --- a/code/crc.cpp +++ b/code/crc.cpp @@ -295,7 +295,7 @@ unsigned int CRC::_Table[ 256 ] = /// The CRC value to accumulate onto. Pass the result of a previous /// call in order to chain several blocks into one value. /// Returns with the CRC of the block. -unsigned int CRC::Memory( unsigned char *data, unsigned int length, unsigned int crc ) +unsigned int CRC::Memory( unsigned char const *data, unsigned int length, unsigned int crc ) { crc ^= 0xFFFFFFFF; // invert previous CRC while ( length-- ) { diff --git a/code/crc.h b/code/crc.h index 490f48cee..9fa56c6ab 100644 --- a/code/crc.h +++ b/code/crc.h @@ -50,7 +50,7 @@ class CRC { public: // get the CRC of a block of memory - static unsigned int Memory( unsigned char *data, unsigned int length, unsigned int crc = 0 ); + static unsigned int Memory( unsigned char const *data, unsigned int length, unsigned int crc = 0 ); // get the CRC of a null-terminated string static unsigned int String( const char *string, unsigned int crc = 0 ); diff --git a/code/cstream.cpp b/code/cstream.cpp deleted file mode 100644 index 65061d9da..000000000 --- a/code/cstream.cpp +++ /dev/null @@ -1,540 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#include "always.h" - -#include "cstream.h" - -#include - -extern ULONG COMRefCount; - -/// -/// Creates a compressing stream object. -/// This routine prepares the working buffers that the LZO codec needs. The object starts -/// out with no storage stream of its own to compress through. -/// -/// Call Link_Stream to attach a storage stream before reading or writing. -CStreamClass::CStreamClass(void) : - StreamPtr(NULL), - RefCount(0), - IsReading(false), - IsWriting(false), - CurOffset(0), - DataBuffer(new unsigned char[BUFFER_SIZE]), - StreamBuffer(new unsigned char[STREAM_BUFFER_SIZE]), - LZODictionary(new unsigned char[LZO1X_1_MEM_COMPRESS]) -{ - BlockHead.CompSize = BUFFER_SIZE - 1; -} - - -/// -/// Destroys the compressing stream object. -/// Any storage stream still attached is unlinked first, so that whatever is left in the -/// work buffer is compressed out rather than lost. -/// -CStreamClass::~CStreamClass(void) -{ - IUnknown **unk = NULL; - if (StreamPtr) { - Unlink_Stream(unk); - } - - delete [] LZODictionary; - LZODictionary = NULL; - delete [] DataBuffer; - DataBuffer = NULL; - delete [] StreamBuffer; - StreamBuffer = NULL; -} - - -/// -/// Takes out a reference on this stream object. -/// -/// Returns with the new reference count. -ULONG CStreamClass::AddRef(void) -{ - COMRefCount++; - return(InterlockedIncrement(&RefCount)); -} - - -/// -/// Releases a reference to this stream object. -/// This routine will destroy the object once the last outstanding reference has been -/// given up. -/// -/// Returns with the number of references that remain. -ULONG CStreamClass::Release(void) -{ - COMRefCount--; - ULONG i = InterlockedDecrement(&RefCount); - - if (i == 0) { - delete this; - } - - return(i); -} - - -/// -/// Fetches an alternate interface to this stream object. -/// The IUnknown, IStream and ILinkStream interfaces are the ones supported. A successful -/// query takes out a reference on this object for the caller. -/// -/// The identifier of the interface being asked for. -/// Pointer to the interface pointer to fill in. -/// Returns with S_OK, or E_NOINTERFACE if the interface is not supported. -LONG CStreamClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - if (riid == IID_IUnknown) { - *ppvObject = this; - } - if (riid == IID_IStream) { - *ppvObject = (IStream *)this; - } - if (riid == IID_ILinkStream) { - *ppvObject = (ILinkStream *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - //reinterpret_cast(*ppvObject)->AddRef(); - this->AddRef(); - return(S_OK); -} - - -/// -/// Attaches the storage stream this object compresses through. -/// Use this routine to bind the compressor to the real stream that the compressed blocks -/// will be written to or read back from. Only one stream may be attached at a time. -/// -/// Pointer to the object to fetch the storage stream from. -/// Returns with S_OK, or E_FAIL if a stream is already attached. -HRESULT CStreamClass::Link_Stream(IUnknown *stream) -{ - if (stream == NULL) { - return(E_POINTER); - } - - if (StreamPtr != NULL) { - return(E_FAIL); - } - - HRESULT hr = stream->QueryInterface(__uuidof(IStream), (void **)&stream); - if (FAILED(hr)) { - /// &StreamPtr; - StreamPtr.Attach(NULL, false); - } else { - StreamPtr.Attach((IStream *)stream, false); - } - - if (FAILED(hr) && (hr != E_NOINTERFACE)) { - _com_issue_error(hr); - } - - return(S_OK); -} - - -/// -/// Detaches the underlying storage stream from this object. -/// Anything still sitting in the work buffer is compressed out first and the storage -/// stream is committed, so the caller gets back a complete stream. -/// -/// Pointer to fill in with the released stream, or NULL if the caller -/// does not want it. -/// Returns with S_OK, or E_FAIL if no stream was attached. A commit failure is -/// returned as it stands. -HRESULT CStreamClass::Unlink_Stream(IUnknown **stream) -{ - Compress(); - - if (StreamPtr == NULL) { - return(E_FAIL); - } - - if (stream != NULL) { - StreamPtr->AddRef(); - *stream = StreamPtr; - } - - HRESULT hr = StreamPtr->Commit(0); - if (SUCCEEDED(hr)) { - StreamPtr.Release(); - } else { - return(hr); - } - - return(S_OK); -} - - -/// -/// Reads and decompresses data from the underlying stream. -/// This routine pulls whole compressed blocks out of the storage stream and doles out -/// pieces of the decompressed result until the caller's request has been satisfied. -/// -/// Pointer to the buffer to fill with the data read. -/// The number of bytes to read. -/// Pointer to fill in with the number of bytes read, or NULL if the -/// count is not wanted. -/// Returns with S_OK, or an error code if the data could not be read. -/// A stream that is being written cannot also be read. -HRESULT CStreamClass::Read(void *pv, ULONG cb, ULONG *pcbRead) -{ - int read_size; - int left; - - read_size = cb; - left = cb; - - if (pv == NULL) { - return(E_POINTER); - } - - if (read_size < 0) { - return(E_INVALIDARG); - } - - if (StreamPtr == NULL) { - return(E_FAIL); - } - - if (IsWriting) { - return(E_FAIL); - } - - IsReading = true; - - if (pcbRead != NULL) { - *pcbRead = 0; - } - - while (left > 0) { - - int offset = CurOffset; - if (offset > 0) { - int len = left; - if (left >= offset) { - len = CurOffset; - } - memmove(pv, (char *)DataBuffer + BlockHead.UncompSize - offset, len); - pv = (char *)pv + len; - left -= len; - CurOffset -= len; - } - - if (left == 0) { - break; - } - - ULONG read = 0; - - HRESULT hr = StreamPtr->Read(&BlockHead, sizeof(BlockHead), &read); - if (FAILED(hr)) { - return(hr); - } - - if (read != sizeof(BlockHead)) { - return(E_FAIL); - } - - if (BlockHead.CompSize > STREAM_BUFFER_SIZE) { - return(E_FAIL); - } - - hr = StreamPtr->Read(StreamBuffer, BlockHead.CompSize, &read); - if (FAILED(hr)) { - return(hr); - } - - unsigned int inlen = BlockHead.CompSize; - if (read != inlen) { - return(E_FAIL); - } - lzo_byte *out = (lzo_byte *)DataBuffer; - lzo_byte *in = (lzo_byte *)StreamBuffer; - lzo_uint out_len = BUFFER_SIZE; - if (lzo1x_decompress_safe(in, inlen, out, &out_len, NULL) != LZO_E_OK) { - return(E_FAIL); - } - // Compress records the whole buffer size rather than the block's own length, so only - // the decompressor's count says how much of the buffer is real. - BlockHead.UncompSize = out_len; - CurOffset = out_len; - } - - if (pcbRead != NULL) { - *pcbRead = read_size; - } - - return(S_OK); -} - - -/// -/// Compresses data out to the underlying stream. -/// This routine gathers the caller's data into a work buffer and hands it to the -/// compressor a block at a time, so what reaches the storage stream is a run of -/// compressed blocks rather than the raw bytes. -/// -/// Pointer to the data to write. -/// The number of bytes to write. -/// Pointer to fill in with the number of bytes accepted, or NULL -/// if the count is not wanted. -/// Returns with S_OK, or an error code if the data could not be written. -/// A stream that is being read cannot also be written. The trailing partial -/// block does not reach the storage stream until the object is flushed or unlinked. -HRESULT CStreamClass::Write(const void *pv, ULONG cb, ULONG *pcbWritten) -{ - unsigned char *ptr; - int write_size; - int left; - int result; - int temp_size; - - ptr = (unsigned char *)pv; - write_size = cb; - left = cb; - - if (pv == NULL) { - return(E_POINTER); - } - - if (write_size < 0) { - return(E_INVALIDARG); - } - - if (StreamPtr == NULL) { - return(E_FAIL); - } - - if (IsReading) { - return(E_FAIL); - } - - IsWriting = true; - - if (cb != 0) { - if (pcbWritten != NULL) { - *pcbWritten = 0; - } - - if (CurOffset > 0) { - if (write_size >= BUFFER_SIZE - CurOffset) { - write_size = BUFFER_SIZE - CurOffset; - } - - memmove((unsigned char *)DataBuffer + CurOffset, ptr, write_size); - temp_size = write_size + CurOffset; - - ptr += write_size; - left -= write_size; - CurOffset = temp_size; - - if (CurOffset == BUFFER_SIZE) { - result = Compress(DataBuffer, CurOffset); - if (result < 0) { - return(result); - } - CurOffset = 0; - } - - write_size = cb; - } - while (left >= BUFFER_SIZE) { - result = Compress(ptr, BUFFER_SIZE); - if (result < 0) { - return(result); - } - left -= BUFFER_SIZE; - ptr += BUFFER_SIZE; - } - - if (left > 0) { - memmove((unsigned char *)DataBuffer, ptr, left); - write_size = cb; - CurOffset = left; - } - - if (pcbWritten) { - *pcbWritten = write_size; - } - } - - return(S_OK); -} - - -/// -/// Moves the file pointer of the underlying stream. -/// -/// Returns with S_OK, or E_FAIL if a transfer is already under way. -/// Seeking is refused once reading or writing has begun, since the compressor -/// keeps state that a seek would invalidate. -HRESULT CStreamClass::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) -{ - if (IsReading || IsWriting) { - return(E_FAIL); - } - - return(StreamPtr->Seek(dlibMove, dwOrigin, plibNewPosition)); -} - - -/// -/// Sets the size of the underlying stream. -/// -/// Returns with S_OK, or E_FAIL if a transfer is already under way. -/// Resizing is refused once reading or writing has begun. -HRESULT CStreamClass::SetSize(ULARGE_INTEGER libNewSize) -{ - if (IsReading || IsWriting) { - return(E_FAIL); - } - - return(StreamPtr->SetSize(libNewSize)); -} - - -/// -/// Copies data from this stream over to another stream. -/// The request is handed straight to the underlying stream, so it is the compressed -/// bytes that get copied rather than the data they stand for. -/// -/// Returns with the result of the underlying stream's copy request. -HRESULT CStreamClass::CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) -{ - return(StreamPtr->CopyTo(pstm, cb, pcbRead, pcbWritten)); -} - - -/// -/// Commits any pending changes to the underlying stream. -/// -/// Returns with the result of the underlying stream's commit request. -HRESULT CStreamClass::Commit(DWORD grfCommitFlags) -{ - return(StreamPtr->Commit(grfCommitFlags)); -} - -/// -/// Discards any uncommitted changes to the stream. -/// -/// Returns with the result of the underlying stream's revert request. -HRESULT CStreamClass::Revert(void) -{ - return(StreamPtr->Revert()); -} - - -/// -/// Locks a byte range of the underlying stream. -/// -/// Returns with the result of the underlying stream's lock request. -HRESULT CStreamClass::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) -{ - return(StreamPtr->LockRegion(libOffset, cb, dwLockType)); -} - - -/// -/// Releases a lock on a byte range of the underlying stream. -/// -/// Returns with the result of the underlying stream's unlock request. -HRESULT CStreamClass::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) -{ - return(StreamPtr->UnlockRegion(libOffset, cb, dwLockType)); -} - - -/// -/// Fetches the statistics of the underlying stream. -/// -/// Returns with the result of the underlying stream's stat request. -HRESULT CStreamClass::Stat(STATSTG *pstatstg, DWORD grfStatFlag) -{ - return(StreamPtr->Stat(pstatstg, grfStatFlag)); -} - - -/// -/// Creates a second stream object over the same storage. -/// The clone is made by the underlying stream, so it is a plain stream rather than a -/// compressing one. -/// -/// Returns with the result of the underlying stream's clone request. -HRESULT CStreamClass::Clone(IStream **ppstm) -{ - return(StreamPtr->Clone(ppstm)); -} - - -/// -/// Compresses a buffer out as a single stream block. -/// This is the low level routine that runs the buffer through the LZO compressor and -/// writes the block header and the compressed bytes to the underlying stream. -/// -/// Pointer to the data to compress. -/// The number of bytes to compress. -/// Returns with S_OK, or an error code if the block could not be written. -HRESULT CStreamClass::Compress(void *in_buffer, ULONG length) -{ - HRESULT hr; - lzo_uint out_len = length; - lzo1x_1_compress((lzo_byte *)in_buffer, length, (lzo_byte *)StreamBuffer, &out_len, (lzo_byte *)LZODictionary); - BlockHead.UncompSize = BUFFER_SIZE; - length = 0; - BlockHead.CompSize = out_len; - - hr = StreamPtr->Write(&BlockHead, sizeof(BlockHead), &length); - - if (SUCCEEDED(hr)) { - if (length != sizeof(BlockHead)) { - return(E_FAIL); - } - - hr = StreamPtr->Write(StreamBuffer, out_len, &length); - if (SUCCEEDED(hr)) { - hr = length != out_len ? (unsigned int)E_FAIL : 0; - } - } - - return(hr); -} - - -/// -/// Flushes any buffered data out as a compressed block. -/// Use this routine to make sure the tail end of a write actually reaches the stream. -/// It does nothing if there is nothing left over to flush. -/// -/// Returns with S_OK, or an error code if the block could not be written. -HRESULT CStreamClass::Compress(void) -{ - if (IsWriting && CurOffset > 0) { - if (StreamPtr == NULL) { - return(E_FAIL); - } - if (CurOffset > 0) { - return(Compress(DataBuffer, CurOffset)); - } - } - return(S_OK); -} diff --git a/code/cstream.h b/code/cstream.h deleted file mode 100644 index e344687e9..000000000 --- a/code/cstream.h +++ /dev/null @@ -1,120 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include "ilinkstm.h" - -#include -#include - -class CStreamClass : public IStream, public ILinkStream -{ - public: - virtual LONG STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; - - virtual HRESULT STDMETHODCALLTYPE Read(void *pv, ULONG cb, ULONG *pcbRead) override; - virtual HRESULT STDMETHODCALLTYPE Write(const void *pv, ULONG cb, ULONG *pcbWritten) override; - - virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) override; - virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER libNewSize) override; - virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) override; - virtual HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags) override; - virtual HRESULT STDMETHODCALLTYPE Revert() override; - virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) override; - virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) override; - virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG *pstatstg, DWORD grfStatFlag) override; - virtual HRESULT STDMETHODCALLTYPE Clone(IStream **ppstm) override; - - virtual HRESULT STDMETHODCALLTYPE Link_Stream(IUnknown *stream) override; - virtual HRESULT STDMETHODCALLTYPE Unlink_Stream(IUnknown **stream) override; - - public: - CStreamClass(void); - virtual ~CStreamClass(void); - - HRESULT Compress(void *in_buffer, ULONG length); - HRESULT Compress(void); - - enum { - BUFFER_SIZE = 64*1024, - - /* - * LZO1X-1 can expand a block rather than shrink it, so the compressed side is - * sized for its worst case. - */ - STREAM_BUFFER_SIZE = BUFFER_SIZE + BUFFER_SIZE/16 + 64 + 3, - }; - - private: - /* - * This points to the stream the compressed data actually travels over. Nothing can be - * read or written until one has been linked in, and the link is broken again once the - * stream has been committed. - */ - IStreamPtr StreamPtr; - - /* - * This is the COM reference count for this object. The stream destroys itself once - * the last reference to it has been released. - */ - LONG RefCount; - - /* - * These flags record which direction the stream has been committed to. The first read - * or write sets one of them, and from that point on the opposite operation is - * refused, as are seeking and resizing. - */ - bool IsReading; - bool IsWriting; - - /* - * This is how much of the data buffer is currently in play, expressed in bytes. While - * reading it counts down the part of the decompressed block not yet handed out; while - * writing it counts up the bytes waiting to be compressed. - */ - int CurOffset; - - /* - * This is the working buffer that holds the data in its uncompressed form, one - * block's worth at a time. - */ - void *DataBuffer; - - /* - * This is the working buffer that holds a block in its compressed form, on its way to - * or from the linked stream. - */ - void *StreamBuffer; - - /* - * This is the scratch memory the LZO compressor keeps its dictionary in. It is of no - * interest outside the compression call itself. - */ - void *LZODictionary; - - /* - * This is the header of the block currently being read or written. Every block on the - * stream is preceded by one, so the reader knows how much compressed data to pull in - * and how far it will expand. - */ - struct BlockHeader { - /* - * This is the number of bytes the block occupies on the stream, compressed. - */ - unsigned int CompSize; - - /* - * This is the number of bytes the block expands to once decompressed. - */ - unsigned int UncompSize; - } BlockHead; -}; diff --git a/code/display.cpp b/code/display.cpp index bd4712eb6..b9775bef5 100644 --- a/code/display.cpp +++ b/code/display.cpp @@ -3859,14 +3859,13 @@ LRESULT DisplayClass::Windows_Message_Proc(HWND hWnd, UINT Msg, WPARAM wParam, L /// Loads the display layers from the save game stream. /// /// The stream to read the layers from. -/// Returns with S_OK if every layer was read, otherwise the failure code of the -/// layer that could not be read. -HRESULT DisplayClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool DisplayClass::Load(SaveStreamClass & stream) { - HRESULT result = S_OK; + bool result = true; for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { result = Layer[layer].Load(stream); - if (FAILED(result)) break; + if (!result) break; } return(result); } @@ -3876,14 +3875,13 @@ HRESULT DisplayClass::Load(IStream * stream) /// Saves the display layers to the save game stream. /// /// The stream to write the layers to. -/// Returns with S_OK if every layer was written, otherwise the failure code of the -/// layer that could not be written. -HRESULT DisplayClass::Save(IStream * stream) +/// bool; Was the record written whole? +bool DisplayClass::Save(SaveStreamClass & stream) { - HRESULT result = S_OK; + bool result = true; for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { result = Layer[layer].Save(stream); - if (FAILED(result)) break; + if (!result) break; } return(result); } diff --git a/code/display.h b/code/display.h index 3c346c1f7..a2ac6197c 100644 --- a/code/display.h +++ b/code/display.h @@ -65,8 +65,8 @@ class DisplayClass: public MapClass friend class Tactical; public: - virtual HRESULT Load(IStream * stream); - virtual HRESULT Save(IStream * stream); + virtual bool Load(SaveStreamClass & stream); + virtual bool Save(SaveStreamClass & stream); virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/drive.cpp b/code/drive.cpp index 538c71bc9..ded8d082f 100644 --- a/code/drive.cpp +++ b/code/drive.cpp @@ -68,6 +68,7 @@ #include "inline.h" #include "overtype.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "tube.h" #include "unit.h" @@ -116,8 +117,7 @@ DriveLocomotionClass::DriveLocomotionClass(void) : SpeedAccum(0), TargetSpeed(0), TrackNumber(-1), - TrackIndex(-1), - Piggybacker(NULL) + TrackIndex(-1) { } @@ -131,68 +131,10 @@ DriveLocomotionClass::~DriveLocomotionClass(void) } -/// -/// Fetches the class identifier of whichever locomotor is driving the unit. -/// That is the identifier of the locomotor riding along on this driver when there is one, -/// and the driver's own otherwise. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK if the identifier was supplied, E_FAIL if the locomotor -/// could not be asked, or E_POINTER if no destination was supplied. -HRESULT DriveLocomotionClass::Piggyback_CLSID(CLSID * classid) -{ - if (classid == NULL) { - return(E_POINTER); - } - - if (Piggybacker != NULL) { - IPersistPtr ptr(Piggybacker); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); - } - - IPersistPtr ptr(this); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); -} - - -/// -/// Fetches an interface supported by this locomotor. -/// The driver answers for the piggyback interface on top of whatever the base locomotor -/// already supports. -/// -/// The identifier of the interface asked for. -/// Pointer to the interface pointer to fill in. -/// Returns with S_OK if the interface was supplied, otherwise -/// E_NOINTERFACE. -HRESULT STDMETHODCALLTYPE DriveLocomotionClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - HRESULT result = BASECLASS::QueryInterface(riid, ppvObject); - - if (result == E_NOINTERFACE) { - if (riid == IID_IPiggyback) { - *ppvObject = (IPiggyback*)this; - } - if (*ppvObject == NULL) { - result = E_NOINTERFACE; - } else { - AddRef(); - result = S_OK; - } - } - return(result); -} - - /// /// Lists the members this driver carries. /// A locomotor riding along on this one is a separate persistent object rather than a -/// member, so it still travels framed by OLE and is recreated as the class it was saved as. +/// member, so it travels as a record of its own and is recreated as the class it was saved as. /// /// The stream carrying the members. void DriveLocomotionClass::Serialize(SaveStreamClass & stream) @@ -220,10 +162,9 @@ void DriveLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - IPersistStreamPtr persist(Piggybacker); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, Piggybacker.get()); } else { - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Piggybacker); + Piggybacker = Load_Locomotor(stream); } } // TrackControl -- constant tables shared by every driver. @@ -237,19 +178,15 @@ void DriveLocomotionClass::Serialize(SaveStreamClass & stream) /// A unit that must travel in some special manner -- through a tunnel, or aboard a /// carrier -- keeps its driver but lets the special locomotor move it for the duration. /// -/// The locomotor that is to take over the unit. -/// Returns with S_OK if the locomotor was taken on, E_FAIL if one is already -/// riding, or E_POINTER if none was supplied. -HRESULT STDMETHODCALLTYPE DriveLocomotionClass::Begin_Piggyback(ILocomotion *pointer) +/// The locomotor that is to take over the unit. +/// bool; Was the locomotor taken on? One already carrying a locomotor refuses. +bool DriveLocomotionClass::Begin_Piggyback(std::unique_ptr & carried) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker == NULL) { - Piggybacker = pointer; - return(S_OK); + if (carried == nullptr || Piggybacker != nullptr) { + return(false); } - return(E_FAIL); + Piggybacker = std::move(carried); + return(true); } @@ -258,20 +195,10 @@ HRESULT STDMETHODCALLTYPE DriveLocomotionClass::Begin_Piggyback(ILocomotion *poi /// The riding locomotor is detached and given up, leaving this driver in sole charge of /// the unit once more. /// -/// Pointer to the locomotor pointer to fill in. -/// Returns with S_OK if a locomotor was handed back, S_FALSE if there was none -/// riding, or E_POINTER if no destination was supplied. -HRESULT DriveLocomotionClass::End_Piggyback(ILocomotion **pointer) +/// Returns with the locomotor that was riding, or nothing when none was. +std::unique_ptr DriveLocomotionClass::End_Piggyback(void) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker != NULL) { - *pointer = Piggybacker; - Piggybacker.Detach(); - return(S_OK); - } - return(S_FALSE); + return(std::move(Piggybacker)); } @@ -282,7 +209,7 @@ HRESULT DriveLocomotionClass::End_Piggyback(ILocomotion **pointer) /// back only once the unit has settled. /// /// bool; Is it safe to end the piggyback? -boolean DriveLocomotionClass::Is_Ok_To_End(void) +bool DriveLocomotionClass::Is_Ok_To_End(void) { if (!Is_Moving() && (Piggybacker != NULL && IsLocomotorUnlocked)) { return(true); @@ -345,7 +272,7 @@ void DriveLocomotionClass::Set_Slope(int ramp) /// when it is first placed on the map. /// /// The ramp the unit is to be sitting on. -void STDMETHODCALLTYPE DriveLocomotionClass::Force_New_Slope(int ramp) +void DriveLocomotionClass::Force_New_Slope(int ramp) { PreviousRamp = ramp; CurrentRamp = ramp; @@ -359,7 +286,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Force_New_Slope(int ramp) /// between cells, even if it is not making any headway at this moment. /// /// bool; Is the unit under way or owing a move? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving(void) +bool DriveLocomotionClass::Is_Moving(void) { if (DestinationCoord != COORD_NONE) { return(true); @@ -377,7 +304,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving(void) /// has been given a destination but has not gotten rolling yet does not. /// /// bool; Is the unit moving right now? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Now(void) +bool DriveLocomotionClass::Is_Moving_Now(void) { if (LinkedTo->PrimaryFacing.Is_Rotating()) { return(true); @@ -394,7 +321,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Now(void) /// /// Returns with the destination coordinate, or COORD_NONE if the unit has /// nowhere it must be. -Coord STDMETHODCALLTYPE DriveLocomotionClass::Destination(void) +Coord DriveLocomotionClass::Destination(void) { return(DestinationCoord); } @@ -405,7 +332,7 @@ Coord STDMETHODCALLTYPE DriveLocomotionClass::Destination(void) /// /// Returns with the coordinate being driven toward. A unit that is not under /// way returns its current position instead. -Coord STDMETHODCALLTYPE DriveLocomotionClass::Head_To_Coord(void) +Coord DriveLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -420,7 +347,7 @@ Coord STDMETHODCALLTYPE DriveLocomotionClass::Head_To_Coord(void) /// raised to the deck, since that is where the vehicle will actually end up driving. /// /// The location to drive to. -void STDMETHODCALLTYPE DriveLocomotionClass::Move_To(Coord to) +void DriveLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { DestinationCoord = to; @@ -438,7 +365,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Move_To(Coord to) /// The destination is given up and the driver begins slowing down. A train engine passes /// the order back along the line so that every car it is pulling stops with it. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Stop_Moving(void) +void DriveLocomotionClass::Stop_Moving(void) { if (HeadToCoord != COORD_NONE) { if (LinkedTo->TClass->IsTrain) { @@ -489,7 +416,7 @@ BOOL DriveLocomotionClass::Is_Angled(void) const /// /// Pointer to the voxel cache key to be updated. May be NULL. /// Returns with the matrix the unit is to be rendered through. -Matrix3D STDMETHODCALLTYPE DriveLocomotionClass::Draw_Matrix(int *key) +Matrix3D DriveLocomotionClass::Draw_Matrix(int *key) { Matrix3D m; @@ -554,7 +481,7 @@ Matrix3D STDMETHODCALLTYPE DriveLocomotionClass::Draw_Matrix(int *key) /// The driver adopts the slope of the cell the vehicle appears on straight away, so that /// a unit unlimboed onto a ramp is never seen tilting itself into place. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Unlimbo(void) +void DriveLocomotionClass::Unlimbo(void) { Force_New_Slope(LinkedTo->Get_Cell_Ptr()->Ramp); } @@ -584,7 +511,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Unlimbo(void) * 09/26/1993 JLB : Created. * * 04/15/1994 JLB : Converted to member function. * *=============================================================================================*/ -boolean STDMETHODCALLTYPE DriveLocomotionClass::Process(void) +bool DriveLocomotionClass::Process(void) { Set_Slope(LinkedTo->Get_Cell_Ptr()->Ramp); @@ -771,7 +698,7 @@ void DriveLocomotionClass::Mark_Track(Coord const & headto, MarkType type) * HISTORY: * * 03/17/1995 JLB : Created. * *=============================================================================================*/ -void STDMETHODCALLTYPE DriveLocomotionClass::Force_Track(int track, Coord coord) +void DriveLocomotionClass::Force_Track(int track, Coord coord) { assert(LinkedTo->IsActive); @@ -2122,24 +2049,15 @@ bool DriveLocomotionClass::Incoming(Cell cell) /// Fetches the display layer the driving unit belongs to. /// /// Returns with LAYER_GROUND, since a driving unit travels on the ground. -LayerType STDMETHODCALLTYPE DriveLocomotionClass::In_Which_Layer(void) +LayerType DriveLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence system uses this to know which locomotor to create when the unit is -/// loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE DriveLocomotionClass::GetClassID(CLSID * retval) +ClassID DriveLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_DriveLocomotion; - return(S_OK); + return(ClassID_DriveLocomotion); } @@ -2148,7 +2066,7 @@ HRESULT STDMETHODCALLTYPE DriveLocomotionClass::GetClassID(CLSID * retval) /// A driving vehicle sits at the depth of the ground it is standing on. /// /// Returns with the adjustment to apply to the unit's draw depth. -int STDMETHODCALLTYPE DriveLocomotionClass::Z_Adjust(void) +int DriveLocomotionClass::Z_Adjust(void) { return(0); } @@ -2158,7 +2076,7 @@ int STDMETHODCALLTYPE DriveLocomotionClass::Z_Adjust(void) /// Fetches the depth gradient the unit is to be drawn with. /// /// Returns with the gradient the base locomotor asks for. -ZGradientType STDMETHODCALLTYPE DriveLocomotionClass::Z_Gradient(void) +ZGradientType DriveLocomotionClass::Z_Gradient(void) { return(BASECLASS::Z_Gradient()); } @@ -2188,7 +2106,7 @@ bool DriveLocomotionClass::Abandon_Navigation(void) /// be told about every cell of the track it is committed to. /// /// The MarkType to apply to the cells occupied. -void STDMETHODCALLTYPE DriveLocomotionClass::Mark_All_Occupation_Bits(int mark) +void DriveLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (HeadToCoord != COORD_NONE) { Mark_Track(HeadToCoord, (MarkType)mark); @@ -2204,7 +2122,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The location to test against. /// bool; Is the unit moving there? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Here(Coord to) +bool DriveLocomotionClass::Is_Moving_Here(Coord to) { Coord coord = Head_To_Coord(); @@ -2250,7 +2168,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Here(Coord to) /// such a hop is due, but performs none of it. /// /// bool; Will the driver jump tracks? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Will_Jump_Tracks(void) +bool DriveLocomotionClass::Will_Jump_Tracks(void) { /// This repeats the track jump test that While_Moving performs. assert(LinkedTo->IsActive); @@ -2304,7 +2222,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Will_Jump_Tracks(void) /// While locked, this driver will not report itself ready to end a piggyback, so a /// temporary locomotor riding on top of it keeps control of the unit. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Lock(void) +void DriveLocomotionClass::Lock(void) { IsLocomotorUnlocked = false; } @@ -2315,7 +2233,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Lock(void) /// This is the counterpart to Lock. The driver may once again report itself ready to /// end a piggyback. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Unlock(void) +void DriveLocomotionClass::Unlock(void) { IsLocomotorUnlocked = true; } @@ -2326,7 +2244,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Unlock(void) /// /// Returns with the track control number, or -1 if the unit is not on a /// track. -int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Number(void) +int DriveLocomotionClass::Get_Track_Number(void) { return(TrackNumber); } @@ -2337,7 +2255,7 @@ int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Number(void) /// /// Returns with the index into the track the unit has reached, or -1 if the /// unit is not following one. -int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Index(void) +int DriveLocomotionClass::Get_Track_Index(void) { return(TrackIndex); } @@ -2347,32 +2265,12 @@ int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Index(void) /// Fetches the movement the driver has banked up along its track. /// /// Returns with the accumulated movement not yet spent advancing the unit. -int STDMETHODCALLTYPE DriveLocomotionClass::Get_Speed_Accum(void) +int DriveLocomotionClass::Get_Speed_Accum(void) { return(SpeedAccum); } -/// -/// Adds a reference to this locomotor. -/// -/// Returns with the reference count once the new reference is counted. -ULONG STDMETHODCALLTYPE DriveLocomotionClass::AddRef(void) -{ - return(BASECLASS::AddRef()); -} - - -/// -/// Releases a reference to this locomotor. -/// -/// Returns with the reference count remaining after the release. -ULONG STDMETHODCALLTYPE DriveLocomotionClass::Release(void) -{ - return(BASECLASS::Release()); -} - - /*************************************************************************** ** Smooth turn track tables. These are coordinate offsets from the center ** of the destination cell. These are the raw tracks that are modified diff --git a/code/drive.h b/code/drive.h index 5011c55ce..54b7d6d8c 100644 --- a/code/drive.h +++ b/code/drive.h @@ -40,6 +40,8 @@ #include "matrix3d.h" #include "timer.h" +#include + #include "mark.hh" /**************************************************************************** @@ -58,43 +60,39 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback DriveLocomotionClass(void); virtual ~DriveLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; - - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual int STDMETHODCALLTYPE Z_Adjust(void) override; - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual void STDMETHODCALLTYPE Unlimbo(void) override; - virtual void STDMETHODCALLTYPE Force_Track(int track, Coord coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual void STDMETHODCALLTYPE Force_New_Slope(int ramp) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; - virtual boolean STDMETHODCALLTYPE Will_Jump_Tracks(void) override; - virtual void STDMETHODCALLTYPE Lock(void) override; - virtual void STDMETHODCALLTYPE Unlock(void) override; - virtual int STDMETHODCALLTYPE Get_Track_Number(void) override; - virtual int STDMETHODCALLTYPE Get_Track_Index(void) override; - virtual int STDMETHODCALLTYPE Get_Speed_Accum(void) override; - - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) override; - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) override; - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) override; - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) override {return(Piggybacker != NULL);} + + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual int Z_Adjust(void) override; + virtual ZGradientType Z_Gradient(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual void Unlimbo(void) override; + virtual void Force_Track(int track, Coord coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual void Force_New_Slope(int ramp) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; + virtual bool Will_Jump_Tracks(void) override; + virtual void Lock(void) override; + virtual void Unlock(void) override; + virtual int Get_Track_Number(void) override; + virtual int Get_Track_Index(void) override; + virtual int Get_Speed_Accum(void) override; + + virtual bool Begin_Piggyback(std::unique_ptr & carried) override; + virtual std::unique_ptr End_Piggyback(void) override; + virtual bool Is_Ok_To_End(void) override; + virtual bool Is_Piggybacking(void) override {return(Piggybacker != nullptr);} /*--------------------------------------------------------------------- ** Member function prototypes. @@ -245,7 +243,7 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback * driver answers for it rather than for itself. If NULL, this driver is in sole * charge of the unit. */ - ILocomotionPtr Piggybacker; + std::unique_ptr Piggybacker; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/droppod.cpp b/code/droppod.cpp index 0e74e2fc8..d4eab0915 100644 --- a/code/droppod.cpp +++ b/code/droppod.cpp @@ -23,6 +23,7 @@ #include "house.h" #include "map.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "sun.h" #include "weapon.h" @@ -37,8 +38,7 @@ DropPodLocomotionClass::DropPodLocomotionClass(void) : BASECLASS(), Direction(DPOD_DIR_NE), - DestinationCoord(COORD_NONE), - Piggybacker(NULL) + DestinationCoord(COORD_NONE) { } @@ -55,7 +55,7 @@ DropPodLocomotionClass::~DropPodLocomotionClass(void) /// Is the drop pod in motion? /// A pod exists only for the duration of its fall, so it always reports movement. /// -boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Moving(void) +bool DropPodLocomotionClass::Is_Moving(void) { return(true); } @@ -66,7 +66,7 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Moving(void) /// /// Returns with the landing coordinate, or COORD_NONE if no destination has /// been assigned yet. -Coord STDMETHODCALLTYPE DropPodLocomotionClass::Destination(void) +Coord DropPodLocomotionClass::Destination(void) { return(DestinationCoord); } @@ -79,8 +79,13 @@ Coord STDMETHODCALLTYPE DropPodLocomotionClass::Destination(void) /// passenger is unlimboed, or destroyed along with its surroundings if there is nowhere /// for it to stand. /// -boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) +bool DropPodLocomotionClass::Process(void) { + // Handing the carried locomotor back leaves this pod unowned, so it holds itself for + // the rest of the routine. The slot is declared here rather than beside the hand-back + // so that the pod outlives every member this routine still reads. + std::unique_ptr self; + Coord coord = LinkedTo->PositionCoord; Coord smoke_coord = coord; @@ -117,8 +122,12 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) coord = linked->PositionCoord; linked->Limbo(); - AddRef(); - End_Piggyback(&LinkedTo->Locomotion); + // A pod that carries nothing stays the object's locomotor. + std::unique_ptr carried = End_Piggyback(); + if (carried != nullptr) { + self = std::move(LinkedTo->Locomotion); + LinkedTo->Locomotion = std::move(carried); + } if (!linked->Unlimbo(coord, DIR_N)) { Explosion_Damage(coord, 100, LinkedTo, Rule->C4Warhead); @@ -132,7 +141,6 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) linked->Commence(); linked->Scatter(COORD_NONE); } - Release(); } else { LinkedTo->PositionCoord = coord; WeaponTypeClass const * weapon = Rule->DropPodWeapon; @@ -163,7 +171,7 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) /// has a destination ignores any later request. /// /// The coordinate the pod should land on. -void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to) +void DropPodLocomotionClass::Move_To(Coord to) { if (DestinationCoord == COORD_NONE) { @@ -213,22 +221,16 @@ void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to) } -/// -/// Fetches the class ID that this locomotor is persisted under. -/// -/// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::GetClassID(CLSID * retval) +ClassID DropPodLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BallisticLocomotion; - return(S_OK); + return(ClassID_BallisticLocomotion); } /// /// Lists the members this drop pod locomotor carries. /// The locomotor set aside while the pod descends is a separate persistent object rather -/// than a member, so it still travels framed by OLE and is recreated as the class it was +/// than a member, so it travels as a record of its own and is recreated as the class it was /// saved as. /// /// The stream carrying the members. @@ -244,10 +246,9 @@ void DropPodLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - IPersistStreamPtr persist(Piggybacker); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, Piggybacker.get()); } else { - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Piggybacker); + Piggybacker = Load_Locomotor(stream); } } } @@ -257,7 +258,7 @@ void DropPodLocomotionClass::Serialize(SaveStreamClass & stream) /// Stops the pod's descent. /// A pod cannot be halted in mid air, so this request is quietly ignored. /// -void STDMETHODCALLTYPE DropPodLocomotionClass::Stop_Moving(void) +void DropPodLocomotionClass::Stop_Moving(void) { // empty } @@ -268,18 +269,15 @@ void STDMETHODCALLTYPE DropPodLocomotionClass::Stop_Moving(void) /// The drop pod holds on to the locomotor it displaces so that the object can be given /// it back when the pod touches down. /// -/// The locomotor to carry. -/// Returns with S_OK, or E_FAIL if something is already being carried. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::Begin_Piggyback(ILocomotion * pointer) +/// The locomotor that is to take over the unit. +/// bool; Was the locomotor taken on? One already carrying a locomotor refuses. +bool DropPodLocomotionClass::Begin_Piggyback(std::unique_ptr & carried) { - if (pointer == NULL) { - return(E_POINTER); + if (carried == nullptr || Piggybacker != nullptr) { + return(false); } - if (Piggybacker == NULL) { - Piggybacker = pointer; - return(S_OK); - } - return(E_FAIL); + Piggybacker = std::move(carried); + return(true); } @@ -288,19 +286,10 @@ HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::Begin_Piggyback(ILocomotion * /// The pod gives up its hold without destroying the locomotor, so the object can resume /// using it once the pod has landed. /// -/// Pointer to the location that receives the carried locomotor. -/// Returns with S_OK, or S_FALSE if nothing was being carried. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::End_Piggyback(ILocomotion ** pointer) +/// Returns with the locomotor that was riding, or nothing when none was. +std::unique_ptr DropPodLocomotionClass::End_Piggyback(void) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker != NULL) { - *pointer = Piggybacker; - Piggybacker.Detach(); - return(S_OK); - } - return(S_FALSE); + return(std::move(Piggybacker)); } @@ -309,7 +298,7 @@ HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::End_Piggyback(ILocomotion ** p /// The carried locomotor may only be given control back once the pod has come to rest. /// /// bool; May the carried locomotor take over again? -boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Ok_To_End(void) +bool DropPodLocomotionClass::Is_Ok_To_End(void) { if (!Is_Moving() && Piggybacker != NULL) { return(true); @@ -318,77 +307,22 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Ok_To_End(void) } -/// -/// Fetches an interface pointer from the drop pod locomotor. -/// This routine extends the base locomotor's interface set with IPiggyback, which is how -/// the pod carries the object's real locomotor while it falls. -/// -/// Returns with S_OK, or E_NOINTERFACE if this object does not offer the -/// interface asked for. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - HRESULT result = BASECLASS::QueryInterface(riid, ppvObject); - - if (result == E_NOINTERFACE) { - if (riid == IID_IPiggyback) { - *ppvObject = (IPiggyback*)this; - } - if (*ppvObject == NULL) { - result = E_NOINTERFACE; - } else { - AddRef(); - result = S_OK; - } - } - return(result); -} - - /// /// Determines which display layer the pod belongs in. /// A pod is always falling, so it draws along with the other airborne objects right up /// until it lands and gives its object back. /// -LayerType STDMETHODCALLTYPE DropPodLocomotionClass::In_Which_Layer(void) +LayerType DropPodLocomotionClass::In_Which_Layer(void) { return(LAYER_AIR); } -/// -/// Fetches the class ID of the locomotor being carried. -/// The save system uses this to record which locomotor is to be restored underneath the -/// drop pod. When nothing is being carried, the pod supplies its own class ID instead. -/// -/// Returns with S_OK, or an error code if the class ID could not be -/// determined. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::Piggyback_CLSID(GUID * classid) -{ - if (classid == NULL) { - return(E_POINTER); - } - - if (Piggybacker != NULL) { - IPersistPtr ptr(Piggybacker); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); - } - - IPersistPtr ptr(this); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); -} - - /// /// Fetches the drawing code for the drop pod. /// The renderer uses this to choose the artwork that suits the pod's approach. /// -int STDMETHODCALLTYPE DropPodLocomotionClass::Drawing_Code(void) +int DropPodLocomotionClass::Drawing_Code(void) { return((unsigned)Direction % 2); } diff --git a/code/droppod.h b/code/droppod.h index ecd9da0a7..de6c0b7cd 100644 --- a/code/droppod.h +++ b/code/droppod.h @@ -16,6 +16,8 @@ #include "ipiggy.h" #include "loco.h" +#include + class DropPodLocomotionClass : public LocomotionClass, public IPiggyback { @@ -29,27 +31,23 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback DropPodLocomotionClass(void); virtual ~DropPodLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override {return(BASECLASS::AddRef());} - virtual ULONG STDMETHODCALLTYPE Release(void) override {return(BASECLASS::Release());} - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual int STDMETHODCALLTYPE Drawing_Code(void) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual LayerType In_Which_Layer(void) override; + virtual int Drawing_Code(void) override; - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) override; - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) override; - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) override; - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) override {return(Piggybacker != NULL);} + virtual bool Begin_Piggyback(std::unique_ptr & carried) override; + virtual std::unique_ptr End_Piggyback(void) override; + virtual bool Is_Ok_To_End(void) override; + virtual bool Is_Piggybacking(void) override {return(Piggybacker != nullptr);} private: enum DropPodDirType { @@ -78,5 +76,5 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback * handed back the moment the pod touches ground, so that the object resumes moving * the way its type normally does. */ - ILocomotionPtr Piggybacker; + std::unique_ptr Piggybacker; }; diff --git a/code/empulse.cpp b/code/empulse.cpp index 501892e45..964bd2293 100644 --- a/code/empulse.cpp +++ b/code/empulse.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "empulse.h" @@ -283,18 +282,9 @@ void EMPulseClass::Compute_CRC(CRCEngine &crc) const } -/// -/// Fetches the class identifier used to persist this object. -/// The save system writes this identifier ahead of the object data so that the loader -/// knows what kind of object to reconstruct. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE EMPulseClass::GetClassID(CLSID * retval) +ClassID EMPulseClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_EMPulseClass; - return(S_OK); + return(ClassID_EMPulseClass); } diff --git a/code/empulse.h b/code/empulse.h index c059e0d4a..d04dc713f 100644 --- a/code/empulse.h +++ b/code/empulse.h @@ -30,7 +30,7 @@ class EMPulseClass : public AbstractClass virtual RTTIType Fetch_RTTI(void) const override {return(RTTI_EMPULSE);} - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/enviro.cpp b/code/enviro.cpp index 2f6cfe9aa..ab82c1097 100644 --- a/code/enviro.cpp +++ b/code/enviro.cpp @@ -110,12 +110,11 @@ void EnvironmentClass::Restore(void) /// restored, before the scenario itself is brought back. /// /// Returns with the result reported by the stream read. -HRESULT EnvironmentClass::Load(IStream * stream) +bool EnvironmentClass::Load(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("EnvironmentClass"); - Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("EnvironmentClass"); + Serialize(stream); + return(!stream.Was_Error()); } @@ -123,11 +122,10 @@ HRESULT EnvironmentClass::Load(IStream * stream) /// Writes the carry over environment out to a save game. /// /// Returns with the result reported by the stream write. -HRESULT EnvironmentClass::Save(IStream * stream) +bool EnvironmentClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(savestream.Result()); + Serialize(stream); + return(!stream.Was_Error()); } diff --git a/code/enviro.h b/code/enviro.h index 01cf06eec..89b46fa4c 100644 --- a/code/enviro.h +++ b/code/enviro.h @@ -13,7 +13,6 @@ #include "diff.hh" -#include class SaveStreamClass; @@ -26,8 +25,8 @@ class EnvironmentClass void Store(void); void Restore(void); - HRESULT Load(IStream * stream); - HRESULT Save(IStream * stream); + bool Load(SaveStreamClass & stream); + bool Save(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); diff --git a/code/factory.cpp b/code/factory.cpp index 2ce156040..efe17eec9 100644 --- a/code/factory.cpp +++ b/code/factory.cpp @@ -47,7 +47,6 @@ * FactoryClass::~FactoryClass -- Default destructor for factory objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "factory.h" @@ -632,17 +631,9 @@ bool FactoryClass::Completed(void) } -/// -/// Fetches the class identifier for a factory. -/// This routine is part of the persistence interface. The save game loader uses the -/// identifier to know what kind of object to create before handing it the stream. -/// -/// Returns with S_OK, or E_POINTER if no return location was supplied. -HRESULT STDMETHODCALLTYPE FactoryClass::GetClassID(CLSID * retval) +ClassID FactoryClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_FactoryClass; - return(S_OK); + return(ClassID_FactoryClass); } diff --git a/code/factory.h b/code/factory.h index 7886611ef..473c80021 100644 --- a/code/factory.h +++ b/code/factory.h @@ -54,7 +54,7 @@ class FactoryClass : public AbstractClass, private StageClass FactoryClass(void); ~FactoryClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/fly.cpp b/code/fly.cpp index 0b64fe905..2cf7d741f 100644 --- a/code/fly.cpp +++ b/code/fly.cpp @@ -117,7 +117,7 @@ FlyLocomotionClass::~FlyLocomotionClass(void) /// an aircraft that has been told to go somewhere but has yet to build up any speed. /// /// bool; Is the aircraft moving or trying to? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving(void) +bool FlyLocomotionClass::Is_Moving(void) { return(IsMoving || LinkedTo->PitchAngle > 0); } @@ -129,7 +129,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving(void) /// the aircraft has any speed at all. /// /// bool; Is the aircraft moving right now? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving_Now(void) +bool FlyLocomotionClass::Is_Moving_Now(void) { if (CurrentSpeed == 0) { return(false); @@ -143,7 +143,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving_Now(void) /// /// Returns with the destination coordinate. If the aircraft is not going /// anywhere, COORD_NONE is returned. -Coord STDMETHODCALLTYPE FlyLocomotionClass::Destination(void) +Coord FlyLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -159,7 +159,7 @@ Coord STDMETHODCALLTYPE FlyLocomotionClass::Destination(void) /// disposed of if it has wandered off the edge of the world. /// /// bool; Is the aircraft still under way? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Process(void) +bool FlyLocomotionClass::Process(void) { if (!IsLanding && !IsTakingOff && TargetSpeed >= 1.0 && FlightLevel == 0) { FlightLevel = LinkedTo->TClass->Flight_Level(); @@ -234,7 +234,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Process(void) /// destination is a request to stop, which brings a flying aircraft down to land. /// /// The coordinate to head for, or COORD_NONE to stop and land. -void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) +void FlyLocomotionClass::Move_To(Coord to) { if (((Coord)to).As_Cell() != DestinationCoord.As_Cell() || !IsLanding) { @@ -242,7 +242,7 @@ void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) if ((Coord)to == COORD_NONE) { int landing_altitude = 0; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl != NULL) { landing_altitude = flyctrl->Landing_Altitude(); } @@ -261,7 +261,7 @@ void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) DestinationCoord.Z = LinkedTo->TClass->Flight_Level() + Map.Get_Height_GL(to); } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); int landing_altitude = 0; if (flyctrl != NULL) { landing_altitude = flyctrl->Landing_Altitude(); @@ -286,7 +286,7 @@ void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) /// assigns that as the new destination. An aircraft with nowhere at all to go is destroyed /// rather than left loitering in an illegal spot. /// -void STDMETHODCALLTYPE FlyLocomotionClass::Stop_Moving(void) +void FlyLocomotionClass::Stop_Moving(void) { if (Is_Moving()) { @@ -608,7 +608,7 @@ void FlyLocomotionClass::Movement_AI(void) if (current_height < FlightLevel && LinkedTo->Strength > 0) { bool is_loaded = false; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl != NULL) { is_loaded = flyctrl->Is_Loaded() != 0; } @@ -698,7 +698,7 @@ void FlyLocomotionClass::Movement_AI(void) } if (LinkedTo->Strength > 0 && Is_In_Flight() && DestinationCoord != COORD_NONE) { - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (!Needs_To_Land()) { TargetSpeed = 1.0; @@ -881,7 +881,7 @@ bool FlyLocomotionClass::Process_Take_Off(void) height -= BRIDGE_LEPTON_HEIGHT; } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); int landing_altitude = 0; if (flyctrl) { landing_altitude = flyctrl->Landing_Altitude(); @@ -962,7 +962,7 @@ bool FlyLocomotionClass::Process_Landing(void) TargetSpeed = 0; int landing_altitude = 0; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl) { landing_altitude = flyctrl->Landing_Altitude(); } @@ -1072,7 +1072,7 @@ bool FlyLocomotionClass::Process_Landing(void) /// Returns with the distance remaining to the destination. int FlyLocomotionClass::Nearing_Target(bool stage_approach, Coord coord) { - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); /* * A strafing aircraft that is over an ammo-bearing attack run should ignore the @@ -1303,7 +1303,7 @@ int FlyLocomotionClass::Nearing_Target(bool stage_approach, Coord coord) /// Optional cache key for the resulting orientation. It may be NULL, and /// is set to -1 for an attitude that is not worth caching. /// Returns with the matrix to draw the aircraft with. -Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Matrix(int * key) +Matrix3D FlyLocomotionClass::Draw_Matrix(int * key) { Matrix3D mtx; mtx.Make_Identity(); @@ -1398,10 +1398,10 @@ Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Matrix(int * key) /// look pinned in place while it hovers. Dropships and grounded aircraft do not bob. /// /// Returns with the pixel offset to shift the aircraft by. -Point2D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Point(void) +Point2D FlyLocomotionClass::Draw_Point(void) { int y = 0; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); int landing_altitude = 0; if (flyctrl) { @@ -1421,7 +1421,7 @@ Point2D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Point(void) /// The shadow is drawn where the aircraft's position puts it, so no adjustment is needed. /// /// Returns with the pixel offset to shift the shadow by. -Point2D STDMETHODCALLTYPE FlyLocomotionClass::Shadow_Point(void) +Point2D FlyLocomotionClass::Shadow_Point(void) { return(Point2D(0, 0)); } @@ -1470,7 +1470,7 @@ void FlyLocomotionClass::Land(void) /// Optional cache key for the shadow orientation. It may be NULL, and a /// value of -1 marks the shadow as not worth caching. /// Returns with the matrix to draw the shadow with. -Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Shadow_Matrix(int * key) +Matrix3D FlyLocomotionClass::Shadow_Matrix(int * key) { int ramp = Map[(Coord const &)LinkedTo->PositionCoord].Ramp; if (LinkedTo->TClass->IsDropship) { @@ -1493,7 +1493,7 @@ Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Shadow_Matrix(int * key) /// This routine snaps the body around immediately rather than rotating it over time. /// /// The facing to set the aircraft body to. -void STDMETHODCALLTYPE FlyLocomotionClass::Do_Turn(DirType coord) +void FlyLocomotionClass::Do_Turn(DirType coord) { LinkedTo->SecondaryFacing.Set(coord); } @@ -1515,18 +1515,9 @@ bool FlyLocomotionClass::Is_In_Flight(void) } -/// -/// Fetches the class identifier of this locomotor. -/// This routine is used by the save and load machinery so that it knows which locomotor to -/// create when the owning object is restored. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE FlyLocomotionClass::GetClassID(CLSID * retval) +ClassID FlyLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_FlyerLocomotion; - return(S_OK); + return(ClassID_FlyerLocomotion); } @@ -1559,7 +1550,7 @@ void FlyLocomotionClass::Serialize(SaveStreamClass & stream) /// /// Returns with LAYER_GROUND while the aircraft is on the deck, or LAYER_TOP once /// it is above it. -LayerType STDMETHODCALLTYPE FlyLocomotionClass::In_Which_Layer(void) +LayerType FlyLocomotionClass::In_Which_Layer(void) { return(LinkedTo->HeightAGL <= 0 ? LAYER_GROUND : LAYER_TOP); } @@ -1572,7 +1563,7 @@ LayerType STDMETHODCALLTYPE FlyLocomotionClass::In_Which_Layer(void) /// stop, so that it falls out of the sky rather than coasting on to its objective. /// /// bool; Was the power successfully cut? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Power_Off(void) +bool FlyLocomotionClass::Power_Off(void) { if (Is_Moving()) { Tumble(); @@ -1587,7 +1578,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Power_Off(void) /// Is the aircraft still under power? /// /// bool; Does the aircraft still have power? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Powered(void) +bool FlyLocomotionClass::Is_Powered(void) { return(BASECLASS::Is_Powered()); } @@ -1599,7 +1590,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Powered(void) /// by one. /// /// bool; Does an ion storm affect this aircraft? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Ion_Sensitive(void) +bool FlyLocomotionClass::Is_Ion_Sensitive(void) { return(!LinkedTo->TClass->IsHunterSeeker); } @@ -1625,7 +1616,7 @@ void FlyLocomotionClass::Tumble(void) /// Fetches the speed the aircraft is currently traveling at. /// /// Returns with the distance the aircraft will cover in one game frame. -int STDMETHODCALLTYPE FlyLocomotionClass::Apparent_Speed(void) +int FlyLocomotionClass::Apparent_Speed(void) { return(LinkedTo->TClass->MaxSpeed * CurrentSpeed); } @@ -1638,7 +1629,7 @@ int STDMETHODCALLTYPE FlyLocomotionClass::Apparent_Speed(void) /// /// Returns with the status code for taking off, landing, moving, or sitting /// idle. -int STDMETHODCALLTYPE FlyLocomotionClass::Get_Status(void) +int FlyLocomotionClass::Get_Status(void) { if (IsLanding) { return(1); @@ -1660,7 +1651,7 @@ int STDMETHODCALLTYPE FlyLocomotionClass::Get_Status(void) /// targets in a multiplay game so that the drone makes a nuisance of itself where it will /// be noticed. /// -void STDMETHODCALLTYPE FlyLocomotionClass::Acquire_Hunter_Seeker_Target(void) +void FlyLocomotionClass::Acquire_Hunter_Seeker_Target(void) { if (LinkedTo->TarCom == NULL) { @@ -1722,7 +1713,7 @@ bool FlyLocomotionClass::Needs_To_Land(void) return(true); } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl != NULL && !flyctrl->Is_Strafe()) { return(true); } @@ -1751,7 +1742,7 @@ bool FlyLocomotionClass::Is_Locked_To_Straight_Flight(void) return(true); } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl) { if (flyctrl->Is_Locked()) { return(true); diff --git a/code/fly.h b/code/fly.h index 96ffe8cc3..3981beb9d 100644 --- a/code/fly.h +++ b/code/fly.h @@ -58,28 +58,28 @@ class FlyLocomotionClass : public LocomotionClass FlyLocomotionClass(void); virtual ~FlyLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual Point2D STDMETHODCALLTYPE Draw_Point(void) override; - virtual Point2D STDMETHODCALLTYPE Shadow_Point(void) override; - virtual Matrix3D STDMETHODCALLTYPE Shadow_Matrix(int *key) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Power_Off(void) override; - virtual boolean STDMETHODCALLTYPE Is_Powered(void) override; - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) override; - virtual int STDMETHODCALLTYPE Apparent_Speed(void) override; - virtual int STDMETHODCALLTYPE Get_Status(void) override; - virtual void STDMETHODCALLTYPE Acquire_Hunter_Seeker_Target(void) override; + virtual bool Is_Moving(void) override; + virtual bool Is_Moving_Now(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual Point2D Draw_Point(void) override; + virtual Point2D Shadow_Point(void) override; + virtual Matrix3D Shadow_Matrix(int *key) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Power_Off(void) override; + virtual bool Is_Powered(void) override; + virtual bool Is_Ion_Sensitive(void) override; + virtual int Apparent_Speed(void) override; + virtual int Get_Status(void) override; + virtual void Acquire_Hunter_Seeker_Target(void) override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/fog.cpp b/code/fog.cpp index 76acc1e3c..b1dbdb91e 100644 --- a/code/fog.cpp +++ b/code/fog.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "fog.h" @@ -593,18 +592,9 @@ RTTIType FoggedObjectClass::Fetch_RTTI(void) const } -/// -/// Fetches the class ID of this object. -/// This routine is part of the persistence interface the save game system uses to -/// recreate objects of the right kind when a game is loaded. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE FoggedObjectClass::GetClassID(CLSID * retval) +ClassID FoggedObjectClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_FoggedObjectClass; - return(S_OK); + return(ClassID_FoggedObjectClass); } diff --git a/code/fog.h b/code/fog.h index 873145049..938207fd1 100644 --- a/code/fog.h +++ b/code/fog.h @@ -40,7 +40,7 @@ class FoggedObjectClass : public AbstractClass FoggedObjectClass(TerrainClass * object); virtual ~FoggedObjectClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/foot.cpp b/code/foot.cpp index 0d4349512..412526c3b 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -115,6 +115,7 @@ #include "partsys.h" #include "revent.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "session.h" #include "swizzle.h" @@ -604,7 +605,6 @@ void FootClass::Advance_Path(int count) } - /*********************************************************************************************** * FootClass::Mission_Move -- AI process for moving a vehicle to its destination. * * * @@ -1132,10 +1132,8 @@ void FootClass::Approach_Target(void) */ bool flyer = (RTTI == RTTI_AIRCRAFT); - CLSID clsid; - IPersistPtr persist(Locomotion); - persist->GetClassID(&clsid); - if (clsid == CLSID_JumpjetLocomotion) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_JumpjetLocomotion) { flyer = true; } @@ -1875,9 +1873,9 @@ bool FootClass::Enter_Idle_Mode(bool, bool resume_waypoint) } bool was_piggybacking = false; - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); was_piggybacking = true; } @@ -2333,9 +2331,9 @@ int FootClass::Do_MISSION_ENTER(void) Enter_Idle_Mode(); } else { if (NavCom == NULL && RouteQueue.Count() > 0 ) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } if (RouteQueue.Count() > 0) { Assign_Destination(RouteQueue[0], false); @@ -2387,11 +2385,9 @@ void FootClass::Assign_Destination(AbstractClass * target, bool) ParticleSystems[ATTACHED_PARTICLE_FIRE] = NULL; } - CLSID locoid; - IPersistPtr persist(Locomotion); - persist->GetClassID(&locoid); + ClassID const locoid = Locomotion_Class_ID(Locomotion.get()); - if (locoid == CLSID_HoverLocomotion && PathDelay == 0) { + if (locoid == ClassID_HoverLocomotion && PathDelay == 0) { PathDelay = 1; } @@ -3317,10 +3313,10 @@ void FootClass::AI(void) Scatter(Coord(0,0,0), true); } - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL) { if (piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } } @@ -3412,7 +3408,7 @@ ZGradientType FootClass::Get_Z_Gradient(void) const /// void FootClass::Draw_Voxel_Shadow(VoxelDataStruct const & voxeldata, int layer_index, int key, VoxelIndexClass * cache, Rect const & cliprect, Point2D const & point, Matrix3D const & matrix, bool force_cache) const { - if (Locomotion != NULL && Locomotion->Is_To_Have_Shadow() == (boolean)true) { + if (Locomotion != nullptr && Locomotion->Is_To_Have_Shadow() == (bool)true) { Point2D drawpoint = point; if (Locomotion != NULL) { drawpoint = Point2D(Locomotion->Shadow_Point()) + point; @@ -3519,19 +3515,13 @@ void FootClass::Serialize(SaveStreamClass & stream) stream.Serialize(BlockagePathDelay); /* - * The locomotor is a COM sub-object rather than a member, so it persists itself onto - * the raw stream through OLE. The one being replaced is released first, since loading - * hands back a fresh interface pointer rather than filling this one in. + * The locomotor is a sub-object rather than a member, so it travels as a record of + * its own. */ if (stream.Is_Saving()) { - IPersistStreamPtr persist(Locomotion); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, Locomotion.get()); } else { - if (Locomotion != NULL) { - ((ILocomotion *)Locomotion)->Release(); - } - Locomotion.Detach(); - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Locomotion); + Locomotion = Load_Locomotor(stream); } stream.Serialize(HeadToCoord); @@ -3595,12 +3585,12 @@ void FootClass::Set_Coord(Coord const & coord) /// void FootClass::Link_DropPod(void) { - ILocomotionPtr locomotion = Locomotion; - ILocomotionPtr ballistic(CLSID_BallisticLocomotion); + std::unique_ptr locomotion = std::move(Locomotion); + std::unique_ptr ballistic = Create_Locomotor(ClassID_BallisticLocomotion); ballistic->Link_To_Object(this); - IPiggybackPtr piggy(ballistic); + IPiggyback * piggy = Piggyback_Of(ballistic.get()); piggy->Begin_Piggyback(locomotion); - Locomotion = ballistic; + Locomotion = std::move(ballistic); } @@ -4766,12 +4756,9 @@ void FootClass::Delete_Me(void) /// bool; Is the object in the air? bool FootClass::In_Air(void) const { - IPersistPtr loco(Locomotion); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); - CLSID clsid; - loco->GetClassID(&clsid); - - if (clsid == CLSID_HoverLocomotion) { + if (clsid == ClassID_HoverLocomotion) { return(false); } @@ -4790,10 +4777,7 @@ bool FootClass::On_Ground(void) const if (BASECLASS::On_Ground()) { return(true); } - IPersistPtr loco(Locomotion); - - CLSID clsid; - loco->GetClassID(&clsid); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); - return(IsDown && clsid == CLSID_HoverLocomotion); + return(IsDown && clsid == ClassID_HoverLocomotion); } diff --git a/code/foot.h b/code/foot.h index 246b96e08..75638b977 100644 --- a/code/foot.h +++ b/code/foot.h @@ -38,6 +38,7 @@ #include "techno.h" #include +#include class UnitClass; class BuildingClass; @@ -210,7 +211,7 @@ class FootClass : public TechnoClass * handed to a ballistic locomotor and a unit crossing a tunnel walks -- so all * movement is asked of this interface rather than of the type's setting. */ - ILocomotionPtr Locomotion; + std::unique_ptr Locomotion; /* ** This is the coordinate that the unit is heading to diff --git a/code/globals.cpp b/code/globals.cpp index 5b6a9a89a..1c61a8f0e 100644 --- a/code/globals.cpp +++ b/code/globals.cpp @@ -29,23 +29,10 @@ *---------------------------------------------------------------------------------------------* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" -/// create all com interfaces here -#include "iblowfish.h" -#include "iblowfish_i.c" #include "sun.h" -#include "isun_i.c" -#include "ilocos.h" -#include "ilocos_i.c" -#include "ipiggy.h" -#include "ipiggy_i.c" -#include "iblockci.h" -#include "iblockci_i.c" -#include "iflyctrl.h" -#include "iflyctrl_i.c" -#undef INCLUDE_COM +#include "classids.h" #include "_voxel.h" #include "globals.h" diff --git a/code/house.cpp b/code/house.cpp index a288589d9..a1bf5b687 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -128,7 +128,6 @@ * HouseClass::Random_Cell_In_Zone -- Find a (technically) legal cell in the zone specified. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "house.h" @@ -690,8 +689,15 @@ HouseClass::~HouseClass (void) } SuperWeapon.Clear(); + // A tag removes itself from this list as it dies; a slot a failed load left empty is + // removed here, or the list would never drain. while (HouseTags.Count() > 0) { - delete HouseTags[0]; + TagClass * const tag = HouseTags[0]; + if (tag == nullptr) { + HouseTags.Delete_Index(0); + } else { + delete tag; + } } AbstractTypePtrTracker.Delete(this); @@ -6432,8 +6438,8 @@ void HouseClass::Compute_CRC(CRCEngine & crc) const /// record, so they are disposed of before the saved members are read over the top of them. /// /// The stream to read the house from. -/// Returns with S_OK, or the failure code reported by the stream. -HRESULT STDMETHODCALLTYPE HouseClass::Load(IStream *stream) +/// bool; Was the record read whole? +bool HouseClass::Load(SaveStreamClass & stream) { while (SuperWeapon.Count()) { delete SuperWeapon[0]; @@ -6623,18 +6629,9 @@ void HouseClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence contract. The load system uses the identifier to -/// discover which class to build when the object is read back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE HouseClass::GetClassID(CLSID * retval) +ClassID HouseClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_HouseClass; - return(S_OK); + return(ClassID_HouseClass); } @@ -9206,30 +9203,6 @@ void HouseClass::AI_Drop_Pods(SuperClass * super) } -/// -/// Adds a reference to this house. -/// Houses are permanent heap objects rather than reference counted ones, so this routine -/// exists only to satisfy the IUnknown contract. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseClass::AddRef(void) -{ - return(1); -} - - -/// -/// Releases a reference to this house. -/// Houses are permanent heap objects rather than reference counted ones, so this routine -/// exists only to satisfy the IUnknown contract. It never destroys the house. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseClass::Release(void) -{ - return(1); -} - - /// /// Fetches the RTTI type of this object. /// diff --git a/code/house.h b/code/house.h index 7f4e50526..fa42696b2 100644 --- a/code/house.h +++ b/code/house.h @@ -735,13 +735,11 @@ class HouseClass : public AbstractClass HouseClass(HouseTypeClass const * type = NULL); virtual ~HouseClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual ClassID Class_ID(void) const override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; int Available_Money(void); int Available_Storage(void); @@ -1060,8 +1058,6 @@ class HouseClass : public AbstractClass BuildChoiceClass(UrgencyType urgency=URGENCY_NONE, StructType structure=STRUCT_NONE) : Urgency(urgency), Structure(structure) {}; bool operator==(BuildChoiceClass const & ) const {return(false);} bool operator!=(BuildChoiceClass const & ) const {return(true);} - HRESULT Save(IStream *) const {return(S_OK);}; - HRESULT Load(IStream *) {return(S_OK);}; }; static DynamicVectorClass BuildChoice; diff --git a/code/houstype.cpp b/code/houstype.cpp index 593e2be76..25721afd6 100644 --- a/code/houstype.cpp +++ b/code/houstype.cpp @@ -39,7 +39,6 @@ * HouseTypeClass::operator new -- Allocates a house type class object from special heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "houstype.h" @@ -231,17 +230,6 @@ void HouseTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Determines if this house type has been changed since it was last saved. -/// House types are written out wholesale rather than on demand, so the answer never varies. -/// -/// Returns with S_OK. -HRESULT STDMETHODCALLTYPE HouseTypeClass::IsDirty(void) -{ - return(false); -} - - /// /// Lists the members this house type carries. /// @@ -270,49 +258,9 @@ void HouseTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the requested interface from this house type. -/// House types serve up the persistence and RTTI interfaces that the save game system asks -/// them for. -/// -/// Returns with S_OK, or E_NOINTERFACE if the interface is not supported. -HRESULT STDMETHODCALLTYPE HouseTypeClass::QueryInterface(REFIID riid, LPVOID * ppvObject) +ClassID HouseTypeClass::Class_ID(void) const { - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - - if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(IPersistStream *)this; - } - if (riid == IID_IPersist) { - *ppvObject = (IPersistStream *)this; - } - if (riid == IID_IPersistStream) { - *ppvObject = (IPersist *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - - AddRef(); - return(S_OK); -} - - -/// -/// Fetches the class identifier of this object. -/// The save game system stores this identifier so that the object can be recreated as the -/// correct class when the game is loaded. -/// -/// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT STDMETHODCALLTYPE HouseTypeClass::GetClassID(CLSID * retval) -{ - if (retval == NULL) return(E_POINTER); - *retval = CLSID_HouseTypeClass; - return(S_OK); + return(ClassID_HouseTypeClass); } @@ -347,27 +295,3 @@ int HouseTypeClass::Fetch_Heap_ID(void) const { return(HeapID); } - - -/// -/// Adds a reference to this house type. -/// House types are not reference counted -- they live for the duration of the game, so this -/// routine exists only to satisfy the IUnknown contract. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseTypeClass::AddRef(void) -{ - return(1); -} - - -/// -/// Releases a reference to this house type. -/// House types are not reference counted -- they live for the duration of the game, so this -/// routine exists only to satisfy the IUnknown contract. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseTypeClass::Release(void) -{ - return(1); -} diff --git a/code/houstype.h b/code/houstype.h index ef3cb3d95..0020ea187 100644 --- a/code/houstype.h +++ b/code/houstype.h @@ -102,12 +102,8 @@ class HouseTypeClass : public AbstractTypeClass HouseTypeClass(char const * ininame = NULL); virtual ~HouseTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE IsDirty(void) override; + virtual ClassID Class_ID(void) const override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/hover.cpp b/code/hover.cpp index b108e633d..369b36fc2 100644 --- a/code/hover.cpp +++ b/code/hover.cpp @@ -66,12 +66,11 @@ HoverLocomotionClass::HoverLocomotionClass(void) : /// /// Pointer to the object this locomotor will drive. /// Returns with the result of the attach operation. -HRESULT STDMETHODCALLTYPE HoverLocomotionClass::Link_To_Object(void *pointer) +void HoverLocomotionClass::Link_To_Object(void *pointer) { - HRESULT res = BASECLASS::Link_To_Object(pointer); + BASECLASS::Link_To_Object(pointer); FacingClass face(2 * LinkedTo->TClass->ROT); Facing = face; - return(res); } @@ -141,7 +140,7 @@ void HoverLocomotionClass::Gravity_AI(void) /// /// Pointer to the render cache key to update; may be NULL. /// Returns with the matrix to render the object with. -Matrix3D STDMETHODCALLTYPE HoverLocomotionClass::Draw_Matrix(int *key) +Matrix3D HoverLocomotionClass::Draw_Matrix(int *key) { if (!Is_Powered()) { int ramp = Map[(Coord const &)(LinkedTo->PositionCoord)].Ramp; @@ -164,7 +163,7 @@ Matrix3D STDMETHODCALLTYPE HoverLocomotionClass::Draw_Matrix(int *key) /// water, and applies the bob and sag of the hover cushion. /// /// bool; Is the object still moving? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Process(void) +bool HoverLocomotionClass::Process(void) { if (Is_Moving() && Is_Moving1()) { Motion_AI(); @@ -288,7 +287,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Process(void) /// Does the object have a move order outstanding? /// /// bool; Is the object either headed somewhere or bound for a destination? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving(void) +bool HoverLocomotionClass::Is_Moving(void) { return(DestinationCoord != COORD_NONE || HeadToCoord != COORD_NONE); } @@ -300,7 +299,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving(void) /// moving, but it is not moving now. /// /// bool; Is the object traveling at this moment? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving_Now(void) +bool HoverLocomotionClass::Is_Moving_Now(void) { return(Is_Moving() && Height != 0.0); } @@ -311,7 +310,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving_Now(void) /// /// Returns with the destination coordinate, or COORD_NONE if the object has no /// move order outstanding. -Coord STDMETHODCALLTYPE HoverLocomotionClass::Destination(void) +Coord HoverLocomotionClass::Destination(void) { if (DestinationCoord != COORD_NONE) { return(DestinationCoord); @@ -325,7 +324,7 @@ Coord STDMETHODCALLTYPE HoverLocomotionClass::Destination(void) /// /// Returns with the intermediate destination, or with the object's current /// position if it is not headed anywhere. -Coord STDMETHODCALLTYPE HoverLocomotionClass::Head_To_Coord(void) +Coord HoverLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -341,7 +340,7 @@ Coord STDMETHODCALLTYPE HoverLocomotionClass::Head_To_Coord(void) /// the drive is started if the object is not already under way. /// /// The coordinate to move to. -void STDMETHODCALLTYPE HoverLocomotionClass::Move_To(Coord to) +void HoverLocomotionClass::Move_To(Coord to) { DestinationCoord = to; if (Is_Powered() && Is_Ion_Sensitive() && IonStormClass::Is_Ion_Storm_Active()) { @@ -678,7 +677,7 @@ void HoverLocomotionClass::Motion_AI(void) /// The object will still coast into the spot it has already reserved, but it will not /// carry on toward its former destination once it arrives. /// -void STDMETHODCALLTYPE HoverLocomotionClass::Stop_Moving(void) +void HoverLocomotionClass::Stop_Moving(void) { if (DestinationCoord != HeadToCoord) { DestinationCoord = COORD_NONE; @@ -918,7 +917,7 @@ void HoverLocomotionClass::Start_Of_Move(int num) /// and slews as it sinks rather than dropping neatly in place. /// /// bool; Was the power turned off? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Power_Off(void) +bool HoverLocomotionClass::Power_Off(void) { if (Is_Powered() && LinkedTo->CurrentMission != MISSION_SLEEP) { Do_Shove(); @@ -937,7 +936,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Power_Off(void) /// way onto the ground, so it is treated as powered for as long as it has height to lose. /// /// bool; Is the object still under power? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Powered(void) +bool HoverLocomotionClass::Is_Powered(void) { if (!BASECLASS::Is_Powered() && LinkedTo->HeightAGL <= 0) { return(false); @@ -953,7 +952,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Powered(void) /// units across the factory doorway. /// /// bool; Should an ion storm cut this object's power? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Ion_Sensitive(void) +bool HoverLocomotionClass::Is_Ion_Sensitive(void) { BuildingClass *bptr; if (LinkedTo->In_Radio_Contact()) { @@ -997,7 +996,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Ion_Sensitive(void) /// /// The direction to push the object toward. /// bool; Was the object pushed? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Push(DirType dir) +bool HoverLocomotionClass::Push(DirType dir) { if (Is_Powered() && !WasPushed) { @@ -1042,7 +1041,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Push(DirType dir) /// /// The direction to shove the object toward. /// bool; Was the object shoved? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Shove(DirType dir) +bool HoverLocomotionClass::Shove(DirType dir) { if (Push(dir)) { Do_Shove(); @@ -1067,18 +1066,9 @@ void HoverLocomotionClass::Do_Shove(void) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence system uses this identifier to create a locomotor of the right kind -/// when the object it drives is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE HoverLocomotionClass::GetClassID(CLSID * retval) +ClassID HoverLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_HoverLocomotion; - return(S_OK); + return(ClassID_HoverLocomotion); } @@ -1109,7 +1099,7 @@ void HoverLocomotionClass::Serialize(SaveStreamClass & stream) /// layer with ordinary vehicles. /// /// Returns with the layer this object belongs in. -LayerType STDMETHODCALLTYPE HoverLocomotionClass::In_Which_Layer(void) +LayerType HoverLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } @@ -1140,7 +1130,7 @@ void HoverLocomotionClass::Start(void) /// /// The marking operation to perform; MARK_UP releases the cell, /// anything else reserves it. -void STDMETHODCALLTYPE HoverLocomotionClass::Mark_All_Occupation_Bits(int mark) +void HoverLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { Coord coord = Head_To_Coord(); @@ -1159,7 +1149,7 @@ void STDMETHODCALLTYPE HoverLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The coordinate to compare the current destination against. /// bool; Is the object moving to this location? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving_Here(Coord to) +bool HoverLocomotionClass::Is_Moving_Here(Coord to) { Coord coord = Head_To_Coord(); diff --git a/code/hover.h b/code/hover.h index ab7bce672..4b22c0869 100644 --- a/code/hover.h +++ b/code/hover.h @@ -36,28 +36,28 @@ class HoverLocomotionClass : public LocomotionClass HoverLocomotionClass(void); virtual ~HoverLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual boolean STDMETHODCALLTYPE Power_Off(void) override; - virtual boolean STDMETHODCALLTYPE Is_Powered(void) override; - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) override; - virtual boolean STDMETHODCALLTYPE Push(DirType dir) override; - virtual boolean STDMETHODCALLTYPE Shove(DirType dir) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; + virtual void Link_To_Object(void *pointer) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual bool Power_Off(void) override; + virtual bool Is_Powered(void) override; + virtual bool Is_Ion_Sensitive(void) override; + virtual bool Push(DirType dir) override; + virtual bool Shove(DirType dir) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; private: diff --git a/code/iblockci.h b/code/iblockci.h deleted file mode 100644 index 0240dffd4..000000000 --- a/code/iblockci.h +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include - -/// Names and comments from TLBs - -EXTERN_C const IID IID_IBlockCipher; - -MIDL_INTERFACE("E0113100-6A7C-11D1-B6F9-00A024DDAFD1") -IBlockCipher : public IUnknown -{ -public: - virtual HRESULT STDMETHODCALLTYPE Set_Key(LONG keylength, const void *key) = 0; - virtual HRESULT STDMETHODCALLTYPE get_Max_Key_Length(LONG *length) = 0; - virtual HRESULT STDMETHODCALLTYPE get_Block_Size(LONG *length) = 0; - virtual HRESULT STDMETHODCALLTYPE Encrypt(LONG length, const void *plaintext, void *cyphertext) = 0; - virtual HRESULT STDMETHODCALLTYPE Decrypt(LONG length, const void *cyphertext, void *plaintext) = 0; -}; diff --git a/code/iblockci_i.c b/code/iblockci_i.c deleted file mode 100644 index 750d2597f..000000000 --- a/code/iblockci_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_IBlockCipher = {0xE0113100,0x6A7C,0x11D1,{0xB6,0xF9,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/iblowfish.h b/code/iblowfish.h deleted file mode 100644 index 4c01ee694..000000000 --- a/code/iblowfish.h +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include - -/// Names and comments from TLBs - -EXTERN_C const IID LIBID_BlowfishLibrary; -EXTERN_C const CLSID CLSID_BlowfishObject; diff --git a/code/iblowfish_i.c b/code/iblowfish_i.c deleted file mode 100644 index 71ad0728b..000000000 --- a/code/iblowfish_i.c +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID LIBID_BlowfishLibrary = {0xE7F91750,0x8861,0x11d1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_BlowfishObject = {0x1440ad10,0x6aa8,0x11d1,{0xb6,0xf9,0x00,0xa0,0x24,0xdd,0xaf,0xd1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/iflyctrl.h b/code/iflyctrl.h index 958e59d47..8a2415ee6 100644 --- a/code/iflyctrl.h +++ b/code/iflyctrl.h @@ -9,43 +9,34 @@ #pragma once -#include +#include "win.h" -/// Names and comments from TLBs -EXTERN_C const IID IID_IFlyControl; -MIDL_INTERFACE("820F501C-4F39-11D2-9B70-00104B972FE8") -IFlyControl : public IUnknown +struct IFlyControl { -public: /* * Landing altitude */ - virtual LONG STDMETHODCALLTYPE Landing_Altitude(void) = 0; + virtual LONG Landing_Altitude(void) = 0; /* * Lading direction */ - virtual LONG STDMETHODCALLTYPE Landing_Direction(void) = 0; + virtual LONG Landing_Direction(void) = 0; /* * Loaded with cargo? */ - virtual BOOL STDMETHODCALLTYPE Is_Loaded(void) = 0; + virtual BOOL Is_Loaded(void) = 0; /* * Does it strafe over the target rather than hover? */ - virtual LONG STDMETHODCALLTYPE Is_Strafe(void) = 0; + virtual LONG Is_Strafe(void) = 0; /* * Is the aircraft locked into straight flight? */ - virtual LONG STDMETHODCALLTYPE Is_Locked(void) = 0; + virtual LONG Is_Locked(void) = 0; }; - -/* - * IFlyControl com smart pointer declaration. - */ -_COM_SMARTPTR_TYPEDEF(IFlyControl, __uuidof(IFlyControl)); diff --git a/code/iflyctrl_i.c b/code/iflyctrl_i.c deleted file mode 100644 index 994c2ab42..000000000 --- a/code/iflyctrl_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_IFlyControl = {0x820F501C,0x4F39,0x11D2,{0x9B,0x70,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/ilinkstm.h b/code/ilinkstm.h deleted file mode 100644 index 055b91573..000000000 --- a/code/ilinkstm.h +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include - -/// Names and comments from TLBs - -EXTERN_C const IID IID_ILinkStream; - -MIDL_INTERFACE("0D5CD78E-6470-11D2-9B74-00104B972FE8") -ILinkStream : public IUnknown -{ -public: - virtual HRESULT STDMETHODCALLTYPE Link_Stream(IUnknown *stream) = 0; - virtual HRESULT STDMETHODCALLTYPE Unlink_Stream(IUnknown **stream) = 0; -}; - -/* - * ILinkStream com smart pointer declaration. - */ -//_COM_SMARTPTR_TYPEDEF(ILinkStream, __uuidof(ILinkStream)); diff --git a/code/iloco.h b/code/iloco.h index 1d43d5e35..d83e775cf 100644 --- a/code/iloco.h +++ b/code/iloco.h @@ -20,251 +20,243 @@ #include "visual.hh" #include "zgrad.hh" -#include -/// Names and comments from TLBs - -EXTERN_C const IID IID_ILocomotion; /* * Game object locomotion handler. */ -MIDL_INTERFACE("070F3290-9841-11D1-B709-00A024DDAFD1") -ILocomotion : public IUnknown +struct ILocomotion { -public: + virtual ~ILocomotion(void) {} + /* * Links object to locomotor. */ - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *pointer) = 0; + virtual void Link_To_Object(void *pointer) = 0; /* * Sees if object is moving. */ - virtual boolean STDMETHODCALLTYPE Is_Moving(void) = 0; + virtual bool Is_Moving(void) = 0; /* * Fetches destination coordinate. */ - virtual Coord STDMETHODCALLTYPE Destination(void) = 0; + virtual Coord Destination(void) = 0; /* * Fetches immediate (next cell) destination coordinate. */ - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) = 0; + virtual Coord Head_To_Coord(void) = 0; /* * Determine if specific cell can be entered. */ - virtual MoveType STDMETHODCALLTYPE Can_Enter_Cell(Cell cell) = 0; + virtual MoveType Can_Enter_Cell(Cell cell) = 0; /* * Should object cast a shadow? */ - virtual boolean STDMETHODCALLTYPE Is_To_Have_Shadow(void) = 0; + virtual bool Is_To_Have_Shadow(void) = 0; /* * Fetch voxel draw matrix. */ - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) = 0; + virtual Matrix3D Draw_Matrix(int *key) = 0; /* * Fetch shadow draw matrix. */ - virtual Matrix3D STDMETHODCALLTYPE Shadow_Matrix(int *key) = 0; + virtual Matrix3D Shadow_Matrix(int *key) = 0; /* * Draw point center location. */ - virtual Point2D STDMETHODCALLTYPE Draw_Point(void) = 0; + virtual Point2D Draw_Point(void) = 0; /* * Shadow draw point center location. */ - virtual Point2D STDMETHODCALLTYPE Shadow_Point(void) = 0; + virtual Point2D Shadow_Point(void) = 0; /* * Visual character for drawing. */ - virtual VisualType STDMETHODCALLTYPE Visual_Character(boolean flag) = 0; + virtual VisualType Visual_Character(bool flag) = 0; /* * Z adjust control value. */ - virtual int STDMETHODCALLTYPE Z_Adjust(void) = 0; + virtual int Z_Adjust(void) = 0; /* * Z gradient control value. */ - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) = 0; + virtual ZGradientType Z_Gradient(void) = 0; /* * Process movement of object. */ - virtual boolean STDMETHODCALLTYPE Process(void) = 0; + virtual bool Process(void) = 0; /* * Instruct to move to location specified. */ - virtual void STDMETHODCALLTYPE Move_To(Coord to) = 0; + virtual void Move_To(Coord to) = 0; /* * Stop moving at first opportunity. */ - virtual void STDMETHODCALLTYPE Stop_Moving(void) = 0; + virtual void Stop_Moving(void) = 0; /* * Try to face direction specified. */ - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) = 0; + virtual void Do_Turn(DirType coord) = 0; /* * Object is appearing in the world. */ - virtual void STDMETHODCALLTYPE Unlimbo(void) = 0; + virtual void Unlimbo(void) = 0; /* * Special tilting AI function. */ - virtual void STDMETHODCALLTYPE Tilt_Pitch_AI(void) = 0; + virtual void Tilt_Pitch_AI(void) = 0; /* * Locomotor becomes powered. */ - virtual boolean STDMETHODCALLTYPE Power_On(void) = 0; + virtual bool Power_On(void) = 0; /* * Locomotor loses power. */ - virtual boolean STDMETHODCALLTYPE Power_Off(void) = 0; + virtual bool Power_Off(void) = 0; /* * Is locomotor powered? */ - virtual boolean STDMETHODCALLTYPE Is_Powered(void) = 0; + virtual bool Is_Powered(void) = 0; /* * Is locomotor sensitive to ion storms? */ - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) = 0; + virtual bool Is_Ion_Sensitive(void) = 0; /* * Push object in direction specified. */ - virtual boolean STDMETHODCALLTYPE Push(DirType dir) = 0; + virtual bool Push(DirType dir) = 0; /* * Shove object (with spin) in direction specified. */ - virtual boolean STDMETHODCALLTYPE Shove(DirType dir) = 0; + virtual bool Shove(DirType dir) = 0; /* * Force drive track -- special case only. */ - virtual void STDMETHODCALLTYPE Force_Track(int track, Coord coord) = 0; + virtual void Force_Track(int track, Coord coord) = 0; /* * What display layer is it located in. */ - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) = 0; + virtual LayerType In_Which_Layer(void) = 0; /* * Don't use this function. */ - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) = 0; + virtual void Force_Immediate_Destination(Coord coord) = 0; /* * Force a voxel unit to a given slope. Used in cratering. */ - virtual void STDMETHODCALLTYPE Force_New_Slope(int ramp) = 0; + virtual void Force_New_Slope(int ramp) = 0; /* * Is it actually moving across the ground this very second? */ - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) = 0; + virtual bool Is_Moving_Now(void) = 0; /* * Actual current speed of object expressed as leptons per game frame. */ - virtual int STDMETHODCALLTYPE Apparent_Speed(void) = 0; + virtual int Apparent_Speed(void) = 0; /* * Special drawing feedback code (locomotor specific meaning) */ - virtual int STDMETHODCALLTYPE Drawing_Code(void) = 0; + virtual int Drawing_Code(void) = 0; /* * Queries if any locomotor specific state prevents the object from firing. */ - virtual FireErrorType STDMETHODCALLTYPE Can_Fire(void) = 0; + virtual FireErrorType Can_Fire(void) = 0; /* * Queries the general state of the locomotor. */ - virtual int STDMETHODCALLTYPE Get_Status(void) = 0; + virtual int Get_Status(void) = 0; /* * Forces a hunter seeker droid to find a target. */ - virtual void STDMETHODCALLTYPE Acquire_Hunter_Seeker_Target(void) = 0; + virtual void Acquire_Hunter_Seeker_Target(void) = 0; /* * Is this object surfacing? */ - virtual boolean STDMETHODCALLTYPE Is_Surfacing(void) = 0; + virtual bool Is_Surfacing(void) = 0; /* * Lifts all occupation bits associated with the object off the map */ - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) = 0; + virtual void Mark_All_Occupation_Bits(int mark) = 0; /* * Is this object in the process of moving into this coord. */ - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) = 0; + virtual bool Is_Moving_Here(Coord to) = 0; /* * Will this object jump tracks? */ - virtual boolean STDMETHODCALLTYPE Will_Jump_Tracks(void) = 0; + virtual bool Will_Jump_Tracks(void) = 0; /* * Infantry moving query function */ - virtual boolean STDMETHODCALLTYPE Is_Really_Moving_Now(void) = 0; + virtual bool Is_Really_Moving_Now(void) = 0; /* * Falsifies the IsReallyMoving flag in WalkLocomotionClass */ - virtual void STDMETHODCALLTYPE Stop_Movement_Animation(void) = 0; + virtual void Stop_Movement_Animation(void) = 0; /* - * Locks the locomotor from being deleted + * Locks the locomotor against being handed back, so that one piggybacking on it keeps + * control of the object. */ - virtual void STDMETHODCALLTYPE Lock(void) = 0; + virtual void Lock(void) = 0; /* - * Unlocks the locomotor from being deleted + * Unlocks the locomotor, so that a piggyback riding on it may end. */ - virtual void STDMETHODCALLTYPE Unlock(void) = 0; + virtual void Unlock(void) = 0; /* * Queries internal variables */ - virtual int STDMETHODCALLTYPE Get_Track_Number(void) = 0; + virtual int Get_Track_Number(void) = 0; /* * Queries internal variables */ - virtual int STDMETHODCALLTYPE Get_Track_Index(void) = 0; + virtual int Get_Track_Index(void) = 0; /* * Queries internal variables */ - virtual int STDMETHODCALLTYPE Get_Speed_Accum(void) = 0; + virtual int Get_Speed_Accum(void) = 0; }; - -/* - * ILocomtion com smart pointer declaration. - */ -_COM_SMARTPTR_TYPEDEF(ILocomotion, __uuidof(ILocomotion)); diff --git a/code/iloco_i.c b/code/iloco_i.c deleted file mode 100644 index cecc3d209..000000000 --- a/code/iloco_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_ILocomotion = {0x070F3290,0x9841,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/ilocos.h b/code/ilocos.h deleted file mode 100644 index ee939a7e0..000000000 --- a/code/ilocos.h +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include "iloco.h" - -/// Names and comments from TLBs - -EXTERN_C const IID LIBID_LocomotionLibrary; - -EXTERN_C const CLSID CLSID_DriveLocomotion; -EXTERN_C const CLSID CLSID_HoverLocomotion; -EXTERN_C const CLSID CLSID_TunnelLocomotion; -EXTERN_C const CLSID CLSID_WalkLocomotion; -EXTERN_C const CLSID CLSID_BallisticLocomotion; -EXTERN_C const CLSID CLSID_FlyerLocomotion; -EXTERN_C const CLSID CLSID_TeleportLocomotion; -EXTERN_C const CLSID CLSID_MechLocomotion; -EXTERN_C const CLSID CLSID_JumpjetLocomotion; -EXTERN_C const CLSID CLSID_LevitateLocomotion; diff --git a/code/ilocos_i.c b/code/ilocos_i.c deleted file mode 100644 index a1fb1faf0..000000000 --- a/code/ilocos_i.c +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - - -const IID LIBID_LocomotionLibrary = {0x4A582740,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_DriveLocomotion = {0x4A582741,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_HoverLocomotion = {0x4A582742,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_TunnelLocomotion = {0x4A582743,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_WalkLocomotion = {0x4A582744,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_BallisticLocomotion = {0x4A582745,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_FlyerLocomotion = {0x4A582746,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_TeleportLocomotion = {0x4A582747,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_MechLocomotion = {0x55D141B8,0xDB94,0x11D1,{0xAC,0x98,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_JumpjetLocomotion = {0x92612C46,0xF71F,0x11D1,{0xAC,0x9F,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_LevitateLocomotion = {0x3DC0B295,0x6546,0x11D3,{0x80,0xB0,0x00,0x90,0x27,0x92,0x49,0x4C}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/infantry.cpp b/code/infantry.cpp index f8f11f6ae..1a6103742 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -79,7 +79,6 @@ * InfantryClass::~InfantryClass -- Default destructor for infantry units. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "infantry.h" @@ -100,6 +99,7 @@ #include "builtype.h" #include "ccrand.h" #include "cell.h" +#include "classids.h" #include "combat.h" #include "data.h" #include "draw.h" @@ -108,7 +108,6 @@ #include "goptions.h" #include "house.h" #include "houstype.h" -#include "ilocos.h" #include "incdec.h" #include "infatype.h" #include "inline.h" @@ -250,7 +249,7 @@ InfantryClass::InfantryClass(InfantryTypeClass const * type, HouseClass * house) Init(); if (Class != NULL) { - Locomotion.CreateInstance(Class->Locomotor, NULL, CLSCTX_ALL); + Locomotion = Create_Locomotor(Class->Locomotor); Locomotion->Link_To_Object(this); } @@ -632,11 +631,9 @@ void InfantryClass::Draw_It(Point2D const & xpoint, Rect const & cliprect) const Cell cell = Get_Target_Cell(); if (CurrentTube == -1) { - IPersistPtr persist = Locomotion; - CLSID clsid; - persist->GetClassID(&clsid); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (HeightAGL > 0 && clsid == CLSID_BallisticLocomotion) { + if (HeightAGL > 0 && clsid == ClassID_BallisticLocomotion) { ShapeSet const * shapefile = (ShapeSet const *)MFCD::Retrieve("POD.SHP"); Point2D spoint = xpoint + Point2D(Locomotion->Shadow_Point()); Draw_Shape( @@ -1174,10 +1171,8 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) } if (target != NULL && Class->IsJumpJet && Locomotion->Is_Moving()) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); - if (clsid == CLSID_WalkLocomotion) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_WalkLocomotion) { NavQueue.Add_Head(target); target = Get_Target_Cell_Ptr(); if (target != NULL && ((CellClass *)target)->IsUnderBridge) { @@ -1190,26 +1185,26 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) bool should_fly = Should_JumpJet_Fly(Destination_Coord().As_Cell(), target->Center_Coord().As_Cell()); if (Is_JumpJet()) { if (!should_fly) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL) { if (piggy->Is_Piggybacking() && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } } - ILocomotionPtr walk(CLSID_WalkLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_WalkLocomotion); walk->Link_To_Object(this); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { piggy->Begin_Piggyback(Locomotion); - Locomotion = walk; + Locomotion = std::move(walk); } } } else { if (should_fly) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL) { if (piggy->Is_Piggybacking() && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } } } @@ -3943,8 +3938,8 @@ void InfantryClass::Clear_Occupy_Bit(Coord const & coord) /// since the one it is about to be given is the one it was saved with. Post_Load enters it /// again once that identity has arrived. /// -/// Returns with S_OK if the object was read successfully. -HRESULT STDMETHODCALLTYPE InfantryClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool InfantryClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); @@ -4154,15 +4149,15 @@ bool InfantryClass::JumpJet_To_Walk(void) if (path_length >= 4) return(false); if (Is_JumpJet()) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && !piggy->Is_Piggybacking()) { - ILocomotionPtr walk(CLSID_WalkLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_WalkLocomotion); walk->Link_To_Object(this); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { Path[0] = FACING_NONE; piggy->Begin_Piggyback(Locomotion); - Locomotion = walk; + Locomotion = std::move(walk); Locomotion->Move_To(NavCom->Center_Coord()); return(true); } @@ -4197,10 +4192,8 @@ bool InfantryClass::Is_JumpJet(void) const return(false); } - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); - return((clsid == CLSID_JumpjetLocomotion) ? true : false); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + return((clsid == ClassID_JumpjetLocomotion) ? true : false); } @@ -4303,18 +4296,9 @@ int InfantryClass::Do_MISSION_GUARD(void) } -/// -/// Fetches the class identifier used to persist this object. -/// The save system records this identifier alongside the object data so that the -/// correct kind of object can be created again when the stream is read back. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE InfantryClass::GetClassID(CLSID * retval) +ClassID InfantryClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_InfantryClass; - return(S_OK); + return(ClassID_InfantryClass); } diff --git a/code/infantry.h b/code/infantry.h index fc6183008..1540aa8e8 100644 --- a/code/infantry.h +++ b/code/infantry.h @@ -126,8 +126,8 @@ class InfantryClass : public FootClass InfantryClass(InfantryTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~InfantryClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual ClassID Class_ID(void) const override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/infatype.cpp b/code/infatype.cpp index e4c42f96b..9d707ff34 100644 --- a/code/infatype.cpp +++ b/code/infatype.cpp @@ -45,7 +45,6 @@ * InfantryTypeClass::operator new -- Allocate an infanty type class object. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "infatype.h" @@ -513,17 +512,9 @@ void InfantryTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save game system stores this identifier so that the object can be recreated as the -/// correct class when the game is loaded. -/// -/// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT STDMETHODCALLTYPE InfantryTypeClass::GetClassID(CLSID * retval) +ClassID InfantryTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_InfantryTypeClass; - return(S_OK); + return(ClassID_InfantryTypeClass); } diff --git a/code/infatype.h b/code/infatype.h index 340f22dcc..e1b717d6c 100644 --- a/code/infatype.h +++ b/code/infatype.h @@ -172,7 +172,7 @@ class InfantryTypeClass : public TechnoTypeClass InfantryTypeClass(char const * ininame = NULL); virtual ~InfantryTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/ini.cpp b/code/ini.cpp index 4db0ee6f2..64a8536f7 100644 --- a/code/ini.cpp +++ b/code/ini.cpp @@ -957,6 +957,86 @@ int INIClass::Get_Int(char const * section, char const * entry, int defvalue) co } +// One field of a class identifier, exactly the many hexadecimal digits it is written with. +static bool Parse_Hex_Field(char const * & ptr, int digits, unsigned int & value) +{ + value = 0; + + for (int index = 0; index < digits; index++) { + char const letter = *ptr++; + unsigned int digit; + + if (letter >= '0' && letter <= '9') { + digit = (unsigned int)(letter - '0'); + } else if (letter >= 'A' && letter <= 'F') { + digit = (unsigned int)(letter - 'A') + 10; + } else if (letter >= 'a' && letter <= 'f') { + digit = (unsigned int)(letter - 'a') + 10; + } else { + return(false); + } + + value = (value << 4) | digit; + } + + return(true); +} + + +// A class identifier as the registry writes it, the surrounding braces optional: eight, +// four, four, four and twelve hexadecimal digits separated by hyphens, and nothing else. +static bool Parse_ClassID(char const * text, ClassID & clsid) +{ + char const * ptr = text; + std::size_t length = strlen(text); + + if (length == 38 && ptr[0] == '{' && ptr[37] == '}') { + ptr++; + length -= 2; + } + if (length != 36) { + return(false); + } + + unsigned int data1; + unsigned int data2; + unsigned int data3; + if (!Parse_Hex_Field(ptr, 8, data1) || *ptr++ != '-') return(false); + if (!Parse_Hex_Field(ptr, 4, data2) || *ptr++ != '-') return(false); + if (!Parse_Hex_Field(ptr, 4, data3) || *ptr++ != '-') return(false); + + unsigned char data4[8]; + for (int index = 0; index < ARRAY_SIZE(data4); index++) { + unsigned int byte; + if (!Parse_Hex_Field(ptr, 2, byte)) { + return(false); + } + data4[index] = (unsigned char)byte; + if (index == 1 && *ptr++ != '-') { + return(false); + } + } + + clsid.Data1 = data1; + clsid.Data2 = (unsigned short)data2; + clsid.Data3 = (unsigned short)data3; + for (int index = 0; index < ARRAY_SIZE(data4); index++) { + clsid.Data4[index] = data4[index]; + } + return(true); +} + + +// The buffer holds the 38 characters of the braced form and its terminator. +static void Format_ClassID(ClassID const & clsid, char * text) +{ + sprintf(text, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + (unsigned long)clsid.Data1, (unsigned int)clsid.Data2, (unsigned int)clsid.Data3, + clsid.Data4[0], clsid.Data4[1], clsid.Data4[2], clsid.Data4[3], + clsid.Data4[4], clsid.Data4[5], clsid.Data4[6], clsid.Data4[7]); +} + + /// /// Fetches a class identifier from the specified section. /// This routine will fetch the printable form of a class identifier from the entry and @@ -968,15 +1048,13 @@ int INIClass::Get_Int(char const * section, char const * entry, int defvalue) co /// The default identifier to use if the entry could not be found. /// Returns with the class identifier specified in the INI database or else returns /// the default value. -CLSID const INIClass::Get_CLSID(char const * section, char const * entry, CLSID defvalue) const +ClassID const INIClass::Get_ClassID(char const * section, char const * entry, ClassID defvalue) const { char buffer[128]; if (Get_String(section, entry, "", buffer, sizeof(buffer))) { - wchar_t olestr[128]; - MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, buffer, -1, olestr, ARRAY_SIZE(olestr)); - CLSID clsid; - if (SUCCEEDED(CLSIDFromString(olestr, &clsid))) { + ClassID clsid; + if (Parse_ClassID(buffer, clsid)) { return(clsid); } } @@ -993,17 +1071,10 @@ CLSID const INIClass::Get_CLSID(char const * section, char const * entry, CLSID /// The entry identifier to tag to the class identifier specified. /// The class identifier to store. /// bool; Was the class identifier placed into the INI database? -bool INIClass::Put_CLSID(char const * section, char const * entry, CLSID const & value) +bool INIClass::Put_ClassID(char const * section, char const * entry, ClassID const & value) { - char buffer[128]; - LPOLESTR olestr = NULL; - - StringFromCLSID(value, &olestr); - if (WideCharToMultiByte(CP_ACP, 0, olestr, -1, buffer, sizeof(buffer), NULL, NULL) == 0) { - /// BUG, return not used - GetLastError(); - } - SysFreeString(olestr); + char buffer[40]; + Format_ClassID(value, buffer); return(Put_String(section, entry, buffer)); } diff --git a/code/ini.h b/code/ini.h index a4f33badd..ac28a63f9 100644 --- a/code/ini.h +++ b/code/ini.h @@ -34,7 +34,7 @@ #include "crc.h" #include "index.h" -#include +#include "classid.h" #include #include #include @@ -113,7 +113,7 @@ class INIClass { TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; TPoint2D const Get_Point(char const * section, char const * entry, TPoint2D const & defvalue) const; TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; - CLSID const Get_CLSID(char const * section, char const * entry, CLSID defvalue) const; + ClassID const Get_ClassID(char const * section, char const * entry, ClassID defvalue) const; /* ** Put a data type to the section and entry specified. @@ -130,7 +130,7 @@ class INIClass { bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint2D const & value); - bool Put_CLSID(char const * section, char const * entry, CLSID const & value); + bool Put_ClassID(char const * section, char const * entry, ClassID const & value); // Callers size the buffers they hand to Get_String from this. It does not bound a line // of the file; the reader keeps a line of any length. diff --git a/code/init.cpp b/code/init.cpp index d99c3b88c..a6994a892 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -6025,7 +6025,7 @@ void Delete_All_Objects(void) } Process_Deferred_Deletion(); while (Bullets.Count()) { - Bullets[0]->Release(); + delete Bullets[0]; } Process_Deferred_Deletion(); while (Objects.Count()) { diff --git a/code/ion.cpp b/code/ion.cpp index c4ce335a1..4348c797e 100644 --- a/code/ion.cpp +++ b/code/ion.cpp @@ -78,11 +78,10 @@ void IonStormClass::Init(void) /// Saves the ion storm state to the save game stream. /// /// Returns with the result reported by the stream write. -HRESULT IonStormClass::Save(IStream * stream) +bool IonStormClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(savestream.Result()); + Serialize(stream); + return(!stream.Was_Error()); } @@ -92,12 +91,11 @@ HRESULT IonStormClass::Save(IStream * stream) /// Returns with the result reported by the stream read. /// Only the bookkeeping is restored here. Post_Load_Game must still call /// Apply_Secondary_Effect to put the world back into its storm bound state. -HRESULT IonStormClass::Load(IStream * stream) +bool IonStormClass::Load(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("IonStormClass"); - Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("IonStormClass"); + Serialize(stream); + return(!stream.Was_Error()); } diff --git a/code/ion.h b/code/ion.h index b3dab97f2..149f25c5e 100644 --- a/code/ion.h +++ b/code/ion.h @@ -13,17 +13,18 @@ #include "theme.hh" -#include class SaveStreamClass; +#include "win.h" + class ShapeSet; class IonStormClass { public: static void Init(void); - static HRESULT Save(IStream * stream); - static HRESULT Load(IStream * stream); + static bool Save(SaveStreamClass & stream); + static bool Load(SaveStreamClass & stream); static void Serialize(SaveStreamClass & stream); diff --git a/code/ipiggy.h b/code/ipiggy.h index 74c1d8216..39cdd1d1f 100644 --- a/code/ipiggy.h +++ b/code/ipiggy.h @@ -11,43 +11,36 @@ #include "iloco.h" -#include +#include -/// Names and comments from TLBs -EXTERN_C const IID IID_IPiggyback; - -MIDL_INTERFACE("92FEA800-A184-11D1-B70A-00A024DDAFD1") -IPiggyback : public IUnknown +struct IPiggyback { -public: /* - * Piggybacks a locomotor onto this one. + * Piggybacks a locomotor onto this one. The locomotor is taken only when the answer + * is true; a refusal leaves it with the caller rather than destroying it. */ - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) = 0; + virtual bool Begin_Piggyback(std::unique_ptr & carried) = 0; /* - * End piggyback process and restore locomotor interface pointer. + * Hands the carried locomotor back, or nothing when none is carried. */ - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) = 0; + virtual std::unique_ptr End_Piggyback(void) = 0; /* * Is it ok to end the piggyback process? */ - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) = 0; - - /* - * Fetches piggybacked locomotor class ID. - */ - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) = 0; + virtual bool Is_Ok_To_End(void) = 0; /* * Is it currently piggy backing another locomotor? */ - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) = 0; + virtual bool Is_Piggybacking(void) = 0; }; -/* - * IPiggyback com smart pointer declaration. - */ -_COM_SMARTPTR_TYPEDEF(IPiggyback, __uuidof(IPiggyback)); + +// The piggyback side of a locomotor, or null when it cannot carry one. +inline IPiggyback * Piggyback_Of(ILocomotion * locomotion) +{ + return(dynamic_cast(locomotion)); +} diff --git a/code/ipiggy_i.c b/code/ipiggy_i.c deleted file mode 100644 index 46006be43..000000000 --- a/code/ipiggy_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_IPiggyback = {0x92FEA800,0xA184,0x11D1,{0xB7,0x0A,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/isotile.cpp b/code/isotile.cpp index 709b3c71b..8f67c3d9d 100644 --- a/code/isotile.cpp +++ b/code/isotile.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "isotile.h" @@ -227,18 +226,9 @@ RTTIType IsometricTileClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence contract and is called by the save system -/// when it must record what kind of object it is about to write out. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE IsometricTileClass::GetClassID(CLSID * retval) +ClassID IsometricTileClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_IsometricTileClass; - return(S_OK); + return(ClassID_IsometricTileClass); } diff --git a/code/isotile.h b/code/isotile.h index f99cc2683..6833b02d1 100644 --- a/code/isotile.h +++ b/code/isotile.h @@ -15,7 +15,6 @@ #include "isotype.hh" -#include class IsometricTileTypeClass; @@ -26,7 +25,7 @@ class IsometricTileClass : public ObjectClass IsometricTileClass(IsometricTileType type, Cell const &cell); virtual ~IsometricTileClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/isotype.cpp b/code/isotype.cpp index ed7132a33..45d6a9920 100644 --- a/code/isotype.cpp +++ b/code/isotype.cpp @@ -11,7 +11,6 @@ * disclaimers apply; see LICENSE.md. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "isotype.h" @@ -2792,16 +2791,9 @@ void IsometricTileTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier that this tile type persists under. -/// -/// Receives the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE IsometricTileTypeClass::GetClassID(CLSID * retval) +ClassID IsometricTileTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_IsometricTileTypeClass; - return(S_OK); + return(ClassID_IsometricTileTypeClass); } diff --git a/code/isotype.h b/code/isotype.h index 70693e2d0..41a9e64b8 100644 --- a/code/isotype.h +++ b/code/isotype.h @@ -208,7 +208,7 @@ class IsometricTileTypeClass : public ObjectTypeClass IsometricTileTypeClass(IsometricTileType type = ISOTILE_CLEAR, int unknown1 = 0, unsigned char unknown2 = 0, char const *ininame = NULL, bool skip_registration = false); virtual ~IsometricTileTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/isun.h b/code/isun.h deleted file mode 100644 index c46215471..000000000 --- a/code/isun.h +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include - -/// Names and comments from TLBs - -#define GAME_VERNAME TEXT("Tiberian Sun") - -EXTERN_C const IID IID_ILinkStream; -EXTERN_C const CLSID CLSID_CompressStream; -EXTERN_C const CLSID CLSID_HouseClass; -EXTERN_C const CLSID CLSID_SuperWeaponTypeClass; -EXTERN_C const CLSID CLSID_SuperWeaponClass; -EXTERN_C const CLSID CLSID_UnitTypeClass; -EXTERN_C const CLSID CLSID_InfantryTypeClass; -EXTERN_C const CLSID CLSID_AircraftTypeClass; -EXTERN_C const CLSID CLSID_BuildingTypeClass; -EXTERN_C const CLSID CLSID_BulletTypeClass; -EXTERN_C const CLSID CLSID_TerrainTypeClass; -EXTERN_C const CLSID CLSID_IsometricTileTypeClass; -EXTERN_C const CLSID CLSID_OverlayTypeClass; -EXTERN_C const CLSID CLSID_SmudgeTypeClass; -EXTERN_C const CLSID CLSID_AnimTypeClass; -EXTERN_C const CLSID CLSID_HouseTypeClass; -EXTERN_C const CLSID CLSID_IsometricTileClass; -EXTERN_C const CLSID CLSID_VoxelAnimClass; -EXTERN_C const CLSID CLSID_AircraftClass; -EXTERN_C const CLSID CLSID_AnimClass; -EXTERN_C const CLSID CLSID_InfantryClass; -EXTERN_C const CLSID CLSID_SmudgeClass; -EXTERN_C const CLSID CLSID_BuildingClass; -EXTERN_C const CLSID CLSID_OverlayClass; -EXTERN_C const CLSID CLSID_ParticleSystemClass; -EXTERN_C const CLSID CLSID_ParticleSystemTypeClass; -EXTERN_C const CLSID CLSID_BulletClass; -EXTERN_C const CLSID CLSID_UnitClass; -EXTERN_C const CLSID CLSID_ParticleClass; -EXTERN_C const CLSID CLSID_ParticleTypeClass; -EXTERN_C const CLSID CLSID_WaveClass; -EXTERN_C const CLSID CLSID_BuildingLightClass; -EXTERN_C const CLSID CLSID_TerrainClass; -EXTERN_C const CLSID CLSID_TubeClass; -EXTERN_C const CLSID CLSID_TeamClass; -EXTERN_C const CLSID CLSID_TaskForceClass; -EXTERN_C const CLSID CLSID_TeamTypeClass; -EXTERN_C const CLSID CLSID_VoxelAnimTypeClass; -EXTERN_C const CLSID CLSID_ScriptClass; -EXTERN_C const CLSID CLSID_ScriptTypeClass; -EXTERN_C const CLSID CLSID_TagClass; -EXTERN_C const CLSID CLSID_TagTypeClass; -EXTERN_C const CLSID CLSID_TriggerClass; -EXTERN_C const CLSID CLSID_TriggerTypeClass; -EXTERN_C const CLSID CLSID_ActionClass; -EXTERN_C const CLSID CLSID_EventClass; -EXTERN_C const CLSID CLSID_FactoryClass; -EXTERN_C const CLSID CLSID_WeaponTypeClass; -EXTERN_C const CLSID CLSID_WarheadTypeClass; -EXTERN_C const CLSID CLSID_WaypointPath; -EXTERN_C const CLSID CLSID_LightSource; -EXTERN_C const CLSID CLSID_CampaignClass; -EXTERN_C const CLSID CLSID_SideClass; -EXTERN_C const CLSID CLSID_TiberiumClass; -EXTERN_C const CLSID CLSID_CellClass; -EXTERN_C const CLSID CLSID_EMPulseClass; -EXTERN_C const CLSID CLSID_TacticalMapClass; -EXTERN_C const CLSID CLSID_AITriggerTypeClass; -EXTERN_C const CLSID CLSID_AITriggerClass; -EXTERN_C const CLSID CLSID_NeuronClass; -EXTERN_C const CLSID CLSID_FoggedObjectClass; -EXTERN_C const CLSID CLSID_AlphaShapeClass; -EXTERN_C const CLSID CLSID_VeinholeMonsterClass; diff --git a/code/isun_i.c b/code/isun_i.c deleted file mode 100644 index d667c8d25..000000000 --- a/code/isun_i.c +++ /dev/null @@ -1,238 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_ILinkStream = {0x0D5CD78E,0x6470,0x11D2,{0x9B,0x74,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - -const CLSID CLSID_CompressStream = {0xB48FA168,0x646F,0x11D2,{0x9B,0x74,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - -const CLSID CLSID_HouseClass = {0xD9D4A910,0x87C6,0x11D1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_SuperWeaponTypeClass = {0x0CF2BCE7,0x36E4,0x11D2,{0xB8,0xD8,0x00,0x60,0x08,0xC8,0x09,0xED}}; - - -const CLSID CLSID_SuperWeaponClass = {0xD7F754C6,0x391C,0x11D2,{0x9B,0x64,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - -const CLSID CLSID_UnitTypeClass = {0xDCBD42EA,0x0546,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_InfantryTypeClass = {0xAE8B33D8,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_AircraftTypeClass = {0xAE8B33D9,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_BuildingTypeClass = {0xAE8B33DB,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_BulletTypeClass = {0x5AF2CE77,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_TerrainTypeClass = {0x5AF2CE7B,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_IsometricTileTypeClass = {0x5AF2CE7A,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_OverlayTypeClass = {0x5AF2CE79,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_SmudgeTypeClass = {0x5AF2CE78,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_AnimTypeClass = {0xAE8B33DA,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_HouseTypeClass = {0x1DD43928,0x046B,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_IsometricTileClass = {0x0E272DC0,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_VoxelAnimClass = {0x0E272DC1,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_AircraftClass = {0x0E272DC2,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_AnimClass = {0x0E272DC3,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_InfantryClass = {0x0E272DC4,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_SmudgeClass = {0x0E272DC5,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_BuildingClass = {0x0E272DC6,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_OverlayClass = {0x0E272DC7,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_ParticleSystemClass = {0x0E272DC8,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_ParticleSystemTypeClass = {0x703E044A,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_BulletClass = {0x0E272DC9,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_UnitClass = {0x0E272DCA,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_ParticleClass = {0x0E272DCC,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_ParticleTypeClass = {0x703E044B,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_WaveClass = {0x0E272DCD,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_BuildingLightClass = {0x54822258,0xD8A8,0x11D1,{0xB4,0x62,0x00,0x60,0x97,0xC6,0xA9,0x79}}; - - -const CLSID CLSID_TerrainClass = {0x0E272DCE,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_TubeClass = {0x0B4CA41C,0xB3A7,0x11D1,{0xB4,0x57,0x00,0x60,0x97,0xC6,0xA9,0x79}}; - - -const CLSID CLSID_TeamClass = {0x0E272DCF,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_TaskForceClass = {0x61DE341E,0x0774,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_TeamTypeClass = {0xD1DBA64E,0x0778,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_VoxelAnimTypeClass = {0x2EBB6D66,0x0D4D,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_ScriptClass = {0x42F3A646,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_ScriptTypeClass = {0x42F3A647,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_TagClass = {0x54F6E432,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_TagTypeClass = {0x54F6E433,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_TriggerClass = {0xC02D1590,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_TriggerTypeClass = {0xC02D1591,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_ActionClass = {0x4F0EC392,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_EventClass = {0x4F0EC393,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_FactoryClass = {0x34ECD9A8,0x0AB0,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_WeaponTypeClass = {0x9FD219CA,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_WarheadTypeClass = {0xA8C54DA4,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_WaypointPath = {0xF73125BA,0x1054,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_LightSource = {0x6F9C48F0,0x1207,0x11D2,{0x81,0x74,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_CampaignClass = {0xFFDAC848,0x1517,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_SideClass = {0xC53DD372,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_TiberiumClass = {0xC53DD373,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_CellClass = {0xC1BF99CE,0x1A8C,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_EMPulseClass = {0xB825CB22,0x200E,0x11D2,{0x9F,0xA9,0x00,0x60,0x08,0x9A,0xD4,0x58}}; - - -const CLSID CLSID_TacticalMapClass = {0xCF56B38A,0x240D,0x11D2,{0x81,0x7C,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - -const CLSID CLSID_AITriggerTypeClass = {0xBA093524,0x4CF4,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; - - -const CLSID CLSID_AITriggerClass = {0x03C4CE76,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; - - -const CLSID CLSID_NeuronClass = {0x241AB316,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; - - -const CLSID CLSID_FoggedObjectClass = {0x1C470B0E,0x69D7,0x11D2,{0xB8,0xF2,0x00,0x60,0x08,0xC8,0x09,0xED}}; - - -const CLSID CLSID_AlphaShapeClass = {0x623C7584,0x74E7,0x11D2,{0xB8,0xF5,0x00,0x60,0x08,0xC8,0x09,0xED}}; - - -const CLSID CLSID_VeinholeMonsterClass = {0x5192D06A,0xC632,0x11D2,{0xB9,0x0B,0x00,0x60,0x08,0xC8,0x09,0xED}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/jumpjet.cpp b/code/jumpjet.cpp index 717456b26..61fd2c653 100644 --- a/code/jumpjet.cpp +++ b/code/jumpjet.cpp @@ -64,7 +64,7 @@ JumpjetLocomotionClass::~JumpjetLocomotionClass(void) /// This asks whether the unit has a destination, not whether it happens to be in the air. /// /// bool; Does the jumpjet have somewhere to be? -boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Is_Moving(void) +bool JumpjetLocomotionClass::Is_Moving(void) { return(IsMoving); } @@ -75,7 +75,7 @@ boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Is_Moving(void) /// /// Returns with the destination coordinate. Otherwise, COORD_NONE is /// returned. -Coord STDMETHODCALLTYPE JumpjetLocomotionClass::Destination(void) +Coord JumpjetLocomotionClass::Destination(void) { if (Is_Moving()) { return(HeadToCoord); @@ -92,7 +92,7 @@ Coord STDMETHODCALLTYPE JumpjetLocomotionClass::Destination(void) /// and resubmits the object to the map when its display layer changes. An ion storm will /// bring down anything caught off the ground. /// -boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Process(void) +bool JumpjetLocomotionClass::Process(void) { LayerType layer = In_Which_Layer(); @@ -167,7 +167,7 @@ boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Process(void) /// /// The coordinate to fly to, or COORD_NONE to give the unit no /// destination at all. -void STDMETHODCALLTYPE JumpjetLocomotionClass::Move_To(Coord to) +void JumpjetLocomotionClass::Move_To(Coord to) { if (HeadToCoord != COORD_NONE && CurrentState != GROUNDED && IsLanding) { LinkedTo->Clear_Occupy_Bit(HeadToCoord); @@ -200,7 +200,7 @@ void STDMETHODCALLTYPE JumpjetLocomotionClass::Move_To(Coord to) /// it could put down in. A unit with nowhere at all to land is destroyed rather than left /// hanging in the air. /// -void STDMETHODCALLTYPE JumpjetLocomotionClass::Stop_Moving(void) +void JumpjetLocomotionClass::Stop_Moving(void) { if (IsMoving) { if (HeadToCoord != COORD_NONE && CurrentState != GROUNDED && IsLanding) { @@ -230,23 +230,15 @@ void STDMETHODCALLTYPE JumpjetLocomotionClass::Stop_Moving(void) /// through the locomotor's own facing tracker. /// /// The direction the unit should be facing. -void STDMETHODCALLTYPE JumpjetLocomotionClass::Do_Turn(DirType coord) +void JumpjetLocomotionClass::Do_Turn(DirType coord) { LinkedTo->PrimaryFacing.Set(coord); } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence machinery uses the identifier to build the right kind of locomotor back -/// when a save game is loaded. -/// -/// Returns with S_OK, or E_POINTER if there is nowhere to put the answer. -HRESULT STDMETHODCALLTYPE JumpjetLocomotionClass::GetClassID(CLSID * retval) +ClassID JumpjetLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_JumpjetLocomotion; - return(S_OK); + return(ClassID_JumpjetLocomotion); } @@ -277,7 +269,7 @@ void JumpjetLocomotionClass::Serialize(SaveStreamClass & stream) /// measured from the bridge deck rather than from the ground. /// /// Returns with the layer this object should be drawn in. -LayerType STDMETHODCALLTYPE JumpjetLocomotionClass::In_Which_Layer(void) +LayerType JumpjetLocomotionClass::In_Which_Layer(void) { int height = LinkedTo->HeightAGL; if (!LinkedTo->IsOnBridge) { @@ -479,7 +471,7 @@ void JumpjetLocomotionClass::Process_Unknown(void) /// not it has been given a destination. /// /// bool; Is the jumpjet in flight toward somewhere? -boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Is_Moving_Now(void) +bool JumpjetLocomotionClass::Is_Moving_Now(void) { if (CurrentState != GROUNDED && CurrentState != HOVERING) { return(true); @@ -684,7 +676,7 @@ int JumpjetLocomotionClass::Desired_Flight_Level(void) const /// that reservation is given up when the object is lifted off the map. /// /// The marking operation being performed, such as MARK_UP. -void STDMETHODCALLTYPE JumpjetLocomotionClass::Mark_All_Occupation_Bits(int mark) +void JumpjetLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { Coord headto = Head_To_Coord(); @@ -702,7 +694,7 @@ void STDMETHODCALLTYPE JumpjetLocomotionClass::Mark_All_Occupation_Bits(int mark /// destination. /// /// Returns with the coordinate being flown to. -Coord STDMETHODCALLTYPE JumpjetLocomotionClass::Head_To_Coord(void) +Coord JumpjetLocomotionClass::Head_To_Coord(void) { if (CurrentState == GROUNDED) { return(LinkedTo->PositionCoord); diff --git a/code/jumpjet.h b/code/jumpjet.h index e8bc9d07e..5a3872031 100644 --- a/code/jumpjet.h +++ b/code/jumpjet.h @@ -25,20 +25,20 @@ class JumpjetLocomotionClass : public LocomotionClass JumpjetLocomotionClass(void); virtual ~JumpjetLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/layer.cpp b/code/layer.cpp index 13ef01533..4c91cf6d9 100644 --- a/code/layer.cpp +++ b/code/layer.cpp @@ -151,17 +151,12 @@ int LayerClass::Sorted_Add(ObjectClass const * const object) /// are written by their own owners -- only the layer's object pointers are recorded here, /// to be swizzled back into real addresses when the game is loaded. /// -/// Returns with S_OK if the layer was written. Otherwise, the failure code from -/// the stream is returned. -HRESULT LayerClass::Save(IStream * stream) +/// bool; Was the record written whole? +bool LayerClass::Save(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - DynamicVectorClass::Serialize(savestream); - return(savestream.Result()); + DynamicVectorClass::Serialize(stream); + return(!stream.Was_Error()); } @@ -171,16 +166,11 @@ HRESULT LayerClass::Save(IStream * stream) /// reconstructed. Whatever the layer was holding is discarded and the object pointers are /// read back, so they do not become usable until the swizzle pass has run. /// -/// Returns with S_OK if the layer was read. Otherwise, the failure code from the -/// stream is returned. -HRESULT LayerClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool LayerClass::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("LayerClass"); - DynamicVectorClass::Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("LayerClass"); + DynamicVectorClass::Serialize(stream); + return(!stream.Was_Error()); } diff --git a/code/layer.h b/code/layer.h index ffa129bc1..25dc07af0 100644 --- a/code/layer.h +++ b/code/layer.h @@ -40,8 +40,8 @@ class ObjectClass; class LayerClass : public DynamicVectorClass { public: - HRESULT Load(IStream * stream); - HRESULT Save(IStream * stream); + bool Load(SaveStreamClass & stream); + bool Save(SaveStreamClass & stream); public: diff --git a/code/levitate.cpp b/code/levitate.cpp index 28836e322..616718351 100644 --- a/code/levitate.cpp +++ b/code/levitate.cpp @@ -77,9 +77,9 @@ LevitateLocomotionClass::LevitateLocomotionClass(void) : /// /// Pointer to the object this locomotor will drive. /// Returns with the result of the attach operation. -HRESULT LevitateLocomotionClass::Link_To_Object(void *pointer) +void LevitateLocomotionClass::Link_To_Object(void *pointer) { - return(BASECLASS::Link_To_Object(pointer)); + BASECLASS::Link_To_Object(pointer); } @@ -825,7 +825,7 @@ bool LevitateLocomotionClass::Needs_New_Target(void) /// the vertical hover (Hover_AI). /// /// True while the unit is still moving. -boolean LevitateLocomotionClass::Process(void) +bool LevitateLocomotionClass::Process(void) { State_AI(); @@ -846,7 +846,7 @@ boolean LevitateLocomotionClass::Process(void) /// Reports whether the locomotor is in any state other than STATE_IDLE. /// /// True while moving. -boolean LevitateLocomotionClass::Is_Moving(void) +bool LevitateLocomotionClass::Is_Moving(void) { return(State != STATE_IDLE); } @@ -856,7 +856,7 @@ boolean LevitateLocomotionClass::Is_Moving(void) /// Reports whether the locomotor is in any state other than STATE_IDLE (identical to Is_Moving). /// /// True while moving. -boolean LevitateLocomotionClass::Is_Moving_Now(void) +bool LevitateLocomotionClass::Is_Moving_Now(void) { return(State != STATE_IDLE); } @@ -892,18 +892,9 @@ void LevitateLocomotionClass::Stop(void) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence system uses this identifier to create a locomotor of the right kind -/// when the object it drives is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT LevitateLocomotionClass::GetClassID(CLSID * retval) +ClassID LevitateLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_LevitateLocomotion; - return(S_OK); + return(ClassID_LevitateLocomotion); } diff --git a/code/levitate.h b/code/levitate.h index bd273ba6b..e5bd84473 100644 --- a/code/levitate.h +++ b/code/levitate.h @@ -28,18 +28,18 @@ class LevitateLocomotionClass : public LocomotionClass LevitateLocomotionClass(void); virtual ~LevitateLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; + virtual void Link_To_Object(void *pointer) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; /*--------------------------------------------------------------------- diff --git a/code/light.cpp b/code/light.cpp index 8ff5eacec..40c6bb860 100644 --- a/code/light.cpp +++ b/code/light.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "light.h" @@ -278,16 +277,9 @@ void LightSourceClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier this object is persisted under. -/// -/// Destination for the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE LightSourceClass::GetClassID(CLSID * retval) +ClassID LightSourceClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_LightSource; - return(S_OK); + return(ClassID_LightSource); } diff --git a/code/light.h b/code/light.h index 0e875e507..9585de25c 100644 --- a/code/light.h +++ b/code/light.h @@ -25,7 +25,7 @@ class LightSourceClass : public AbstractClass LightSourceClass(void); virtual ~LightSourceClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/loco.cpp b/code/loco.cpp index 7030a98fd..f75059f61 100644 --- a/code/loco.cpp +++ b/code/loco.cpp @@ -14,10 +14,12 @@ #include "_map.h" #include "_tactica.h" #include "cell.h" +#include "classfactory.h" #include "coord.h" #include "foot.h" #include "globals.h" #include "map.h" +#include "saveload.h" #include "savestream.h" #include "swizzle.h" #include "tactical.h" @@ -28,8 +30,8 @@ #include "zgrad.hh" #include +#include -extern ULONG COMRefCount; /// @@ -41,8 +43,7 @@ extern ULONG COMRefCount; LocomotionClass::LocomotionClass(void) : LinkedTo(NULL), IsPowered(true), - Dirty(true), - RefCount(0) + Dirty(true) { } @@ -62,11 +63,9 @@ LocomotionClass::~LocomotionClass(void) /// offers depends on it having been called first. /// /// Pointer to the foot class object this locomotor will carry about. -/// Returns with S_OK, since the attachment cannot fail. -HRESULT STDMETHODCALLTYPE LocomotionClass::Link_To_Object(void *pointer) +void LocomotionClass::Link_To_Object(void *pointer) { LinkedTo = (FootClass *)pointer; - return(S_OK); } @@ -79,7 +78,7 @@ HRESULT STDMETHODCALLTYPE LocomotionClass::Link_To_Object(void *pointer) /// Optional cache key for the voxel renderer, which the facing is folded /// into. May be NULL, and a key of -1 means the drawing is not to be cached. /// Returns with the matrix to transform the object by. -Matrix3D STDMETHODCALLTYPE LocomotionClass::Draw_Matrix(int *key) +Matrix3D LocomotionClass::Draw_Matrix(int *key) { Matrix3D draw_matrix(true); @@ -101,7 +100,7 @@ Matrix3D STDMETHODCALLTYPE LocomotionClass::Draw_Matrix(int *key) /// Optional cache key for the voxel renderer, which the slope and facing are /// folded into. May be NULL, and a key of -1 means the shadow is not to be cached. /// Returns with the matrix to transform the shadow by. -Matrix3D STDMETHODCALLTYPE LocomotionClass::Shadow_Matrix(int *key) +Matrix3D LocomotionClass::Shadow_Matrix(int *key) { int ramp = Map[LinkedTo->Get_Coord()].Ramp; @@ -122,7 +121,7 @@ Matrix3D STDMETHODCALLTYPE LocomotionClass::Shadow_Matrix(int *key) /// down by however far the object is flying above the terrain. /// /// Returns with the pixel offset to shift the shadow by when drawing. -Point2D STDMETHODCALLTYPE LocomotionClass::Shadow_Point(void) +Point2D LocomotionClass::Shadow_Point(void) { Point2D pt; @@ -139,7 +138,7 @@ Point2D STDMETHODCALLTYPE LocomotionClass::Shadow_Point(void) /// more. /// /// bool; Is the locomotor powered after the change? -boolean STDMETHODCALLTYPE LocomotionClass::Power_On(void) +bool LocomotionClass::Power_On(void) { IsPowered = true; return(Is_Powered()); @@ -152,7 +151,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Power_On(void) /// by an EMP pulse or its owner loses base power. /// /// bool; Is the locomotor powered after the change? -boolean STDMETHODCALLTYPE LocomotionClass::Power_Off(void) +bool LocomotionClass::Power_Off(void) { IsPowered = false; return(Is_Powered()); @@ -165,7 +164,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Power_Off(void) /// loss of base power leaves a unit stranded. /// /// bool; Is the locomotor powered? -boolean STDMETHODCALLTYPE LocomotionClass::Is_Powered(void) +bool LocomotionClass::Is_Powered(void) { return(IsPowered); } @@ -177,79 +176,33 @@ boolean STDMETHODCALLTYPE LocomotionClass::Is_Powered(void) /// this routine so that a storm can bring their objects down. /// /// bool; Is the locomotor sensitive to ion storms? -boolean STDMETHODCALLTYPE LocomotionClass::Is_Ion_Sensitive(void) +bool LocomotionClass::Is_Ion_Sensitive(void) { return(false); } -/// -/// Adds a reference to this locomotor. -/// Anything that holds on to a locomotor takes a reference first, which keeps the -/// locomotor alive until that holder releases it again. -/// -/// Returns with the number of references now outstanding. -ULONG STDMETHODCALLTYPE LocomotionClass::AddRef(void) +std::unique_ptr Create_Locomotor(ClassID const & classid) { - ++COMRefCount; - return(InterlockedIncrement(&RefCount)); + std::unique_ptr object = Create_Object(classid); + ILocomotion * const locomotion = dynamic_cast(object.get()); + if (locomotion != nullptr) { + object.release(); + } + return(std::unique_ptr(locomotion)); } -/// -/// Releases a reference to this locomotor. -/// When the last reference goes away the locomotor destroys itself, so the caller must -/// not touch its pointer afterward. -/// -/// Returns with the number of references still outstanding. -ULONG STDMETHODCALLTYPE LocomotionClass::Release(void) +std::unique_ptr Load_Locomotor(SaveStreamClass & stream) { - --COMRefCount; - - ULONG count = InterlockedDecrement(&RefCount); - if (count == 0) { - delete this; - } - return(count); + return(Load_Object_As(stream)); } -/// -/// Fetches one of the interfaces this locomotor implements. -/// A locomotor answers to IUnknown, IPersist, IPersistStream, and ILocomotion. Any other -/// interface asked for is refused. -/// -/// The identifier of the interface being asked for. -/// Pointer to the location to store the interface pointer in. -/// Returns with S_OK, or E_NOINTERFACE if the interface is not supported. -/// An interface fetched successfully carries a reference. The caller must release -/// it when finished with it. -LONG STDMETHODCALLTYPE LocomotionClass::QueryInterface(REFIID riid, LPVOID *ppvObject) +ClassID Locomotion_Class_ID(ILocomotion * locomotion) { - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - - if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(ILocomotion *)this; - } - if (riid == IID_IPersistStream) { - *ppvObject = (IPersistStream *)this; - } - if (riid == IID_ILocomotion) { - *ppvObject = (ILocomotion *)this; - } - if (riid == IID_IPersist) { - *ppvObject = (IPersist *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - - AddRef(); - return(S_OK); + IPersistent const * const persist = dynamic_cast(locomotion); + return(persist != nullptr ? persist->Class_ID() : ClassID()); } @@ -259,32 +212,15 @@ LONG STDMETHODCALLTYPE LocomotionClass::QueryInterface(REFIID riid, LPVOID *ppvO /// swizzle manager remap every pointer to it when the game is loaded again. /// /// Should the locomotor be marked as no longer needing a save? -/// Returns with the result of the write, or E_POINTER if no stream was supplied. -HRESULT STDMETHODCALLTYPE LocomotionClass::Save(IStream * stream, BOOL cleardirty) +/// Returns with the result of the write. +bool LocomotionClass::Save(SaveStreamClass & stream, bool cleardirty) { - if (stream == NULL) { - return(E_POINTER); /// E_INVALIDARG - } - return(Save_Members(stream, cleardirty)); } -/// -/// Loads the locomotor back from a save game stream. -/// The locomotor announces its new address to the swizzle manager before its data is -/// read in, so that every saved pointer to it can be remapped and its link back to the -/// object it drives can be restored. The reference count belongs to the running session -/// rather than to the saved state, so it survives the load untouched. -/// -/// The stream to read the locomotor back from. -/// Returns with the result of the read, or E_POINTER if no stream was supplied. -HRESULT STDMETHODCALLTYPE LocomotionClass::Load(IStream * stream) +bool LocomotionClass::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); /// E_INVALIDARG - } - return(Load_Members(stream)); } @@ -296,77 +232,44 @@ HRESULT STDMETHODCALLTYPE LocomotionClass::Load(IStream * stream) /// /// The stream to write to. /// Should the locomotor be marked clean once it has been written? -/// Returns with S_OK when the record was written, otherwise a failure code. -HRESULT LocomotionClass::Save_Members(IStream * stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool LocomotionClass::Save_Members(SaveStreamClass & stream, bool cleardirty) { - if (stream == NULL) { - return(E_POINTER); - } - SwizzleIDType id = Swizzler.ID_Of(this); - - HRESULT result = stream->Write(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); - } - - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - - if (SUCCEEDED(savestream.Result()) && cleardirty) { + stream.Serialize(id); + Serialize(stream); + if (!stream.Was_Error() && cleardirty) { Dirty = false; } - - return(savestream.Result()); + return(!stream.Was_Error()); } -/// -/// Reads the members this locomotor describes back from the save stream. -/// The saved identity is handed to the swizzle system so that pointers elsewhere in the -/// save game can be remapped onto this locomotor, and the members follow. -/// -/// The stream to read from. -/// Returns with S_OK when the record was read, otherwise a failure code. -HRESULT LocomotionClass::Load_Members(IStream * stream) +bool LocomotionClass::Load_Members(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); - } - - SwizzleIDType id; - - HRESULT result = stream->Read(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); + SwizzleIDType id = 0; + stream.Serialize(id); + if (stream.Was_Error()) { + return(false); } - assert(id != 0); Swizzle_Here_I_Am(id, this); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context(typeid(*this).name(), id); - Serialize(savestream); + char const * const outertype = stream.Context_Type(); + SwizzleIDType const outerid = stream.Context_ID(); + stream.Set_Context(typeid(*this).name(), id); + Serialize(stream); + stream.Set_Context(outertype, outerid); - if (SUCCEEDED(savestream.Result())) { - Post_Load(); - } - - return(savestream.Result()); + return(!stream.Was_Error()); } -/// -/// Lists the members every locomotor carries. -/// -/// The stream carrying the members. void LocomotionClass::Serialize(SaveStreamClass & stream) { stream.Serialize(LinkedTo); stream.Serialize(IsPowered); stream.Serialize(Dirty); - - // RefCount -- belongs to the running session rather than the record. } @@ -379,27 +282,13 @@ void LocomotionClass::Post_Load(void) } -/// -/// Fetches the number of bytes needed to save this locomotor. -/// A record is as long as the members a class names, so the count is not known before -/// the members have been written. Nothing in the game asks for it, so rather than -/// walk the locomotor twice this reports that the size cannot be supplied. -/// -/// Pointer to the value to fill in with the required byte count. -/// Returns with E_NOTIMPL. -LONG STDMETHODCALLTYPE LocomotionClass::GetSizeMax(ULARGE_INTEGER *pcbSize) -{ - return(E_NOTIMPL); -} - - /// /// Asks the object to step out of the way in the direction specified. /// This routine is used when another object needs the cell this one happens to be /// occupying. The base locomotor cannot be moved and declines. /// /// bool; Did the object step out of the way? -boolean STDMETHODCALLTYPE LocomotionClass::Push(DirType dir) +bool LocomotionClass::Push(DirType dir) { return(false); } @@ -411,7 +300,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Push(DirType dir) /// displaced to clear the way. The base locomotor will not budge. /// /// bool; Was the object shoved out of the way? -boolean STDMETHODCALLTYPE LocomotionClass::Shove(DirType dir) +bool LocomotionClass::Shove(DirType dir) { return(false); } @@ -422,7 +311,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Shove(DirType dir) /// Locomotors that rock their object about -- over bumps, on landing, or when it takes a /// hit -- use this routine to ease the body back toward level. /// -void STDMETHODCALLTYPE LocomotionClass::Tilt_Pitch_AI(void) +void LocomotionClass::Tilt_Pitch_AI(void) { } @@ -433,7 +322,7 @@ void STDMETHODCALLTYPE LocomotionClass::Tilt_Pitch_AI(void) /// against the terrain it is traveling over. The base locomotor needs no such favor. /// /// Returns with the depth adjustment to apply when drawing the object. -int STDMETHODCALLTYPE LocomotionClass::Z_Adjust(void) +int LocomotionClass::Z_Adjust(void) { return(0); } @@ -445,7 +334,7 @@ int STDMETHODCALLTYPE LocomotionClass::Z_Adjust(void) /// shape. The base locomotor reports the upright case. /// /// Returns with the Z gradient to render the object with. -ZGradientType STDMETHODCALLTYPE LocomotionClass::Z_Gradient(void) +ZGradientType LocomotionClass::Z_Gradient(void) { return(ZGRAD_90DEG); } @@ -457,7 +346,7 @@ ZGradientType STDMETHODCALLTYPE LocomotionClass::Z_Gradient(void) /// otherwise leaves plain sight. The base locomotor never alters the appearance. /// /// Returns with the visual character to render the object with. -VisualType STDMETHODCALLTYPE LocomotionClass::Visual_Character(boolean flag) +VisualType LocomotionClass::Visual_Character(bool flag) { return(VISUAL_NORMAL); } @@ -469,7 +358,7 @@ VisualType STDMETHODCALLTYPE LocomotionClass::Visual_Character(boolean flag) /// locomotor makes its object bob, hop, or sink. The base locomotor draws in place. /// /// Returns with the pixel offset to shift the object by when drawing. -Point2D STDMETHODCALLTYPE LocomotionClass::Draw_Point(void) +Point2D LocomotionClass::Draw_Point(void) { Point2D pt; pt.X = 0; @@ -484,7 +373,7 @@ Point2D STDMETHODCALLTYPE LocomotionClass::Draw_Point(void) /// otherwise hidden -- will override this routine to suppress the shadow. /// /// bool; Should a shadow be drawn for the object? -boolean STDMETHODCALLTYPE LocomotionClass::Is_To_Have_Shadow(void) +bool LocomotionClass::Is_To_Have_Shadow(void) { return(true); } @@ -497,7 +386,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Is_To_Have_Shadow(void) /// unrestricted and welcomes every cell. /// /// Returns with the move legality of the cell. -MoveType STDMETHODCALLTYPE LocomotionClass::Can_Enter_Cell(Cell cell) +MoveType LocomotionClass::Can_Enter_Cell(Cell cell) { return(MOVE_OK); } @@ -509,7 +398,7 @@ MoveType STDMETHODCALLTYPE LocomotionClass::Can_Enter_Cell(Cell cell) /// outside code must dictate exactly where the object ends up next. /// /// The coordinate the object should head to immediately. -void STDMETHODCALLTYPE LocomotionClass::Force_Immediate_Destination(Coord coord) +void LocomotionClass::Force_Immediate_Destination(Coord coord) { } @@ -521,7 +410,7 @@ void STDMETHODCALLTYPE LocomotionClass::Force_Immediate_Destination(Coord coord) /// /// The track number the object should be placed onto. /// The coordinate to treat as the start of the track. -void STDMETHODCALLTYPE LocomotionClass::Force_Track(int track, Coord coord) +void LocomotionClass::Force_Track(int track, Coord coord) { } @@ -531,7 +420,7 @@ void STDMETHODCALLTYPE LocomotionClass::Force_Track(int track, Coord coord) /// This gives derived locomotors their chance to pick up a starting facing, slope, or /// altitude from the ground the object has just arrived on. /// -void STDMETHODCALLTYPE LocomotionClass::Unlimbo(void) +void LocomotionClass::Unlimbo(void) { } @@ -541,7 +430,7 @@ void STDMETHODCALLTYPE LocomotionClass::Unlimbo(void) /// The base locomotor has no body of its own to rotate, so the request goes unheeded. /// /// The direction that the object should come to face. -void STDMETHODCALLTYPE LocomotionClass::Do_Turn(DirType coord) +void LocomotionClass::Do_Turn(DirType coord) { } @@ -551,7 +440,7 @@ void STDMETHODCALLTYPE LocomotionClass::Do_Turn(DirType coord) /// This routine is called when the object must give up on wherever it was going. Derived /// locomotors use it to abandon their journey and bring the object to a legal rest. /// -void STDMETHODCALLTYPE LocomotionClass::Stop_Moving(void) +void LocomotionClass::Stop_Moving(void) { } @@ -561,7 +450,7 @@ void STDMETHODCALLTYPE LocomotionClass::Stop_Moving(void) /// This is how the object hands its locomotor a new place to go. The base locomotor /// cannot move anything, so the request is quietly ignored. /// -void STDMETHODCALLTYPE LocomotionClass::Move_To(Coord to) +void LocomotionClass::Move_To(Coord to) { } @@ -573,7 +462,7 @@ void STDMETHODCALLTYPE LocomotionClass::Move_To(Coord to) /// is already at rest. /// /// bool; Is the locomotor at rest, with nothing further to do? -boolean STDMETHODCALLTYPE LocomotionClass::Process(void) +bool LocomotionClass::Process(void) { return(true); } @@ -585,7 +474,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Process(void) /// destination at all. /// /// Returns with the destination coordinate, or COORD_NONE if there is none. -Coord STDMETHODCALLTYPE LocomotionClass::Destination(void) +Coord LocomotionClass::Destination(void) { Coord coord; coord.X = COORD_NONE.X; @@ -601,7 +490,7 @@ Coord STDMETHODCALLTYPE LocomotionClass::Destination(void) /// has nowhere to go, it reports the object's own position. /// /// Returns with the coordinate currently being moved toward. -Coord STDMETHODCALLTYPE LocomotionClass::Head_To_Coord(void) +Coord LocomotionClass::Head_To_Coord(void) { return(LinkedTo->PositionCoord); } @@ -613,7 +502,7 @@ Coord STDMETHODCALLTYPE LocomotionClass::Head_To_Coord(void) /// The base locomotor never carries its object anywhere, so it always answers no. /// /// bool; Is the object moving? -boolean STDMETHODCALLTYPE LocomotionClass::Is_Moving(void) +bool LocomotionClass::Is_Moving(void) { return(false); } @@ -625,7 +514,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Is_Moving(void) /// Only locomotors that tilt their object with the ground need to act on it. /// /// The ramp type of the slope the object should now conform to. -void STDMETHODCALLTYPE LocomotionClass::Force_New_Slope(int ramp) +void LocomotionClass::Force_New_Slope(int ramp) { } @@ -636,7 +525,7 @@ void STDMETHODCALLTYPE LocomotionClass::Force_New_Slope(int ramp) /// currently traveling. The base locomotor has no preference. /// /// Returns with the drawing code, or zero for the ordinary presentation. -int STDMETHODCALLTYPE LocomotionClass::Drawing_Code(void) +int LocomotionClass::Drawing_Code(void) { return(0); } @@ -648,7 +537,7 @@ int STDMETHODCALLTYPE LocomotionClass::Drawing_Code(void) /// will override this routine. The base locomotor never stands in the way. /// /// Returns with the reason firing is disallowed, or FIRE_OK if it is permitted. -FireErrorType STDMETHODCALLTYPE LocomotionClass::Can_Fire(void) +FireErrorType LocomotionClass::Can_Fire(void) { return(FIRE_OK); } @@ -660,14 +549,7 @@ FireErrorType STDMETHODCALLTYPE LocomotionClass::Can_Fire(void) /// visible motion differs from the object's logical speed will override this routine. /// /// Returns with the apparent speed of the linked object. -int STDMETHODCALLTYPE LocomotionClass::Apparent_Speed(void) +int LocomotionClass::Apparent_Speed(void) { return(LinkedTo->Current_Speed()); } - - -/// Unlike the other interface identifiers, this one is defined in the locomotion module. -#define INITGUID -#undef DEFINE_GUID -#include -#include "iloco_i.c" diff --git a/code/loco.h b/code/loco.h index ba9f5f627..dc00c0c92 100644 --- a/code/loco.h +++ b/code/loco.h @@ -9,72 +9,83 @@ #pragma once +#include "classids.h" #include "coord.h" -#include "ilocos.h" +#include "iloco.h" +#include "persist.h" + +#include class FootClass; class SaveStreamClass; -class LocomotionClass : public IPersistStream, public ILocomotion +// The class identifier of a locomotor reached through its locomotion interface, or +// all zero when it is not one of ours. +ClassID Locomotion_Class_ID(ILocomotion * locomotion); + +// A new, unlinked locomotor of the registered class, or nothing when the identifier +// names no locomotor. +std::unique_ptr Create_Locomotor(ClassID const & classid); + +// The locomotor whose record is next in the stream, or nothing when the record names +// something that is not one, which fails the stream. +std::unique_ptr Load_Locomotor(SaveStreamClass & stream); + + +class LocomotionClass : public IPersistent, public ILocomotion { public: LocomotionClass(void); virtual ~LocomotionClass(void); - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppvObj) override; - virtual ULONG STDMETHODCALLTYPE AddRef() override; - virtual ULONG STDMETHODCALLTYPE Release() override; - - virtual LONG STDMETHODCALLTYPE IsDirty(void) override {return(Dirty ? S_OK : S_FALSE);} - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream, BOOL cleardirty) override; - virtual LONG STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER *pcbSize) override; - - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *object) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual MoveType STDMETHODCALLTYPE Can_Enter_Cell(Cell cell) override; - virtual boolean STDMETHODCALLTYPE Is_To_Have_Shadow(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual Matrix3D STDMETHODCALLTYPE Shadow_Matrix(int *key) override; - virtual Point2D STDMETHODCALLTYPE Draw_Point(void) override; - virtual Point2D STDMETHODCALLTYPE Shadow_Point(void) override; - virtual VisualType STDMETHODCALLTYPE Visual_Character(boolean flag) override; - virtual int STDMETHODCALLTYPE Z_Adjust(void) override; - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual void STDMETHODCALLTYPE Unlimbo(void) override; - virtual void STDMETHODCALLTYPE Tilt_Pitch_AI(void) override; - virtual boolean STDMETHODCALLTYPE Power_On(void) override; - virtual boolean STDMETHODCALLTYPE Power_Off(void) override; - virtual boolean STDMETHODCALLTYPE Is_Powered(void) override; - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) override; - virtual boolean STDMETHODCALLTYPE Push(DirType dir) override; - virtual boolean STDMETHODCALLTYPE Shove(DirType dir) override; - virtual void STDMETHODCALLTYPE Force_Track(int track, Coord coord) override; - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) override; - virtual void STDMETHODCALLTYPE Force_New_Slope(int ramp) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override {return(Is_Moving());} - virtual int STDMETHODCALLTYPE Apparent_Speed(void) override; - virtual int STDMETHODCALLTYPE Drawing_Code(void) override; - virtual FireErrorType STDMETHODCALLTYPE Can_Fire(void) override; - virtual int STDMETHODCALLTYPE Get_Status() override {return(0);} - virtual void STDMETHODCALLTYPE Acquire_Hunter_Seeker_Target(void) override {} - virtual boolean STDMETHODCALLTYPE Is_Surfacing() override {return(false);} - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override {} - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override {return(false);} - virtual boolean STDMETHODCALLTYPE Will_Jump_Tracks(void) override {return(false);} - virtual boolean STDMETHODCALLTYPE Is_Really_Moving_Now(void) override {return(Is_Moving_Now());} - virtual void STDMETHODCALLTYPE Stop_Movement_Animation(void) override {} - virtual void STDMETHODCALLTYPE Lock(void) override {} - virtual void STDMETHODCALLTYPE Unlock(void) override {} - virtual int STDMETHODCALLTYPE Get_Track_Number(void) override {return(-1);} - virtual int STDMETHODCALLTYPE Get_Track_Index(void) override {return(-1);} - virtual int STDMETHODCALLTYPE Get_Speed_Accum(void) override {return(-1);} + virtual bool Load(SaveStreamClass & stream) override; + virtual bool Save(SaveStreamClass & stream, bool cleardirty) override; + + virtual void Link_To_Object(void *object) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual MoveType Can_Enter_Cell(Cell cell) override; + virtual bool Is_To_Have_Shadow(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual Matrix3D Shadow_Matrix(int *key) override; + virtual Point2D Draw_Point(void) override; + virtual Point2D Shadow_Point(void) override; + virtual VisualType Visual_Character(bool flag) override; + virtual int Z_Adjust(void) override; + virtual ZGradientType Z_Gradient(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual void Unlimbo(void) override; + virtual void Tilt_Pitch_AI(void) override; + virtual bool Power_On(void) override; + virtual bool Power_Off(void) override; + virtual bool Is_Powered(void) override; + virtual bool Is_Ion_Sensitive(void) override; + virtual bool Push(DirType dir) override; + virtual bool Shove(DirType dir) override; + virtual void Force_Track(int track, Coord coord) override; + virtual void Force_Immediate_Destination(Coord coord) override; + virtual void Force_New_Slope(int ramp) override; + virtual bool Is_Moving_Now(void) override {return(Is_Moving());} + virtual int Apparent_Speed(void) override; + virtual int Drawing_Code(void) override; + virtual FireErrorType Can_Fire(void) override; + virtual int Get_Status() override {return(0);} + virtual void Acquire_Hunter_Seeker_Target(void) override {} + virtual bool Is_Surfacing() override {return(false);} + virtual void Mark_All_Occupation_Bits(int mark) override {} + virtual bool Is_Moving_Here(Coord to) override {return(false);} + virtual bool Will_Jump_Tracks(void) override {return(false);} + virtual bool Is_Really_Moving_Now(void) override {return(Is_Moving_Now());} + virtual void Stop_Movement_Animation(void) override {} + virtual void Lock(void) override {} + virtual void Unlock(void) override {} + virtual int Get_Track_Number(void) override {return(-1);} + virtual int Get_Track_Index(void) override {return(-1);} + virtual int Get_Speed_Accum(void) override {return(-1);} /* @@ -85,9 +96,9 @@ class LocomotionClass : public IPersistStream, public ILocomotion virtual void Serialize(SaveStreamClass & stream); /* - * Restores whatever the record could not carry. Load_Members calls this once the - * members are in place, so a base class fixup runs even when the load was entered - * through a derived class. + * Restores whatever the record could not carry. Load_Object calls this once the + * record has been checked, so a locomotor never takes its place while its record + * is still in doubt. */ virtual void Post_Load(void); @@ -98,8 +109,8 @@ class LocomotionClass : public IPersistStream, public ILocomotion * from its Load and Save; the record is the swizzle identity followed by whatever * members the class names. */ - HRESULT Save_Members(IStream * stream, BOOL cleardirty); - HRESULT Load_Members(IStream * stream); + bool Save_Members(SaveStreamClass & stream, bool cleardirty); + bool Load_Members(SaveStreamClass & stream); protected: /* @@ -121,11 +132,4 @@ class LocomotionClass : public IPersistStream, public ILocomotion * persistence machinery never assumes a locomotor is already safely on disk. */ bool Dirty; - - /* - * This is the number of outstanding references to this locomotor. Releasing the - * last one destroys the locomotor, which is how its lifetime is managed through - * the COM interfaces it presents. - */ - LONG RefCount; }; diff --git a/code/logic.cpp b/code/logic.cpp index 2935b2a1e..3d1129fe3 100644 --- a/code/logic.cpp +++ b/code/logic.cpp @@ -79,12 +79,6 @@ #include -/* - * Global COM reference count. - */ -ULONG COMRefCount = 0; - - unsigned FramesThisSecond=0; unsigned LastFramesPerSecond=0; unsigned TotalFrames=0; diff --git a/code/map.cpp b/code/map.cpp index 7c824e39b..45441ad4a 100644 --- a/code/map.cpp +++ b/code/map.cpp @@ -299,6 +299,16 @@ void MapClass::Serialize(SaveStreamClass & stream) stream.Serialize(XSize); stream.Serialize(YSize); stream.Serialize(Size); + + // The cell array is reallocated to Size and every cell then installs itself in it by + // coordinate, so a saved extent other than the one this build lays out is refused + // before anything is sized from it. + if (stream.Is_Loading() + && (XSize != MAP_CELL_W || YSize != MAP_CELL_H || Size != MAP_CELL_TOTAL)) { + stream.Fail(); + return; + } + stream.Serialize(Crates); stream.Serialize(Redraws); stream.Serialize(TaggedCells); @@ -482,6 +492,26 @@ bool MapClass::Is_Valid(Cell const & cell) } +/// +/// Fetches the slot a cell coordinate names in the cell array. +/// A cell coordinate arrives from a saved game as two signed shorts, so this is what tells +/// a coordinate that names a slot from one that does not. +/// +/// Returns with the index into Array, or -1 when the coordinate names no slot. +int MapClass::Cell_Slot(Cell const & cell) const +{ + if (cell.X < 0 || cell.X >= MAP_CELL_W || cell.Y < 0 || cell.Y >= MAP_CELL_H) { + return(-1); + } + + int const cellnum = cell.X + cell.Y * MAP_CELL_W; + if (cellnum >= Array.Length()) { + return(-1); + } + return(cellnum); +} + + /*********************************************************************************************** * MapClass::One_Time -- Performs special one time initializations for the map. * * * diff --git a/code/map.h b/code/map.h index af96ee643..ce521d7a5 100644 --- a/code/map.h +++ b/code/map.h @@ -87,6 +87,7 @@ class MapClass: public GScreenClass int ID(CellClass * ptr) {return(Array.ID(ptr));}; int ID(CellClass & ptr) {return(Array.ID(&ptr));}; bool Is_Valid(Cell const & cell); + int Cell_Slot(Cell const & cell) const; /* ** Initialization diff --git a/code/mech.cpp b/code/mech.cpp index ee1d17cd5..0ddf517a6 100644 --- a/code/mech.cpp +++ b/code/mech.cpp @@ -56,7 +56,7 @@ MechLocomotionClass::~MechLocomotionClass(void) /// Has the mech been given somewhere to walk to? /// /// bool; Is the mech under movement orders? -boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving(void) +bool MechLocomotionClass::Is_Moving(void) { return(IsMoving); } @@ -67,7 +67,7 @@ boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving(void) /// /// Returns with the destination assigned, or COORD_NONE if the unit has not been /// given one. -Coord STDMETHODCALLTYPE MechLocomotionClass::Destination(void) +Coord MechLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -81,7 +81,7 @@ Coord STDMETHODCALLTYPE MechLocomotionClass::Destination(void) /// /// Returns with the location being stepped into, or the unit's own location if it /// is not part way between cells. -Coord STDMETHODCALLTYPE MechLocomotionClass::Head_To_Coord(void) +Coord MechLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -95,7 +95,7 @@ Coord STDMETHODCALLTYPE MechLocomotionClass::Head_To_Coord(void) /// This is the locomotor's entry point from the owning unit's AI. /// /// bool; Does the mech still have somewhere to walk to? -boolean STDMETHODCALLTYPE MechLocomotionClass::Process(void) +bool MechLocomotionClass::Process(void) { Movement_AI(true); return(Is_Moving()); @@ -108,7 +108,7 @@ boolean STDMETHODCALLTYPE MechLocomotionClass::Process(void) /// raised to the deck above it, since that is where a walking unit can actually get to. /// /// The location to walk to. -void STDMETHODCALLTYPE MechLocomotionClass::Move_To(Coord to) +void MechLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { Coord coord = to; @@ -126,7 +126,7 @@ void STDMETHODCALLTYPE MechLocomotionClass::Move_To(Coord to) /// A unit caught part way between cells is left in motion so that it finishes the step it /// is taking before coming to rest. /// -void STDMETHODCALLTYPE MechLocomotionClass::Stop_Moving(void) +void MechLocomotionClass::Stop_Moving(void) { DestinationCoord = COORD_NONE; if (HeadToCoord == COORD_NONE) { @@ -155,7 +155,7 @@ void MechLocomotionClass::Do_Turn(DirType coord) /// destination -- it will walk there and then pick its path up again. /// /// The location to step into immediately. -void STDMETHODCALLTYPE MechLocomotionClass::Force_Immediate_Destination(Coord coord) +void MechLocomotionClass::Force_Immediate_Destination(Coord coord) { HeadToCoord = coord; } @@ -651,18 +651,9 @@ bool MechLocomotionClass::Mark_Head_To(Coord const & coord) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence layer uses this identifier to create a locomotor of the right kind -/// when a saved game is loaded. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE MechLocomotionClass::GetClassID(CLSID * retval) +ClassID MechLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_MechLocomotion; - return(S_OK); + return(ClassID_MechLocomotion); } @@ -684,7 +675,7 @@ void MechLocomotionClass::Serialize(SaveStreamClass & stream) /// Fetches the display layer that the mech is rendered in. /// /// Returns with the layer appropriate to a unit that walks on the ground. -LayerType STDMETHODCALLTYPE MechLocomotionClass::In_Which_Layer(void) +LayerType MechLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } @@ -696,7 +687,7 @@ LayerType STDMETHODCALLTYPE MechLocomotionClass::In_Which_Layer(void) /// standing still -- blocked, or waiting on a path -- is not moving now. /// /// bool; Is the mech turning or walking right now? -boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving_Now(void) +bool MechLocomotionClass::Is_Moving_Now(void) { if (LinkedTo->PrimaryFacing.Is_Rotating()) { return(true); @@ -714,7 +705,7 @@ boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving_Now(void) /// it is walking toward stays reserved for it. /// /// The marking operation to perform; MARK_UP releases the cell. -void STDMETHODCALLTYPE MechLocomotionClass::Mark_All_Occupation_Bits(int mark) +void MechLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { LinkedTo->Clear_Occupy_Bit((Coord)Head_To_Coord()); @@ -731,7 +722,7 @@ void STDMETHODCALLTYPE MechLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The location to test against. /// bool; Is the mech heading into that location? -boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving_Here(Coord to) +bool MechLocomotionClass::Is_Moving_Here(Coord to) { Coord coord = Head_To_Coord(); diff --git a/code/mech.h b/code/mech.h index 9a0a1cab6..cb7a6b6ec 100644 --- a/code/mech.h +++ b/code/mech.h @@ -27,22 +27,22 @@ class MechLocomotionClass : public LocomotionClass MechLocomotionClass(void); virtual ~MechLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual void Force_Immediate_Destination(Coord coord) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/mouse.cpp b/code/mouse.cpp index 32a5718ce..92b046afc 100644 --- a/code/mouse.cpp +++ b/code/mouse.cpp @@ -51,6 +51,7 @@ #include "mixfile.h" #include "overtype.h" #include "rawfile.h" +#include "saveload.h" #include "savestream.h" #include "scenario.h" #include "shapeset.h" @@ -58,6 +59,8 @@ #include "terrtype.h" #include "xmouse.h" +#include + #define MOUSE_HOTSPOT_MIN 0 #define MOUSE_HOTSPOT_CENTER 12345 @@ -391,17 +394,17 @@ void MouseClass::Init_Clear(void) /// back into it. Object pointers within the restored state are remapped by the swizzle /// manager, and the theater specific type data is reinitialized to match the scenario. /// -/// Returns with S_OK if the map was loaded, otherwise the stream error. -HRESULT MouseClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool MouseClass::Load(SaveStreamClass & stream) { int i; - HRESULT result = BASECLASS::Load(stream); - if (SUCCEEDED(result)) { + bool result = BASECLASS::Load(stream); + if (result) { int theater; - result = stream->Read(&theater, sizeof(theater), NULL); - if (FAILED(result)) { - return(result); + stream.Serialize(theater); + if (stream.Was_Error()) { + return(false); } LastTheater = THEATER_NONE; @@ -433,12 +436,10 @@ HRESULT MouseClass::Load(IStream * stream) Array.Clear(); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("MouseClass"); - Serialize(savestream); - result = savestream.Result(); - if (FAILED(result)) { - return(result); + stream.Set_Context("MouseClass"); + Serialize(stream); + if (stream.Was_Error()) { + return(false); } /* @@ -465,32 +466,24 @@ HRESULT MouseClass::Load(IStream * stream) /* * These blocks are read raw, so a file whose records are a different size would drag - * the rest of the stream out of step. A short read reports S_FALSE, not a failure. + * the rest of the stream out of step. */ - ULONG readcount = 0; - result = stream->Read(CellZones, sizeof(*CellZones) * CellZoneCount, &readcount); - if (FAILED(result)) { - return(result); - } - if (readcount != sizeof(*CellZones) * CellZoneCount) { - return(E_FAIL); + stream.Serialize_Bytes(CellZones, (int)(sizeof(*CellZones) * CellZoneCount)); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < MZONE_COUNT; i++) { Zones[i] = new int[ZoneCount]; - result = stream->Read(Zones[i], sizeof(*Zones[i]) * ZoneCount, &readcount); - if (FAILED(result)) { - return(result); - } - if (readcount != sizeof(*Zones[i]) * ZoneCount) { - return(E_FAIL); + stream.Serialize_Bytes(Zones[i], (int)(sizeof(*Zones[i]) * ZoneCount)); + if (stream.Was_Error()) { + return(false); } } - savestream.Serialize(ZoneConnections); - result = savestream.Result(); - if (FAILED(result)) { - return(result); + stream.Serialize(ZoneConnections); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < Array.Length(); i++) { @@ -498,13 +491,18 @@ HRESULT MouseClass::Load(IStream * stream) Array[i] = NULL; } int count; - result = stream->Read(&count, sizeof(count), NULL); - if (FAILED(result)) { - return(result); + stream.Serialize(count); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < count; i++) { - LPVOID ptr; - OleLoadFromStream(stream, IID_IUnknown, &ptr); + std::unique_ptr cell = Load_Object_As(stream); + if (cell == nullptr) { + return(false); + } + // The cell put itself into the map's array as it finished loading, and the map + // is what deletes it from here on. + cell.release(); } TerrainTypeClass::Init(Scen->Theater); @@ -521,7 +519,7 @@ HRESULT MouseClass::Load(IStream * stream) DraggedWaypoint = NULL; LastTheater = Scen->Theater; - result = S_OK; + result = true; } return(result); } @@ -531,45 +529,42 @@ HRESULT MouseClass::Load(IStream * stream) /// Saves the map layer to a save game stream. /// This routine writes the theater, the members of the whole display chain, the zone tables /// and zone connections, and then every valid cell, in the order that Load expects to find -/// them. The cells persist themselves through OLE, so each one writes its own contents. +/// them. Each cell writes its own contents as a record of its own. /// -/// Returns with S_OK if the map was written, otherwise the stream error. -HRESULT MouseClass::Save(IStream * stream) +/// bool; Was the record written whole? +bool MouseClass::Save(SaveStreamClass & stream) { int i; int count; - HRESULT result = BASECLASS::Save(stream); - if (SUCCEEDED(result)) { + bool result = BASECLASS::Save(stream); + if (result) { int theater = Scen->Theater; - result = stream->Write(&theater, sizeof(theater), NULL); - if (FAILED(result)) { - return(result); + stream.Serialize(theater); + if (stream.Was_Error()) { + return(false); } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - result = savestream.Result(); - if (FAILED(result)) { - return(result); + Serialize(stream); + if (stream.Was_Error()) { + return(false); } - result = stream->Write(CellZones, sizeof(*CellZones) * CellZoneCount, NULL); - if (FAILED(result)) { - return(result); + stream.Serialize_Bytes(CellZones, (int)(sizeof(*CellZones) * CellZoneCount)); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < MZONE_COUNT; i++) { - result = stream->Write(Zones[i], sizeof(*Zones[i]) * ZoneCount, NULL); - if (FAILED(result)) { - return(result); + stream.Serialize_Bytes(Zones[i], (int)(sizeof(*Zones[i]) * ZoneCount)); + if (stream.Was_Error()) { + return(false); } } - savestream.Serialize(ZoneConnections); - result = savestream.Result(); - if (FAILED(result)) { - return(result); + stream.Serialize(ZoneConnections); + if (stream.Was_Error()) { + return(false); } count = 0; @@ -582,25 +577,28 @@ HRESULT MouseClass::Save(IStream * stream) } cptr = Iterate(); } - result = stream->Write(&count, sizeof(count), NULL); - if (FAILED(result)) { - return(result); + stream.Serialize(count); + if (stream.Was_Error()) { + return(false); } Reset_Iterator(); cptr = Iterate(); while (cptr != NULL) { Cell cell = cptr->CellID; if (Is_Valid(cell)) { - OleSaveToStream(cptr, stream); + Save_Object(stream, cptr); count--; } cptr = Iterate(); } + // The count was written before the cells, so a second pass that disagrees with it + // has already written a map no load can read back. if (count != 0) { - return(result); + stream.Fail(); + return(false); } - result = S_OK; + result = true; } return(result); } diff --git a/code/mouse.h b/code/mouse.h index 9a2da1d17..09d260c65 100644 --- a/code/mouse.h +++ b/code/mouse.h @@ -42,8 +42,8 @@ class MouseClass: public ScrollClass typedef ScrollClass BASECLASS; public: - virtual HRESULT Load(IStream * stream) override; - virtual HRESULT Save(IStream * stream) override; + virtual bool Load(SaveStreamClass & stream) override; + virtual bool Save(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/overlay.cpp b/code/overlay.cpp index 2000810a1..2de57e657 100644 --- a/code/overlay.cpp +++ b/code/overlay.cpp @@ -36,7 +36,6 @@ * OverlayClass::new -- Allocates a overlay object from pool * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "overlay.h" diff --git a/code/overlay.h b/code/overlay.h index 7ee7f1254..bf5a122a3 100644 --- a/code/overlay.h +++ b/code/overlay.h @@ -32,7 +32,7 @@ #pragma once -#include "isun.h" +#include "classids.h" #include "object.h" #include "overlay.hh" @@ -63,7 +63,7 @@ class OverlayClass : public ObjectClass OverlayClass(OverlayTypeClass const * ttype, Cell const & pos = CELL_NONE, HousesType = HOUSE_NONE); virtual ~OverlayClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override {if (retval == NULL) return(E_POINTER);*retval = CLSID_OverlayClass;return(S_OK);} + virtual ClassID Class_ID(void) const override {return(ClassID_OverlayClass);} virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/overtype.cpp b/code/overtype.cpp index c00b1614a..8ce1f2cdd 100644 --- a/code/overtype.cpp +++ b/code/overtype.cpp @@ -46,7 +46,6 @@ * OverlayTypeClass::operator new -- Allocate an overlay type class object from pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "overtype.h" @@ -482,17 +481,9 @@ void OverlayTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save system asks for this so that it knows which class to construct when the object -/// is read back out of a save file. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE OverlayTypeClass::GetClassID(CLSID * retval) +ClassID OverlayTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_OverlayTypeClass; - return(S_OK); + return(ClassID_OverlayTypeClass); } diff --git a/code/overtype.h b/code/overtype.h index 1920dd7f3..b80e1f56b 100644 --- a/code/overtype.h +++ b/code/overtype.h @@ -159,7 +159,7 @@ class OverlayTypeClass: public ObjectTypeClass OverlayTypeClass(char const * ininame = NULL); ~OverlayTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/particle.cpp b/code/particle.cpp index 6aeefabef..f5e660884 100644 --- a/code/particle.cpp +++ b/code/particle.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "particle.h" @@ -929,10 +928,10 @@ void ParticleClass::Serialize(SaveStreamClass & stream) /// /// The stream to write this particle to. /// Should the modified flag be cleared once written? -/// Returns with S_OK if the particle was written successfully. -HRESULT STDMETHODCALLTYPE ParticleClass::Save(IStream * stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool ParticleClass::Save(SaveStreamClass & stream, bool cleardirty) { - HRESULT result = BASECLASS::Save(stream, cleardirty); + bool result = BASECLASS::Save(stream, cleardirty); WasSaved = true; return(result); } @@ -993,18 +992,9 @@ int ParticleClass::Shape_Number(void) const } -/// -/// Fetches the class identifier of this object. -/// The persistence code uses this identifier to recreate the correct object when the -/// save file is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ParticleClass::GetClassID(CLSID * retval) +ClassID ParticleClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleClass; - return(S_OK); + return(ClassID_ParticleClass); } diff --git a/code/particle.h b/code/particle.h index b7b32a9c9..a8c16b9e0 100644 --- a/code/particle.h +++ b/code/particle.h @@ -31,8 +31,8 @@ class ParticleClass : public ObjectClass ParticleClass(ParticleTypeClass const * type, Coord const & origin, Coord const & target = COORD_NONE, ParticleSystemClass * partsys = NULL); virtual ~ParticleClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream, BOOL cleardirty) override; + virtual ClassID Class_ID(void) const override; + virtual bool Save(SaveStreamClass & stream, bool cleardirty) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/partsys.cpp b/code/partsys.cpp index cf5f1d608..9629812fe 100644 --- a/code/partsys.cpp +++ b/code/partsys.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "partsys.h" @@ -872,18 +871,9 @@ void ParticleSystemClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier for this object. -/// This routine is part of the persistence support. The save process records the -/// identifier so that the load process knows what kind of object to build. -/// -/// Pointer to the class identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ParticleSystemClass::GetClassID(CLSID * retval) +ClassID ParticleSystemClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleSystemClass; - return(S_OK); + return(ClassID_ParticleSystemClass); } diff --git a/code/partsys.h b/code/partsys.h index 91db45788..41bc50380 100644 --- a/code/partsys.h +++ b/code/partsys.h @@ -31,7 +31,7 @@ class ParticleSystemClass : public ObjectClass ParticleSystemClass(void); virtual ~ParticleSystemClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/persist.h b/code/persist.h new file mode 100644 index 000000000..1b9524367 --- /dev/null +++ b/code/persist.h @@ -0,0 +1,30 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include "win.h" + +#include "classid.h" + +class SaveStreamClass; + +// What a saved game asks of an object it carries: the class identifier the record is +// tagged with, and the record itself. +struct IPersistent +{ + virtual ~IPersistent(void) {} + + virtual ClassID Class_ID(void) const = 0; + virtual bool Load(SaveStreamClass & stream) = 0; + // Restores what the record could not carry, once the record has been checked; an object + // takes its place in the map or a side table here, never while its record is still in doubt. + virtual void Post_Load(void) {} + virtual bool Save(SaveStreamClass & stream, bool cleardirty) = 0; +}; diff --git a/code/psystype.cpp b/code/psystype.cpp index 4a6cae08f..439a4fdd9 100644 --- a/code/psystype.cpp +++ b/code/psystype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "psystype.h" @@ -181,18 +180,9 @@ void ParticleSystemTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence interface. The save system records the class -/// ID so that the right kind of object can be manufactured when the game is reloaded. -/// -/// Pointer to the class ID to be filled in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ParticleSystemTypeClass::GetClassID(CLSID * retval) +ClassID ParticleSystemTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleSystemTypeClass; - return(S_OK); + return(ClassID_ParticleSystemTypeClass); } diff --git a/code/psystype.h b/code/psystype.h index 3e1b60e9f..c9d1fb379 100644 --- a/code/psystype.h +++ b/code/psystype.h @@ -25,7 +25,7 @@ class ParticleSystemTypeClass : public ObjectTypeClass ParticleSystemTypeClass(char const * ininame = NULL); virtual ~ParticleSystemTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/ptype.cpp b/code/ptype.cpp index 4ff73a7d5..5644fea15 100644 --- a/code/ptype.cpp +++ b/code/ptype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "ptype.h" @@ -220,18 +219,9 @@ void ParticleTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this identifier to know which kind of object to build -/// when the stream is read back in. -/// -/// Pointer to the location to store the class identifier. -/// Returns with S_OK, or E_POINTER if no storage location was supplied. -HRESULT STDMETHODCALLTYPE ParticleTypeClass::GetClassID(CLSID * retval) +ClassID ParticleTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleTypeClass; - return(S_OK); + return(ClassID_ParticleTypeClass); } diff --git a/code/ptype.h b/code/ptype.h index fd5f29303..23599468b 100644 --- a/code/ptype.h +++ b/code/ptype.h @@ -28,7 +28,7 @@ class ParticleTypeClass : public ObjectTypeClass ParticleTypeClass(char const * ininame = NULL); virtual ~ParticleTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/reinf.cpp b/code/reinf.cpp index 540a86469..d27e1be6d 100644 --- a/code/reinf.cpp +++ b/code/reinf.cpp @@ -47,10 +47,10 @@ #include "airctype.h" #include "building.h" #include "cell.h" +#include "classids.h" #include "foot.h" #include "globals.h" #include "house.h" -#include "ilocos.h" #include "incdec.h" #include "inline.h" #include "mouse.h" @@ -533,7 +533,7 @@ inline bool _Can_Burrow(FootClass * object) { while (object != NULL) { TechnoTypeClass const * tclass = object->TClass; - if (tclass->Locomotor != CLSID_TunnelLocomotion) { + if (tclass->Locomotor != ClassID_TunnelLocomotion) { return(false); } object = (FootClass *)object->Next; diff --git a/code/revent.cpp b/code/revent.cpp index 8d0a51dee..ff4c07403 100644 --- a/code/revent.cpp +++ b/code/revent.cpp @@ -366,20 +366,19 @@ void RadarEventClass::Get_Event_Rect(Point2D (& event_rect)[4]) const /// /// The stream to write the radar events to. /// bool; Were the events written successfully? -bool RadarEventClass::Save(IStream * stream) +bool RadarEventClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); int count = RadarEvents.Count(); - savestream.Serialize(count); + stream.Serialize(count); for (int index = 0; index < count; index++) { - RadarEvents[index]->Serialize(savestream); + RadarEvents[index]->Serialize(stream); } - savestream.Serialize(LastRadarEventCell); + stream.Serialize(LastRadarEventCell); - return(SUCCEEDED(savestream.Result())); + return(!stream.Was_Error()); } @@ -390,27 +389,29 @@ bool RadarEventClass::Save(IStream * stream) /// /// The stream to read the radar events from. /// bool; Were the events read successfully? -bool RadarEventClass::Load(IStream * stream) +bool RadarEventClass::Load(SaveStreamClass & stream) { + // The destructor takes the event off the list, so the list drains as they are deleted. for (int i = RadarEvents.Count() - 1; i >= 0; i--) { delete RadarEvents[i]; - RadarEvents.Delete_Index(i); } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("RadarEventClass"); + stream.Set_Context("RadarEventClass"); int count = 0; - savestream.Serialize(count); + stream.Serialize(count); + if (!stream.Fits(count, 1)) { + return(false); + } - for (int index = 0; index < count; index++) { + for (int index = 0; index < count && !stream.Was_Error(); index++) { RadarEventClass * event = new RadarEventClass(RADAREVENT_NONE, Cell(0, 0)); - event->Serialize(savestream); + event->Serialize(stream); } - savestream.Serialize(LastRadarEventCell); + stream.Serialize(LastRadarEventCell); - return(SUCCEEDED(savestream.Result())); + return(!stream.Was_Error()); } diff --git a/code/revent.h b/code/revent.h index 4c3264376..e27232e87 100644 --- a/code/revent.h +++ b/code/revent.h @@ -18,15 +18,14 @@ #include "revent.hh" -struct IStream; class SaveStreamClass; template class DynamicVectorClass; class RadarEventClass { public: - static bool Save(IStream * stream); - static bool Load(IStream * stream); + static bool Save(SaveStreamClass & stream); + static bool Load(SaveStreamClass & stream); public: RadarEventClass(RadarEventType event, Cell cell); diff --git a/code/rules.cpp b/code/rules.cpp index 2979154b7..f3cf6a7cb 100644 --- a/code/rules.cpp +++ b/code/rules.cpp @@ -2092,10 +2092,9 @@ bool RulesClass::Do_Movies(CCINIClass const & ini) /// /// Writes the rule data out to a save game stream. /// -void RulesClass::Save(IStream * stream) +void RulesClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); + Serialize(stream); } @@ -2104,11 +2103,10 @@ void RulesClass::Save(IStream * stream) /// /// Be sure the object heaps have been loaded before calling this routine, since /// the pointer swizzle needs them. -void RulesClass::Load(IStream * stream) +void RulesClass::Load(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("RulesClass"); - Serialize(savestream); + stream.Set_Context("RulesClass"); + Serialize(stream); } diff --git a/code/rules.h b/code/rules.h index 1b8eaa80c..f05b06927 100644 --- a/code/rules.h +++ b/code/rules.h @@ -138,8 +138,8 @@ class RulesClass bool Do_Movies(CCINIClass const & ini); bool Objects(CCINIClass const & ini); - void Save(IStream * stream); - void Load(IStream * stream); + void Save(SaveStreamClass & stream); + void Load(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); diff --git a/code/savefile.cpp b/code/savefile.cpp new file mode 100644 index 000000000..ae47dfcb7 --- /dev/null +++ b/code/savefile.cpp @@ -0,0 +1,539 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "savefile.h" + +#include "crc.h" + +#include + +#include +#include +#include +#include + +namespace { + +unsigned char const Signature[4] = { 'O', 'T', 'S', 'V' }; + +constexpr std::uint32_t FLAG_LZO = 0x0001; +constexpr std::uint32_t FIELD_HEADER_SIZE = 8; +constexpr std::uint32_t MAX_FIELD_LENGTH = 0x10000; +// No game state comes near this, and a header asking for more is asking for memory. +constexpr std::uint32_t MAX_CONTENT_LENGTH = 0x10000000; +// A listing is a dozen short fields; a table beyond this is not one. +constexpr std::uint32_t MAX_TABLE_LENGTH = 0x100000; + + +std::uint32_t Get_U16(unsigned char const * from) +{ + return((std::uint32_t)from[0] | ((std::uint32_t)from[1] << 8)); +} + + +std::uint32_t Get_U32(unsigned char const * from) +{ + return((std::uint32_t)from[0] | ((std::uint32_t)from[1] << 8) + | ((std::uint32_t)from[2] << 16) | ((std::uint32_t)from[3] << 24)); +} + + +void Put_U16(unsigned char * into, std::uint32_t value) +{ + into[0] = (unsigned char)(value & 0xFF); + into[1] = (unsigned char)((value >> 8) & 0xFF); +} + + +void Put_U32(unsigned char * into, std::uint32_t value) +{ + into[0] = (unsigned char)(value & 0xFF); + into[1] = (unsigned char)((value >> 8) & 0xFF); + into[2] = (unsigned char)((value >> 16) & 0xFF); + into[3] = (unsigned char)((value >> 24) & 0xFF); +} + + +void Append(std::vector & into, void const * data, std::size_t length) +{ + unsigned char const * bytes = (unsigned char const *)data; + into.insert(into.end(), bytes, bytes + length); +} + + +// Sizes a buffer the header asked for, and says so rather than throw when the process +// cannot hold it. +bool Reserve(std::vector & buffer, std::size_t length) +{ + try { + buffer.resize(length); + } catch (std::bad_alloc const &) { + buffer.clear(); + return(false); + } + return(true); +} + + +bool Read_Range(HANDLE file, void * into, std::uint32_t length) +{ + unsigned char * cursor = (unsigned char *)into; + + while (length > 0) { + DWORD got = 0; + if (!ReadFile(file, cursor, length, &got, nullptr) || got == 0) return(false); + cursor += got; + length -= got; + } + + return(true); +} + + +bool Write_Range(HANDLE file, void const * data, std::uint32_t length) +{ + unsigned char const * cursor = (unsigned char const *)data; + + while (length > 0) { + DWORD const block = (length > 0x100000) ? 0x100000 : length; + DWORD written = 0; + if (!WriteFile(file, cursor, block, &written, nullptr) || written != block) return(false); + cursor += written; + length -= written; + } + + return(true); +} + + +struct HeaderType { + std::uint32_t Version; + std::uint32_t Flags; + std::uint32_t TableLength; + std::uint32_t ContentOffset; + std::uint32_t StoredLength; + std::uint32_t ContentLength; + std::uint32_t ContentCRC; + std::uint32_t HeaderCRC; +}; + + +// The header checksum continues over the field table, so a listing can verify what it +// reads without touching the content. +std::uint32_t Header_CRC(unsigned char const * header, unsigned char const * table, std::uint32_t length) +{ + return(SaveFileClass::Checksum(table, length, SaveFileClass::Checksum(header, SaveFileClass::HEADER_SIZE - 4))); +} + + +// Decides everything the first 32 bytes can decide, in the order a caller wants to +// hear about it: not ours, a version we do not read, or damage. +SaveFileClass::ResultType Parse_Header(unsigned char const * bytes, std::uint32_t available, HeaderType & header) +{ + if (available < sizeof(Signature) || memcmp(bytes, Signature, sizeof(Signature)) != 0) { + return(SaveFileClass::RESULT_NOT_A_SAVE); + } + if (available < SaveFileClass::HEADER_SIZE) { + return(SaveFileClass::RESULT_CORRUPT); + } + + header.Version = Get_U16(bytes + 4); + header.Flags = Get_U16(bytes + 6); + header.TableLength = Get_U32(bytes + 8); + header.ContentOffset = Get_U32(bytes + 12); + header.StoredLength = Get_U32(bytes + 16); + header.ContentLength = Get_U32(bytes + 20); + header.ContentCRC = Get_U32(bytes + 24); + header.HeaderCRC = Get_U32(bytes + 28); + + if (header.Version == 0 || header.Version > SaveFileClass::FORMAT_VERSION) { + return(SaveFileClass::RESULT_UNSUPPORTED_VERSION); + } + if ((header.Flags & ~FLAG_LZO) != 0) { + return(SaveFileClass::RESULT_UNSUPPORTED_VERSION); + } + if (header.TableLength > MAX_TABLE_LENGTH) { + return(SaveFileClass::RESULT_CORRUPT); + } + if (header.ContentOffset != SaveFileClass::HEADER_SIZE + header.TableLength) { + return(SaveFileClass::RESULT_CORRUPT); + } + if (header.StoredLength > MAX_CONTENT_LENGTH || header.ContentLength > MAX_CONTENT_LENGTH) { + return(SaveFileClass::RESULT_CORRUPT); + } + + return(SaveFileClass::RESULT_OK); +} + +} // namespace + + +SaveFileClass::SaveFileClass(void) +{ +} + + +std::uint32_t SaveFileClass::Checksum(unsigned char const * data, std::uint32_t length, std::uint32_t seed) +{ + return(CRC::Memory(data, length, seed)); +} + + +char const * SaveFileClass::Result_Text(ResultType result) +{ + switch (result) { + case RESULT_OK: return("ok"); + case RESULT_MISSING: return("the file is missing"); + case RESULT_NOT_A_SAVE: return("the file is not a saved game"); + case RESULT_UNSUPPORTED_VERSION: return("the file uses a format version this build does not read"); + case RESULT_CORRUPT: return("the file is damaged"); + case RESULT_WRITE_FAILED: return("the file could not be written"); + case RESULT_NO_MEMORY: return("there is not enough memory to read the file"); + case RESULT_TOO_LARGE: return("the game state is larger than a saved game can hold"); + } + return("unknown"); +} + + +SaveFileClass::FieldType const * SaveFileClass::Find(int id, int kind) const +{ + for (FieldType const & field : Fields) { + if (field.ID == id && field.Kind == kind) return(&field); + } + return(nullptr); +} + + +void SaveFileClass::Set(int id, int kind, void const * data, std::size_t length) +{ + for (FieldType & field : Fields) { + if (field.ID == id && field.Kind == kind) { + field.Bytes.assign((unsigned char const *)data, (unsigned char const *)data + length); + return; + } + } + + FieldType field; + field.ID = id; + field.Kind = kind; + field.Bytes.assign((unsigned char const *)data, (unsigned char const *)data + length); + Fields.push_back(field); +} + + +void SaveFileClass::Set_String(int id, char const * text) +{ + if (text == nullptr) text = ""; + Set(id, FIELD_STRING, text, strlen(text)); +} + + +void SaveFileClass::Set_Int(int id, int value) +{ + unsigned char bytes[4]; + Put_U32(bytes, (std::uint32_t)value); + Set(id, FIELD_INT, bytes, sizeof(bytes)); +} + + +void SaveFileClass::Set_Time(int id, FILETIME const & time) +{ + unsigned char bytes[8]; + Put_U32(bytes, time.dwLowDateTime); + Put_U32(bytes + 4, time.dwHighDateTime); + Set(id, FIELD_TIME, bytes, sizeof(bytes)); +} + + +// A string that does not fit is truncated to what does; the result is always terminated. +bool SaveFileClass::Get_String(int id, char * text, int size) const +{ + if (text == nullptr || size <= 0) return(false); + + FieldType const * const field = Find(id, FIELD_STRING); + if (field == nullptr) { + text[0] = '\0'; + return(false); + } + + std::size_t length = field->Bytes.size(); + if (length > (std::size_t)(size - 1)) { + // A cut never splits a UTF-8 sequence, so a shortened description stays text. + length = (std::size_t)(size - 1); + while (length > 0 && (field->Bytes[length] & 0xC0) == 0x80) length--; + } + memcpy(text, field->Bytes.data(), length); + text[length] = '\0'; + + return(true); +} + + +bool SaveFileClass::Get_Int(int id, int * value) const +{ + FieldType const * const field = Find(id, FIELD_INT); + if (field == nullptr || field->Bytes.size() != 4) return(false); + + if (value != nullptr) *value = (int)Get_U32(field->Bytes.data()); + return(true); +} + + +bool SaveFileClass::Get_Time(int id, FILETIME * time) const +{ + FieldType const * const field = Find(id, FIELD_TIME); + if (field == nullptr || field->Bytes.size() != 8) return(false); + + if (time != nullptr) { + time->dwLowDateTime = Get_U32(field->Bytes.data()); + time->dwHighDateTime = Get_U32(field->Bytes.data() + 4); + } + return(true); +} + + +void SaveFileClass::Clear_Fields(void) +{ + Fields.clear(); +} + + +void SaveFileClass::Serialize_Fields(std::vector & table) const +{ + table.clear(); + + for (FieldType const & field : Fields) { + unsigned char head[FIELD_HEADER_SIZE]; + Put_U16(head, (std::uint32_t)field.ID); + Put_U16(head + 2, (std::uint32_t)field.Kind); + Put_U32(head + 4, (std::uint32_t)field.Bytes.size()); + Append(table, head, sizeof(head)); + Append(table, field.Bytes.data(), field.Bytes.size()); + } +} + + +SaveFileClass::ResultType SaveFileClass::Parse_Fields(unsigned char const * table, std::uint32_t length) +{ + Fields.clear(); + + std::uint32_t offset = 0; + while (offset < length) { + if (length - offset < FIELD_HEADER_SIZE) return(RESULT_CORRUPT); + + FieldType field; + field.ID = (int)Get_U16(table + offset); + field.Kind = (int)Get_U16(table + offset + 2); + std::uint32_t const bytes = Get_U32(table + offset + 4); + offset += FIELD_HEADER_SIZE; + + if (bytes > MAX_FIELD_LENGTH || bytes > length - offset) return(RESULT_CORRUPT); + field.Bytes.assign(table + offset, table + offset + bytes); + offset += bytes; + + Fields.push_back(field); + } + + return(RESULT_OK); +} + + +// The file lands under its final name only once every byte is on disk, so a save +// interrupted at any point leaves the previous file untouched. +SaveFileClass::ResultType SaveFileClass::Write(char const * path) const +{ + if (path == nullptr) return(RESULT_WRITE_FAILED); + + // The reader's limits bind the writer too, so a save this build writes is one it reads, + // and one it cannot write leaves the file on disk alone. + if (Content.size() > MAX_CONTENT_LENGTH) return(RESULT_TOO_LARGE); + for (FieldType const & field : Fields) { + if (field.Bytes.size() > MAX_FIELD_LENGTH) return(RESULT_TOO_LARGE); + } + + std::vector table; + Serialize_Fields(table); + if (table.size() > MAX_TABLE_LENGTH) return(RESULT_TOO_LARGE); + + // The compressed block is kept only when it is smaller than the content; otherwise + // the content is written where it already sits, rather than copied to be written. + std::vector compressed; + unsigned char const * payload = Content.data(); + std::uint32_t payload_length = (std::uint32_t)Content.size(); + std::uint32_t flags = 0; + + if (!Content.empty()) { + std::vector work; + if (!Reserve(work, LZO1X_MEM_COMPRESS) + || !Reserve(compressed, Content.size() + Content.size() / 16 + 64 + 3)) { + return(RESULT_NO_MEMORY); + } + + lzo_uint packed = 0; + int const status = lzo1x_1_compress(Content.data(), (lzo_uint)Content.size(), + compressed.data(), &packed, work.data()); + + if (status == LZO_E_OK && packed < Content.size()) { + payload = compressed.data(); + payload_length = (std::uint32_t)packed; + flags |= FLAG_LZO; + } + } + + unsigned char header[HEADER_SIZE]; + memcpy(header, Signature, sizeof(Signature)); + Put_U16(header + 4, FORMAT_VERSION); + Put_U16(header + 6, flags); + Put_U32(header + 8, (std::uint32_t)table.size()); + Put_U32(header + 12, HEADER_SIZE + (std::uint32_t)table.size()); + Put_U32(header + 16, payload_length); + Put_U32(header + 20, (std::uint32_t)Content.size()); + Put_U32(header + 24, Checksum(payload, payload_length)); + // The header checksum covers everything before itself, so it is filled in last. + Put_U32(header + 28, Header_CRC(header, table.data(), (std::uint32_t)table.size())); + + std::string const temporary = std::string(path) + ".tmp"; + + HANDLE const file = CreateFileA(temporary.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return(RESULT_WRITE_FAILED); + + bool ok = Write_Range(file, header, HEADER_SIZE); + if (ok && !table.empty()) ok = Write_Range(file, table.data(), (std::uint32_t)table.size()); + if (ok && payload_length > 0) ok = Write_Range(file, payload, payload_length); + if (ok) ok = (FlushFileBuffers(file) != FALSE); + if (!CloseHandle(file)) ok = false; + + if (ok) ok = (MoveFileExA(temporary.c_str(), path, MOVEFILE_REPLACE_EXISTING) != FALSE); + + if (!ok) { + DeleteFileA(temporary.c_str()); + return(RESULT_WRITE_FAILED); + } + + return(RESULT_OK); +} + + +SaveFileClass::ResultType SaveFileClass::Read(char const * path) +{ + Fields.clear(); + Content.clear(); + + if (path == nullptr) return(RESULT_MISSING); + + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return(RESULT_MISSING); + + // The header is judged before anything the file's size could ask for is allocated. + unsigned char head[HEADER_SIZE]; + DWORD got = 0; + bool const ok = (ReadFile(file, head, HEADER_SIZE, &got, nullptr) != FALSE); + + HeaderType header; + ResultType result = ok ? Parse_Header(head, got, header) : RESULT_CORRUPT; + + std::vector image; + if (result == RESULT_OK) { + DWORD const size = GetFileSize(file, nullptr); + if (size == INVALID_FILE_SIZE || size != header.ContentOffset + header.StoredLength) { + result = RESULT_CORRUPT; + } else if (!Reserve(image, size)) { + result = RESULT_NO_MEMORY; + } else { + memcpy(image.data(), head, HEADER_SIZE); + if (!Read_Range(file, image.data() + HEADER_SIZE, size - HEADER_SIZE)) result = RESULT_CORRUPT; + } + } + CloseHandle(file); + if (result != RESULT_OK) return(result); + + if (Header_CRC(image.data(), image.data() + HEADER_SIZE, header.TableLength) != header.HeaderCRC) { + return(RESULT_CORRUPT); + } + + result = Parse_Fields(image.data() + HEADER_SIZE, header.TableLength); + if (result != RESULT_OK) return(result); + + unsigned char const * const stored = image.data() + header.ContentOffset; + if (Checksum(stored, header.StoredLength) != header.ContentCRC) { + Fields.clear(); + return(RESULT_CORRUPT); + } + + if ((header.Flags & FLAG_LZO) != 0) { + if (!Reserve(Content, header.ContentLength)) { + Fields.clear(); + return(RESULT_NO_MEMORY); + } + + lzo_uint unpacked = (lzo_uint)Content.size(); + int const status = lzo1x_decompress_safe(stored, (lzo_uint)header.StoredLength, + Content.data(), &unpacked, nullptr); + + if (status != LZO_E_OK || unpacked != header.ContentLength) { + Fields.clear(); + Content.clear(); + return(RESULT_CORRUPT); + } + } else { + if (header.StoredLength != header.ContentLength) { + Fields.clear(); + return(RESULT_CORRUPT); + } + if (!Reserve(Content, header.StoredLength)) { + Fields.clear(); + return(RESULT_NO_MEMORY); + } + memcpy(Content.data(), stored, header.StoredLength); + } + + return(RESULT_OK); +} + + +// Reads the header and the field table only, so listing a folder of saves touches a +// few hundred bytes of each file. +SaveFileClass::ResultType SaveFileClass::Read_Fields(char const * path) +{ + Fields.clear(); + Content.clear(); + + if (path == nullptr) return(RESULT_MISSING); + + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return(RESULT_MISSING); + + unsigned char head[HEADER_SIZE]; + DWORD got = 0; + bool ok = (ReadFile(file, head, HEADER_SIZE, &got, nullptr) != FALSE); + + HeaderType header; + ResultType result = ok ? Parse_Header(head, got, header) : RESULT_CORRUPT; + + std::vector table; + if (result == RESULT_OK && header.TableLength > 0) { + DWORD const size = GetFileSize(file, nullptr); + if (size == INVALID_FILE_SIZE || header.TableLength > size - HEADER_SIZE) { + result = RESULT_CORRUPT; + } else if (!Reserve(table, header.TableLength)) { + result = RESULT_NO_MEMORY; + } else { + if (!Read_Range(file, table.data(), header.TableLength)) result = RESULT_CORRUPT; + } + } + CloseHandle(file); + + if (result != RESULT_OK) return(result); + if (Header_CRC(head, table.data(), (std::uint32_t)table.size()) != header.HeaderCRC) return(RESULT_CORRUPT); + + return(Parse_Fields(table.data(), (std::uint32_t)table.size())); +} diff --git a/code/savefile.h b/code/savefile.h new file mode 100644 index 000000000..2a25968e0 --- /dev/null +++ b/code/savefile.h @@ -0,0 +1,79 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include + +// The file a saved game is kept in: a fixed header, a table of listing fields, and one +// compressed block of game state. docs/SAVE-FORMAT.md records the layout. +class SaveFileClass +{ + public: + enum ResultType { + RESULT_OK, + RESULT_MISSING, // No file under that name. + RESULT_NOT_A_SAVE, // The file does not begin with the signature. + RESULT_UNSUPPORTED_VERSION, // A format version, or a header flag, this build does not read. + RESULT_CORRUPT, // A length, checksum or block that does not add up. + RESULT_WRITE_FAILED, // The file could not be written or moved into place. + RESULT_NO_MEMORY, // The file is within its limits but the process cannot hold it. + RESULT_TOO_LARGE, // The content or a listing field is more than a save can hold. + }; + + enum { + FORMAT_VERSION = 1, + HEADER_SIZE = 32, + }; + + SaveFileClass(void); + + void Set_String(int id, char const * text); + void Set_Int(int id, int value); + void Set_Time(int id, FILETIME const & time); + bool Get_String(int id, char * text, int size) const; + bool Get_Int(int id, int * value) const; + bool Get_Time(int id, FILETIME * time) const; + void Clear_Fields(void); + + ResultType Write(char const * path) const; + ResultType Read(char const * path); + ResultType Read_Fields(char const * path); + + static char const * Result_Text(ResultType result); + static std::uint32_t Checksum(unsigned char const * data, std::uint32_t length, std::uint32_t seed = 0); + + std::vector Content; + + private: + enum FieldKind { + FIELD_STRING = 1, + FIELD_INT = 2, + FIELD_TIME = 3, + }; + + struct FieldType { + int ID; + int Kind; + std::vector Bytes; + }; + + FieldType const * Find(int id, int kind) const; + void Set(int id, int kind, void const * data, std::size_t length); + void Serialize_Fields(std::vector & table) const; + ResultType Parse_Fields(unsigned char const * table, std::uint32_t length); + + std::vector Fields; +}; diff --git a/code/saveload.cpp b/code/saveload.cpp index 1353bf15b..df3ff9062 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -42,7 +42,6 @@ * Save_Misc_Values -- saves miscellaneous variables * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "saveload.h" @@ -68,6 +67,7 @@ #include "builtype.h" #include "bullet.h" #include "bullettype.h" +#include "classfactory.h" #include "data.h" #include "dbgprint.h" #include "deploymentconfig.h" @@ -79,7 +79,6 @@ #include "globals.h" #include "goptions.h" #include "houstype.h" -#include "ilinkstm.h" #include "infantry.h" #include "infatype.h" #include "init.h" @@ -93,10 +92,12 @@ #include "ovrlight.h" #include "particle.h" #include "partsys.h" +#include "persist.h" #include "psystype.h" #include "ptype.h" #include "revent.h" #include "rules.h" +#include "savefile.h" #include "savemgr.h" #include "savestream.h" #include "savever.h" @@ -143,6 +144,9 @@ #include "objheaps.hh" +#include +#include +#include #include //#define SAVE_BLOCK_SIZE 512 @@ -154,68 +158,165 @@ */ unsigned int ExpectedGameVersion = LoadOptionsClass::GAMEVER_OPENTS; -_COM_SMARTPTR_TYPEDEF(ILinkStream, __uuidof(ILinkStream)); + +/// +/// Writes one object to the save stream as a record of its own. A reader that does not +/// consume exactly the record's length has read a record of another shape than was +/// written, and a missing object fails the stream rather than leaving a gap where the +/// reader expects one. +/// +/// bool; Was the record written whole? +bool Save_Object(SaveStreamClass & stream, IPersistent * persist) +{ + if (persist == nullptr) { + stream.Fail(); + return(false); + } + + ClassID classid = persist->Class_ID(); + stream.Serialize_Bytes(&classid, sizeof(classid)); + unsigned int const lengthat = stream.Offset(); + unsigned int length = 0; + stream.Serialize(length); + unsigned int const start = stream.Offset(); + + bool result = persist->Save(stream, true); + if (!result) { + return(false); + } + + length = stream.Offset() - start; + stream.Overwrite_Bytes(lengthat, &length, sizeof(length)); + return(!stream.Was_Error()); +} + + +bool Save_Object(SaveStreamClass & stream, ILocomotion * locomotion) +{ + IPersistent * const persist = dynamic_cast(locomotion); + if (persist == nullptr) { + stream.Fail(); + return(false); + } + return(Save_Object(stream, persist)); +} + + +/// +/// Recreates one object from the save stream. It reattaches itself to its own heap as it +/// is constructed, so the caller is handed it only to keep or to refuse. +/// +/// Asked whether the object is of the class the caller expects, once +/// the record has been read and before the object takes its place. May be null when any +/// class will do. +/// The object, owned by the caller, or nothing with the stream failed when the +/// identifier names no registered class, the object could not read its record, the record's +/// length does not match what the object consumed, or the class is not the one asked +/// for. +std::unique_ptr Load_Object(SaveStreamClass & stream, bool (*accepts)(IPersistent const * object)) +{ + ClassID classid; + unsigned int length = 0; + stream.Serialize_Bytes(&classid, sizeof(classid)); + stream.Serialize(length); + if (stream.Was_Error()) { + return(nullptr); + } + + unsigned int const start = stream.Offset(); + if (length > stream.Size() - start) { + DebugString("Save record at %u claims %u bytes, past the end of the save\n", start, length); + stream.Fail(); + return(nullptr); + } + + SwizzleManagerClass::MarkType const mark = Swizzler.Mark(); + std::unique_ptr persist = Create_Object(classid); + if (persist == nullptr) { + DebugString("Save record at %u names a class this build does not register\n", start); + stream.Fail(); + return(nullptr); + } + + bool ok; + { + SaveStreamClass::BoundScope const bound(stream, start + length); + ok = persist->Load(stream); + } + if (ok && stream.Offset() != start + length) { + DebugString("Save record of %s at %u is %u bytes but %u were read\n", + typeid(*persist).name(), start, length, stream.Offset() - start); + ok = false; + } + if (ok && accepts != nullptr && !accepts(persist.get())) { + DebugString("Save record of %s at %u is not the class expected there\n", + typeid(*persist).name(), start); + ok = false; + } + if (!ok) { + Swizzler.Abandon(mark); + stream.Fail(); + return(nullptr); + } + + persist->Post_Load(); + return(persist); +} /// /// Loads a vector of persistent objects from the save game stream. -/// This routine reads the element count and then recreates each object through OLE. The -/// objects are not handed back -- each one reattaches itself to its own heap as it is -/// constructed, which is what refills the game's vectors. +/// The objects are not handed back -- each one reattaches itself to its own heap as it is +/// constructed, which is what refills the game's vectors. A record naming any class other +/// than the heap's fails the load, since nothing else belongs in that heap. /// -/// Returns with S_OK, or the failure code of the read that went wrong. -__forceinline HRESULT Load_Vector(IStream * stream) +/// bool; Was the record read whole? +template +static bool Load_Vector(SaveStreamClass & stream) { - int count; - int index; - LPVOID obj; - - HRESULT result = stream->Read(&count, sizeof(count), NULL); - if (FAILED(result)) { - return(result); - } - for (index = 0; index < count; index++) { - result = OleLoadFromStream(stream, IID_IUnknown, &obj); - if (FAILED(result)) { - return(result); + int count = 0; + stream.Serialize(count); + if (stream.Was_Error()) { + return(false); + } + if (count < 0) { + stream.Fail(); + return(false); + } + + for (int index = 0; index < count; index++) { + std::unique_ptr object = Load_Object_As(stream); + if (object == nullptr) { + return(false); } + // The object attached itself to its own heap as it was constructed, and the heap + // is what deletes it from here on. + object.release(); } - return(S_OK); + return(true); } /// /// Saves a vector of persistent objects to the save game stream. -/// This routine writes the element count and then streams out each object in turn through -/// its IPersistStream interface. /// -/// Returns with S_OK, or the failure code of the first object that refused to -/// save. +/// bool; Was the record read whole? template -__forceinline HRESULT Save_Vector(IStream * stream, const DynamicVectorClass &list) +static bool Save_Vector(SaveStreamClass & stream, const DynamicVectorClass &list) { int count = list.Count(); - HRESULT result = stream->Write(&count, sizeof(count), NULL); - if (SUCCEEDED(result)) { - for (int index = 0; index < count; index++) { - LPPERSISTSTREAM lpPS = NULL; - result = list[index]->QueryInterface(IID_IPersistStream, (LPVOID *)&lpPS); - if (FAILED(result)) { - return(result); - } - result = OleSaveToStream(lpPS, stream); - if (FAILED(result)) { - return(result); - } - result = lpPS->Release(); - if (FAILED(result)) { - return(result); - } + stream.Serialize(count); + + for (int index = 0; index < count; index++) { + bool const result = Save_Object(stream, list[index]); + if (!result) { + return(false); } - result = S_OK; } - return(result); + return(!stream.Was_Error()); } + + /// /// Builds a checksum over the whole of the game object state. /// This routine walks the scenario and every object and type heap, folding each one's own @@ -301,7 +402,7 @@ void Print_Heap_CRCs(FILE * fp) * HISTORY: * * 07/08/1996 JLB : Created. * *=============================================================================================*/ -static bool Put_All(IStream *stream, int save_net) +static bool Put_All(SaveStreamClass & stream, int save_net) { /* ** Save the scenario global information. @@ -311,7 +412,7 @@ static bool Put_All(IStream *stream, int save_net) Rule->Save(stream); DebugString("Saving AnimTypes\n"); - if (FAILED(Save_Vector(stream, AnimTypes))) { + if (!Save_Vector(stream, AnimTypes)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -320,13 +421,13 @@ static bool Put_All(IStream *stream, int save_net) ** Save the map. The map must be saved first, since it saves the Theater. */ DebugString("Saving Map\n"); - if (FAILED(Map.Save(stream))) { + if (!Map.Save(stream)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Tunnels\n"); - if (FAILED(Save_Vector(stream, Tubes))) { + if (!Save_Vector(stream, Tubes)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -335,7 +436,7 @@ static bool Put_All(IStream *stream, int save_net) ** Save miscellaneous variables. */ DebugString("Saving Misc. Values\n"); - if (FAILED(Save_Misc_Values(stream))) { + if (!Save_Misc_Values(stream)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -344,13 +445,13 @@ static bool Put_All(IStream *stream, int save_net) ** Save the Logic & Map layers */ DebugString("Saving Logic\n"); - if (FAILED(Logic.Save(stream))) { + if (!Logic.Save(stream)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TacticalMap\n"); - if (FAILED(OleSaveToStream(TacticalMap, stream))) { + if (!Save_Object(stream, TacticalMap)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -360,248 +461,248 @@ static bool Put_All(IStream *stream, int save_net) ** TFixedIHeap class. */ DebugString("Saving HouseTypes\n"); - if (FAILED(Save_Vector(stream, HouseTypes))) { + if (!Save_Vector(stream, HouseTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Houses\n"); - if (FAILED(Save_Vector(stream, Houses))) { + if (!Save_Vector(stream, Houses)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Units\n"); - if (FAILED(Save_Vector(stream, Units))) { + if (!Save_Vector(stream, Units)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving UnitTypes\n"); - if (FAILED(Save_Vector(stream, UnitTypes))) { + if (!Save_Vector(stream, UnitTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving InfantryTypes\n"); - if (FAILED(Save_Vector(stream, InfantryTypes))) { + if (!Save_Vector(stream, InfantryTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Infantry\n"); - if (FAILED(Save_Vector(stream, Infantry))) { + if (!Save_Vector(stream, Infantry)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving BuildingTypes\n"); - if (FAILED(Save_Vector(stream, BuildingTypes))) { + if (!Save_Vector(stream, BuildingTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Buildings\n"); - if (FAILED(Save_Vector(stream, Buildings))) { + if (!Save_Vector(stream, Buildings)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving AircraftTypes\n"); - if (FAILED(Save_Vector(stream, AircraftTypes))) { + if (!Save_Vector(stream, AircraftTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Aircraft\n"); - if (FAILED(Save_Vector(stream, Aircraft))) { + if (!Save_Vector(stream, Aircraft)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Anims\n"); - if (FAILED(Save_Vector(stream, Anims))) { + if (!Save_Vector(stream, Anims)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TaskForces\n"); - if (FAILED(Save_Vector(stream, TaskForces))) { + if (!Save_Vector(stream, TaskForces)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TeamTypes\n"); - if (FAILED(Save_Vector(stream, TeamTypes))) { + if (!Save_Vector(stream, TeamTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Teams\n"); - if (FAILED(Save_Vector(stream, Teams))) { + if (!Save_Vector(stream, Teams)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ScriptTypes\n"); - if (FAILED(Save_Vector(stream, ScriptTypes))) { + if (!Save_Vector(stream, ScriptTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Scripts\n"); - if (FAILED(Save_Vector(stream, Scripts))) { + if (!Save_Vector(stream, Scripts)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TagTypes\n"); - if (FAILED(Save_Vector(stream, TagTypes))) { + if (!Save_Vector(stream, TagTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Tags\n"); - if (FAILED(Save_Vector(stream, Tags))) { + if (!Save_Vector(stream, Tags)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TriggerTypes\n"); - if (FAILED(Save_Vector(stream, TriggerTypes))) { + if (!Save_Vector(stream, TriggerTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Triggers\n"); - if (FAILED(Save_Vector(stream, Triggers))) { + if (!Save_Vector(stream, Triggers)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving AITriggerTypes\n"); - if (FAILED(Save_Vector(stream, AITriggerTypes))) { + if (!Save_Vector(stream, AITriggerTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Actions\n"); - if (FAILED(Save_Vector(stream, Actions))) { + if (!Save_Vector(stream, Actions)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Events\n"); - if (FAILED(Save_Vector(stream, Events))) { + if (!Save_Vector(stream, Events)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Factories\n"); - if (FAILED(Save_Vector(stream, Factories))) { + if (!Save_Vector(stream, Factories)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving VoxelAnimTypes\n"); - if (FAILED(Save_Vector(stream, VoxelAnimTypes))) { + if (!Save_Vector(stream, VoxelAnimTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving VoxelAnims\n"); - if (FAILED(Save_Vector(stream, VoxelAnims))) { + if (!Save_Vector(stream, VoxelAnims)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Warheads\n"); - if (FAILED(Save_Vector(stream, Warheads))) { + if (!Save_Vector(stream, Warheads)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Weapons\n"); - if (FAILED(Save_Vector(stream, Weapons))) { + if (!Save_Vector(stream, Weapons)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ParticleTypes\n"); - if (FAILED(Save_Vector(stream, ParticleTypes))) { + if (!Save_Vector(stream, ParticleTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Particles\n"); - if (FAILED(Save_Vector(stream, Particles))) { + if (!Save_Vector(stream, Particles)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ParticleSystemTypes\n"); - if (FAILED(Save_Vector(stream, ParticleSystemTypes))) { + if (!Save_Vector(stream, ParticleSystemTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ParticleSystems\n"); - if (FAILED(Save_Vector(stream, ParticleSystems))) { + if (!Save_Vector(stream, ParticleSystems)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving BulletTypes\n"); - if (FAILED(Save_Vector(stream, BulletTypes))) { + if (!Save_Vector(stream, BulletTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Bullets\n"); - if (FAILED(Save_Vector(stream, Bullets))) { + if (!Save_Vector(stream, Bullets)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving WaypointPaths\n"); - if (FAILED(Save_Vector(stream, WaypointPaths))) { + if (!Save_Vector(stream, WaypointPaths)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving SmudgeTypes\n"); - if (FAILED(Save_Vector(stream, SmudgeTypes))) { + if (!Save_Vector(stream, SmudgeTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving OverlayTypes\n"); - if (FAILED(Save_Vector(stream, OverlayTypes))) { + if (!Save_Vector(stream, OverlayTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving LightSources\n"); - if (FAILED(Save_Vector(stream, LightSources))) { + if (!Save_Vector(stream, LightSources)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving BuildingLights\n"); - if (FAILED(Save_Vector(stream, BuildingLights))) { + if (!Save_Vector(stream, BuildingLights)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Sides\n"); - if (FAILED(Save_Vector(stream, Sides))) { + if (!Save_Vector(stream, Sides)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Tiberiums\n"); - if (FAILED(Save_Vector(stream, Tiberiums))) { + if (!Save_Vector(stream, Tiberiums)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Empulses\n"); - if (FAILED(Save_Vector(stream, EMPulseClass::EMPulses))) { + if (!Save_Vector(stream, EMPulseClass::EMPulses)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving SuperWeaponTypes\n"); - if (FAILED(Save_Vector(stream, SuperWeaponTypes))) { + if (!Save_Vector(stream, SuperWeaponTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving SuperWeapons\n"); - if (FAILED(Save_Vector(stream, SuperWeapons))) { + if (!Save_Vector(stream, SuperWeapons)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TerrianTypes\n"); - if (FAILED(Save_Vector(stream, TerrainTypes))) { + if (!Save_Vector(stream, TerrainTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Terrains\n"); - if (FAILED(Save_Vector(stream, Terrains))) { + if (!Save_Vector(stream, Terrains)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving FoggedObjects\n"); - if (FAILED(Save_Vector(stream, FoggedObjectClass::FoggyObjects))) { + if (!Save_Vector(stream, FoggedObjectClass::FoggyObjects)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving AlphaShapes\n"); - if (FAILED(Save_Vector(stream, AlphaShapes))) { + if (!Save_Vector(stream, AlphaShapes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Waves\n"); - if (FAILED(Save_Vector(stream, Waves))) { + if (!Save_Vector(stream, Waves)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -628,7 +729,7 @@ static bool Put_All(IStream *stream, int save_net) } } - return(true); + return(!stream.Was_Error()); } @@ -640,7 +741,7 @@ static bool Put_All(IStream *stream, int save_net) /// order they were written out. /// /// bool; Was the game state restored? -static bool Get_All(IStream *stream, bool save_net) +static bool Get_All(SaveStreamClass & stream, bool save_net) { Clear_Scenario(); Scen->Load(stream); @@ -685,177 +786,184 @@ static bool Get_All(IStream *stream, bool save_net) return(false); } - if (FAILED(Load_Vector(stream))) { /// AnimTypes + if (!Load_Vector(stream)) { /// AnimTypes return(false); } - Map.Load(stream); + if (!Map.Load(stream)) { + return(false); + } - if (FAILED(Load_Vector(stream))) { /// Tubes + if (!Load_Vector(stream)) { /// Tubes return(false); } - if (FAILED(Load_Misc_Values(stream))) { + if (!Load_Misc_Values(stream)) { return(false); } Map.Reset_All_Subzones(); - Logic.Load(stream); + if (!Logic.Load(stream)) { + return(false); + } if (TacticalMap != NULL) { delete TacticalMap; TacticalMap = NULL; } - Tactical * old_tactical; - if (FAILED(OleLoadFromStream(stream, IID_IUnknown, (LPVOID *)&old_tactical))) { + std::unique_ptr tactical = Load_Object_As(stream); + if (tactical == nullptr) { return(false); } + // The map installed itself in TacticalMap as it was constructed, and that global is + // what deletes it from here on. + tactical.release(); - if (FAILED(Load_Vector(stream))) { /// HouseTypes + if (!Load_Vector(stream)) { /// HouseTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Houses + if (!Load_Vector(stream)) { /// Houses return(false); } - if (FAILED(Load_Vector(stream))) { /// Units + if (!Load_Vector(stream)) { /// Units return(false); } - if (FAILED(Load_Vector(stream))) { /// UnitTypes + if (!Load_Vector(stream)) { /// UnitTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// InfantryTypes + if (!Load_Vector(stream)) { /// InfantryTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Infantry + if (!Load_Vector(stream)) { /// Infantry return(false); } - if (FAILED(Load_Vector(stream))) { /// BuildingTypes + if (!Load_Vector(stream)) { /// BuildingTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Buildings + if (!Load_Vector(stream)) { /// Buildings return(false); } - if (FAILED(Load_Vector(stream))) { /// AircraftTypes + if (!Load_Vector(stream)) { /// AircraftTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Aircraft + if (!Load_Vector(stream)) { /// Aircraft return(false); } - if (FAILED(Load_Vector(stream))) { /// Anims + if (!Load_Vector(stream)) { /// Anims return(false); } - if (FAILED(Load_Vector(stream))) { /// TaskForces + if (!Load_Vector(stream)) { /// TaskForces return(false); } - if (FAILED(Load_Vector(stream))) { /// TeamTypes + if (!Load_Vector(stream)) { /// TeamTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Teams + if (!Load_Vector(stream)) { /// Teams return(false); } - if (FAILED(Load_Vector(stream))) { /// ScriptTypes + if (!Load_Vector(stream)) { /// ScriptTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Scripts + if (!Load_Vector(stream)) { /// Scripts return(false); } - if (FAILED(Load_Vector(stream))) { /// TagTypes + if (!Load_Vector(stream)) { /// TagTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Tags + if (!Load_Vector(stream)) { /// Tags return(false); } - if (FAILED(Load_Vector(stream))) { /// TriggerTypes + if (!Load_Vector(stream)) { /// TriggerTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Triggers + if (!Load_Vector(stream)) { /// Triggers return(false); } - if (FAILED(Load_Vector(stream))) { /// AITriggerTypes + if (!Load_Vector(stream)) { /// AITriggerTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Actions + if (!Load_Vector(stream)) { /// Actions return(false); } - if (FAILED(Load_Vector(stream))) { /// Events + if (!Load_Vector(stream)) { /// Events return(false); } - if (FAILED(Load_Vector(stream))) { /// Factories + if (!Load_Vector(stream)) { /// Factories return(false); } - if (FAILED(Load_Vector(stream))) { /// VoxelAnimTypes + if (!Load_Vector(stream)) { /// VoxelAnimTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// VoxelAnims + if (!Load_Vector(stream)) { /// VoxelAnims return(false); } - if (FAILED(Load_Vector(stream))) { /// Warheads + if (!Load_Vector(stream)) { /// Warheads return(false); } - if (FAILED(Load_Vector(stream))) { /// Weapons + if (!Load_Vector(stream)) { /// Weapons return(false); } - if (FAILED(Load_Vector(stream))) { /// ParticleTypes + if (!Load_Vector(stream)) { /// ParticleTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Particles + if (!Load_Vector(stream)) { /// Particles return(false); } - if (FAILED(Load_Vector(stream))) { /// ParticleSystemTypes + if (!Load_Vector(stream)) { /// ParticleSystemTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// ParticleSystems + if (!Load_Vector(stream)) { /// ParticleSystems return(false); } - if (FAILED(Load_Vector(stream))) { /// BulletTypes + if (!Load_Vector(stream)) { /// BulletTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Bullets + if (!Load_Vector(stream)) { /// Bullets return(false); } - if (FAILED(Load_Vector(stream))) { /// WaypointPaths + if (!Load_Vector(stream)) { /// WaypointPaths return(false); } - if (FAILED(Load_Vector(stream))) { /// SmudgeTypes + if (!Load_Vector(stream)) { /// SmudgeTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// OverlayTypes + if (!Load_Vector(stream)) { /// OverlayTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// LightSources + if (!Load_Vector(stream)) { /// LightSources return(false); } - if (FAILED(Load_Vector(stream))) { /// BuildingLights + if (!Load_Vector(stream)) { /// BuildingLights return(false); } - if (FAILED(Load_Vector(stream))) { /// Sides + if (!Load_Vector(stream)) { /// Sides return(false); } - if (FAILED(Load_Vector(stream))) { /// Tiberiums + if (!Load_Vector(stream)) { /// Tiberiums return(false); } - if (FAILED(Load_Vector(stream))) { /// EMPulseClass::EMPulses + if (!Load_Vector(stream)) { /// EMPulseClass::EMPulses return(false); } - if (FAILED(Load_Vector(stream))) { /// SuperWeaponTypes + if (!Load_Vector(stream)) { /// SuperWeaponTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// SuperWeapons + if (!Load_Vector(stream)) { /// SuperWeapons return(false); } - if (FAILED(Load_Vector(stream))) { /// TerrainTypes + if (!Load_Vector(stream)) { /// TerrainTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Terrains + if (!Load_Vector(stream)) { /// Terrains return(false); } - if (FAILED(Load_Vector(stream))) { /// FoggedObjectClass::FoggyObjects + if (!Load_Vector(stream)) { /// FoggedObjectClass::FoggyObjects return(false); } - if (FAILED(Load_Vector(stream))) { /// AlphaShapes + if (!Load_Vector(stream)) { /// AlphaShapes return(false); } - if (FAILED(Load_Vector(stream))) { /// Waves + if (!Load_Vector(stream)) { /// Waves return(false); } if (!VeinholeMonsterClass::Load_All(stream)) { @@ -875,7 +983,7 @@ static bool Get_All(IStream *stream, bool save_net) Map.Flag_To_Redraw(GS_REDRAW_ALL); - return(true); + return(!stream.Was_Error()); } /*************************************************************************** @@ -919,34 +1027,10 @@ static bool Get_All(IStream *stream, bool save_net) *=========================================================================*/ bool Save_Game(const char *file_name, char const * descr) { - WCHAR name[MAX_PATH]; - DebugString("\nSAVING GAME [%s - %s]\n", file_name, descr); Swizzler.Begin_Save(); - MultiByteToWideChar(0,0, Saved_Game_Name(file_name).c_str(), -1, name, sizeof(name)/sizeof(WCHAR)); - - /* - ** Open the file - */ - DebugString("Creating DocFile\n"); - IStoragePtr storage; - if (FAILED(StgCreateDocfile(name, STGM_CREATE|STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, &storage))) { - DebugString("\t***** FAILED!\n"); - return(false); - } - - - /* - ** Save the description, scenario #, and house - ** (scenario # & house are saved separately from the actual Scenario & - ** PlayerPtr globals for convenience; we can quickly find out which - ** house & scenario this save-game file is for by reading these values. - ** Also, PlayerPtr is stored in a coded form in Save_Misc_Values(), - ** which may or may not be a HousesType number; so, saving 'house' - ** here ensures we can always pull out the house for this file.) - */ SaveVersionInfo info; info.Set_Internal_Version(ExpectedGameVersion); info.Set_Scenario_Description(descr); @@ -956,66 +1040,37 @@ bool Save_Game(const char *file_name, char const * descr) info.Set_Scenario_Number(Scen->Scenario); info.Set_Executable_Name("SUN.EXE"); info.Set_Game_Type(Session.Type); + FILETIME FileTime; - CoFileTimeNow(&FileTime); + GetSystemTimeAsFileTime(&FileTime); info.Set_Last_Time(FileTime); info.Set_Start_Time(FileTime); info.Set_Play_Time(FileTime); - /* - ** Save the save-game version, for loading verification - */ - DebugString("Saving version information\n"); - if (FAILED(info.Save(storage))) { - DebugString("\t***** FAILED!\n"); - return(false); - } - - DebugString("Creating content stream\n"); - IStreamPtr content; - if (FAILED(storage->CreateStream(L"CONTENTS", STGM_CREATE|STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &content))) { - DebugString("\t***** FAILED!\n"); - return(false); - } + SaveFileClass file; + info.Save(file); - DebugString("Linking content stream to compressor\n"); - ILinkStreamPtr link; - link.CreateInstance(CLSID_CompressStream, NULL, CLSCTX_INPROC|CLSCTX_LOCAL_SERVER); - if (FAILED(link->Link_Stream(content))) { - DebugString("\t***** FAILED!\n"); - return(false); - } - IStreamPtr stream(link); - - /* - ** Dump the save game data to the file. The data is compressed - ** and then encrypted. The message digest is calculated in the - ** process by using the data just as it is written to disk. - */ DebugString("Calling Put_All()\n"); - bool res = Put_All(stream,0); - - DebugString("Unlinking content stream from compressor\n"); - if (FAILED(link->Unlink_Stream(NULL))) { + SaveStreamClass stream(file.Content, SaveStreamClass::MODE_SAVE); + bool res = Put_All(stream, 0); + if (!res) { DebugString("\t***** FAILED!\n"); - return(false); } - DebugString("Releasing content stream\n"); - content.Release(); - - DebugString("Closing DocFile\n"); - if (FAILED(storage->Commit(0))) { - DebugString("\t***** FAILED!\n"); - return(false); + if (res) { + DebugString("Writing %s\n", file_name); + SaveFileClass::ResultType const result = file.Write(Saved_Game_Name(file_name).c_str()); + if (result != SaveFileClass::RESULT_OK) { + DebugString("\t***** FAILED! (%s)\n", SaveFileClass::Result_Text(result)); + res = false; + } } - DebugString("SAVING GAME [%s - %s] - Complete\n\n", file_name, descr); + DebugString("SAVING GAME [%s - %s] - %s\n\n", file_name, descr, res ? "Complete" : "Failed"); if (res) { SaveManager.Autosave.Schedule(Frame); } - return(res); } @@ -1062,62 +1117,54 @@ bool Save_Game(const char *file_name, char const * descr) *=========================================================================*/ bool Load_Game(const char *file_name) { - WCHAR name[MAX_PATH]; - DebugString("\nLOADING GAME [%s]\n", file_name); - /* - ** Read & discard the save-game's header info - */ - SaveVersionInfo info; - if (!Get_Savefile_Info(file_name, &info)) { + // The whole file is checked before the running game is torn down, so a damaged + // save costs nothing. The listing fields come back with it, so the version this + // build will not read is judged on the same read rather than on a second one. + SaveFileClass file; + SaveFileClass::ResultType const result = file.Read(Saved_Game_Name(file_name).c_str()); + if (result != SaveFileClass::RESULT_OK) { + DebugString("\t***** FAILED! (%s)\n", SaveFileClass::Result_Text(result)); return(false); } - /* - * The load dialog screens the saves it lists, but a network save reaches this routine - * without passing through it, so the stamp is checked here as well. - */ + SaveVersionInfo info; + if (!info.Load(file)) { + return(false); + } if (info.Get_Internal_Version() != ExpectedGameVersion) { return(false); } - LoadedSaveVersion = info.Get_Internal_Version(); + LoadedSaveVersion = info.Get_Internal_Version(); Session.Type = (GameType)info.Get_Game_Type(); - Swizzler.Discard(); - - /* - ** Open the file - */ - IStoragePtr storage; - - // Structured storage goes straight to Windows, so the saved game is named in full first. - MultiByteToWideChar(0,0,Saved_Game_Name(file_name).c_str(), -1, name, (sizeof(name)/sizeof(WCHAR))); - if (FAILED(StgOpenStorage(name, 0, STGM_SHARE_DENY_WRITE, 0, 0, &storage))) { - return(false); - } - - IStreamPtr content; - if (FAILED(storage->OpenStream(L"CONTENTS", 0, STGM_SHARE_EXCLUSIVE, 0, &content))) { - return(false); - } + Swizzler.Discard(); - IUnknown *pUnknown = NULL; - ILinkStreamPtr link; - link.CreateInstance(CLSID_CompressStream, pUnknown,CLSCTX_INPROC|CLSCTX_LOCAL_SERVER); - if (FAILED(link->Link_Stream(content))) { - return(false); + SaveStreamClass stream(file.Content, SaveStreamClass::MODE_LOAD); + bool res = false; + // The catch sits here rather than around the whole routine because what was already + // loaded still has to be abandoned below. Both of the ways a count read from the file + // can end an allocation are refused here; anything else still raises. + try { + res = Get_All(stream, false); + } catch (std::bad_alloc const &) { + DebugString("\t***** FAILED! (out of memory at %u of %u bytes)\n", stream.Offset(), stream.Size()); + } catch (std::length_error const &) { + DebugString("\t***** FAILED! (a count no container can hold at %u of %u bytes)\n", + stream.Offset(), stream.Size()); } - IStreamPtr stream(link); - - bool res = Get_All(stream, false); - - link->Unlink_Stream(NULL); - if (!res) { + DebugString("\t***** FAILED! (at %u of %u bytes)\n", stream.Offset(), stream.Size()); + // What was loaded stays in the heaps until the next teardown, so the requests it + // registered must not be answered into it once the game that follows has moved on. + Swizzler.Discard(); return(false); } + if (stream.Offset() != stream.Size()) { + DebugString("Save carries %u bytes past its last record\n", stream.Size() - stream.Offset()); + } Swizzler.Resolve(); @@ -1211,11 +1258,10 @@ static void Serialize_Misc_Values(SaveStreamClass & stream) } -int Save_Misc_Values(IStream * stream) +int Save_Misc_Values(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize_Misc_Values(savestream); - return(savestream.Result()); + Serialize_Misc_Values(stream); + return(!stream.Was_Error()); } @@ -1232,12 +1278,11 @@ int Save_Misc_Values(IStream * stream) * 06/24/1995 BRR : Created. * * 03/12/1996 JLB : Simplified. * *=============================================================================================*/ -int Load_Misc_Values(IStream * stream) +int Load_Misc_Values(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("Load_Misc_Values"); - Serialize_Misc_Values(savestream); - return(savestream.Result()); + stream.Set_Context("Load_Misc_Values"); + Serialize_Misc_Values(stream); + return(!stream.Was_Error()); } @@ -1261,23 +1306,20 @@ int Load_Misc_Values(IStream * stream) *=========================================================================*/ bool Get_Savefile_Info(char const * name, SaveVersionInfo * info) { - IStoragePtr storage; - WCHAR wname[MAX_PATH]; - - // Structured storage goes straight to Windows, so the saved game is named in full first. - MultiByteToWideChar(0, 0, Saved_Game_Name(name).c_str(), -1, wname, sizeof(wname) / sizeof(WCHAR)); - - HRESULT result = StgOpenStorage(wname, NULL, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, NULL, 0, &storage); - if (FAILED(result)) { + if (name == nullptr || info == nullptr) { return(false); } - result = info->Load(storage); - if (FAILED(result)) { + SaveFileClass file; + SaveFileClass::ResultType const result = file.Read_Fields(Saved_Game_Name(name).c_str()); + if (result != SaveFileClass::RESULT_OK) { + if (result != SaveFileClass::RESULT_MISSING) { + DebugString("Saved game %s: %s\n", name, SaveFileClass::Result_Text(result)); + } return(false); } - return(true); + return(info->Load(file)); } diff --git a/code/saveload.h b/code/saveload.h index c92bc5df8..556fe04d9 100644 --- a/code/saveload.h +++ b/code/saveload.h @@ -13,16 +13,49 @@ #pragma once +#include "persist.h" + #include +#include -struct IStream; +class SaveStreamClass; class SaveVersionInfo; +struct ILocomotion; /* ** SAVELOAD.CPP */ -int Load_Misc_Values(IStream * stream); -int Save_Misc_Values(IStream * stream); +int Load_Misc_Values(SaveStreamClass & stream); +int Save_Misc_Values(SaveStreamClass & stream); + +// A loaded object is handed back owned; one that belongs to a heap is released there by +// the caller that puts it in one. docs/SAVE-FORMAT.md records what a record holds. +bool Save_Object(SaveStreamClass & stream, IPersistent * object); +bool Save_Object(SaveStreamClass & stream, ILocomotion * locomotion); +std::unique_ptr Load_Object(SaveStreamClass & stream, + bool (*accepts)(IPersistent const * object) = nullptr); + +/// +/// Loads the record next in the stream and requires it to be of the class asked for. +/// +/// The object, owned by the caller, or nothing with the stream failed when the +/// record holds another class. A record of the wrong class is destroyed before it can take +/// its place, so the test happens while the object is still only the reader's. +template +std::unique_ptr Load_Object_As(SaveStreamClass & stream) +{ + std::unique_ptr object = Load_Object(stream, [](IPersistent const * candidate) { + return(dynamic_cast(candidate) != nullptr); + }); + + // The record was accepted only if it holds a T, so this cast answers for what was loaded. + T * const wanted = dynamic_cast(object.get()); + if (wanted != nullptr) { + object.release(); + } + return(std::unique_ptr(wanted)); +} + bool Get_Savefile_Info(char const * name, SaveVersionInfo * info); bool Save_Game(const char *file_name, char const * descr); bool Load_Game(const char *file_name); diff --git a/code/savestream.cpp b/code/savestream.cpp index fd15e72f6..ca5e7d1d3 100644 --- a/code/savestream.cpp +++ b/code/savestream.cpp @@ -7,26 +7,30 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "savestream.h" #include "saveload.h" +#include + unsigned int LoadedSaveVersion = 0; /// -/// Builds a save stream over the stream given. +/// Builds a save stream over the buffer given, appending to it when saving and reading +/// it from the start when loading. /// -/// The stream the members are to be read from or written to. +/// The bytes of the saved game, which must outlive this stream. /// Is this stream saving or loading? -SaveStreamClass::SaveStreamClass(IStream * stream, ModeType mode) : - Stream(stream), +SaveStreamClass::SaveStreamClass(std::vector & buffer, ModeType mode) : + Buffer(&buffer), + Cursor(mode == MODE_SAVE ? (unsigned int)buffer.size() : 0), + Limit((unsigned int)buffer.size()), Mode(mode), - ErrorCode(stream != NULL ? S_OK : E_POINTER), + Failed(false), FormatVersion(mode == MODE_LOAD ? LoadedSaveVersion : ExpectedGameVersion), OwnerType(NULL), OwnerID(0) @@ -34,51 +38,59 @@ SaveStreamClass::SaveStreamClass(IStream * stream, ModeType mode) : } -/// -/// Stops the pass, as though the stream itself had failed. -/// This is for a record that reads back as something no save could hold -- a length that -/// is negative, or one that does not fit the object waiting for it. An earlier failure is -/// left in place, since it is the one that explains the rest. -/// void SaveStreamClass::Fail(void) { - if (SUCCEEDED(ErrorCode)) { - ErrorCode = E_FAIL; - } + Failed = true; } /// -/// Moves a block of bytes between the object and the stream. -/// Every other Serialize reaches the stream through this one. Once something has gone -/// wrong the block is left alone and the failure is kept, so the rest of the pass runs -/// harmlessly and the caller finds out at the end. +/// Moves the bytes of one value between the caller and the stream. +/// A load that runs out of stream in the middle of a value fails the stream rather than +/// hand back a partly read value, and every later call is ignored. A negative length is +/// a count that wrapped, and fails the same way. /// -/// The bytes to write, or the place to read them back into. -/// The number of bytes to move. void SaveStreamClass::Serialize_Bytes(void * data, int length) { - if (FAILED(ErrorCode)) { + if (Failed) { + return; + } + if (length < 0) { + Failed = true; + return; + } + if (length == 0) { return; } - ULONG moved = 0; - HRESULT result; + unsigned char * const bytes = (unsigned char *)data; if (Mode == MODE_SAVE) { - result = Stream->Write(data, length, &moved); + Buffer->insert(Buffer->end(), bytes, bytes + length); + Cursor = (unsigned int)Buffer->size(); } else { - result = Stream->Read(data, length, &moved); + // The bound is the record being read rather than the whole stream, so a member that + // reads more than its own is refused instead of quietly spending the bytes of the + // record after it. + if ((unsigned int)length > Limit - Cursor) { + Failed = true; + return; + } + memcpy(bytes, Buffer->data() + Cursor, (std::size_t)length); + Cursor += (unsigned int)length; } +} - /* - * A stream that stops early has run out in the middle of an object, which leaves - * the rest of the members holding whatever they held before. Treat it as a failure - * rather than let a half-read object reach the game. - */ - if (SUCCEEDED(result) && moved != (ULONG)length) { - result = E_FAIL; - } - ErrorCode = result; +// A saver patches a length it could not know until the record was written. +void SaveStreamClass::Overwrite_Bytes(unsigned int offset, void const * data, int length) +{ + if (Failed || Mode != MODE_SAVE || length <= 0) { + return; + } + if (offset > Buffer->size() || (unsigned int)length > Buffer->size() - offset) { + Failed = true; + return; + } + memcpy(Buffer->data() + offset, data, (std::size_t)length); } diff --git a/code/savestream.h b/code/savestream.h index 0d7fc56c2..9655cbde3 100644 --- a/code/savestream.h +++ b/code/savestream.h @@ -15,7 +15,9 @@ #include "win.h" #include +#include #include +#include #include #include #include @@ -73,7 +75,7 @@ class SaveStreamClass MODE_LOAD }; - SaveStreamClass(IStream * stream, ModeType mode); + SaveStreamClass(std::vector & buffer, ModeType mode); bool Is_Saving(void) const {return(Mode == MODE_SAVE);} bool Is_Loading(void) const {return(Mode == MODE_LOAD);} @@ -83,8 +85,7 @@ class SaveStreamClass * does nothing, so a class lists its members without checking each one and the * caller asks once whether the whole pass worked. */ - HRESULT Result(void) const {return(ErrorCode);} - bool Was_Error(void) const {return(FAILED(ErrorCode));} + bool Was_Error(void) const {return(Failed);} /* * Stops the pass here. A container that reads back a length no honest save could @@ -99,11 +100,6 @@ class SaveStreamClass */ unsigned int Version(void) const {return(FormatVersion);} - /* - * The stream underneath, for the sub-objects that are still framed by OLE. - */ - IStream * Get_Stream(void) const {return(Stream);} - /* * Names the record this stream is carrying, so that a pointer which nothing * answers for can be reported against the object that asked for it. @@ -113,9 +109,86 @@ class SaveStreamClass OwnerType = ownertype; OwnerID = ownerid; } + char const * Context_Type(void) const {return(OwnerType);} + SwizzleIDType Context_ID(void) const {return(OwnerID);} + + /* + * Where the next byte goes or comes from, so a record can be framed by its length. + */ + unsigned int Offset(void) const {return(Cursor);} + + /* + * Holds a load to one record while it is read, so that a member reading more than + * its record holds is refused rather than spending the bytes of the record after + * it. A record nested in another leaves the outer one bounded as it was. + */ + class BoundScope + { + public: + BoundScope(SaveStreamClass & stream, unsigned int end) + : Stream(stream), Previous(stream.Limit) + { + if (end <= Stream.Limit) { + Stream.Limit = end; + } + } + + ~BoundScope(void) {Stream.Limit = Previous;} + + BoundScope(BoundScope const &) = delete; + BoundScope & operator=(BoundScope const &) = delete; + + private: + SaveStreamClass & Stream; + unsigned int Previous; + }; + unsigned int Size(void) const {return((unsigned int)Buffer->size());} + void Overwrite_Bytes(unsigned int offset, void const * data, int length); void Serialize_Bytes(void * data, int length); + /* + * Refuses a count that the bytes left in the stream could not hold, so a damaged + * count fails the load before anything is allocated for it. Nothing serializes + * an element in less than a byte. + */ + bool Fits(int count, std::size_t each) + { + if (Is_Loading()) { + std::size_t const room = (std::size_t)(Limit - Cursor) / (each > 0 ? each : 1); + if (count < 0 || (std::size_t)count > room) { + Fail(); + return(false); + } + } + return(true); + } + + /* + * Sizes a container the count asked for, failing the pass rather than throwing when + * the process cannot hold it. A count within the bytes remaining still asks for that + * many elements, which is more memory than the stream itself occupies, and for a + * wide element more than the container itself will hold. + */ + template + bool Reserve(C & container, int count) + { + if (count < 0 || (std::size_t)count > container.max_size()) { + Fail(); + return(false); + } + + try { + container.clear(); + container.resize((std::size_t)count); + } catch (std::bad_alloc const &) { + container.clear(); + Fail(); + return(false); + } + return(true); + } + /* * Numbers and enumerations travel as their declared width. */ @@ -175,6 +248,39 @@ class SaveStreamClass } } + // How much room a buffer keeps for its text is this build's business rather than + // the file's, so only the text travels. The last byte stays the terminator, since + // every reader of these buffers treats them as C strings. This claims every + // char[N], so one holding bytes rather than text would be cut at its first zero. + template + void Serialize(char (&value)[N], std::source_location const & = std::source_location::current()) + { + int count = 0; + + if (Is_Saving()) { + while (count < N - 1 && value[count] != '\0') { + count++; + } + } + + Serialize(count); + + if (Is_Loading() && (count < 0 || count >= N)) { + Fail(); + return; + } + + if (count > 0) { + Serialize_Bytes(value, count); + } + + if (Is_Loading()) { + for (int index = count; index < N; index++) { + value[index] = '\0'; + } + } + } + /* * The standard library's fixed size array travels as the built in one does. */ @@ -203,12 +309,12 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, (std::is_arithmetic_v || std::is_enum_v) ? sizeof(T) : 1)) { + return; + } + if (!Reserve(value, count)) { return; } - value.clear(); - value.resize(count); } if constexpr (std::is_arithmetic_v || std::is_enum_v) { @@ -254,12 +360,12 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, 1)) { + return; + } + if (!Reserve(value, count)) { return; } - value.clear(); - value.resize(count); } for (int index = 0; index < count; index++) { @@ -278,11 +384,9 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, 1) || !Reserve(value, count)) { return; } - value.assign((std::size_t)count, false); } for (int index = 0; index < count; index++) { @@ -301,11 +405,9 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, 1) || !Reserve(value, count)) { return; } - value.resize(count); } if (count > 0) { @@ -338,9 +440,13 @@ class SaveStreamClass } } - IStream * Stream; + std::vector * Buffer; + unsigned int Cursor; + + // A read is judged against the record being loaded rather than the whole stream. + unsigned int Limit; ModeType Mode; - HRESULT ErrorCode; + bool Failed; unsigned int FormatVersion; /* @@ -365,8 +471,7 @@ class SaveStreamClass /* - * The version stamp of the save game currently being read. Each object builds its own - * stream inside IPersistStream::Load, which has no way to be told, so the value is left - * here by the load as a whole. + * The version stamp of the save game currently being read, left here by the load as a + * whole so every stream built during it reports the same version. */ extern unsigned int LoadedSaveVersion; diff --git a/code/savever.cpp b/code/savever.cpp index 414bb7ce6..68424fb9b 100644 --- a/code/savever.cpp +++ b/code/savever.cpp @@ -11,13 +11,11 @@ #include "savever.h" -#include "utf8.h" +#include "savefile.h" #include "dbgprint.h" #include "session.h" -#include - /// /// Creates an empty save file information block. @@ -33,8 +31,6 @@ SaveVersionInfo::SaveVersionInfo(void) : { ScenarioDescription[0] = '\0'; PlayerHouse[0] = '\0'; - UnknownString[0] = '\0'; - PlayerName[0] = '\0'; ExecutableName[0] = '\0'; StartTime.dwLowDateTime = 0; @@ -177,50 +173,6 @@ int SaveVersionInfo::Get_Scenario_Number(void) } -/// -/// Records the spare string kept with the save information. -/// The string is truncated if it will not fit the buffer it is kept in. -/// -void SaveVersionInfo::Set_Unknown_String(const char * str) -{ - UnknownString[sizeof(UnknownString) - 1] = 0; - strncpy(UnknownString, str, sizeof(UnknownString) - 1); -} - - -/// -/// Fetches the spare string kept with the save information. -/// Neither the save nor the load routine records this string, so it only ever holds what -/// the current session put there. -/// -/// Returns with the string most recently set. -const char * SaveVersionInfo::Get_Unknown_String(void) -{ - return(UnknownString); -} - - -/// -/// Records the name of the player making the save. -/// The name is truncated if it will not fit the buffer it is kept in. -/// -void SaveVersionInfo::Set_Player_Name(const char * name) -{ - PlayerName[sizeof(PlayerName) - 1] = 0; - strncpy(PlayerName, name, sizeof(PlayerName) - 1); -} - - -/// -/// Fetches the name of the player who made the save. -/// -/// Returns with the player name recorded in the save. -const char * SaveVersionInfo::Get_Player_Name(void) -{ - return(PlayerName); -} - - /// /// Records the name of the program writing the save. /// The name is truncated if it will not fit the buffer it is kept in. @@ -320,796 +272,40 @@ int SaveVersionInfo::Get_Game_Type(void) /// -/// Saves the version information into a save file. -/// This routine is called while a save game is being written. It records every value into -/// the summary information property set and then again as one stream per value, so that a -/// reader which knows only the older layout can still identify the save. +/// Writes every listing field into the file's field table. /// -/// Returns with S_OK once every value has been written, otherwise the failure code -/// from the storage layer. -HRESULT SaveVersionInfo::Save(IStorage *storage) +void SaveVersionInfo::Save(SaveFileClass & file) const { - if (storage == NULL) { - return(E_POINTER); - } - - DebugString("Attempting to obtain PropertySetStorage interface\n"); - - IPropertySetStoragePtr storageset; - HRESULT res; - - res = storage->QueryInterface(IID_IPropertySetStorage, (void **)&storageset); - if (SUCCEEDED(res)) { - - DebugString("Saving version information the NEW way.\n"); - - res = Save_String_Set(storageset, PIDSI_SCEN_DESCRIP, ScenarioDescription); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_PLAYER_HOUSE, PlayerHouse); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_G_VERSION, Version); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_INTERNAL_VER, InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Save_Time_Set(storageset, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time_Set(storageset, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time_Set(storageset, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_EXEC_NAME, ExecutableName); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_PLAYER_NAME1, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_PLAYER_NAME2, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_SCENARIO_NUM, ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_CAMPAIGN_NUM, CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_GAME_TYPE, GameType); - if (FAILED(res)) { - return(res); - } - - } else { - DebugString("\t***** FAILED!\n"); - } - - DebugString("Saving version information the old way.\n"); - - res = Save_String(storage, PIDSI_SCEN_DESCRIP, ScenarioDescription); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_PLAYER_HOUSE, PlayerHouse); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_G_VERSION, Version); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_INTERNAL_VER, InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Save_Time(storage, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time(storage, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time(storage, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_EXEC_NAME, ExecutableName); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_PLAYER_NAME1, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_PLAYER_NAME2, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_SCENARIO_NUM, ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_CAMPAIGN_NUM, CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_GAME_TYPE, GameType); - if (FAILED(res)) { - return(res); - } - - return(S_OK); + file.Set_String(PIDSI_SCEN_DESCRIP, ScenarioDescription); + file.Set_String(PIDSI_PLAYER_HOUSE, PlayerHouse); + file.Set_Int(PIDSI_G_VERSION, Version); + file.Set_Int(PIDSI_INTERNAL_VER, InternalVersion); + file.Set_Time(PIDSI_G_START_TIME, StartTime); + file.Set_Time(PIDSI_LAST_SAVE_TIME, LastSaveTime); + file.Set_Time(PIDSI_G_PLAY_TIME, PlayTime); + file.Set_String(PIDSI_EXEC_NAME, ExecutableName); + file.Set_Int(PIDSI_SCENARIO_NUM, ScenarioNumber); + file.Set_Int(PIDSI_CAMPAIGN_NUM, CampaignNumber); + file.Set_Int(PIDSI_GAME_TYPE, GameType); } /// -/// Loads the version information out of a save file. -/// This routine is called when a save game is scanned or restored. It prefers the property -/// set that the current game writes and falls back to the one stream per value layout that -/// older save files use, so that both generations of save file stay readable. +/// Reads the listing fields the file carries; a field the file lacks keeps its default. /// -/// Returns with S_OK once every value has been recovered, otherwise the failure -/// code from the storage layer. -HRESULT SaveVersionInfo::Load(IStorage *storage) +/// bool; Does the file record an internal version at all? +bool SaveVersionInfo::Load(SaveFileClass const & file) { - if (storage == NULL) { - return(E_POINTER); - } - - IPropertySetStoragePtr storageset; - HRESULT res; - - if (SUCCEEDED(storage->QueryInterface(IID_IPropertySetStorage, (void **)&storageset)) - && SUCCEEDED(Load_String_Set(storageset, PIDSI_SCEN_DESCRIP, ScenarioDescription, sizeof(ScenarioDescription)))) { - - - res = Load_String_Set(storageset, PIDSI_PLAYER_HOUSE, PlayerHouse, sizeof(PlayerHouse)); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_G_VERSION, &Version); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_INTERNAL_VER, &InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Load_Time_Set(storageset, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time_Set(storageset, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time_Set(storageset, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Load_String_Set(storageset, PIDSI_EXEC_NAME, ExecutableName, sizeof(ExecutableName)); - if (FAILED(res)) { - return(res); - } - - res = Load_String_Set(storageset, PIDSI_PLAYER_NAME1, PlayerName, sizeof(PlayerName)); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_SCENARIO_NUM, &ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_CAMPAIGN_NUM, &CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_GAME_TYPE, &GameType); - if (FAILED(res)) { - return(res); - } - - } else { - - res = Load_String(storage, PIDSI_SCEN_DESCRIP, ScenarioDescription, sizeof(ScenarioDescription)); - if (FAILED(res)) { - return(res); - } - - - res = Load_String(storage, PIDSI_PLAYER_HOUSE, PlayerHouse, sizeof(PlayerHouse)); - if (FAILED(res)) { - return(res); - } - - - res = Load_Int(storage, PIDSI_G_VERSION, &Version); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_INTERNAL_VER, &InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Load_Time(storage, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time(storage, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time(storage, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Load_String(storage, PIDSI_EXEC_NAME, ExecutableName, sizeof(ExecutableName)); - if (FAILED(res)) { - return(res); - } - - res = Load_String(storage, PIDSI_PLAYER_NAME1, PlayerName, sizeof(PlayerName)); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_SCENARIO_NUM, &ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_CAMPAIGN_NUM, &CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_GAME_TYPE, &GameType); - if (FAILED(res)) { - return(res); - } - } - - return(S_OK); -} - - -/// -/// Reads a string from a stream of its own. -/// The wide text held in the stream is narrowed into the caller's buffer, which is emptied -/// first. This is the old style counterpart of Load_String_Set, used for save files written -/// before the version information moved into a property set. -/// -/// The property identifier naming the stream to open. -/// Returns with the result of the read. A failure means the stream is absent or -/// ended before the text was terminated. -/// The capacity of string; longer text is cut on a character boundary. -HRESULT SaveVersionInfo::Load_String(IStorage *storage, int id, char *string, int size) -{ - *string = '\0'; - - HRESULT res; - IStreamPtr stm; - - res = storage->OpenStream(Stream_Name_From_ID(id), NULL, STGM_SHARE_EXCLUSIVE, 0, &stm); - if (FAILED(res)) { - return(res); - } - - WCHAR buf[128]; - ULONG count; - - int i = 0; - for (; i < ARRAY_SIZE(buf); i++) { - res = stm->Read(&buf[i], sizeof(buf[i]), &count); - if (FAILED(res)) { - return(res); - } - if (res != S_OK || count != sizeof(buf[i])) { - return(E_FAIL); - } - if (buf[i] == '\0') { - break; - } - } - - if (i == ARRAY_SIZE(buf)) { - return(E_FAIL); - } - - char text[512]; - if (WideCharToMultiByte(CP_ACP, 0, buf, -1, text, sizeof(text), 0, 0) == 0) { - text[0] = '\0'; - } - UTF8::Copy(string, size, text); - - return(S_OK); -} - - -/// -/// Reads a string from the save file's property set. -/// The wide text held in the property is narrowed back into the caller's buffer. That buffer -/// is emptied before the read is attempted, so a missing property yields an empty string. -/// -/// The summary information property identifier to read. -/// Returns with the result of the read. A failure means the property set could not -/// be opened. -/// The capacity of string; longer text is cut on a character boundary. -HRESULT SaveVersionInfo::Load_String_Set(IPropertySetStorage *storageset, int id, char *string, int size) -{ - *string = '\0'; - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - return(res); - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - res = storage->ReadMultiple(1, &propsec, &propvar); - if (FAILED(res)) { - return(res); - } - - if (propvar.vt == VT_LPWSTR) { - char text[1024]; - if (WideCharToMultiByte(CP_ACP, 0, propvar.pwszVal, -1, text, sizeof(text), 0, 0) == 0) { - text[0] = '\0'; - } - UTF8::Copy(string, size, text); - } - - return(res); -} - - -/// -/// Reads an integer from a stream of its own. -/// This is the old style counterpart of Load_Int_Set, used for save files written before -/// the version information moved into a property set. The value is cleared first. -/// -/// The property identifier naming the stream to open. -/// Returns with the result of the read. A failure means the stream is absent. -HRESULT SaveVersionInfo::Load_Int(IStorage *storage, int id, int *integer) -{ - *integer = 0; - - HRESULT res; - IStreamPtr stm; - - res = storage->OpenStream(Stream_Name_From_ID(id), NULL, STGM_SHARE_EXCLUSIVE, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Read(integer, sizeof(*integer), NULL); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Reads an integer from the save file's property set. -/// The value is cleared before the read is attempted, so a save file that does not carry -/// the property leaves the caller with zero. -/// -/// The summary information property identifier to read. -/// Returns with the result of the read. A failure means the property set could not -/// be opened. -HRESULT SaveVersionInfo::Load_Int_Set(IPropertySetStorage *storageset, int id, int *integer) -{ - *integer = 0; - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - return(res); - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - res = storage->ReadMultiple(1, &propsec, &propvar); - if (FAILED(res)) { - return(res); - } - - if (propvar.vt == VT_I4) { - *integer = propvar.lVal; - } - - return(res); -} - - -/// -/// Writes a string to a stream of its own. -/// The text is widened before it is written. This is the old style counterpart of -/// Save_String_Set, kept for readers that do not understand property sets. -/// -/// The property identifier naming the stream to create. -/// Returns with the result of the write. A failure means the stream was never -/// committed. -HRESULT SaveVersionInfo::Save_String(IStorage *storage, int id, char *string) -{ - WCHAR buf[260]; - - if (MultiByteToWideChar(CP_ACP, 0, string, -1, buf, ARRAY_SIZE(buf)) == 0) { - buf[0] = L'\0'; - } - - IStreamPtr stm(NULL); - - HRESULT res = storage->CreateStream(Stream_Name_From_ID(id), STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Write(buf, sizeof(WCHAR) * wcslen(buf) + 2, NULL); - if (FAILED(res)) { - return(res); - } - res = stm->Commit(0); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes a string into the save file's property set. -/// The text is widened before it is stored, since the summary information properties are -/// held as wide characters. The property set is created if the save file has none yet. -/// -/// The summary information property identifier to write. -/// Returns with the result of the write. A failure means the property set could -/// neither be opened nor created. -HRESULT SaveVersionInfo::Save_String_Set(IPropertySetStorage *storageset, int id, const char *string) -{ - WCHAR buf[260]; - - if (MultiByteToWideChar(CP_ACP, 0, string, -1, buf, ARRAY_SIZE(buf)) == 0) { - buf[0] = L'\0'; - } - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - res = storageset->Create(FMTID_SummaryInformation, NULL, PROPSETFLAG_DEFAULT, STGM_SHARE_EXCLUSIVE|STGM_READWRITE|STGM_CREATE, &storage); - if (FAILED(res)) { - return(res); - } - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - propvar.vt = VT_LPWSTR; - propvar.pwszVal = buf; - - res = storage->WriteMultiple(1, &propsec, &propvar, PID_FIRST_USABLE); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes an integer to a stream of its own. -/// This is the old style counterpart of Save_Int_Set, kept so that a reader which does not -/// understand property sets can still recover the value. -/// -/// The property identifier naming the stream to create. -/// Returns with the result of the write. A failure means the stream was never -/// committed. -HRESULT SaveVersionInfo::Save_Int(IStorage *storage, int id, int integer) -{ - IStreamPtr stm(NULL); - - HRESULT res = storage->CreateStream(Stream_Name_From_ID(id), STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Write(&integer, sizeof(integer), NULL); - if (FAILED(res)) { - return(res); - } - res = stm->Commit(STGM_READ); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes an integer into the save file's property set. -/// This routine stores the value as a summary information property, creating the property -/// set first if the save file does not carry one yet. -/// -/// The summary information property identifier to write. -/// Returns with the result of the write. A failure means the property set could -/// neither be opened nor created. -HRESULT SaveVersionInfo::Save_Int_Set(IPropertySetStorage *storageset, int id, int integer) -{ - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - res = storageset->Create(FMTID_SummaryInformation, NULL, PROPSETFLAG_DEFAULT, STGM_SHARE_EXCLUSIVE|STGM_READWRITE|STGM_CREATE, &storage); - if (FAILED(res)) { - return(res); - } - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - propvar.vt = VT_I4; - propvar.lVal = integer; - - res = storage->WriteMultiple(1, &propsec, &propvar, PID_FIRST_USABLE); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Reads a time stamp from a stream of its own. -/// This is the old style counterpart of Load_Time_Set, used for save files written before -/// the version information moved into a property set. The time is cleared first. -/// -/// The property identifier naming the stream to open. -/// Returns with the result of the read. A failure means the stream is absent. -HRESULT SaveVersionInfo::Load_Time(IStorage *storage, int id, FILETIME *time) -{ - time->dwLowDateTime = 0; - time->dwHighDateTime = 0; - - HRESULT res; - IStreamPtr stm; - - res = storage->OpenStream(Stream_Name_From_ID(id), NULL, STGM_SHARE_EXCLUSIVE, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Read(time, sizeof(*time), NULL); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Reads a time stamp from the save file's property set. -/// The time is cleared before the read is attempted, so a save file that does not carry the -/// property leaves the caller with a zero time rather than with garbage. -/// -/// The summary information property identifier to read. -/// Returns with the result of the read. A failure means the property set could not -/// be opened. -HRESULT SaveVersionInfo::Load_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time) -{ - time->dwLowDateTime = 0; - time->dwHighDateTime = 0; - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - return(res); - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - res = storage->ReadMultiple(1, &propsec, &propvar); - if (FAILED(res)) { - return(res); - } - - if (propvar.vt == VT_FILETIME) { - *time = propvar.filetime; - } - - return(res); -} - - -/// -/// Writes a time stamp to a stream of its own. -/// This is the old style counterpart of Save_Time_Set. The save routine records every value -/// this way as well, so that a reader which does not understand property sets can still -/// recover it. -/// -/// The property identifier naming the stream to create. -/// Returns with the result of the write. A failure means the stream was never -/// committed. -HRESULT SaveVersionInfo::Save_Time(IStorage *storage, int id, FILETIME *time) -{ - IStreamPtr stm(NULL); - - HRESULT res = storage->CreateStream(Stream_Name_From_ID(id), STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Write(time, sizeof(*time), NULL); - if (FAILED(res)) { - return(res); - } - res = stm->Commit(STGM_READ); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes a time stamp into the save file's property set. -/// This routine stores the time as a summary information property, creating the property -/// set first if the save file does not carry one yet. -/// -/// The summary information property identifier to write. -/// Returns with the result of the write. A failure means the property set could -/// neither be opened nor created. -HRESULT SaveVersionInfo::Save_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time) -{ - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - res = storageset->Create(FMTID_SummaryInformation, NULL, PROPSETFLAG_DEFAULT, STGM_SHARE_EXCLUSIVE|STGM_READWRITE|STGM_CREATE, &storage); - if (FAILED(res)) { - return(res); - } - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - propvar.vt = VT_FILETIME; - propvar.filetime = *time; - - res = storage->WriteMultiple(1, &propsec, &propvar, PID_FIRST_USABLE); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Fetches the stream name that a save version property is stored under. -/// This routine maps the summary information property identifiers onto the wide names used -/// by the old style save format, where every value lives in a stream of its own. The stream -/// based save and load helpers call it to name the stream they are about to open. -/// -/// The summary information property identifier to look up. -/// Returns with the stream name for the property, or NULL if the identifier is not -/// one of the recorded version properties. -const WCHAR *Stream_Name_From_ID(int id) -{ - static struct pidsiStruct { - int ID; - WCHAR const *Name; - } _ids[] = { - {PIDSI_SCEN_DESCRIP, L"Scenario Description"}, - {PIDSI_PLAYER_HOUSE, L"Player House"}, - {PIDSI_G_VERSION, L"Version"}, - {PIDSI_INTERNAL_VER, L"Internal Version"}, - {PIDSI_G_START_TIME, L"Start Time"}, - {PIDSI_LAST_SAVE_TIME, L"Last Save Time"}, - {PIDSI_G_PLAY_TIME, L"Play Time"}, - {PIDSI_EXEC_NAME, L"Executable Name"}, - {PIDSI_PLAYER_NAME1, L"Player Name"}, - {PIDSI_PLAYER_NAME2, L"Player Name2"}, - {PIDSI_SCENARIO_NUM, L"Scenario Number"}, - {PIDSI_CAMPAIGN_NUM, L"Campaign"}, - {PIDSI_GAME_TYPE, L"GameType"}, - }; - - for (int i = 0; i < ARRAY_SIZE(_ids); i++) { - if (_ids[i].ID == id) { - return(_ids[i].Name); - } - } + file.Get_String(PIDSI_SCEN_DESCRIP, ScenarioDescription, sizeof(ScenarioDescription)); + file.Get_String(PIDSI_PLAYER_HOUSE, PlayerHouse, sizeof(PlayerHouse)); + file.Get_Int(PIDSI_G_VERSION, &Version); + file.Get_Time(PIDSI_G_START_TIME, &StartTime); + file.Get_Time(PIDSI_LAST_SAVE_TIME, &LastSaveTime); + file.Get_Time(PIDSI_G_PLAY_TIME, &PlayTime); + file.Get_String(PIDSI_EXEC_NAME, ExecutableName, sizeof(ExecutableName)); + file.Get_Int(PIDSI_SCENARIO_NUM, &ScenarioNumber); + file.Get_Int(PIDSI_CAMPAIGN_NUM, &CampaignNumber); + file.Get_Int(PIDSI_GAME_TYPE, &GameType); - return(NULL); + return(file.Get_Int(PIDSI_INTERNAL_VER, &InternalVersion)); } diff --git a/code/savever.h b/code/savever.h index 291dd7bde..337ef0eb6 100644 --- a/code/savever.h +++ b/code/savever.h @@ -11,12 +11,13 @@ #include "win.h" -struct IStorage; -struct IPropertySetStorage; +class SaveFileClass; enum { PIDSI_SCEN_DESCRIP = 2, PIDSI_PLAYER_HOUSE = 3, + // Nothing writes a player name; the identifiers stay reserved because the property set + // this table replaced numbered them. PIDSI_PLAYER_NAME1 = 4, PIDSI_PLAYER_NAME2 = 8, PIDSI_G_VERSION = 9, @@ -54,12 +55,6 @@ class SaveVersionInfo void Set_Scenario_Number(int num); int Get_Scenario_Number(void); - void Set_Unknown_String(const char * name); - const char * Get_Unknown_String(void); - - void Set_Player_Name(const char * name); - const char * Get_Player_Name(void); - void Set_Executable_Name(const char * name); const char * Get_Executable_Name(void); @@ -75,27 +70,8 @@ class SaveVersionInfo void Set_Game_Type(int id); int Get_Game_Type(void); - HRESULT Save(IStorage *storage); - HRESULT Load(IStorage *storage); - - private: - HRESULT Load_String(IStorage *storage, int id, char *string, int size); - HRESULT Load_String_Set(IPropertySetStorage *storageset, int id, char *string, int size); - - HRESULT Load_Int(IStorage *storage, int id, int *integer); - HRESULT Load_Int_Set(IPropertySetStorage *storageset, int id, int *integer); - - HRESULT Save_String(IStorage *storage, int id, char *string); - HRESULT Save_String_Set(IPropertySetStorage *storageset, int id, const char *string); - - HRESULT Save_Int(IStorage *storage, int id, int integer); - HRESULT Save_Int_Set(IPropertySetStorage *storageset, int id, int integer); - - HRESULT Load_Time(IStorage *storage, int id, FILETIME *time); - HRESULT Load_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time); - - HRESULT Save_Time(IStorage *storage, int id, FILETIME *time); - HRESULT Save_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time); + void Save(SaveFileClass & file) const; + bool Load(SaveFileClass const & file); private: /* @@ -128,18 +104,6 @@ class SaveVersionInfo int CampaignNumber; int ScenarioNumber; - /* - * This is a spare string carried with the save information, reachable only through - * its own accessors. Neither the save nor the load routine records it. - */ - char UnknownString[260]; - - /* - * This is the name of the player who made the save, which is recorded separately - * from the house so that the person and the side are both known. - */ - char PlayerName[64]; - /* * This is the name of the program that wrote the save, so a file can be traced back * to what produced it rather than merely to a version number. @@ -159,5 +123,3 @@ class SaveVersionInfo */ int GameType; }; - -const WCHAR *Stream_Name_From_ID(int id); diff --git a/code/scenario.cpp b/code/scenario.cpp index 2e651211a..7019c5fc8 100644 --- a/code/scenario.cpp +++ b/code/scenario.cpp @@ -1075,11 +1075,7 @@ void Clear_Scenario(void) LightSourceClass::Recalc = false; while (Objects.Count()) { - if (Objects[0]->RTTI == RTTI_BULLET) { - Objects[0]->Release(); - } else { - delete Objects[0]; - } + delete Objects[0]; } LightSourceClass::Recalc = true; @@ -3334,18 +3330,17 @@ static Cell const Clip_Move(Cell const & cell, FacingType facing, int dist) /// The elapsed mission clock is halted across the write so that the time recorded is the /// one the player will be given back when the game is resumed. /// -void ScenarioClass::Save(IStream * stream) const +void ScenarioClass::Save(SaveStreamClass & stream) const { DebugString("Scenario Save: ElapsedTimer = %d\n", (int)ElapsedTimer); ElapsedTimer.Stop(); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); /* * One member list serves both directions, so it cannot be declared const even though * writing changes nothing. */ - const_cast(this)->Serialize(savestream); + const_cast(this)->Serialize(stream); ElapsedTimer.Start(); } @@ -3356,13 +3351,12 @@ void ScenarioClass::Save(IStream * stream) const /// The elapsed mission clock is halted across the read for the same reason it is halted /// across the write, so that it does not advance over the value coming back in. /// -void ScenarioClass::Load(IStream * stream) +void ScenarioClass::Load(SaveStreamClass & stream) { ElapsedTimer.Stop(); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("ScenarioClass"); - Serialize(savestream); + stream.Set_Context("ScenarioClass"); + Serialize(stream); ElapsedTimer.Start(); DebugString("Scenario Load: ElapsedTimer = %d\n", (int)ElapsedTimer); diff --git a/code/scenario.h b/code/scenario.h index f1bb213d6..eea2cb16b 100644 --- a/code/scenario.h +++ b/code/scenario.h @@ -101,8 +101,8 @@ class ScenarioClass { bool Read_INI(CCINIClass const & ini); bool Write_INI(CCINIClass & ini, bool mplayer=false) const; - void Save(IStream * stream) const; - void Load(IStream * stream); + void Save(SaveStreamClass & stream) const; + void Load(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); diff --git a/code/script.cpp b/code/script.cpp index 977dad004..c1be22e65 100644 --- a/code/script.cpp +++ b/code/script.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "script.h" @@ -151,18 +150,9 @@ bool ScriptClass::Has_Missions_Remaining(void) } -/// -/// Fetches the class identifier used to persist this script. -/// This routine is part of the IPersistStream contract that the save game system relies -/// on to recreate objects when a game is loaded. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ScriptClass::GetClassID(CLSID * retval) +ClassID ScriptClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ScriptClass; - return(S_OK); + return(ClassID_ScriptClass); } @@ -368,18 +358,9 @@ ScriptTypeClass * ScriptTypeClass::Find_Or_Make(char const * name) } -/// -/// Fetches the class identifier used to persist this script type. -/// This routine is part of the IPersistStream contract that the save game system relies -/// on to recreate objects when a game is loaded. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ScriptTypeClass::GetClassID(CLSID * retval) +ClassID ScriptTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ScriptTypeClass; - return(S_OK); + return(ClassID_ScriptTypeClass); } diff --git a/code/script.h b/code/script.h index f13faa0d1..cdabf2457 100644 --- a/code/script.h +++ b/code/script.h @@ -29,7 +29,7 @@ class ScriptClass : public AbstractClass ScriptClass(ScriptTypeClass *type = NULL); virtual ~ScriptClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; @@ -76,7 +76,7 @@ class ScriptTypeClass : public AbstractTypeClass static ScriptTypeClass * Find_Or_Make(char const * ininame = NULL); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static void Read_All(CCINIClass const & ini, INIScopeType scope); static void Write_All(CCINIClass & ini, INIScopeType scope); diff --git a/code/session.cpp b/code/session.cpp index 3164bb476..c3e10e3e9 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -1333,15 +1333,11 @@ void SessionClass::Init_Fixed_Alliances(void) /// Saves the game options to a save game. /// /// bool; Were the options written successfully? -bool GameOptionsType::Save(IStream * stream) +bool GameOptionsType::Save(SaveStreamClass & stream) { - if (stream == NULL) { - return(false); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(SUCCEEDED(savestream.Result())); + Serialize(stream); + return(!stream.Was_Error()); } @@ -1351,17 +1347,13 @@ bool GameOptionsType::Save(IStream * stream) /// scenario with it. /// /// bool; Were the options read back successfully? -bool GameOptionsType::Load(IStream * stream) +bool GameOptionsType::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(false); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("GameOptionsType"); - Serialize(savestream); + stream.Set_Context("GameOptionsType"); + Serialize(stream); ScenarioIndex = -1; - return(SUCCEEDED(savestream.Result())); + return(!stream.Was_Error()); } diff --git a/code/session.h b/code/session.h index 98b724ab2..b29202d04 100644 --- a/code/session.h +++ b/code/session.h @@ -462,8 +462,8 @@ struct GameOptionsType { bool ScrapMetal; // A wreck leaves the animations its type names in ScrapExplosion. char ScenarioDescription [DESCRIP_MAX]; //Used on client machines only - bool Save(IStream * stream); - bool Load(IStream * stream); + bool Save(SaveStreamClass & stream); + bool Load(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); }; diff --git a/code/side.cpp b/code/side.cpp index 1e081a06b..679ba158a 100644 --- a/code/side.cpp +++ b/code/side.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "side.h" @@ -132,18 +131,9 @@ bool SideClass::Read_INI(CCINIClass const & ini) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the IPersist interface. It is used by the save and load -/// system to recognize what kind of object it is about to create. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SideClass::GetClassID(CLSID * retval) +ClassID SideClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SideClass; - return(S_OK); + return(ClassID_SideClass); } diff --git a/code/side.h b/code/side.h index be97d6b84..f93d10c45 100644 --- a/code/side.h +++ b/code/side.h @@ -30,7 +30,7 @@ class SideClass : public AbstractTypeClass SideClass(char const * ininame = NULL); virtual ~SideClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; /* ** Query functions. diff --git a/code/smudge.cpp b/code/smudge.cpp index dd77b643d..ef0a2c89d 100644 --- a/code/smudge.cpp +++ b/code/smudge.cpp @@ -38,7 +38,6 @@ * SmudgeClass::operator new -- Creator of smudge objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "smudge.h" @@ -301,16 +300,7 @@ RTTIType SmudgeClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the IPersist interface. It is used by the save and load -/// system to recognize what kind of object it is about to create. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SmudgeClass::GetClassID(CLSID * retval) +ClassID SmudgeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SmudgeClass; - return(S_OK); + return(ClassID_SmudgeClass); } diff --git a/code/smudge.h b/code/smudge.h index 3fb659f24..f37359599 100644 --- a/code/smudge.h +++ b/code/smudge.h @@ -59,7 +59,7 @@ class SmudgeClass : public ObjectClass SmudgeClass(SmudgeTypeClass const * type, Coord const & pos = COORD_NONE, HousesType = HOUSE_NONE); virtual ~SmudgeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/smudtype.cpp b/code/smudtype.cpp index afbdf5090..8f3e12cb2 100644 --- a/code/smudtype.cpp +++ b/code/smudtype.cpp @@ -44,7 +44,6 @@ * SmudgetypeClass::Occupy_List -- Determines occupation list for smudge object. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "smudtype.h" @@ -337,17 +336,9 @@ void SmudgeTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save system asks for this so that it knows which class to construct when the object -/// is read back out of a save file. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SmudgeTypeClass::GetClassID(CLSID * retval) +ClassID SmudgeTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SmudgeTypeClass; - return(S_OK); + return(ClassID_SmudgeTypeClass); } diff --git a/code/smudtype.h b/code/smudtype.h index 373a0bda7..ad920b460 100644 --- a/code/smudtype.h +++ b/code/smudtype.h @@ -59,7 +59,7 @@ class SmudgeTypeClass : public ObjectTypeClass SmudgeTypeClass(char const * ininame = NULL); virtual ~SmudgeTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/startup.cpp b/code/startup.cpp index 43ab863af..ee17b98dc 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -32,7 +32,6 @@ * main -- Initial startup routine (preps library systems). * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "_alpha.h" @@ -64,7 +63,6 @@ #include "classfactory.h" #include "command.h" #include "conquer.h" -#include "cstream.h" #include "data.h" #include "dbgprint.h" #include "deploymentconfig.h" @@ -82,7 +80,6 @@ #include "house.h" #include "houstype.h" #include "hover.h" -#include "iblowfish.h" #include "infantry.h" #include "infatype.h" #include "init.h" @@ -174,20 +171,9 @@ extern HINSTANCE LanguageResources; #define AUTOPLAY_GUID "b350c6d2-2f36-11d3-a72c-0090272fa661" -#ifndef NO_BLOWFISH_DLL -const struct RegStruct { - const GUID *clsid; - const char *name; -} RegisterTheseDLLs[] = { - { &CLSID_BlowfishObject, "blowfish.dll" } -}; -#endif - HANDLE AppMutex; HANDLE AutoPlayMutex; -DynamicVectorClass RegisteredClasses; - //WinTimerClass * WinTimer; /// @@ -234,130 +220,80 @@ void Reset_Surfaces(void) } /// -/// Registers the game's COM classes with OLE. -/// This routine is called during startup, before anything that lives in the object -/// database can be created. It first ensures the support DLLs are present, asking any -/// that OLE cannot yet instantiate to register themselves, and then publishes a class -/// factory for every persistent game class so that objects can be created by CLSID. The -/// player is told by way of a message box if a support DLL could not be prepared. +/// Registers every class a saved game or a unit type can name by class identifier. +/// This runs during startup, before anything that lives in the object database can be +/// created. /// -/// bool; Did the preparation fail? Note the sense -- true means trouble. -static bool RegisterClasses(void) +static void RegisterClasses(void) { - - bool failed = false; -#ifndef NO_BLOWFISH_DLL - for (int i = 0; i < ARRAY_SIZE(RegisterTheseDLLs); i++) { - IUnknownPtr ptr; - HRESULT result = ptr.CreateInstance(*RegisterTheseDLLs[i].clsid, NULL, CLSCTX_ALL); - failed = FAILED(result); - if (failed) { - failed = false; - HINSTANCE hModule = LoadLibrary(RegisterTheseDLLs[i].name); - if (hModule != NULL) { - FARPROC fprocDllReg = (FARPROC)GetProcAddress(hModule, "DllRegisterServer"); - if (!fprocDllReg || (fprocDllReg(), FAILED(ptr.CreateInstance(*RegisterTheseDLLs[i].clsid, NULL, CLSCTX_ALL)))) { - failed = true; - } - FreeLibrary(hModule); - } else { - failed = true; - } - } - if (failed) { - break; - } - ptr.Release(); - } -#endif - - DWORD dwRegister; - IClassFactory *t; - - /// Handy macros to easily register the class factories. - - /// Register a class-object with OLE. - #define REGISTER_CLASS(_class, _clsid) \ - { \ - t = new TClassFactory<_class>; \ - CoRegisterClassObject(_clsid, t, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &dwRegister); \ - RegisteredClasses.Add(dwRegister); \ - } \ - - REGISTER_CLASS(CStreamClass, CLSID_CompressStream); - REGISTER_CLASS(WaveClass, CLSID_WaveClass); - REGISTER_CLASS(TerrainTypeClass, CLSID_TerrainTypeClass); - REGISTER_CLASS(TerrainClass, CLSID_TerrainClass); - REGISTER_CLASS(SuperWeaponTypeClass, CLSID_SuperWeaponTypeClass); - REGISTER_CLASS(SuperClass, CLSID_SuperWeaponClass); - REGISTER_CLASS(Tactical, CLSID_TacticalMapClass); - REGISTER_CLASS(CellClass, CLSID_CellClass); - REGISTER_CLASS(EMPulseClass, CLSID_EMPulseClass); - REGISTER_CLASS(LightSourceClass, CLSID_LightSource); - REGISTER_CLASS(SideClass, CLSID_SideClass); - REGISTER_CLASS(TiberiumClass, CLSID_TiberiumClass); - REGISTER_CLASS(TubeClass, CLSID_TubeClass); - REGISTER_CLASS(CampaignClass, CLSID_CampaignClass); - REGISTER_CLASS(BuildingLightClass, CLSID_BuildingLightClass); - REGISTER_CLASS(WaypointPathClass, CLSID_WaypointPath); - REGISTER_CLASS(TEventClass, CLSID_EventClass); - REGISTER_CLASS(VoxelAnimTypeClass, CLSID_VoxelAnimTypeClass); - REGISTER_CLASS(VoxelAnimClass, CLSID_VoxelAnimClass); - REGISTER_CLASS(TActionClass, CLSID_ActionClass); - REGISTER_CLASS(TriggerClass, CLSID_TriggerClass); - REGISTER_CLASS(TriggerTypeClass, CLSID_TriggerTypeClass); - REGISTER_CLASS(ScriptClass, CLSID_ScriptClass); - REGISTER_CLASS(ScriptTypeClass, CLSID_ScriptTypeClass); - REGISTER_CLASS(TagClass, CLSID_TagClass); - REGISTER_CLASS(TagTypeClass, CLSID_TagTypeClass); - REGISTER_CLASS(TeamClass, CLSID_TeamClass); - REGISTER_CLASS(TeamTypeClass, CLSID_TeamTypeClass); - REGISTER_CLASS(TaskForceClass, CLSID_TaskForceClass); - REGISTER_CLASS(UnitTypeClass, CLSID_UnitTypeClass); - REGISTER_CLASS(BuildingTypeClass, CLSID_BuildingTypeClass); - REGISTER_CLASS(AircraftTypeClass, CLSID_AircraftTypeClass); - REGISTER_CLASS(InfantryTypeClass, CLSID_InfantryTypeClass); - REGISTER_CLASS(BulletTypeClass, CLSID_BulletTypeClass); - REGISTER_CLASS(IsometricTileTypeClass, CLSID_IsometricTileTypeClass); - REGISTER_CLASS(OverlayTypeClass, CLSID_OverlayTypeClass); - REGISTER_CLASS(SmudgeTypeClass, CLSID_SmudgeTypeClass); - REGISTER_CLASS(UnitClass, CLSID_UnitClass); - REGISTER_CLASS(BuildingClass, CLSID_BuildingClass); - REGISTER_CLASS(AircraftClass, CLSID_AircraftClass); - REGISTER_CLASS(InfantryClass, CLSID_InfantryClass); - REGISTER_CLASS(AnimClass, CLSID_AnimClass); - REGISTER_CLASS(AnimTypeClass, CLSID_AnimTypeClass); - REGISTER_CLASS(HouseTypeClass, CLSID_HouseTypeClass); - REGISTER_CLASS(HouseClass, CLSID_HouseClass); - REGISTER_CLASS(DriveLocomotionClass, CLSID_DriveLocomotion); - REGISTER_CLASS(JumpjetLocomotionClass, CLSID_JumpjetLocomotion); - REGISTER_CLASS(HoverLocomotionClass, CLSID_HoverLocomotion); - REGISTER_CLASS(TunnelLocomotionClass, CLSID_TunnelLocomotion); - REGISTER_CLASS(WalkLocomotionClass, CLSID_WalkLocomotion); - REGISTER_CLASS(DropPodLocomotionClass, CLSID_BallisticLocomotion); - REGISTER_CLASS(FlyLocomotionClass, CLSID_FlyerLocomotion); - REGISTER_CLASS(TeleportLocomotionClass, CLSID_TeleportLocomotion); - REGISTER_CLASS(MechLocomotionClass, CLSID_MechLocomotion); - REGISTER_CLASS(LevitateLocomotionClass, CLSID_LevitateLocomotion); - REGISTER_CLASS(BulletClass, CLSID_BulletClass); - REGISTER_CLASS(FactoryClass, CLSID_FactoryClass); - REGISTER_CLASS(WarheadTypeClass, CLSID_WarheadTypeClass); - REGISTER_CLASS(WeaponTypeClass, CLSID_WeaponTypeClass); - REGISTER_CLASS(ParticleClass, CLSID_ParticleClass); - REGISTER_CLASS(ParticleTypeClass, CLSID_ParticleTypeClass); - REGISTER_CLASS(ParticleSystemClass, CLSID_ParticleSystemClass); - REGISTER_CLASS(ParticleSystemTypeClass, CLSID_ParticleSystemTypeClass); - REGISTER_CLASS(AITriggerTypeClass, CLSID_AITriggerTypeClass); - REGISTER_CLASS(NeuronClass, CLSID_NeuronClass); - REGISTER_CLASS(FoggedObjectClass, CLSID_FoggedObjectClass); - REGISTER_CLASS(AlphaShapeClass, CLSID_AlphaShapeClass); - - if (failed) { - MessageBox(NULL, Fetch_String(TXT_PREPARECOM_FAILED), Fetch_String(TXT_SHORT_TITLE), MB_ICONEXCLAMATION); - } - - return(failed); - + #define REGISTER_CLASS(_class, _clsid) Register_Class<_class>(_clsid); + + REGISTER_CLASS(WaveClass, ClassID_WaveClass); + REGISTER_CLASS(TerrainTypeClass, ClassID_TerrainTypeClass); + REGISTER_CLASS(TerrainClass, ClassID_TerrainClass); + REGISTER_CLASS(SuperWeaponTypeClass, ClassID_SuperWeaponTypeClass); + REGISTER_CLASS(SuperClass, ClassID_SuperWeaponClass); + REGISTER_CLASS(Tactical, ClassID_TacticalMapClass); + REGISTER_CLASS(CellClass, ClassID_CellClass); + REGISTER_CLASS(EMPulseClass, ClassID_EMPulseClass); + REGISTER_CLASS(LightSourceClass, ClassID_LightSource); + REGISTER_CLASS(SideClass, ClassID_SideClass); + REGISTER_CLASS(TiberiumClass, ClassID_TiberiumClass); + REGISTER_CLASS(TubeClass, ClassID_TubeClass); + REGISTER_CLASS(CampaignClass, ClassID_CampaignClass); + REGISTER_CLASS(BuildingLightClass, ClassID_BuildingLightClass); + REGISTER_CLASS(WaypointPathClass, ClassID_WaypointPath); + REGISTER_CLASS(TEventClass, ClassID_EventClass); + REGISTER_CLASS(VoxelAnimTypeClass, ClassID_VoxelAnimTypeClass); + REGISTER_CLASS(VoxelAnimClass, ClassID_VoxelAnimClass); + REGISTER_CLASS(TActionClass, ClassID_ActionClass); + REGISTER_CLASS(TriggerClass, ClassID_TriggerClass); + REGISTER_CLASS(TriggerTypeClass, ClassID_TriggerTypeClass); + REGISTER_CLASS(ScriptClass, ClassID_ScriptClass); + REGISTER_CLASS(ScriptTypeClass, ClassID_ScriptTypeClass); + REGISTER_CLASS(TagClass, ClassID_TagClass); + REGISTER_CLASS(TagTypeClass, ClassID_TagTypeClass); + REGISTER_CLASS(TeamClass, ClassID_TeamClass); + REGISTER_CLASS(TeamTypeClass, ClassID_TeamTypeClass); + REGISTER_CLASS(TaskForceClass, ClassID_TaskForceClass); + REGISTER_CLASS(UnitTypeClass, ClassID_UnitTypeClass); + REGISTER_CLASS(BuildingTypeClass, ClassID_BuildingTypeClass); + REGISTER_CLASS(AircraftTypeClass, ClassID_AircraftTypeClass); + REGISTER_CLASS(InfantryTypeClass, ClassID_InfantryTypeClass); + REGISTER_CLASS(BulletTypeClass, ClassID_BulletTypeClass); + REGISTER_CLASS(IsometricTileTypeClass, ClassID_IsometricTileTypeClass); + REGISTER_CLASS(OverlayTypeClass, ClassID_OverlayTypeClass); + REGISTER_CLASS(SmudgeTypeClass, ClassID_SmudgeTypeClass); + REGISTER_CLASS(UnitClass, ClassID_UnitClass); + REGISTER_CLASS(BuildingClass, ClassID_BuildingClass); + REGISTER_CLASS(AircraftClass, ClassID_AircraftClass); + REGISTER_CLASS(InfantryClass, ClassID_InfantryClass); + REGISTER_CLASS(AnimClass, ClassID_AnimClass); + REGISTER_CLASS(AnimTypeClass, ClassID_AnimTypeClass); + REGISTER_CLASS(HouseTypeClass, ClassID_HouseTypeClass); + REGISTER_CLASS(HouseClass, ClassID_HouseClass); + REGISTER_CLASS(DriveLocomotionClass, ClassID_DriveLocomotion); + REGISTER_CLASS(JumpjetLocomotionClass, ClassID_JumpjetLocomotion); + REGISTER_CLASS(HoverLocomotionClass, ClassID_HoverLocomotion); + REGISTER_CLASS(TunnelLocomotionClass, ClassID_TunnelLocomotion); + REGISTER_CLASS(WalkLocomotionClass, ClassID_WalkLocomotion); + REGISTER_CLASS(DropPodLocomotionClass, ClassID_BallisticLocomotion); + REGISTER_CLASS(FlyLocomotionClass, ClassID_FlyerLocomotion); + REGISTER_CLASS(TeleportLocomotionClass, ClassID_TeleportLocomotion); + REGISTER_CLASS(MechLocomotionClass, ClassID_MechLocomotion); + REGISTER_CLASS(LevitateLocomotionClass, ClassID_LevitateLocomotion); + REGISTER_CLASS(BulletClass, ClassID_BulletClass); + REGISTER_CLASS(FactoryClass, ClassID_FactoryClass); + REGISTER_CLASS(WarheadTypeClass, ClassID_WarheadTypeClass); + REGISTER_CLASS(WeaponTypeClass, ClassID_WeaponTypeClass); + REGISTER_CLASS(ParticleClass, ClassID_ParticleClass); + REGISTER_CLASS(ParticleTypeClass, ClassID_ParticleTypeClass); + REGISTER_CLASS(ParticleSystemClass, ClassID_ParticleSystemClass); + REGISTER_CLASS(ParticleSystemTypeClass, ClassID_ParticleSystemTypeClass); + REGISTER_CLASS(AITriggerTypeClass, ClassID_AITriggerTypeClass); + REGISTER_CLASS(NeuronClass, ClassID_NeuronClass); + REGISTER_CLASS(FoggedObjectClass, ClassID_FoggedObjectClass); + REGISTER_CLASS(AlphaShapeClass, ClassID_AlphaShapeClass); } /// @@ -525,11 +461,7 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho return(EXIT_SUCCESS); } - OleInitialize(NULL); - - if (RegisterClasses()) { - exit(EXIT_FAILURE); - } + RegisterClasses(); /* ** Get the full path to the .EXE @@ -603,7 +535,6 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho wsprintf (buffer, Fetch_String(TXT_CRITICALLY_LOW), (INIT_FREE_DISK_SPACE) / (1024 * 1024)); int reply = MessageBox(NULL, buffer, Fetch_String(TXT_SHORT_TITLE), MB_ICONQUESTION|MB_YESNO); if (reply == IDNO) { - OleUninitialize(); return(EXIT_FAILURE); } } @@ -734,7 +665,6 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho Debug_Console_Hold(); } - OleUninitialize(); return(error_code); } @@ -1055,10 +985,7 @@ void __cdecl Prog_End(void) Scen = NULL; } - for (i = 0; i < RegisteredClasses.Count(); i++) { - CoRevokeClassObject((DWORD)RegisteredClasses[i]); - } - RegisteredClasses.Clear(); + Unregister_Classes(); if (LanguageResources) { FreeLibrary(LanguageResources); @@ -1106,7 +1033,6 @@ void Emergency_Exit(void) } } - OleUninitialize(); if (MouseCursor) { MouseCursor->Release_Mouse(); diff --git a/code/sun.h b/code/sun.h index ac6a07811..f0f06b943 100644 --- a/code/sun.h +++ b/code/sun.h @@ -13,9 +13,7 @@ #pragma once -#ifdef INCLUDE_COM -#include "isun.h" -#endif +#include "classids.h" #include /// Everything from here on is the content of defines.h. diff --git a/code/super.cpp b/code/super.cpp index c1339fa81..e9b9117ed 100644 --- a/code/super.cpp +++ b/code/super.cpp @@ -40,7 +40,6 @@ * SuperClass::Suspend -- Suspend the charging of the super weapon. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "super.h" @@ -801,15 +800,9 @@ bool SuperClass::Is_Charging(void) const } -/// -/// Fetches the persistent class identifier for the super weapon. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SuperClass::GetClassID(CLSID * retval) +ClassID SuperClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SuperWeaponClass; - return(S_OK); + return(ClassID_SuperWeaponClass); } diff --git a/code/super.h b/code/super.h index d5dd0954e..2de481e3c 100644 --- a/code/super.h +++ b/code/super.h @@ -48,7 +48,7 @@ class SuperClass : public AbstractClass SuperClass(SuperWeaponTypeClass * type, HouseClass * owner); virtual ~SuperClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/suprtype.cpp b/code/suprtype.cpp index 6c5834955..67a37738d 100644 --- a/code/suprtype.cpp +++ b/code/suprtype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "suprtype.h" @@ -97,17 +96,9 @@ SuperWeaponTypeClass::~SuperWeaponTypeClass(void) } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this to know which class to construct when the object is -/// read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SuperWeaponTypeClass::GetClassID(CLSID * retval) +ClassID SuperWeaponTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SuperWeaponTypeClass; - return(S_OK); + return(ClassID_SuperWeaponTypeClass); } diff --git a/code/suprtype.h b/code/suprtype.h index 8542bcbc9..66ff6b4e0 100644 --- a/code/suprtype.h +++ b/code/suprtype.h @@ -33,7 +33,7 @@ class SuperWeaponTypeClass : public AbstractTypeClass SuperWeaponTypeClass(char const * ininame = NULL); virtual ~SuperWeaponTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/swizzle.cpp b/code/swizzle.cpp index 48b2ac401..421128df5 100644 --- a/code/swizzle.cpp +++ b/code/swizzle.cpp @@ -229,6 +229,19 @@ void SwizzleManagerClass::Resolve(void) } +/// +/// Takes back everything registered since the mark. +/// The slots those requests name were left null by Swizzle and nothing has filled them, +/// since Resolve does not run until the load has succeeded, so dropping the requests is +/// all it takes to let the objects holding them be destroyed. +/// +void SwizzleManagerClass::Abandon(MarkType const & mark) +{ + RequestTable.resize(mark.Requests); + PointerTable.resize(mark.Pointers); +} + + /// /// Throws away every pending request and announcement. /// The load code calls this routine before it starts reading, so that whatever a load that diff --git a/code/swizzle.h b/code/swizzle.h index f49054119..6c536fb8b 100644 --- a/code/swizzle.h +++ b/code/swizzle.h @@ -78,6 +78,17 @@ class SwizzleManagerClass void Resolve(void); void Discard(void); + /* + * The tables' extent at some point of a load, so that a record which fails after + * it can take back what it registered before its object is destroyed. + */ + struct MarkType { + std::size_t Requests; + std::size_t Pointers; + }; + MarkType Mark(void) const {return(MarkType{RequestTable.size(), PointerTable.size()});} + void Abandon(MarkType const & mark); + private: /* * These are the pointers read back from the save file that still hold a swizzle ID diff --git a/code/tactical.cpp b/code/tactical.cpp index bca9e834e..1935c14eb 100644 --- a/code/tactical.cpp +++ b/code/tactical.cpp @@ -11,7 +11,6 @@ * disclaimers apply; see LICENSE.md. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tactical.h" @@ -3724,17 +3723,9 @@ bool Tactical::Draw_3D_Line(Coord const & coord1, Coord const & coord2, int colo } -/// -/// Fetches the class identifier of the tactical map. -/// This routine is used by the persistence system to recognize the object when it is read -/// back out of a save game. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE Tactical::GetClassID(CLSID * retval) +ClassID Tactical::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TacticalMapClass; - return(S_OK); + return(ClassID_TacticalMapClass); } diff --git a/code/tactical.h b/code/tactical.h index 9c5a73732..91ba6de7d 100644 --- a/code/tactical.h +++ b/code/tactical.h @@ -103,7 +103,7 @@ class Tactical : public AbstractClass Tactical(void); virtual ~Tactical(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual RTTIType Fetch_RTTI(void) const override {return(RTTI_TACTICALMAP);} diff --git a/code/taction.cpp b/code/taction.cpp index 97d2ff0c6..55623b3e8 100644 --- a/code/taction.cpp +++ b/code/taction.cpp @@ -40,7 +40,6 @@ * ActionChoiceClass::Draw_It -- Display the action choice as part of a list box. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "taction.h" @@ -2961,18 +2960,9 @@ NeedType Action_Needs(TActionType action) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery to recognize what kind of object it -/// is about to load back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TActionClass::GetClassID(CLSID * retval) +ClassID TActionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ActionClass; - return(S_OK); + return(ClassID_ActionClass); } diff --git a/code/taction.h b/code/taction.h index 1dd6a0595..c87a3f454 100644 --- a/code/taction.h +++ b/code/taction.h @@ -140,7 +140,7 @@ class TActionClass : public AbstractClass TActionClass(void); virtual ~TActionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tag.cpp b/code/tag.cpp index 518b7d56d..83dd8384a 100644 --- a/code/tag.cpp +++ b/code/tag.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tag.h" @@ -424,16 +423,9 @@ void TagClass::Detach(AbstractClass const * target, bool all) } -/// -/// Fetches the class identifier that this tag persists under. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TagClass::GetClassID(CLSID * retval) +ClassID TagClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TagClass; - return(S_OK); + return(ClassID_TagClass); } diff --git a/code/tag.h b/code/tag.h index fe24ffe53..8e04d6ab9 100644 --- a/code/tag.h +++ b/code/tag.h @@ -29,7 +29,7 @@ class TagClass : public AbstractClass TagClass(TagTypeClass * type=NULL); virtual ~TagClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tagtype.cpp b/code/tagtype.cpp index 95afd121a..26bef7fbf 100644 --- a/code/tagtype.cpp +++ b/code/tagtype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tagtype.h" @@ -376,18 +375,9 @@ TagTypeClass * TagTypeClass::Find_Or_Make(char const * name) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save and load system so that a tag type can be -/// recognized when it is read back out of a stream. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TagTypeClass::GetClassID(CLSID * retval) +ClassID TagTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TagTypeClass; - return(S_OK); + return(ClassID_TagTypeClass); } diff --git a/code/tagtype.h b/code/tagtype.h index 1d99464e3..59c60e29a 100644 --- a/code/tagtype.h +++ b/code/tagtype.h @@ -33,7 +33,7 @@ class TagTypeClass : public AbstractTypeClass TagTypeClass(char const * name = NULL); virtual ~TagTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static TagTypeClass * From_Name(char const * name); diff --git a/code/taskforc.cpp b/code/taskforc.cpp index 5e32142cf..6f8a85060 100644 --- a/code/taskforc.cpp +++ b/code/taskforc.cpp @@ -11,7 +11,6 @@ * disclaimers apply; see LICENSE.md. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "taskforc.h" @@ -319,17 +318,9 @@ void TaskForceClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game code so that an object of the right kind can -/// be created when the game is loaded back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TaskForceClass::GetClassID(CLSID * retval) +ClassID TaskForceClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TaskForceClass; - return(S_OK); + return(ClassID_TaskForceClass); } diff --git a/code/taskforc.h b/code/taskforc.h index dc0d30b99..d9ab3d0cd 100644 --- a/code/taskforc.h +++ b/code/taskforc.h @@ -25,7 +25,7 @@ class TaskForceClass : public AbstractTypeClass TaskForceClass(char const *name=NULL); virtual ~TaskForceClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static TaskForceClass * Find_Or_Make(char const * name); diff --git a/code/team.cpp b/code/team.cpp index 270cecb7a..4c360d150 100644 --- a/code/team.cpp +++ b/code/team.cpp @@ -70,7 +70,6 @@ * _Is_It_Playing -- Determines if unit is active and an initiated team member. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "team.h" @@ -2296,18 +2295,9 @@ void TeamClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence contract and is what allows the save game loader -/// to recognize a team when it reads one back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TeamClass::GetClassID(CLSID * retval) +ClassID TeamClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TeamClass; - return(S_OK); + return(ClassID_TeamClass); } diff --git a/code/team.h b/code/team.h index bc465e36f..c110225ea 100644 --- a/code/team.h +++ b/code/team.h @@ -265,7 +265,7 @@ class TeamClass : public AbstractClass TeamClass(TeamTypeClass const * team=0, HouseClass * owner=0, void * = NULL); virtual ~TeamClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/teamtype.cpp b/code/teamtype.cpp index c406abec7..a4809b702 100644 --- a/code/teamtype.cpp +++ b/code/teamtype.cpp @@ -54,7 +54,6 @@ * TeamTypeClass::~TeamTypeClass -- class destructor * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "teamtype.h" @@ -890,18 +889,9 @@ void TeamTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This is part of the persistence contract that the save and load code leans on to -/// recognize what it is reading back. -/// -/// Returns with S_OK and the class identifier filled in, or E_POINTER if no -/// destination was supplied. -HRESULT STDMETHODCALLTYPE TeamTypeClass::GetClassID(CLSID * retval) +ClassID TeamTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TeamTypeClass; - return(S_OK); + return(ClassID_TeamTypeClass); } diff --git a/code/teamtype.h b/code/teamtype.h index 850e04144..d7c369fd1 100644 --- a/code/teamtype.h +++ b/code/teamtype.h @@ -68,7 +68,7 @@ class TeamTypeClass : public AbstractTypeClass TeamTypeClass(char const * name = NULL); virtual ~TeamTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static TeamTypeClass * Find_Or_Make(char const * ininame = NULL); diff --git a/code/techno.cpp b/code/techno.cpp index 20c2c3ffe..b1c553ad1 100644 --- a/code/techno.cpp +++ b/code/techno.cpp @@ -4064,7 +4064,7 @@ BulletClass * TechnoClass::Fire_At(AbstractClass * target, int which) if (valid_arc) { if (!bullet->Unlimbo(turret_coord, velocity)) { - bullet->Release(); + delete bullet; bullet = NULL; } else { @@ -4187,7 +4187,7 @@ BulletClass * TechnoClass::Fire_At(AbstractClass * target, int which) } } } else { - bullet->Release(); + delete bullet; bullet = NULL; } } diff --git a/code/techtype.cpp b/code/techtype.cpp index a273870cf..03b73af01 100644 --- a/code/techtype.cpp +++ b/code/techtype.cpp @@ -24,10 +24,10 @@ #include "bullet.h" #include "bullettype.h" #include "cell.h" +#include "classids.h" #include "combat.h" #include "findmake.h" #include "globals.h" -#include "ilocos.h" #include "infatype.h" #include "mixfile.h" #include "psystype.h" @@ -129,7 +129,7 @@ TechnoTypeClass::TechnoTypeClass(char const * ininame, SpeedType speed) : CloakingSpeed(7), DebrisTypes(), DebrisMaximums(), - Locomotor(CLSID_TeleportLocomotion), + Locomotor(ClassID_TeleportLocomotion), VoxelCenterY(0), VoxelCenterX(0), Weight(1), @@ -565,7 +565,7 @@ bool TechnoTypeClass::Read_INI(CCINIClass const & ini) } PitchSpeed = ini.Get_Float(Name(), "PitchSpeed", PitchSpeed); - Locomotor = ini.Get_CLSID(IniName, "Locomotor", Locomotor); + Locomotor = ini.Get_ClassID(IniName, "Locomotor", Locomotor); CloakingSpeed = ini.Get_Int(Name(), "CloakingSpeed", CloakingSpeed); ThreatAvoidanceCoefficient = ini.Get_Float(Name(), "ThreatAvoidanceCoefficient", ThreatAvoidanceCoefficient); SlowdownDistance = ini.Get_Int(Name(), "SlowdownDistance", SlowdownDistance); diff --git a/code/techtype.h b/code/techtype.h index aeb48195c..5743e3900 100644 --- a/code/techtype.h +++ b/code/techtype.h @@ -14,6 +14,7 @@ #pragma once #include "_weapon.h" +#include "classids.h" #include "objtype.h" #include "typelist.h" @@ -122,7 +123,7 @@ class TechnoTypeClass : public ObjectTypeClass * about. It is what decides whether the object drives, walks, hovers, flies or * tunnels, and an instance of it is created for every object as it is unlimboed. */ - CLSID Locomotor; + ClassID Locomotor; /* * These are the half extents of this object's voxel model, measured off the artwork diff --git a/code/teleport.cpp b/code/teleport.cpp index ab912d72e..5442f1cc6 100644 --- a/code/teleport.cpp +++ b/code/teleport.cpp @@ -36,7 +36,7 @@ TeleportLocomotionClass::TeleportLocomotionClass(void) : /// The object counts as moving from the moment a destination is handed to this /// locomotor until the jump has actually been made. /// -boolean STDMETHODCALLTYPE TeleportLocomotionClass::Is_Moving(void) +bool TeleportLocomotionClass::Is_Moving(void) { if (DestinationCoord != COORD_NONE) { return(true); @@ -50,7 +50,7 @@ boolean STDMETHODCALLTYPE TeleportLocomotionClass::Is_Moving(void) /// This is the plain opposite of Is_Moving. An object with a teleport ordered counts as /// being on the move even though it has not gone anywhere yet. /// -boolean TeleportLocomotionClass::Is_Stationary(void) +bool TeleportLocomotionClass::Is_Stationary(void) { if (Is_Moving() == false) { return(true); @@ -64,7 +64,7 @@ boolean TeleportLocomotionClass::Is_Stationary(void) /// /// Returns with the pending teleport destination, or with the object's current /// position if no teleport has been ordered. -Coord STDMETHODCALLTYPE TeleportLocomotionClass::Destination(void) +Coord TeleportLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -78,7 +78,7 @@ Coord STDMETHODCALLTYPE TeleportLocomotionClass::Destination(void) /// The jump is not made here. It happens the next time this locomotor is processed. /// /// The coordinate to teleport the object to. -void STDMETHODCALLTYPE TeleportLocomotionClass::Move_To(Coord to) +void TeleportLocomotionClass::Move_To(Coord to) { DestinationCoord = to; } @@ -89,7 +89,7 @@ void STDMETHODCALLTYPE TeleportLocomotionClass::Move_To(Coord to) /// The pending destination is forgotten, so the object stays where it is rather than /// making the jump. /// -void STDMETHODCALLTYPE TeleportLocomotionClass::Stop_Moving(void) +void TeleportLocomotionClass::Stop_Moving(void) { DestinationCoord = COORD_NONE; } @@ -102,7 +102,7 @@ void STDMETHODCALLTYPE TeleportLocomotionClass::Stop_Moving(void) /// it now stands. The whole journey is over by the time this routine returns. /// /// bool; Is there more movement still to process? A teleport never leaves any. -boolean STDMETHODCALLTYPE TeleportLocomotionClass::Process(void) +bool TeleportLocomotionClass::Process(void) { if (Is_Moving()) { LinkedTo->Mark(MARK_UP); @@ -112,22 +112,13 @@ boolean STDMETHODCALLTYPE TeleportLocomotionClass::Process(void) LinkedTo->Per_Cell_Process(PCP_END); LinkedTo->Look(); } - return(VARIANT_FALSE); + return(false); } -/// -/// Fetches the class identifier of this locomotor. -/// This routine is used by the persistence system to record which locomotor was -/// written, so that the right one can be created when the save game is loaded. -/// -/// Pointer to the class identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TeleportLocomotionClass::GetClassID(CLSID * retval) +ClassID TeleportLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TeleportLocomotion; - return(S_OK); + return(ClassID_TeleportLocomotion); } @@ -149,7 +140,7 @@ void TeleportLocomotionClass::Serialize(SaveStreamClass & stream) /// the way to its destination, so it never rises out of the ground layer. /// /// Returns with the layer the object should be rendered in. -LayerType STDMETHODCALLTYPE TeleportLocomotionClass::In_Which_Layer(void) +LayerType TeleportLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } diff --git a/code/teleport.h b/code/teleport.h index 4efad8853..3e41d3396 100644 --- a/code/teleport.h +++ b/code/teleport.h @@ -19,18 +19,18 @@ class TeleportLocomotionClass : public LocomotionClass public: TeleportLocomotionClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual LayerType In_Which_Layer(void) override; - virtual boolean Is_Stationary(void); + virtual bool Is_Stationary(void); private: /* diff --git a/code/terrain.cpp b/code/terrain.cpp index 78039fecb..ee4cb3fff 100644 --- a/code/terrain.cpp +++ b/code/terrain.cpp @@ -52,7 +52,6 @@ * TerrainClass::~TerrainClass -- Default destructor for terrain class objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "terrain.h" @@ -914,8 +913,8 @@ bool TerrainClass::Render(Rect & cliprect, bool forced, bool extras_only) const /// under the identity it was constructed with is dropped before the members arrive. /// /// The stream to read the object from. -/// Returns with S_OK if the object was read successfully. -HRESULT STDMETHODCALLTYPE TerrainClass::Load(IStream * stream) +/// bool; Was the record read whole? +bool TerrainClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); @@ -1090,16 +1089,7 @@ RTTIType TerrainClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier for this object. -/// This routine is part of the IPersistStream implementation. The save system records -/// the identifier so that it knows what to recreate when the game is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TerrainClass::GetClassID(CLSID * retval) +ClassID TerrainClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TerrainClass; - return(S_OK); + return(ClassID_TerrainClass); } diff --git a/code/terrain.h b/code/terrain.h index 537160275..83fefb448 100644 --- a/code/terrain.h +++ b/code/terrain.h @@ -59,8 +59,8 @@ class TerrainClass : public ObjectClass, public StageClass TerrainClass(TerrainTypeClass const * type, Cell const & cell); virtual ~TerrainClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual ClassID Class_ID(void) const override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/terrtype.cpp b/code/terrtype.cpp index 0e86c765b..82932b0ff 100644 --- a/code/terrtype.cpp +++ b/code/terrtype.cpp @@ -44,7 +44,6 @@ * TerrainTypeClass::operator new -- Allocates a terrain type object from special pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "terrtype.h" @@ -430,18 +429,9 @@ void TerrainTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save system uses this identifier to know what kind of object to create when the -/// save file is loaded back in. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE TerrainTypeClass::GetClassID(CLSID * retval) +ClassID TerrainTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TerrainTypeClass; - return(S_OK); + return(ClassID_TerrainTypeClass); } diff --git a/code/terrtype.h b/code/terrtype.h index 9bf8ec3c0..37b1fdee9 100644 --- a/code/terrtype.h +++ b/code/terrtype.h @@ -111,7 +111,7 @@ class TerrainTypeClass : public ObjectTypeClass TerrainTypeClass(char const * ininame = NULL); virtual ~TerrainTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/tevent.cpp b/code/tevent.cpp index 167c46731..2b724d56a 100644 --- a/code/tevent.cpp +++ b/code/tevent.cpp @@ -39,7 +39,6 @@ * TEventClass::operator () -- Action operator to see if event is satisfied. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "tevent.h" @@ -839,18 +838,9 @@ void TEventClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery to recognize what kind of object it -/// is about to load back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TEventClass::GetClassID(CLSID * retval) +ClassID TEventClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_EventClass; - return(S_OK); + return(ClassID_EventClass); } diff --git a/code/tevent.h b/code/tevent.h index 6dab13515..c7dc9e1bf 100644 --- a/code/tevent.h +++ b/code/tevent.h @@ -104,7 +104,7 @@ class TEventClass : public AbstractClass TEventClass(void); virtual ~TEventClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tiberium.cpp b/code/tiberium.cpp index 4908a18a4..59de5b97f 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tiberium.h" @@ -193,17 +192,9 @@ void TiberiumClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of the tiberium class. -/// This routine tells the save game loader which kind of object to create when this -/// tiberium type is read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TiberiumClass::GetClassID(CLSID * retval) +ClassID TiberiumClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TiberiumClass; - return(S_OK); + return(ClassID_TiberiumClass); } @@ -212,10 +203,10 @@ HRESULT STDMETHODCALLTYPE TiberiumClass::GetClassID(CLSID * retval) /// The spread and growth pools are dropped before the members arrive, since the counts /// they track are about to be replaced with the saved ones. /// -/// Returns with S_OK if the tiberium type was loaded. +/// bool; Was the record read whole? /// The spread and growth systems are not saved, so they come back empty. They /// must be rebuilt once the game has finished loading. -HRESULT STDMETHODCALLTYPE TiberiumClass::Load(IStream * stream) +bool TiberiumClass::Load(SaveStreamClass & stream) { Clear_Spread(); Clear_Growth(); diff --git a/code/tiberium.h b/code/tiberium.h index 9a71bf265..c7f7e8888 100644 --- a/code/tiberium.h +++ b/code/tiberium.h @@ -37,8 +37,8 @@ class TiberiumClass : public AbstractTypeClass TiberiumClass(char const * ininame = NULL); virtual ~TiberiumClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual ClassID Class_ID(void) const override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tracker.cpp b/code/tracker.cpp index 1658bb586..146974e1d 100644 --- a/code/tracker.cpp +++ b/code/tracker.cpp @@ -224,15 +224,13 @@ void Process_Deferred_Deletion(void) break; } } - if (obj->Release()) { - if (typeid(BuildingClass) == typeid(*obj) - || typeid(UnitClass) == typeid(*obj) - || typeid(InfantryClass) == typeid(*obj) - || typeid(AircraftClass) == typeid(*obj)) { - ((ObjectClass *)obj)->IsActive = true; - } - delete obj; + if (typeid(BuildingClass) == typeid(*obj) + || typeid(UnitClass) == typeid(*obj) + || typeid(InfantryClass) == typeid(*obj) + || typeid(AircraftClass) == typeid(*obj)) { + ((ObjectClass *)obj)->IsActive = true; } + delete obj; } else { ++index; } diff --git a/code/trigger.cpp b/code/trigger.cpp index bc1c9720d..2bf8a078a 100644 --- a/code/trigger.cpp +++ b/code/trigger.cpp @@ -42,7 +42,6 @@ * TriggerClass::~TriggerClass -- Destructor for trigger objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "trigger.h" @@ -500,18 +499,9 @@ void TriggerClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence support. The save game system uses the -/// identifier to work out what kind of object to build when the stream is read back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TriggerClass::GetClassID(CLSID * retval) +ClassID TriggerClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TriggerClass; - return(S_OK); + return(ClassID_TriggerClass); } diff --git a/code/trigger.h b/code/trigger.h index 771341c7d..35bf3233d 100644 --- a/code/trigger.h +++ b/code/trigger.h @@ -65,7 +65,7 @@ class TriggerClass : public AbstractClass TriggerClass(TriggerTypeClass * trigtype=NULL); virtual ~TriggerClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/trigtype.cpp b/code/trigtype.cpp index 9014fe2ae..849f89755 100644 --- a/code/trigtype.cpp +++ b/code/trigtype.cpp @@ -46,7 +46,6 @@ * TriggerTypeClass::~TriggerTypeClass -- Deleting a trigger type deletes associated triggers* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "trigtype.h" @@ -811,18 +810,9 @@ void TriggerTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this identifier to know which kind of object to build -/// when the stream is read back in. -/// -/// Pointer to the location to store the class identifier. -/// Returns with S_OK, or E_POINTER if no storage location was supplied. -HRESULT STDMETHODCALLTYPE TriggerTypeClass::GetClassID(CLSID * retval) +ClassID TriggerTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TriggerTypeClass; - return(S_OK); + return(ClassID_TriggerTypeClass); } diff --git a/code/trigtype.h b/code/trigtype.h index f8ba9e5a2..77c95d070 100644 --- a/code/trigtype.h +++ b/code/trigtype.h @@ -55,7 +55,7 @@ class TriggerTypeClass : public AbstractTypeClass static TriggerTypeClass * Find_Or_Make(char const * ininame = NULL); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; /* ** File I/O routines diff --git a/code/tube.cpp b/code/tube.cpp index 0d67042bc..e0f17408a 100644 --- a/code/tube.cpp +++ b/code/tube.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tube.h" @@ -267,16 +266,7 @@ RTTIType TubeClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game system so that it knows what kind of object to -/// construct when the stream is read back in. -/// -/// Pointer to the place to store the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TubeClass::GetClassID(CLSID * retval) +ClassID TubeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TubeClass; - return(S_OK); + return(ClassID_TubeClass); } diff --git a/code/tube.h b/code/tube.h index 71e193c44..f511cd589 100644 --- a/code/tube.h +++ b/code/tube.h @@ -22,7 +22,7 @@ class TubeClass : public AbstractClass { typedef AbstractClass BASECLASS; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tunnel.cpp b/code/tunnel.cpp index aa3fa9ff1..e4727006e 100644 --- a/code/tunnel.cpp +++ b/code/tunnel.cpp @@ -53,7 +53,7 @@ TunnelLocomotionClass::TunnelLocomotionClass(void) : /// Reports whether the unit is anywhere in the dig cycle (State != STATE_IDLE). /// /// True while dig-moving. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving(void) +bool TunnelLocomotionClass::Is_Moving(void) { if (State != STATE_IDLE) { return(true); @@ -67,7 +67,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving(void) /// (not STATE_IDLE and not STATE_TURNING). /// /// True while actively moving. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving_Now(void) +bool TunnelLocomotionClass::Is_Moving_Now(void) { if (Is_Moving() && State != STATE_TURNING) { return(true); @@ -80,7 +80,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving_Now(void) /// Returns the burrow destination while moving, or the current position when idle. /// /// The destination coordinate. -Coord STDMETHODCALLTYPE TunnelLocomotionClass::Destination(void) +Coord TunnelLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -96,7 +96,7 @@ Coord STDMETHODCALLTYPE TunnelLocomotionClass::Destination(void) /// ignores the order altogether. /// /// The location to travel to. -void STDMETHODCALLTYPE TunnelLocomotionClass::Move_To(Coord to) +void TunnelLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { Coord coord = to; @@ -121,7 +121,7 @@ void STDMETHODCALLTYPE TunnelLocomotionClass::Move_To(Coord to) /// already traveling underground must make for the nearest ground it can surface on. If /// there is no such ground to be had, it stays buried for good. /// -void STDMETHODCALLTYPE TunnelLocomotionClass::Stop_Moving(void) +void TunnelLocomotionClass::Stop_Moving(void) { switch (State) { case STATE_ABORTING: @@ -180,7 +180,7 @@ void STDMETHODCALLTYPE TunnelLocomotionClass::Stop_Moving(void) /// the ground and underground layers. /// /// bool; Is the unit still working through its dig cycle? -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Process(void) +bool TunnelLocomotionClass::Process(void) { if (Is_Moving()) { int agl = LinkedTo->HeightAGL; @@ -256,7 +256,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Process(void) /// /// Should the buried unit be hidden outright rather than rippled? /// Returns with the visual treatment to draw the unit with. -VisualType STDMETHODCALLTYPE TunnelLocomotionClass::Visual_Character(boolean flag) +VisualType TunnelLocomotionClass::Visual_Character(bool flag) { if (State == STATE_TUNNELING) { if (flag) { @@ -457,7 +457,7 @@ void TunnelLocomotionClass::Process_Emerging(void) /// /// The shape cache key to fold this pose into. May be NULL. /// Returns with the matrix to draw the unit with. -Matrix3D STDMETHODCALLTYPE TunnelLocomotionClass::Draw_Matrix(int * key) +Matrix3D TunnelLocomotionClass::Draw_Matrix(int * key) { if (State == STATE_IDLE) { int ramp = Map[(Coord const &)(LinkedTo->PositionCoord)].Ramp; @@ -528,7 +528,7 @@ Matrix3D STDMETHODCALLTYPE TunnelLocomotionClass::Draw_Matrix(int * key) /// derived from the terrain-height delta and the rotation progress. /// /// The Z pixel adjustment. -int STDMETHODCALLTYPE TunnelLocomotionClass::Z_Adjust(void) +int TunnelLocomotionClass::Z_Adjust(void) { static int tunnel_Z_Adjust[] = {45, 45}; @@ -583,7 +583,7 @@ int STDMETHODCALLTYPE TunnelLocomotionClass::Z_Adjust(void) /// be shaded down its length rather than across the flat, as the base locomotor would. /// /// Returns with the Z gradient to draw the unit with. -ZGradientType STDMETHODCALLTYPE TunnelLocomotionClass::Z_Gradient(void) +ZGradientType TunnelLocomotionClass::Z_Gradient(void) { if (State == STATE_DESCENDING || State == STATE_DIGGING_IN || State == STATE_ABORTING || State == STATE_EMERGING || State == STATE_ASCENDING) { return(ZGRAD_90DEG); @@ -597,7 +597,7 @@ ZGradientType STDMETHODCALLTYPE TunnelLocomotionClass::Z_Gradient(void) /// dig-in, emerging, aborting), false once it is pitched down or underground. /// /// True if it casts a shadow. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_To_Have_Shadow(void) +bool TunnelLocomotionClass::Is_To_Have_Shadow(void) { if (State == STATE_IDLE || State == STATE_TURNING || State == STATE_ABORTING || State == STATE_DIGGING_IN || State == STATE_EMERGING) { return(true); @@ -612,7 +612,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_To_Have_Shadow(void) /// /// Cell to test. /// MOVE_OK or MOVE_NO. -MoveType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Enter_Cell(Cell cell) +MoveType TunnelLocomotionClass::Can_Enter_Cell(Cell cell) { if (!Debug_Map && !Map[cell].Can_Burrow_Here()) { return(MOVE_NO); @@ -625,25 +625,16 @@ MoveType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Enter_Cell(Cell cell) /// Sets the unit's desired facing (used while turning to face the dig destination). /// /// Desired facing. -void STDMETHODCALLTYPE TunnelLocomotionClass::Do_Turn(DirType coord) +void TunnelLocomotionClass::Do_Turn(DirType coord) { DirType dir = coord; LinkedTo->PrimaryFacing.Set_Desired(dir); } -/// -/// Fetches the class identifier for this locomotor. -/// This routine is part of the COM persistence support. The save system records the -/// identifier so that the right locomotor can be created again when the game is loaded. -/// -/// The location to store the class identifier in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TunnelLocomotionClass::GetClassID(CLSID * retval) +ClassID TunnelLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TunnelLocomotion; - return(S_OK); + return(ClassID_TunnelLocomotion); } @@ -666,7 +657,7 @@ void TunnelLocomotionClass::Serialize(SaveStreamClass & stream) /// Returns the render layer: underground while travelling (STATE_TUNNELING), ground otherwise. /// /// The render layer. -LayerType STDMETHODCALLTYPE TunnelLocomotionClass::In_Which_Layer(void) +LayerType TunnelLocomotionClass::In_Which_Layer(void) { if (State != STATE_TUNNELING) { return(LAYER_GROUND); @@ -681,7 +672,7 @@ LayerType STDMETHODCALLTYPE TunnelLocomotionClass::In_Which_Layer(void) /// subterranean unit has no shot while it is lining up, digging, or under the ground. /// /// Returns with the fire error, or FIRE_OK if the unit is free to shoot. -FireErrorType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Fire(void) +FireErrorType TunnelLocomotionClass::Can_Fire(void) { FireErrorType fire = BASECLASS::Can_Fire(); @@ -697,7 +688,7 @@ FireErrorType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Fire(void) /// Reports whether the unit is in the act of surfacing (ascending or emerging). /// /// True while surfacing. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Surfacing(void) +bool TunnelLocomotionClass::Is_Surfacing(void) { return(State == STATE_ASCENDING || State == STATE_EMERGING); } diff --git a/code/tunnel.h b/code/tunnel.h index 2aaf0ad0a..03ae0794f 100644 --- a/code/tunnel.h +++ b/code/tunnel.h @@ -29,26 +29,26 @@ class TunnelLocomotionClass : public LocomotionClass */ TunnelLocomotionClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual VisualType STDMETHODCALLTYPE Visual_Character(boolean flag) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual int STDMETHODCALLTYPE Z_Adjust(void) override; - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) override; - virtual boolean STDMETHODCALLTYPE Is_To_Have_Shadow(void) override; - virtual MoveType STDMETHODCALLTYPE Can_Enter_Cell(Cell cell) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual FireErrorType STDMETHODCALLTYPE Can_Fire(void) override; - virtual boolean STDMETHODCALLTYPE Is_Surfacing(void) override; + virtual bool Is_Moving(void) override; + virtual bool Is_Moving_Now(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual VisualType Visual_Character(bool flag) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual int Z_Adjust(void) override; + virtual ZGradientType Z_Gradient(void) override; + virtual bool Is_To_Have_Shadow(void) override; + virtual MoveType Can_Enter_Cell(Cell cell) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual FireErrorType Can_Fire(void) override; + virtual bool Is_Surfacing(void) override; void Process_Turning(void); void Process_Digging_In(void); diff --git a/code/typelist.h b/code/typelist.h index f1572a0a1..4dcafb564 100644 --- a/code/typelist.h +++ b/code/typelist.h @@ -19,7 +19,6 @@ #include "win.h" #include -#include template class TypeList : public DynamicVectorClass diff --git a/code/unit.cpp b/code/unit.cpp index 9274d3060..9673d37de 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -95,7 +95,6 @@ * UnitClass::~UnitClass -- Destructor for unit objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "unit.h" @@ -121,13 +120,13 @@ #include "bullettype.h" #include "ccrand.h" #include "cell.h" +#include "classids.h" #include "combat.h" #include "conquer.h" #include "draw.h" #include "fog.h" #include "house.h" #include "houstype.h" -#include "ilocos.h" #include "incdec.h" #include "infantry.h" #include "infatype.h" @@ -223,7 +222,7 @@ UnitClass::UnitClass(UnitTypeClass const * type, HouseClass * house) : SecondaryFacing.Set(PrimaryFacing.Current()); if (Class != NULL) { - Locomotion = ILocomotionPtr(Class->Locomotor, NULL, CLSCTX_ALL); + Locomotion = Create_Locomotor(Class->Locomotor); Locomotion->Link_To_Object(this); } @@ -2107,10 +2106,8 @@ void UnitClass::Per_Cell_Process(PCPType why) Cell center = Center_Coord(); Cell whom_center = whom->Center_Coord(); if (Center_Coord().As_Cell() == whom->Center_Coord().As_Cell() && whom->RTTI == RTTI_BUILDING) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); - if (clsid == CLSID_HoverLocomotion && static_cast(whom)->Class->IsCanUnitRepair && NavCom == NULL) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_HoverLocomotion && static_cast(whom)->Class->IsCanUnitRepair && NavCom == nullptr) { NavCom = whom; } if (whom == NavCom) { @@ -5251,10 +5248,8 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * re-target the nearest reachable cell when driving rather than burrowing. */ if (target != NULL && Class->IsSubterranean && Locomotion->Is_Moving()) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); - if (clsid == CLSID_DriveLocomotion) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_DriveLocomotion) { NavQueue.Add_Head(target); RouteQueue.Clear(); CellClass * tcell = Get_Target_Cell_Ptr(); @@ -5345,10 +5340,8 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * (Mirrors BuildingClass weapons-factory exit, building.cpp:6236-6251.) */ if (target != NULL && !Locomotion->Is_Moving()) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); - if (clsid == CLSID_TunnelLocomotion && Get_Height_AGL() == 0) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_TunnelLocomotion && Get_Height_AGL() == 0) { Coord tc = target->Center_Coord(); int gl = Map.Get_Height_GL(tc); if (tc.Z < gl) tc.Z = gl; @@ -5369,16 +5362,16 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) } if (doswap) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && piggy->Is_Piggybacking()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } - ILocomotionPtr walk(CLSID_DriveLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_DriveLocomotion); walk->Link_To_Object(this); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { piggy->Begin_Piggyback(Locomotion); - Locomotion = walk; + Locomotion = std::move(walk); Locomotion->Force_New_Slope(Map[Get_Coord()].Ramp); } } @@ -6044,8 +6037,8 @@ bool UnitClass::Ready_To_Commence(void) /// again once that identity has arrived. /// /// The stream to read this unit from. -/// Returns with S_OK if the unit was read successfully. -HRESULT STDMETHODCALLTYPE UnitClass::Load(IStream *stream) +/// bool; Was the record read whole? +bool UnitClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); @@ -6658,16 +6651,9 @@ bool UnitClass::Is_Immobilized(void) const } -/// -/// Fetches the class identifier used by the save game persistence system. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE UnitClass::GetClassID(CLSID * retval) +ClassID UnitClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_UnitClass; - return(S_OK); + return(ClassID_UnitClass); } diff --git a/code/unit.h b/code/unit.h index 98c0ca05e..bf2d13086 100644 --- a/code/unit.h +++ b/code/unit.h @@ -136,8 +136,8 @@ class UnitClass : public FootClass UnitClass(UnitTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~UnitClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual ClassID Class_ID(void) const override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/unittype.cpp b/code/unittype.cpp index 00723fb19..ecbe34b95 100644 --- a/code/unittype.cpp +++ b/code/unittype.cpp @@ -45,7 +45,6 @@ * UnitTypeClass::operator new -- Allocates an object from the unit type class heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "unittype.h" @@ -495,15 +494,9 @@ int UnitTypeClass::Repair_Step(void) const } -/// -/// Fetches the persistent class identifier for the unit type. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE UnitTypeClass::GetClassID(CLSID * retval) +ClassID UnitTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_UnitTypeClass; - return(S_OK); + return(ClassID_UnitTypeClass); } diff --git a/code/unittype.h b/code/unittype.h index b5b5f63d9..acfea5043 100644 --- a/code/unittype.h +++ b/code/unittype.h @@ -313,7 +313,7 @@ class UnitTypeClass : public TechnoTypeClass UnitTypeClass(char const * ininame = NULL); virtual ~UnitTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vanim.cpp b/code/vanim.cpp index ea0b2f8d0..afa32b025 100644 --- a/code/vanim.cpp +++ b/code/vanim.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "vanim.h" @@ -546,18 +545,9 @@ void VoxelAnimClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier used to persist this object. -/// The save system writes this identifier ahead of the object data so that the loader -/// knows what kind of object to reconstruct. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE VoxelAnimClass::GetClassID(CLSID * retval) +ClassID VoxelAnimClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_VoxelAnimClass; - return(S_OK); + return(ClassID_VoxelAnimClass); } diff --git a/code/vanim.h b/code/vanim.h index d7e9f9e4e..8d8af415e 100644 --- a/code/vanim.h +++ b/code/vanim.h @@ -33,7 +33,7 @@ class VoxelAnimClass : public ObjectClass, public BounceClass VoxelAnimClass(VoxelAnimTypeClass const * type, Coord const & coord, HouseClass * house = NULL); virtual ~VoxelAnimClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/vanimtype.cpp b/code/vanimtype.cpp index a2f6646b3..0af776362 100644 --- a/code/vanimtype.cpp +++ b/code/vanimtype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "vanimtype.h" @@ -247,18 +246,9 @@ void VoxelAnimTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence system to record what kind of object was -/// written, so that the right class can be created when the save game is loaded. -/// -/// Pointer to the class identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE VoxelAnimTypeClass::GetClassID(CLSID * retval) +ClassID VoxelAnimTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_VoxelAnimTypeClass; - return(S_OK); + return(ClassID_VoxelAnimTypeClass); } diff --git a/code/vanimtype.h b/code/vanimtype.h index aa01e9e9a..9abc1adfe 100644 --- a/code/vanimtype.h +++ b/code/vanimtype.h @@ -32,7 +32,7 @@ class VoxelAnimTypeClass : public ObjectTypeClass VoxelAnimTypeClass(char const * ininame = NULL); ~VoxelAnimTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vector.h b/code/vector.h index f608fa4c0..7466a1fae 100644 --- a/code/vector.h +++ b/code/vector.h @@ -123,8 +123,7 @@ class VectorClass stream.Serialize(count); if (stream.Is_Loading()) { - if (count < 0) { - stream.Fail(); + if (!stream.Fits(count, (std::is_arithmetic_v || std::is_enum_v) ? sizeof(T) : 1)) { return; } Clear(); @@ -518,8 +517,7 @@ class DynamicVectorClass : public VectorClass stream.Serialize(count); if (stream.Is_Loading()) { - if (count < 0) { - stream.Fail(); + if (!stream.Fits(count, (std::is_arithmetic_v || std::is_enum_v) ? sizeof(T) : 1)) { return; } Clear(); diff --git a/code/vein.cpp b/code/vein.cpp index 89de17ba7..5db7f3fd4 100644 --- a/code/vein.cpp +++ b/code/vein.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "vein.h" @@ -860,19 +859,21 @@ void VeinholeMonsterClass::Remove_Dead(void) /// growth records and handed to the swizzler and the target tracker. /// /// bool; Were all the monsters read successfully? -bool VeinholeMonsterClass::Load_All(IStream * stream) +bool VeinholeMonsterClass::Load_All(SaveStreamClass & stream) { Reset(); int cell_count = Map_Cell_Count(); int monster_count; - if (FAILED(stream->Read(&monster_count, sizeof(monster_count), NULL))) { + stream.Serialize(monster_count); + if (stream.Was_Error()) { return(false); } GlobalGrowthState = new bool[cell_count]; - if (FAILED(stream->Read(GlobalGrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(GlobalGrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } @@ -885,20 +886,21 @@ bool VeinholeMonsterClass::Load_All(IStream * stream) VeinholeMonsterClass * monster = new VeinholeMonsterClass(); SwizzleIDType id; - if (FAILED(stream->Read(&id, sizeof(id), NULL))) { + stream.Serialize(id); + if (stream.Was_Error()) { return(false); } Swizzler.Here_I_Am(id, monster); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context(typeid(*monster).name(), id); - monster->Serialize(savestream); - if (FAILED(savestream.Result())) { + stream.Set_Context(typeid(*monster).name(), id); + monster->Serialize(stream); + if (stream.Was_Error()) { return(false); } - if (FAILED(stream->Read(monster->GrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(monster->GrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } @@ -939,31 +941,34 @@ void VeinholeMonsterClass::Serialize(SaveStreamClass & stream) /// with its vein growth records so that growth can pick up where it left off. /// /// bool; Were all the monsters written successfully? -bool VeinholeMonsterClass::Save_All(IStream * stream) +bool VeinholeMonsterClass::Save_All(SaveStreamClass & stream) { int monster_count = VeinholeMonsters.Count(); - if (FAILED(stream->Write(&monster_count, sizeof(monster_count), NULL))) { + stream.Serialize(monster_count); + if (stream.Was_Error()) { return(false); } int cell_count = Map_Cell_Count(); - if (FAILED(stream->Write(GlobalGrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(GlobalGrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } for (int i = 0; i < monster_count; i++) { SwizzleIDType id = Swizzler.ID_Of(VeinholeMonsters[i]); - if (FAILED(stream->Write(&id, sizeof(id), NULL))) { + stream.Serialize(id); + if (stream.Was_Error()) { return(false); } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - VeinholeMonsters[i]->Serialize(savestream); - if (FAILED(savestream.Result())) { + VeinholeMonsters[i]->Serialize(stream); + if (stream.Was_Error()) { return(false); } - if (FAILED(stream->Write(VeinholeMonsters[i]->GrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(VeinholeMonsters[i]->GrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } @@ -1041,15 +1046,7 @@ void VeinholeMonsterClass::Reduce_Veins_At(CellClass * cellptr) } -/// -/// Fetches the class identifier used by the save game system. -/// This routine is called by the persistence layer so that it knows which class to -/// recreate when the saved game is read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE VeinholeMonsterClass::GetClassID(CLSID * retval) +ClassID VeinholeMonsterClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_VeinholeMonsterClass; - return(S_OK); + return(ClassID_VeinholeMonsterClass); } diff --git a/code/vein.h b/code/vein.h index 4b53f0acc..dba7355a7 100644 --- a/code/vein.h +++ b/code/vein.h @@ -34,7 +34,7 @@ class VeinholeMonsterClass : public ObjectClass VeinholeMonsterClass(Cell const & cell); ~VeinholeMonsterClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; /*--------------------------------------------------------------------- ** Member function prototypes. @@ -61,8 +61,8 @@ class VeinholeMonsterClass : public ObjectClass void Clear_Growth(void); void Destroy_Monster(void); static void Remove_Dead(void); - static bool Load_All(IStream * stream); - static bool Save_All(IStream * stream); + static bool Load_All(SaveStreamClass & stream); + static bool Save_All(SaveStreamClass & stream); void Reduce_Veins_At(CellClass * cellptr); virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/walk.cpp b/code/walk.cpp index d3996e10b..887e4e58a 100644 --- a/code/walk.cpp +++ b/code/walk.cpp @@ -27,6 +27,7 @@ #include "inline.h" #include "overtype.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "tactical.h" #include "tube.h" @@ -35,7 +36,6 @@ #include "layer.hh" - /// /// Constructs a walking locomotor. /// This is the locomotor used by infantry, who travel on foot between the sub-cell @@ -64,7 +64,7 @@ WalkLocomotionClass::~WalkLocomotionClass(void) /// Is the infantry traveling somewhere? /// /// bool; Does the infantry have somewhere it is trying to get to? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving(void) +bool WalkLocomotionClass::Is_Moving(void) { return(IsMoving); } @@ -76,7 +76,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving(void) /// merely under orders to travel but is standing still. /// /// bool; Is the infantry moving right now? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Now(void) +bool WalkLocomotionClass::Is_Moving_Now(void) { if (Is_Moving() && LinkedTo->Speed > 0 && HeadToCoord != COORD_NONE) { return(true); @@ -90,7 +90,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Now(void) /// /// Returns with the coordinate being traveled to, or COORD_NONE if the infantry has /// nowhere it needs to be. -Coord STDMETHODCALLTYPE WalkLocomotionClass::Destination(void) +Coord WalkLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -104,7 +104,7 @@ Coord STDMETHODCALLTYPE WalkLocomotionClass::Destination(void) /// /// Returns with the immediate destination, or the current position if the infantry /// is not part way between spots. -Coord STDMETHODCALLTYPE WalkLocomotionClass::Head_To_Coord(void) +Coord WalkLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -119,7 +119,7 @@ Coord STDMETHODCALLTYPE WalkLocomotionClass::Head_To_Coord(void) /// infantry along its path. /// /// bool; Is the infantry still traveling somewhere? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Process(void) +bool WalkLocomotionClass::Process(void) { IsProcessingMovement = true; Movement_AI(true); @@ -135,7 +135,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Process(void) /// than beneath it. /// /// The coordinate to travel to, or COORD_NONE to clear the destination. -void STDMETHODCALLTYPE WalkLocomotionClass::Move_To(Coord to) +void WalkLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { DestinationCoord = to; @@ -158,7 +158,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Move_To(Coord to) /// The step already under way is allowed to finish; it is the ultimate destination /// that is forgotten. /// -void STDMETHODCALLTYPE WalkLocomotionClass::Stop_Moving(void) +void WalkLocomotionClass::Stop_Moving(void) { DestinationCoord = COORD_NONE; if (HeadToCoord == COORD_NONE) { @@ -172,7 +172,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Stop_Moving(void) /// Infantry snap around instantly, so there is no rotation to play out over time. /// /// The direction the infantry should face. -void STDMETHODCALLTYPE WalkLocomotionClass::Do_Turn(DirType dir) +void WalkLocomotionClass::Do_Turn(DirType dir) { LinkedTo->PrimaryFacing.Set(dir); } @@ -184,7 +184,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Do_Turn(DirType dir) /// redirected without waiting for the current step to finish. /// /// The coordinate to step to, or COORD_NONE to abandon the step. -void STDMETHODCALLTYPE WalkLocomotionClass::Force_Immediate_Destination(Coord coord) +void WalkLocomotionClass::Force_Immediate_Destination(Coord coord) { HeadToCoord = coord; if (HeadToCoord == COORD_NONE && DestinationCoord == COORD_NONE) { @@ -608,25 +608,16 @@ bool WalkLocomotionClass::Mark_Head_To(Coord const & coord) } -/// -/// Fetches the class ID of this locomotor. -/// The persistence system uses this to recreate the correct locomotor when a saved -/// game is loaded. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK if the class ID was fetched, otherwise E_POINTER. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::GetClassID(CLSID * retval) +ClassID WalkLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WalkLocomotion; - return(S_OK); + return(ClassID_WalkLocomotion); } /// /// Lists the members this walk locomotor carries. /// The locomotor this one was stacked on top of is a separate persistent object rather -/// than a member, so it still travels framed by OLE and is recreated as the class it was +/// than a member, so it travels as a record of its own and is recreated as the class it was /// saved as. /// /// The stream carrying the members. @@ -645,10 +636,9 @@ void WalkLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - IPersistStreamPtr persist(Piggybacker); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, Piggybacker.get()); } else { - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Piggybacker); + Piggybacker = Load_Locomotor(stream); } } } @@ -658,56 +648,26 @@ void WalkLocomotionClass::Serialize(SaveStreamClass & stream) /// Fetches the display layer that walking objects belong in. /// /// Returns with the layer that objects using this locomotor render into. -LayerType STDMETHODCALLTYPE WalkLocomotionClass::In_Which_Layer(void) +LayerType WalkLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } -/// -/// Fetches an interface pointer from this locomotor. -/// This routine extends the base locomotor with the piggyback interface. -/// -/// The interface identifier being asked for. -/// Pointer to the interface pointer to fill in. -/// Returns with S_OK if the interface was supplied, otherwise E_NOINTERFACE. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - HRESULT result = BASECLASS::QueryInterface(riid, ppvObject); - - if (result == E_NOINTERFACE) { - if (riid == IID_IPiggyback) { - *ppvObject = (IPiggyback*)this; - } - if (*ppvObject == NULL) { - result = E_NOINTERFACE; - } else { - AddRef(); - result = S_OK; - } - } - return(result); -} - - /// /// Attaches a piggybacking locomotor to this one. /// This routine is used when some temporary means of travel, such as being carried /// along, must take over from ordinary walking. /// -/// The locomotor that will ride along on this one. -/// Returns with S_OK if the locomotor was attached, or E_FAIL if one is already -/// piggybacking. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::Begin_Piggyback(ILocomotion * pointer) +/// The locomotor that is to take over the unit. +/// bool; Was the locomotor taken on? One already carrying a locomotor refuses. +bool WalkLocomotionClass::Begin_Piggyback(std::unique_ptr & carried) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker == NULL) { - Piggybacker = pointer; - return(S_OK); + if (carried == nullptr || Piggybacker != nullptr) { + return(false); } - return(E_FAIL); + Piggybacker = std::move(carried); + return(true); } @@ -715,20 +675,10 @@ HRESULT STDMETHODCALLTYPE WalkLocomotionClass::Begin_Piggyback(ILocomotion * poi /// Ends the piggyback session and hands back the locomotor that was riding along. /// Ownership of the piggybacking locomotor passes to the caller. /// -/// Pointer to the locomotor pointer to fill in. -/// Returns with S_OK if a piggybacking locomotor was handed back, or S_FALSE if -/// there was none. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::End_Piggyback(ILocomotion ** pointer) +/// Returns with the locomotor that was riding, or nothing when none was. +std::unique_ptr WalkLocomotionClass::End_Piggyback(void) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker != NULL) { - *pointer = Piggybacker; - Piggybacker.Detach(); - return(S_OK); - } - return(S_FALSE); + return(std::move(Piggybacker)); } @@ -738,7 +688,7 @@ HRESULT STDMETHODCALLTYPE WalkLocomotionClass::End_Piggyback(ILocomotion ** poin /// not resumed part way through a step. /// /// bool; Is it safe to end the piggyback? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Ok_To_End(void) +bool WalkLocomotionClass::Is_Ok_To_End(void) { if (!Is_Moving() && Piggybacker != NULL && !IsProcessingMovement) { return(true); @@ -747,42 +697,13 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Ok_To_End(void) } -/// -/// Fetches the class ID of whichever locomotor is in charge. -/// This routine reports the piggybacking locomotor's identity when one has taken -/// over, otherwise it identifies this walking locomotor. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK if the class ID was fetched, otherwise an error code. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::Piggyback_CLSID(GUID * classid) -{ - if (classid == NULL) { - return(E_POINTER); - } - - if (Piggybacker != NULL) { - IPersistPtr ptr(Piggybacker); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); - } - - IPersistPtr ptr(this); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); -} - - /// /// Releases the sub-cell spot that this infantry has reserved. /// This routine is called when the infantry is being lifted off the map so that the /// spot it had claimed becomes available to others again. /// /// The occupancy marking operation being performed. -void STDMETHODCALLTYPE WalkLocomotionClass::Mark_All_Occupation_Bits(int mark) +void WalkLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { LinkedTo->Clear_Occupy_Bit(Head_To_Coord()); @@ -797,7 +718,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The coordinate to test the immediate destination against. /// bool; Is the infantry walking to that spot? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Here(Coord to) +bool WalkLocomotionClass::Is_Moving_Here(Coord to) { Coord headto = Head_To_Coord(); if (headto.As_Cell() == Coord(to).As_Cell() && abs(headto.Z - to.Z) <= LEVEL_LEPTON_H) { @@ -813,7 +734,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Here(Coord to) /// merely holds orders to travel but has yet to take a step. /// /// bool; Is the infantry really moving at this moment? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Really_Moving_Now(void) +bool WalkLocomotionClass::Is_Really_Moving_Now(void) { return(IsReallyMoving); } diff --git a/code/walk.h b/code/walk.h index 9ea0db2b3..267a54d66 100644 --- a/code/walk.h +++ b/code/walk.h @@ -16,6 +16,8 @@ #include "ipiggy.h" #include "loco.h" +#include + class WalkLocomotionClass : public LocomotionClass, public IPiggyback { @@ -29,34 +31,30 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback WalkLocomotionClass(void); virtual ~WalkLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override {return(BASECLASS::AddRef());} - virtual ULONG STDMETHODCALLTYPE Release(void) override {return(BASECLASS::Release());} - - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) override; - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) override; - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) override; - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) override {return(Piggybacker != NULL);} - - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType dir) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; - virtual boolean STDMETHODCALLTYPE Is_Really_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Stop_Movement_Animation(void) override {IsReallyMoving = false;}; + + virtual bool Begin_Piggyback(std::unique_ptr & carried) override; + virtual std::unique_ptr End_Piggyback(void) override; + virtual bool Is_Ok_To_End(void) override; + virtual bool Is_Piggybacking(void) override {return(Piggybacker != nullptr);} + + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType dir) override; + virtual LayerType In_Which_Layer(void) override; + virtual void Force_Immediate_Destination(Coord coord) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; + virtual bool Is_Really_Moving_Now(void) override; + virtual void Stop_Movement_Animation(void) override {IsReallyMoving = false;}; void Movement_AI(bool first_pass); bool Mark_Head_To(Coord const & coord); @@ -100,5 +98,5 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback * temporarily -- a jump jet coming down to cover the last few cells on foot, say -- * and the suspended locomotor is handed back when the walk is finished. */ - ILocomotionPtr Piggybacker; + std::unique_ptr Piggybacker; }; diff --git a/code/warhead.cpp b/code/warhead.cpp index 11cd19645..39f8e8e02 100644 --- a/code/warhead.cpp +++ b/code/warhead.cpp @@ -35,7 +35,6 @@ * WarheadTypeClass::operator new -- Allocate a warhead object from the special heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "warhead.h" @@ -248,18 +247,9 @@ void WarheadTypeClass::Compute_CRC(CRCEngine &crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence interface. The save code stores the identifier -/// so that the object can be recognized when the game is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE WarheadTypeClass::GetClassID(CLSID * retval) +ClassID WarheadTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WarheadTypeClass; - return(S_OK); + return(ClassID_WarheadTypeClass); } diff --git a/code/warhead.h b/code/warhead.h index 1449ecd30..02e1a2a64 100644 --- a/code/warhead.h +++ b/code/warhead.h @@ -52,7 +52,7 @@ class WarheadTypeClass : public AbstractTypeClass WarheadTypeClass(char const * ininame = NULL); virtual ~WarheadTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/wave.cpp b/code/wave.cpp index d2ae55f06..a9484d2ef 100644 --- a/code/wave.cpp +++ b/code/wave.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "wave.h" @@ -466,18 +465,9 @@ void WaveClass::Post_Load(void) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence interface. The save game loader uses the -/// identifier to recreate the object as the right kind of class. -/// -/// Pointer to the buffer to store the class identifier in. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE WaveClass::GetClassID(CLSID * retval) +ClassID WaveClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WaveClass; - return(S_OK); + return(ClassID_WaveClass); } diff --git a/code/wave.h b/code/wave.h index 8444e4dcc..d1d291d5c 100644 --- a/code/wave.h +++ b/code/wave.h @@ -25,7 +25,7 @@ class WaveClass : public ObjectClass WaveClass(void); virtual ~WaveClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/waypoint.cpp b/code/waypoint.cpp index a71d3c6f5..59d2f4fb3 100644 --- a/code/waypoint.cpp +++ b/code/waypoint.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "waypoint.h" @@ -281,18 +280,9 @@ void WaypointPathClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery, which records the identifier so that -/// it knows what kind of object to create when the game is loaded back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE WaypointPathClass::GetClassID(CLSID * retval) +ClassID WaypointPathClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WaypointPath; - return(S_OK); + return(ClassID_WaypointPath); } diff --git a/code/waypoint.h b/code/waypoint.h index ff7f11d10..5fdac3fb5 100644 --- a/code/waypoint.h +++ b/code/waypoint.h @@ -49,7 +49,7 @@ class WaypointPathClass : public AbstractClass WaypointPathClass(int index); virtual ~WaypointPathClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/weapon.cpp b/code/weapon.cpp index 697c9e14d..736582178 100644 --- a/code/weapon.cpp +++ b/code/weapon.cpp @@ -39,7 +39,6 @@ * WeaponTypeClass::Allowed_Threats -- Determine what threats this weapon can address. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "weapon.h" @@ -363,18 +362,9 @@ void WeaponTypeClass::Compute_CRC(CRCEngine &crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery to recognize what kind of object it -/// is about to load back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE WeaponTypeClass::GetClassID(CLSID * retval) +ClassID WeaponTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WeaponTypeClass; - return(S_OK); + return(ClassID_WeaponTypeClass); } diff --git a/code/weapon.h b/code/weapon.h index 22df6f3ed..3e1e01f76 100644 --- a/code/weapon.h +++ b/code/weapon.h @@ -60,7 +60,7 @@ class WeaponTypeClass : public AbstractTypeClass WeaponTypeClass(char const * ininame = NULL); ~WeaponTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static WeaponType From_Name(char const * name); diff --git a/docs/README.md b/docs/README.md index d983f12aa..963715287 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,8 @@ The developer guides are split by subject: - [Project direction](DIRECTION.md) — long-term architecture. - [UI system design](UI_DESIGN.md) — proposed RmlUi and ImGui integration, screen-level interchangeable views, and the migration from OwnerDraw. +- [The saved game format](SAVE-FORMAT.md) — the layout of a `.SAV` file: its + header, listing fields, compressed content, and object records. See [CONTRIBUTING.md](../CONTRIBUTING.md) for contribution and review rules. Player and modder documentation is under [manual/](../manual/README.md). When a diff --git a/docs/SAVE-FORMAT.md b/docs/SAVE-FORMAT.md new file mode 100644 index 000000000..987883a50 --- /dev/null +++ b/docs/SAVE-FORMAT.md @@ -0,0 +1,178 @@ +# The saved game format + +A saved game is one `.SAV` file written by `code/savefile.cpp` and read back by +it. This document owns the layout. Where the files live, how they are named, +and when they are written is on the manual's +[save games page](../manual/content/formats/save-games.md). + +Every integer is little-endian. Offsets are from the start of the file. + +## Header + +| Offset | Size | Field | +| --- | --- | --- | +| 0 | 4 | Signature, the bytes `OTSV` | +| 4 | 2 | Format version, currently 1 | +| 6 | 2 | Flags; bit 0 set when the content is LZO-compressed | +| 8 | 4 | Length of the field table | +| 12 | 4 | Offset of the content | +| 16 | 4 | Stored length of the content | +| 20 | 4 | Uncompressed length of the content | +| 24 | 4 | CRC-32 of the stored content | +| 28 | 4 | CRC-32 of the first 28 bytes of the header, continued over the field table | + +The header is 32 bytes, the field table follows it directly, and the content +follows the table directly. The content offset is recorded rather than assumed +so a later format version can put something between the two; this version +refuses a file whose offset says otherwise. A field table is refused above +1 MiB, since a listing is a dozen short fields. + +Both checksums are the CRC-32 of IEEE 802.3, polynomial `0xEDB88320` +reflected, initial value and final complement of all ones, as PNG and gzip +use it. The header checksum continues over the field table so a listing can +verify what it shows without reading the content. + +## Field table + +The fields are what the load dialog lists a save by. Each is: + +| Size | Field | +| --- | --- | +| 2 | Identifier | +| 2 | Kind: 1 string, 2 integer, 3 file time | +| 4 | Length of the value | +| | The value: string bytes without a terminator, a 4-byte integer, or an 8-byte `FILETIME` | + +The identifiers are the `PIDSI_` values in `code/savever.h`, the same ones the +compound-document property set carried before this format. A field holds at +most 64 KiB. A reader takes the +first field that matches both identifier and kind and ignores the rest, so a +field it does not know costs nothing. A string longer than the buffer it is +read into is cut on a character boundary, so a shortened description stays +UTF-8. `SaveVersionInfo` in `code/savever.cpp` is the only writer and reader. + +## Content + +The content is the game state: the bytes `Put_All` in `code/saveload.cpp` +writes through `SaveStreamClass`, compressed as one block with LZO1X-1 when +that makes it smaller, and stored as it is otherwise. The reader checks the +stored length and checksum before decompressing, and refuses a block that does +not expand to exactly the recorded length. An uncompressed length above 256 MiB +is refused before anything is allocated for it. + +The block is decompressed through `lzo1x_decompress_safe`, which stops at the +end of the output buffer, so a block forged to expand past the recorded length +is refused rather than written past it. The records after the header are still +read into live objects, so treat a save file from an untrusted source as +untrusted input. + +### Object records + +The state is a sequence of values and object records in the order `Put_All` +names them. An object record is: + +| Size | Field | +| --- | --- | +| 16 | The class identifier of the object | +| 4 | Length of the record body | +| | The body: the swizzle identity, then the members the class's `Serialize` names | + +The class identifier is the `ClassID` the object's `Class_ID` reports, the +same one registered in `code/startup.cpp` and, for a locomotor, named by the +`Locomotor=` key. Its sixteen bytes are those of the COM class identifier +the class once registered, kept because the `Locomotor=` values in rules +files carry them. The reader creates the object through that +registration, hands it the stream, checks that it consumed exactly the +recorded length, and only then lets it finish restoring itself, so a refused +record never reaches the map or a side table. A record that comes up short +or long fails the load with the object's type and offset in the debug log, +which is what a member added to one build and not the other looks like. A +record read where a locomotor belongs fails the load the same way when its +class is not one. A vector of objects is a 4-byte count followed by that +many records, all of the heap's own class; a record naming any other class +fails the load, since nothing else belongs in that heap. A locomotor nested +inside a unit's record is a record of its own. A count that the bytes +remaining in the content could not hold fails the load before anything is +allocated for it. + +An object whose record fails is destroyed before the load fails. The pointer +slots it had registered are cleared first, since they still hold identities +rather than addresses, and the slots the records before it registered are +cleared the same way. Those earlier objects stay in their heaps, and the ones +that had finished loading have already taken their place in the map or a side +table. A failed load therefore leaves a partly built game that the caller has +to clear, not one it can carry on from. + +A character buffer travels as its text: a length and that many characters, and +a load clears the rest of the buffer. How much room a build keeps for a string +is its own business, so the file carries neither the capacity nor whatever the +memory held past the terminator. The text is at most one character shorter than +the buffer, so a loaded buffer is always terminated; a length that would fill it +outright fails the load, since the engine reads these buffers as C strings. + +The body is what each class's `Serialize` produces, member by member, in host +byte order. It is not described here; the classes are the description. + +The swizzle identity and every pointer member travel as four bytes. The save +numbers the objects it meets rather than writing the address one sat at, so +the body depends neither on the pointer width of the build that wrote it nor +on where the objects were in memory. + +## Versions + +Two numbers gate a save. The format version in the header says how to parse +the file, and a reader refuses a version above its own. The header flags are +gated the same way: a reader refuses a file with a flag bit it does not know, +so a later version can mark content it stores differently without moving the +format version. The internal version in the field table, +`PIDSI_INTERNAL_VER`, is `ExpectedGameVersion`, the packed project version, +and a save whose value differs from the running build's is not offered to the +player. The format version moves only when the layout in this document +changes; the internal version moves with every release. + +## What the reader refuses + +`SaveFileClass::Read` and `Read_Fields` answer one of: + +| Result | When | +| --- | --- | +| `RESULT_MISSING` | No file under that name | +| `RESULT_NOT_A_SAVE` | The first bytes are not the signature | +| `RESULT_UNSUPPORTED_VERSION` | A format version above the reader's, or a header flag it does not know | +| `RESULT_CORRUPT` | A length, checksum or compressed block that does not add up, including a truncated file, a forged block, a field table above 1 MiB, a content offset that does not follow the table, or a content length above 256 MiB | +| `RESULT_NO_MEMORY` | A file within those limits that the process cannot hold | + +`Read` judges the header before it reads or allocates anything else, so a file +of any size costs the reader no more than the limits above allow, and +`Read_Fields` reads the header and the table only, so listing a folder never +allocates for a file's content. + +`Load_Game` reads and checks the whole file before it tears down the running +game, so a refused file costs nothing. + +A save written before this format is an OLE compound document, which begins +with a signature of its own, so the reader answers `RESULT_NOT_A_SAVE` and the +load dialog leaves the file out of its list. Nothing converts those files. + +## Writing + +`SaveFileClass::Write` builds the whole image in memory, writes it to the +target name with `.tmp` appended, flushes and closes it, and then moves it over +the target with `MoveFileExA` and `MOVEFILE_REPLACE_EXISTING`. A save +interrupted at any point leaves the previous file untouched under its name, +and at most a `.tmp` beside it, which the next successful save replaces. +The reader's limits bind the writer too: content above 256 MiB, a field above +64 KiB or a table above 1 MiB is refused with `RESULT_TOO_LARGE` before +anything is written, so a save this build writes is one it reads, and the +file on disk is left as it was. + +## Checks + +`tests/save` builds `code/savefile.cpp` against the vendored LZO library and +covers the round trip, the fields-only read, replacement of an existing file +and of a stale `.tmp`, and each refusal above, including a later version, an +unknown flag, a file cut at every boundary, a byte flipped in the header, the +table and the content, a field table above its limit, a gap before the +content, a block that ends before or expands past its declared length, and a +write above each limit that leaves the earlier save in place. It reads no game +data. diff --git a/manual/content/formats/save-games.md b/manual/content/formats/save-games.md index 0e375f94e..8d60d0bb7 100644 --- a/manual/content/formats/save-games.md +++ b/manual/content/formats/save-games.md @@ -1,7 +1,7 @@ --- format_id: save-games title: Save games -summary: Stores versioned OpenTS game state in `.SAV` compound-document files. +summary: Stores versioned OpenTS game state in `.SAV` files of the engine's own format. kind: binary extensions: - .SAV @@ -18,6 +18,7 @@ source_files: - code/mainloop.cpp - code/mpload.cpp - code/netdlg.cpp + - code/savefile.cpp - code/saveload.cpp - code/savemgr.cpp - code/savestream.cpp @@ -30,7 +31,7 @@ source_files: - code/voc.cpp --- -The save dialog creates `.SAV` files. Each file is an OLE compound document: the listing details live in the document's own property set, and the game state goes into a single `CONTENTS` stream that is compressed as it is written. +The save dialog creates `.SAV` files. Each file begins with a fixed header and a table of the details the load dialog lists a save by, followed by the game state as one compressed block. The listing is read from the header and table alone, and a file that is truncated, damaged, or written by a later format version is refused before anything is loaded. A save is written under a temporary name and moved into place once complete, so an interrupted save leaves the previous file intact. ## Where the files are @@ -60,7 +61,7 @@ Timed saves in a game against other machines run only when a launch file set the The [`QuickSave`](/commands/quicksave/) command writes a campaign to `QUICKSAVE.SAV` and a skirmish to `QUICKSAVE_SKIRMISH.SAV`, replacing the previous file of that kind, so a skirmish never writes over a campaign. The save is written at the frame boundary after the key was pressed, once the frame has retired its dead objects, behind the saving box a menu save shows; the message list then reports `Game saved.` or that the game could not be saved. Each file is described as `Quick Save` and the scenario's description, and the load dialog lists it like any other save. A quick save starts the automatic-save interval over like any completed save. -[`QuickLoad`](/commands/quickload/) restores the file for the kind of game being played. It first reads the file's property set and refuses, with a line in the message list, when the file is missing or carries another version's stamp; otherwise the load runs when the frame ends, in the place the options menu runs, and the mission clock resumes in the restored game. A load that fails partway through the restore shows the same error box as the load dialog and leaves the player in the options menu. +[`QuickLoad`](/commands/quickload/) restores the file for the kind of game being played. It first reads the file's listing fields and refuses, with a line in the message list, when the file is missing or carries another version's stamp; otherwise the load runs when the frame ends, in the place the options menu runs, and the mission clock resumes in the restored game. A load that fails partway through the restore shows the same error box as the load dialog and leaves the player in the options menu. Both commands are refused in a game against other machines, during playback, while a scripted sequence has locked input, and once the game is being won or lost. Both arrive unbound. @@ -74,19 +75,21 @@ In a game against other machines the master can load one of the match's saved ga ## What the file holds -The property set carries the description shown in the list, the player's name and house, the campaign and scenario numbers, the game type, three timestamps, the name of the program that wrote the save, and two version stamps — the save format's own version and the build version of the game that wrote it. +The field table carries the description shown in the list, the player's house, the campaign and scenario numbers, the game type, three timestamps, the name of the program that wrote the save, and the build version of the game that wrote it. The header carries the format's own version. -The `CONTENTS` stream is a fixed sequence of records — the scenario, the environment, the rules, the map, the loose global values, and every list of type definitions and runtime objects — written and read back in the same order. Among the loose values are the looping sounds left at waypoints by [Play Sound Effect At](/mapping/actions/taction-play-sound-at/) and the sounds attached to objects, each as the sound and its place or object; the playing sound itself is not saved and starts again on the first sound tick after the load. Each list stores its own length ahead of its members, and each member writes out the members its class declares, in the order that class lists them. What a save holds is therefore a field-by-field record of each object rather than a copy of the bytes it occupied in memory. Type definitions travel with the save, so a save carries the rules types it was made with rather than looking them up again on load. Artwork does not travel with it: once a restored type's members have been read, its shape and voxel pointers are released and fetched from the archives again, so a save loaded against a changed set of files gets the current artwork. One piece does not come back. A UnitType drawn from shapes is given a [voxel turret](/formats/vxl-hva/) when the rules are read, by a routine no restore calls; the restore takes the ordinary voxel path instead, which releases that turret along with the body model it could not find. Its voxel barrel is fetched back, and the barrel is what the shape path draws. +The game state is a fixed sequence of records — the scenario, the environment, the rules, the map, the loose global values, and every list of type definitions and runtime objects — written and read back in the same order. Among the loose values are the looping sounds left at waypoints by [Play Sound Effect At](/mapping/actions/taction-play-sound-at/) and the sounds attached to objects, each as the sound and its place or object; the playing sound itself is not saved and starts again on the first sound tick after the load. Each list stores its own length ahead of its members, and each member writes out the members its class declares, in the order that class lists them. What a save holds is therefore a field-by-field record of each object rather than a copy of the bytes it occupied in memory. Type definitions travel with the save, so a save carries the rules types it was made with rather than looking them up again on load. Artwork does not travel with it: once a restored type's members have been read, its shape and voxel pointers are released and fetched from the archives again, so a save loaded against a changed set of files gets the current artwork. One piece does not come back. A UnitType drawn from shapes is given a [voxel turret](/formats/vxl-hva/) when the rules are read, by a routine no restore calls; the restore takes the ordinary voxel path instead, which releases that turret along with the body model it could not find. Its voxel barrel is fetched back, and the barrel is what the shape path draws. The scenario record also holds the scenario file itself, name and bytes, where the deployment's [`CarryScenarioFile`](/formats/opents-ini/#what-a-save-carries) asks for it; the record is written either way, empty when nothing is carried. A [restart or replay](/systems/campaign-progression/#losing-and-restarting) after a load reads that copy, not the file on disk, which a client resuming the save may have replaced. A random map holds no file. ## What is checked The project-version stamp decides whether a file is offered at all, and only -the running version's stamp is accepted. The load dialog reads the property set -of every `.SAV` in the saved-games folder and skips every file stamped by -anything else, including the Tiberian Sun release and another OpenTS -release-cycle version. A save that reaches the engine without passing through +the running version's stamp is accepted. The load dialog reads the header and +field table of every `.SAV` in the saved-games folder and skips every file +stamped by anything else, including another OpenTS release-cycle version. A +file in the compound-document layout that earlier OpenTS releases and Tiberian +Sun wrote is not a saved game to this reader and is skipped as well; there is +no conversion. A save that reaches the engine without passing through the dialog, as a network save or one resumed from a [launch file](/formats/spawn-ini/) does, is checked the same way and refused. Development snapshots within one cycle share the stamp, and their save layouts @@ -95,4 +98,4 @@ leading `*`. Beyond that stamp and the add-on the scenario declares, nothing about a save is measured against the game it is being loaded into. A save made under one set of rules and loaded under another is not detected, and the type definitions stored in the file are simply restored over the ones the rules built. -Reading `CONTENTS` clears the scenario before it starts and gives up at the first record it cannot restore. A load that stops there fails, rather than carrying on into a scenario that was cleared and never refilled. +The file is read and checked in full, checksums included, before the running game is touched, so a truncated or damaged file is refused at no cost. Restoring the game state then clears the scenario before it starts and gives up at the first record it cannot restore. A load that stops there fails, rather than carrying on into a scenario that was cleared and never refilled. diff --git a/manual/content/internals/class-hierarchy.md b/manual/content/internals/class-hierarchy.md index 0e3097469..97897fc73 100644 --- a/manual/content/internals/class-hierarchy.md +++ b/manual/content/internals/class-hierarchy.md @@ -22,7 +22,7 @@ source_files: `AbstractClass` is the common base for persistent engine entities. Map objects and INI-backed type definitions are separate branches of that hierarchy. A runtime instance stores state for one object in the current match; a type definition stores data shared by every instance with the same INI identifier. -This page covers simulation objects and their definitions. UI controls, file classes, and locomotion COM objects use other hierarchies. +This page covers simulation objects and their definitions. UI controls, file classes, and locomotors use other hierarchies. ## Terms diff --git a/manual/content/internals/locomotion.md b/manual/content/internals/locomotion.md index 2ac0434cb..d9e912943 100644 --- a/manual/content/internals/locomotion.md +++ b/manual/content/internals/locomotion.md @@ -14,13 +14,13 @@ source_files: - code/droppod.cpp --- -`FootClass::Locomotion` is the current `ILocomotion` COM interface for one mobile runtime instance. The locomotor is a separate object linked to the `FootClass`; it is not a behavioral base class of `FootClass`. +`FootClass::Locomotion` owns the current `ILocomotion` locomotor for one mobile runtime instance. The locomotor is a separate object linked to the `FootClass`; it is not a behavioral base class of `FootClass`. ## Object locomotion -`TechnoTypeClass::Locomotor` stores the CLSID used to create a type's ordinary locomotor. Concrete `FootClass` constructors create that COM object, call `Link_To_Object`, and assign it to `FootClass::Locomotion`. +`TechnoTypeClass::Locomotor` stores the class identifier used to create a type's ordinary locomotor. Concrete `FootClass` constructors create that locomotor, call `Link_To_Object`, and assign it to `FootClass::Locomotion`. -Movement, destination, layer, occupation, and locomotor-specific drawing queries go through the current interface. Code must therefore inspect the runtime `Locomotion` pointer when temporary locomotion is possible; the type's `Locomotor` CLSID describes the ordinary implementation, not necessarily the one currently in control. +Movement, destination, layer, occupation, and locomotor-specific drawing queries go through the current interface. Code must therefore inspect the runtime `Locomotion` pointer when temporary locomotion is possible; the type's `Locomotor` identifier describes the ordinary implementation, not necessarily the one currently in control. ## Piggybacking @@ -28,16 +28,16 @@ Movement, destination, layer, occupation, and locomotor-specific drawing queries | Operation | State transition | | --- | --- | -| `Begin_Piggyback(previous)` | Stores `previous` inside the new locomotor. A null pointer returns `E_POINTER`; an already occupied slot returns `E_FAIL`. | +| `Begin_Piggyback(previous)` | Stores `previous` inside the new locomotor and takes ownership of it. Answers `false` and leaves `previous` with the caller when there is nothing to store or the slot is already occupied. | | Replace `FootClass::Locomotion` | Makes the new locomotor the object's active movement interface. The new locomotor must already be linked to the same object. | -| `End_Piggyback(&FootClass::Locomotion)` | Writes the stored locomotor back into the object member and releases the piggyback slot. No stored locomotor returns `S_FALSE`; a null output pointer returns `E_POINTER`. | +| `End_Piggyback()` | Hands the stored locomotor back to the caller and empties the piggyback slot. Answers nothing when no locomotor was stored. | -`FootClass::Link_DropPod` applies this sequence with the ballistic locomotor: it retains the passenger's current locomotor through `Begin_Piggyback`, then installs the ballistic interface. Drop-pod touchdown passes the address of `FootClass::Locomotion` to `End_Piggyback` before attempting ground placement. +`FootClass::Link_DropPod` applies this sequence with the ballistic locomotor: it retains the passenger's current locomotor through `Begin_Piggyback`, then installs the ballistic interface. Drop-pod touchdown assigns the locomotor `End_Piggyback` returns back to `FootClass::Locomotion`, when the pod carried one, before attempting ground placement. Callers that perform opportunistic restoration first consult `Is_Ok_To_End`. The drop-pod touchdown path calls `End_Piggyback` directly at ground contact because its descent state already establishes the transition. ## Persistence identity -`FootClass::Serialize` writes the active locomotor through `IPersistStream` when saving and restores it through `OleLoadFromStream` when loading. A piggyback-capable locomotor writes whether it carries another locomotor and serializes that nested COM object when present. A save made during a temporary movement state therefore retains both the active locomotor and the one to restore. +`FootClass::Serialize` writes the active locomotor as a record of its own, headed by its class identifier, and recreates it from that identifier when loading. A piggyback-capable locomotor writes whether it carries another locomotor and serializes that nested locomotor when present. A save made during a temporary movement state therefore retains both the active locomotor and the one to restore. -`GetClassID` identifies the active locomotor implementation. `Piggyback_CLSID` returns the carried locomotor's `GetClassID` while piggybacking and the active locomotor's ID otherwise. These identities are distinct while a temporary locomotor is in control. +`Class_ID` identifies the active locomotor implementation, and the carried locomotor keeps its own. These identities are distinct while a temporary locomotor is in control. diff --git a/manual/data/ini-keys.yaml b/manual/data/ini-keys.yaml index 2f4d3e79a..7d90356fb 100644 --- a/manual/data/ini-keys.yaml +++ b/manual/data/ini-keys.yaml @@ -13123,7 +13123,7 @@ Locomotor: section: kind: identifier source: object-type - value_type: Locomotor CLSID + value_type: Locomotor class identifier status: generated _provenance: default_candidate: the Teleport locomotor diff --git a/manual/site/scripts/check-render.mjs b/manual/site/scripts/check-render.mjs index 94a46df6e..e6d7b1f77 100644 --- a/manual/site/scripts/check-render.mjs +++ b/manual/site/scripts/check-render.mjs @@ -57,7 +57,7 @@ const cases = [ ['mapping/missions/tmission-loop/index.html', ['Jump to line', 'one-based']], ['internals/class-hierarchy/index.html', ['Object and type system', 'Primary runtime hierarchy', 'Type-definition hierarchy', 'AbstractTypeClass', 'code/abstype.h']], ['internals/radio/index.html', ['Radio contact protocol', 'Contact state', 'Messages and responses', 'Compute_CRC', 'code/radio.cpp']], - ['internals/locomotion/index.html', ['Locomotion and piggybacking', 'FootClass::Locomotion', 'IPiggyback', 'Piggyback_CLSID']], + ['internals/locomotion/index.html', ['Locomotion and piggybacking', 'FootClass::Locomotion', 'IPiggyback', 'Class_ID']], ['reference/enums/mission/index.html', ['data-enum-table', 'MISSION_HUNT', 'Stored value', 'Used by', 'TMISSION_DO']], ['systems/drop-pods/index.html', ['ots-page-subtitle', 'Entry paths', 'Approach and descent', 'Touchdown']], ['systems/base-adjacency/index.html', ['ots-page-subtitle', 'Base placement and adjacency', 'Placement decision order', 'Adjacent']], diff --git a/manual/site/tests/documentation-source-contract.test.mjs b/manual/site/tests/documentation-source-contract.test.mjs index 817e341ba..b9b9d3713 100644 --- a/manual/site/tests/documentation-source-contract.test.mjs +++ b/manual/site/tests/documentation-source-contract.test.mjs @@ -36,7 +36,7 @@ test('Drop pod approach selection keeps its ordered candidates and unconditional const droppod = source('code/droppod.cpp'); const moveTo = functionBody( droppod, - 'void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to)', + 'void DropPodLocomotionClass::Move_To(Coord to)', ); assert.match( @@ -74,7 +74,7 @@ test('Drop pod directions retain their hard-coded airborne and landing-art mappi const drawingCode = functionBody( droppod, - 'int STDMETHODCALLTYPE DropPodLocomotionClass::Drawing_Code(void)', + 'int DropPodLocomotionClass::Drawing_Code(void)', ); assert.match(drawingCode, /Direction\s*%\s*2/); assertOrdered(infantry, [ @@ -84,13 +84,13 @@ test('Drop pod directions retain their hard-coded airborne and landing-art mappi const process = functionBody( droppod, - 'boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void)', + 'bool DropPodLocomotionClass::Process(void)', ); assert.match(process, /Rule->DropPod\[Direction\s*%\s*Rule->DropPod\.Count\(\)\]/); const moveTo = functionBody( droppod, - 'void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to)', + 'void DropPodLocomotionClass::Move_To(Coord to)', ); assertOrdered(moveTo, [ 'dropcoord.Z += Rule->DropPodHeight;', @@ -102,13 +102,13 @@ test('Drop pod directions retain their hard-coded airborne and landing-art mappi test('Blocked Drop pod touchdown retains its exact damage, animation, and deletion payload', () => { const process = functionBody( source('code/droppod.cpp'), - 'boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void)', + 'bool DropPodLocomotionClass::Process(void)', ); assertOrdered(process, [ 'FootClass * linked = LinkedTo;', 'coord = linked->PositionCoord;', 'linked->Limbo();', - 'End_Piggyback(&LinkedTo->Locomotion);', + 'LinkedTo->Locomotion = std::move(carried);', 'if (!linked->Unlimbo(coord, DIR_N)) {', 'Explosion_Damage(coord, 100, LinkedTo, Rule->C4Warhead);', 'Combat_Anim(100, Rule->C4Warhead, LAND_CLEAR, coord)', diff --git a/manual/tools/extract_engine.py b/manual/tools/extract_engine.py index c4f3cf656..f999f560c 100644 --- a/manual/tools/extract_engine.py +++ b/manual/tools/extract_engine.py @@ -93,7 +93,7 @@ def _yaml(): "SpeedType": "SpeedType", "MPHType": "speed", "Side": "Side", - "CLSID": "Locomotor CLSID", + "ClassID": "Locomotor class identifier", "Owners": "list of HouseTypes", "Scheme_Index": "colour scheme", "BuildingType_List": "list of BuildingTypes", @@ -975,7 +975,7 @@ def resolve_default(rec, all_defaults, tree): "DIR_N": "0", "CALL_WAIT_CUSTOM": "3", "MAX_PLAYERS": "8", - "CLSID_TeleportLocomotion": "the Teleport locomotor", + "ClassID_TeleportLocomotion": "the Teleport locomotor", "CELL_LEPTON_W": "256", "4*CELL_LEPTON_W": "1024", "9*CELL_LEPTON_W": "2304", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4b335c072..b2c0e9a8b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -97,6 +97,6 @@ add_subdirectory(deploymentconfig) add_subdirectory(tutorial) add_subdirectory(utf8) add_subdirectory(shapefacing) -add_subdirectory(cstream) add_subdirectory(zbufring) add_subdirectory(priorityqueue) +add_subdirectory(save) diff --git a/tests/cstream/CMakeLists.txt b/tests/cstream/CMakeLists.txt deleted file mode 100644 index c996e004c..000000000 --- a/tests/cstream/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -opents_add_test(CStreamContract - NAME cstream - SOURCES cstreamcontract.cpp - ENGINE - cstream.cpp - isun_i.c - DEFINITIONS WIN32 _WINDOWS _MBCS NOMINMAX - LIBRARIES lzo - FLOAT -) diff --git a/tests/cstream/cstreamcontract.cpp b/tests/cstream/cstreamcontract.cpp deleted file mode 100644 index c08ad63a0..000000000 --- a/tests/cstream/cstreamcontract.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#include "cstream.h" - -#include - -#include -#include -#include -#include -#include - -ULONG COMRefCount = 0; - -namespace { - -int Failures = 0; - - -void Report(char const * name, bool ok) -{ - std::printf("%-64s %s\n", name, ok ? "ok" : "FAILED"); - if (!ok) Failures++; -} - - -bool Create_Storage(IStreamPtr & storage) -{ - IStream * stream = nullptr; - if (FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream))) { - return(false); - } - storage.Attach(stream, false); - return(true); -} - - -bool Rewind(IStream * storage) -{ - LARGE_INTEGER const start = {}; - return(SUCCEEDED(storage->Seek(start, STREAM_SEEK_SET, nullptr))); -} - - -std::vector Make_Source(ULONG size) -{ - std::vector source(size); - std::uint32_t seed = 123456789; - for (unsigned char & value : source) { - seed ^= seed << 13; - seed ^= seed >> 17; - seed ^= seed << 5; - value = static_cast(seed & 15); - } - return(source); -} - - -void Test_Roundtrip(bool fragmented, ULONG tail) -{ - std::vector const source = Make_Source(CStreamClass::BUFFER_SIZE + tail); - IStreamPtr storage; - bool ok = Create_Storage(storage); - if (ok) { - CStreamClass writer; - ok = SUCCEEDED(writer.Link_Stream(storage)); - for (ULONG offset = 0; ok && offset < source.size();) { - ULONG const count = std::min(static_cast(source.size()) - offset, fragmented ? 997UL : static_cast(source.size())); - ULONG written = 0; - ok = SUCCEEDED(writer.Write(source.data() + offset, count, &written)) && written == count; - offset += count; - } - ok = SUCCEEDED(writer.Unlink_Stream(nullptr)) && ok; - } - - std::array header = {}; - if (ok) { - ULONG read = 0; - ok = Rewind(storage) && SUCCEEDED(storage->Read(header.data(), sizeof(header), &read)) && read == sizeof(header); - ok = ok && header[0] > CStreamClass::BUFFER_SIZE && header[0] <= CStreamClass::STREAM_BUFFER_SIZE; - std::printf("First compressed block: %lu bytes\n", header[0]); - } - - if (ok) { - ok = Rewind(storage); - CStreamClass reader; - ok = SUCCEEDED(reader.Link_Stream(storage)) && ok; - std::vector restored(source.size()); - for (ULONG offset = 0; ok && offset < restored.size();) { - ULONG const count = std::min(static_cast(restored.size()) - offset, fragmented ? 613UL : static_cast(restored.size())); - ULONG read = 0; - ok = SUCCEEDED(reader.Read(restored.data() + offset, count, &read)) && read == count; - offset += count; - } - ok = ok && restored == source; - - unsigned char extra = 0xA5; - ULONG read = 123; - ok = FAILED(reader.Read(&extra, sizeof(extra), &read)) && read == 0 && extra == 0xA5 && ok; - } - - Report(fragmented ? "Fragmented writes and reads with a partial final block" : "Full expanded block and exact end of stream", ok); -} - - -void Test_Read_Bound(void) -{ - IStreamPtr storage; - bool ok = Create_Storage(storage); - if (ok) { - std::array const header = {CStreamClass::STREAM_BUFFER_SIZE + 1, CStreamClass::BUFFER_SIZE}; - unsigned char const payload = 0; - ULONG written = 0; - ok = SUCCEEDED(storage->Write(header.data(), sizeof(header), &written)) && written == sizeof(header); - ok = SUCCEEDED(storage->Write(&payload, sizeof(payload), &written)) && written == sizeof(payload) && ok; - ok = Rewind(storage) && ok; - - CStreamClass reader; - ok = SUCCEEDED(reader.Link_Stream(storage)) && ok; - unsigned char result = 0xA5; - ULONG read = 123; - ok = FAILED(reader.Read(&result, sizeof(result), &read)) && read == 0 && result == 0xA5 && ok; - - LARGE_INTEGER const offset = {}; - ULARGE_INTEGER position = {}; - ok = SUCCEEDED(storage->Seek(offset, STREAM_SEEK_CUR, &position)) && position.QuadPart == sizeof(header) && ok; - } - Report("Oversized compressed header rejected before reading payload", ok); -} - -} - - -int main(void) -{ - Report("LZO initialization", lzo_init() == LZO_E_OK); - Test_Roundtrip(false, 0); - Test_Roundtrip(true, 137); - Test_Read_Bound(); - return(Failures == 0 ? 0 : 1); -} diff --git a/tests/save/CMakeLists.txt b/tests/save/CMakeLists.txt new file mode 100644 index 000000000..fd69fde2f --- /dev/null +++ b/tests/save/CMakeLists.txt @@ -0,0 +1,14 @@ +# The harness drives the file a saved game is kept in: savefile.cpp's writer and reader over +# the field table, the compressed content, and every refusal the reader makes. It links the +# same vendored LZO library the engine does, and compiles crc.cpp for the checksum the +# header and the content carry. + +opents_add_test(SaveTest + NAME save + SOURCES savetest.cpp + ENGINE + crc.cpp + savefile.cpp + DEFINITIONS WIN32 _WINDOWS _MBCS NOMINMAX + LIBRARIES lzo +) diff --git a/tests/save/savetest.cpp b/tests/save/savetest.cpp new file mode 100644 index 000000000..24bf41d0e --- /dev/null +++ b/tests/save/savetest.cpp @@ -0,0 +1,485 @@ +// Exercises the file a saved game is kept in: the field table the load dialog lists from, +// the compressed content block, and every way the reader refuses a file that is not a +// whole, intact save of a version it knows. +// +// Every file it touches it creates itself, in a scratch directory named by the first +// argument or the working directory, so it reads no game data and leaves nothing behind. + +#include "savefile.h" + +#include + +#include +#include +#include +#include + +static int Failures = 0; +static int Checks = 0; +static std::string Scratch; + + +static void Check(char const * name, bool condition) +{ + Checks++; + if (condition) return; + Failures++; + printf("FAIL %s\n", name); +} + + +static void Check_Result(char const * name, SaveFileClass::ResultType actual, SaveFileClass::ResultType expected) +{ + Checks++; + if (actual == expected) return; + Failures++; + printf("FAIL %s: got \"%s\", expected \"%s\"\n", name, + SaveFileClass::Result_Text(actual), SaveFileClass::Result_Text(expected)); +} + + +static std::string Scratch_Path(char const * name) +{ + return(Scratch + "\\" + name); +} + + +static std::vector Noise(std::size_t length, unsigned int seed) +{ + std::vector data(length); + unsigned int state = seed * 2654435761u + 1u; + for (std::size_t index = 0; index < length; index++) { + state = state * 1103515245u + 12345u; + data[index] = (unsigned char)((state >> 16) & 0xFF); + } + return(data); +} + + +static std::vector Prose(std::size_t length) +{ + static char const text[] = "The quick brown fox jumps over the lazy dog. "; + std::vector data; + while (data.size() < length) { + data.push_back((unsigned char)text[data.size() % (sizeof(text) - 1)]); + } + return(data); +} + + +static std::vector Read_Whole_File(char const * path) +{ + std::vector data; + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return(data); + DWORD const size = GetFileSize(file, nullptr); + if (size != INVALID_FILE_SIZE && size > 0) { + data.resize(size); + DWORD got = 0; + if (!ReadFile(file, data.data(), size, &got, nullptr) || got != size) data.clear(); + } + CloseHandle(file); + return(data); +} + + +static bool Write_Whole_File(char const * path, std::vector const & data) +{ + HANDLE const file = CreateFileA(path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return(false); + DWORD written = 0; + bool ok = true; + if (!data.empty()) { + ok = WriteFile(file, data.data(), (DWORD)data.size(), &written, nullptr) && written == data.size(); + } + CloseHandle(file); + return(ok); +} + + +static bool File_Exists(char const * path) +{ + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) return(false); + CloseHandle(file); + return(true); +} + + +enum { + FIELD_TITLE = 2, + FIELD_HOUSE = 3, + FIELD_VERSION = 16, + FIELD_WHEN = 13, + FIELD_MISSING = 77, +}; + + +static void Fill(SaveFileClass & save, std::vector const & content) +{ + FILETIME when; + when.dwLowDateTime = 0x12345678u; + when.dwHighDateTime = 0x01D2C3B4u; + + save.Set_String(FIELD_TITLE, "GDI 04: Eviction Notice"); + save.Set_String(FIELD_HOUSE, "GDI"); + save.Set_Int(FIELD_VERSION, 0x00010203); + save.Set_Time(FIELD_WHEN, when); + save.Content = content; +} + + +static void Check_Fields(char const * prefix, SaveFileClass const & save) +{ + char text[64]; + int value = 0; + FILETIME when = {}; + + Check((std::string(prefix) + ": title present").c_str(), save.Get_String(FIELD_TITLE, text, sizeof(text))); + Check((std::string(prefix) + ": title text").c_str(), strcmp(text, "GDI 04: Eviction Notice") == 0); + Check((std::string(prefix) + ": house present").c_str(), save.Get_String(FIELD_HOUSE, text, sizeof(text))); + Check((std::string(prefix) + ": house text").c_str(), strcmp(text, "GDI") == 0); + Check((std::string(prefix) + ": version present").c_str(), save.Get_Int(FIELD_VERSION, &value)); + Check((std::string(prefix) + ": version value").c_str(), value == 0x00010203); + Check((std::string(prefix) + ": time present").c_str(), save.Get_Time(FIELD_WHEN, &when)); + Check((std::string(prefix) + ": time value").c_str(), + when.dwLowDateTime == 0x12345678u && when.dwHighDateTime == 0x01D2C3B4u); + Check((std::string(prefix) + ": a missing field is absent").c_str(), !save.Get_String(FIELD_MISSING, text, sizeof(text))); + Check((std::string(prefix) + ": a field is not found under another kind").c_str(), !save.Get_Int(FIELD_TITLE, &value)); + + Check((std::string(prefix) + ": a short buffer is clipped").c_str(), + save.Get_String(FIELD_TITLE, text, 4) && strcmp(text, "GDI") == 0); +} + + +static void Test_Round_Trip(void) +{ + std::string const path = Scratch_Path("ROUNDTRIP.SAV"); + std::vector const content = Prose(300000); + + SaveFileClass written; + Fill(written, content); + Check_Result("round trip: write", written.Write(path.c_str()), SaveFileClass::RESULT_OK); + Check("round trip: no temporary file is left behind", !File_Exists((path + ".tmp").c_str())); + + std::vector const image = Read_Whole_File(path.c_str()); + Check("round trip: the prose was compressed", !image.empty() && image.size() < content.size() / 4); + + SaveFileClass read; + Check_Result("round trip: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + Check_Fields("round trip", read); + Check("round trip: content reads back whole", read.Content == content); + + SaveFileClass listed; + Check_Result("round trip: fields alone", listed.Read_Fields(path.c_str()), SaveFileClass::RESULT_OK); + Check_Fields("fields alone", listed); + Check("fields alone: no content is read", listed.Content.empty()); +} + + +static void Test_Cuts(void) +{ + SaveFileClass save; + save.Set_String(FIELD_TITLE, "ab\xC3\xA9" "cd"); + + char text[8]; + Check("cuts: a cut never splits a character", save.Get_String(FIELD_TITLE, text, 4) && strcmp(text, "ab") == 0); + Check("cuts: a cut after a character keeps it whole", save.Get_String(FIELD_TITLE, text, 5) && strcmp(text, "ab\xC3\xA9") == 0); + Check("cuts: a buffer that fits keeps everything", save.Get_String(FIELD_TITLE, text, 8) && strcmp(text, "ab\xC3\xA9" "cd") == 0); +} + + +static void Test_Incompressible(void) +{ + std::string const path = Scratch_Path("NOISE.SAV"); + std::vector const content = Noise(70000, 7); + + SaveFileClass written; + Fill(written, content); + Check_Result("noise: write", written.Write(path.c_str()), SaveFileClass::RESULT_OK); + + std::vector const image = Read_Whole_File(path.c_str()); + Check("noise: stored as it is when compression does not pay", image.size() >= content.size() + SaveFileClass::HEADER_SIZE); + + SaveFileClass read; + Check_Result("noise: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + Check("noise: content reads back whole", read.Content == content); +} + + +static void Test_Empty(void) +{ + std::string const path = Scratch_Path("EMPTY.SAV"); + + SaveFileClass written; + Check_Result("empty: write", written.Write(path.c_str()), SaveFileClass::RESULT_OK); + + std::vector const image = Read_Whole_File(path.c_str()); + Check("empty: a header alone", image.size() == SaveFileClass::HEADER_SIZE); + + SaveFileClass read; + Check_Result("empty: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + Check("empty: no content", read.Content.empty()); + char text[8]; + Check("empty: no fields", !read.Get_String(FIELD_TITLE, text, sizeof(text))); +} + + +static void Test_Overwrite(void) +{ + std::string const path = Scratch_Path("REPLACE.SAV"); + + SaveFileClass first; + Fill(first, Prose(5000)); + first.Set_String(FIELD_TITLE, "the earlier save"); + Check_Result("replace: first write", first.Write(path.c_str()), SaveFileClass::RESULT_OK); + + Check("replace: a stale temporary is planted", Write_Whole_File((path + ".tmp").c_str(), Noise(100, 3))); + + SaveFileClass second; + Fill(second, Noise(20000, 11)); + second.Set_String(FIELD_TITLE, "the later save"); + Check_Result("replace: second write", second.Write(path.c_str()), SaveFileClass::RESULT_OK); + Check("replace: the stale temporary is gone", !File_Exists((path + ".tmp").c_str())); + + SaveFileClass read; + Check_Result("replace: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + char text[64]; + Check("replace: the later save is the one on disk", + read.Get_String(FIELD_TITLE, text, sizeof(text)) && strcmp(text, "the later save") == 0); + Check("replace: the later content is the one on disk", read.Content == second.Content); + + SaveFileClass rewritten; + rewritten.Set_String(FIELD_TITLE, "overwritten field"); + rewritten.Set_String(FIELD_TITLE, "final field"); + Check_Result("replace: field rewrite", rewritten.Write(path.c_str()), SaveFileClass::RESULT_OK); + Check_Result("replace: field rewrite read", read.Read_Fields(path.c_str()), SaveFileClass::RESULT_OK); + Check("replace: a field set twice keeps the last value", + read.Get_String(FIELD_TITLE, text, sizeof(text)) && strcmp(text, "final field") == 0); +} + + +static void Put_U32(std::vector & image, std::size_t at, unsigned int value) +{ + image[at] = (unsigned char)(value & 0xFF); + image[at + 1] = (unsigned char)((value >> 8) & 0xFF); + image[at + 2] = (unsigned char)((value >> 16) & 0xFF); + image[at + 3] = (unsigned char)((value >> 24) & 0xFF); +} + + +static void Test_Limits(void) +{ + std::string const path = Scratch_Path("LIMITS.SAV"); + + SaveFileClass kept; + Fill(kept, Prose(3000)); + kept.Set_String(FIELD_TITLE, "the save that stays"); + Check_Result("limits: the save that stays", kept.Write(path.c_str()), SaveFileClass::RESULT_OK); + + SaveFileClass wide; + Fill(wide, Prose(3000)); + wide.Set_String(FIELD_TITLE, std::string(0x10001, 'x').c_str()); + Check_Result("limits: a field beyond its limit is refused", wide.Write(path.c_str()), SaveFileClass::RESULT_TOO_LARGE); + + SaveFileClass many; + Fill(many, Prose(3000)); + for (int id = 100; id < 117; id++) { + many.Set_String(id, std::string(0x10000, 'y').c_str()); + } + Check_Result("limits: a table beyond its limit is refused", many.Write(path.c_str()), SaveFileClass::RESULT_TOO_LARGE); + + SaveFileClass huge; + huge.Content.resize(0x10000001); + Check_Result("limits: content beyond its limit is refused", huge.Write(path.c_str()), SaveFileClass::RESULT_TOO_LARGE); + + Check("limits: no temporary is left behind", !File_Exists((path + ".tmp").c_str())); + SaveFileClass read; + Check_Result("limits: the earlier save still reads", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + char text[64]; + Check("limits: the earlier save is the one on disk", + read.Get_String(FIELD_TITLE, text, sizeof(text)) && strcmp(text, "the save that stays") == 0); +} + + +// Recomputes the header checksum after a test has changed a header byte on purpose. +static void Reseal_Header(std::vector & image, unsigned int table) +{ + unsigned int crc = SaveFileClass::Checksum(image.data(), SaveFileClass::HEADER_SIZE - 4); + crc = SaveFileClass::Checksum(image.data() + SaveFileClass::HEADER_SIZE, table, crc); + Put_U32(image, 28, crc); +} + + +// Rebuilds a save image around a field table of the test's own making, with the content +// kept and every checksum made good. +static std::vector Forge_Table(std::vector const & image, unsigned int table, + std::vector const & newtable) +{ + std::vector forged(image.begin(), image.begin() + SaveFileClass::HEADER_SIZE); + forged.insert(forged.end(), newtable.begin(), newtable.end()); + forged.insert(forged.end(), image.begin() + SaveFileClass::HEADER_SIZE + table, image.end()); + Put_U32(forged, 8, (unsigned int)newtable.size()); + Put_U32(forged, 12, SaveFileClass::HEADER_SIZE + (unsigned int)newtable.size()); + Reseal_Header(forged, (unsigned int)newtable.size()); + return(forged); +} + + +// Replaces the content of a save image with a compressed block of the test's own making, +// declared as expanding to the length given, with every checksum made good. +static std::vector Forge_Content(std::vector const & image, unsigned int table, + std::vector const & stored, unsigned int expands_to) +{ + std::vector forged(image.begin(), image.begin() + SaveFileClass::HEADER_SIZE + table); + forged.insert(forged.end(), stored.begin(), stored.end()); + forged[6] |= 0x01; + Put_U32(forged, 12, SaveFileClass::HEADER_SIZE + table); + Put_U32(forged, 16, (unsigned int)stored.size()); + Put_U32(forged, 20, expands_to); + Put_U32(forged, 24, SaveFileClass::Checksum(stored.data(), (unsigned int)stored.size())); + Reseal_Header(forged, table); + return(forged); +} + + +static void Test_Refusals(void) +{ + SaveFileClass read; + + std::string const missing = Scratch_Path("MISSING.SAV"); + Check_Result("refuse: a missing file", read.Read(missing.c_str()), SaveFileClass::RESULT_MISSING); + Check_Result("refuse: a missing file's fields", read.Read_Fields(missing.c_str()), SaveFileClass::RESULT_MISSING); + + std::string const plain = Scratch_Path("PLAIN.SAV"); + std::vector hello = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; + Write_Whole_File(plain.c_str(), hello); + Check_Result("refuse: a file that is not a save", read.Read(plain.c_str()), SaveFileClass::RESULT_NOT_A_SAVE); + Check_Result("refuse: its fields", read.Read_Fields(plain.c_str()), SaveFileClass::RESULT_NOT_A_SAVE); + + std::string const good = Scratch_Path("GOOD.SAV"); + SaveFileClass written; + Fill(written, Prose(40000)); + Check_Result("refuse: the reference save", written.Write(good.c_str()), SaveFileClass::RESULT_OK); + std::vector const image = Read_Whole_File(good.c_str()); + Check("refuse: the reference save is readable", !image.empty()); + + std::string const damaged = Scratch_Path("DAMAGED.SAV"); + + unsigned int const table = (unsigned int)image[8] | ((unsigned int)image[9] << 8) + | ((unsigned int)image[10] << 16) | ((unsigned int)image[11] << 24); + + std::vector future = image; + future[4] = 99; + future[5] = 0; + Reseal_Header(future, table); + Write_Whole_File(damaged.c_str(), future); + Check_Result("refuse: a later format version", read.Read(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + + std::vector flagged = image; + flagged[6] |= 0x02; + Reseal_Header(flagged, table); + Write_Whole_File(damaged.c_str(), flagged); + Check_Result("refuse: a header flag this build does not know", read.Read(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + + unsigned int const content_offset = (unsigned int)image[12] | ((unsigned int)image[13] << 8) + | ((unsigned int)image[14] << 16) | ((unsigned int)image[15] << 24); + unsigned int const content_length = (unsigned int)image[20] | ((unsigned int)image[21] << 8) + | ((unsigned int)image[22] << 16) | ((unsigned int)image[23] << 24); + Check("refuse: the reference save is compressed", (image[6] & 0x01) != 0); + std::vector const stored(image.begin() + content_offset, image.end()); + + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, stored, content_length + 1)); + Check_Result("refuse: a block that ends before its declared length", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + // The reader sizes the output buffer from the declared length, so this block runs past + // the end of it. The bounds-checked decompressor stops there rather than writing on. + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, stored, content_length - 1)); + Check_Result("refuse: a block that expands past its declared length", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, stored, 0x10000001)); + Check_Result("refuse: a block declared larger than any save", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector const oversized(0x100001, 0); + Write_Whole_File(damaged.c_str(), Forge_Table(image, table, oversized)); + Check_Result("refuse: a field table longer than any listing", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector gapped = image; + gapped.insert(gapped.begin() + content_offset, 8, 0); + Put_U32(gapped, 12, content_offset + 8); + Reseal_Header(gapped, table); + Check("refuse: the gapped image is longer", gapped.size() == image.size() + 8); + Write_Whole_File(damaged.c_str(), gapped); + Check_Result("refuse: a gap between the table and the content", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector header_hit = image; + header_hit[9] ^= 0x01; + Write_Whole_File(damaged.c_str(), header_hit); + Check_Result("refuse: a header byte flipped", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector table_hit = image; + table_hit[SaveFileClass::HEADER_SIZE + 10] ^= 0x20; + Write_Whole_File(damaged.c_str(), table_hit); + Check_Result("refuse: a field byte flipped", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector content_hit = image; + content_hit[image.size() - 40] ^= 0x80; + Write_Whole_File(damaged.c_str(), content_hit); + Check_Result("refuse: a content byte flipped", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: a flipped content byte still lists", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_OK); + + std::size_t const cuts[] = { 3, 12, SaveFileClass::HEADER_SIZE - 1, SaveFileClass::HEADER_SIZE + 5, + SaveFileClass::HEADER_SIZE + table, image.size() / 2, image.size() - 1 }; + for (std::size_t cut : cuts) { + std::vector truncated(image.begin(), image.begin() + cut); + Write_Whole_File(damaged.c_str(), truncated); + char name[80]; + snprintf(name, sizeof(name), "refuse: a file cut at %u bytes", (unsigned int)cut); + SaveFileClass::ResultType const result = read.Read(damaged.c_str()); + Check(name, result == SaveFileClass::RESULT_CORRUPT || (cut < 4 && result == SaveFileClass::RESULT_NOT_A_SAVE)); + } + + std::vector appended = image; + appended.push_back(0); + Write_Whole_File(damaged.c_str(), appended); + Check_Result("refuse: a file with a trailing byte", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); +} + + +int main(int argc, char ** argv) +{ + if (lzo_init() != LZO_E_OK) { + printf("lzo_init failed\n"); + return(2); + } + + Scratch = (argc > 1) ? argv[1] : "."; + CreateDirectoryA(Scratch.c_str(), nullptr); + + Test_Round_Trip(); + Test_Cuts(); + Test_Incompressible(); + Test_Empty(); + Test_Overwrite(); + Test_Limits(); + Test_Refusals(); + + char const * const names[] = { "ROUNDTRIP.SAV", "NOISE.SAV", "EMPTY.SAV", "REPLACE.SAV", + "LIMITS.SAV", "PLAIN.SAV", "GOOD.SAV", "DAMAGED.SAV" }; + for (char const * name : names) { + DeleteFileA(Scratch_Path(name).c_str()); + } + + printf("%d checks, %d failures\n", Checks, Failures); + return(Failures == 0 ? 0 : 1); +} From 026f14fdd5442eb6a952b0ff385067831f1126d2 Mon Sep 17 00:00:00 2001 From: Michael Snow <2400208+msnow345@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:17:20 +0100 Subject: [PATCH 18/18] Drop the size guards upstream's own assertions now duplicate --- code/audio/audiodecode.h | 2 -- code/iff.h | 1 - code/isotype.h | 3 --- code/mixfile.h | 1 - code/pcx.h | 2 -- code/rgb.h | 2 -- code/shapeset.h | 4 ---- code/vqalib/loader.cpp | 3 --- code/vqalib/vqafile.h | 1 - 9 files changed, 19 deletions(-) diff --git a/code/audio/audiodecode.h b/code/audio/audiodecode.h index 39b2d446b..48caea132 100644 --- a/code/audio/audiodecode.h +++ b/code/audio/audiodecode.h @@ -42,9 +42,7 @@ struct AUDChunkHeaderType { static_assert(sizeof(AUDChunkHeaderType) == 8, "an AUD chunk header is 8 bytes on disk"); #pragma pack(pop) -static_assert(sizeof(AUDHeaderType) == 12, "AUD file header layout changed"); static_assert(offsetof(AUDHeaderType, Flags) == 10, "AUD file header layout changed"); -static_assert(sizeof(AUDChunkHeaderType) == 8, "AUD chunk header layout changed"); static_assert(offsetof(AUDChunkHeaderType, Magic) == 4, "AUD chunk header layout changed"); diff --git a/code/iff.h b/code/iff.h index ab5ddf7c0..433a7cf1e 100644 --- a/code/iff.h +++ b/code/iff.h @@ -85,7 +85,6 @@ struct CompHeaderType { static_assert(sizeof(CompHeaderType) == 8, "the compressed file header is 8 bytes on disk"); #pragma pack(pop) -static_assert(sizeof(CompHeaderType) == 8, "Compressed block header layout changed"); static_assert(offsetof(CompHeaderType, Size) == 2, "Compressed block header layout changed"); static_assert(offsetof(CompHeaderType, Skip) == 6, "Compressed block header layout changed"); diff --git a/code/isotype.h b/code/isotype.h index 300ee7d6f..1bef21f33 100644 --- a/code/isotype.h +++ b/code/isotype.h @@ -113,7 +113,6 @@ struct IsoTileRecord static_assert(sizeof(IsoTileRecord) == 52, "a TMP tile record is 52 bytes on disk"); #pragma pack() -static_assert(sizeof(IsoTileRecord) == 52, "Isometric tile record layout changed"); static_assert(offsetof(IsoTileRecord, ExtraZOffset) == 16, "Isometric tile record layout changed"); static_assert(offsetof(IsoTileRecord, Height) == 40, "Isometric tile record layout changed"); static_assert(offsetof(IsoTileRecord, LowColor) == 43, "Isometric tile record layout changed"); @@ -205,8 +204,6 @@ class IsoTileSet static_assert(sizeof(IsoTileSet) == 20, "the TMP header is 16 bytes on disk, followed by the four-byte tile offsets"); #pragma pack() -static_assert(sizeof(IsoTileSet) == 20, "Isometric tile set header layout changed"); - /**************************************************************************** ** The tile type objects are controlled by this class. It specifies the form diff --git a/code/mixfile.h b/code/mixfile.h index 1efcdb8e9..221bcd6e3 100644 --- a/code/mixfile.h +++ b/code/mixfile.h @@ -88,7 +88,6 @@ class MixFileClass : public Node static_assert(sizeof(FileHeader) == 6, "the MIX header is 6 bytes on disk"); #pragma pack() - static_assert(sizeof(FileHeader) == 6, "Mixfile header layout changed"); static_assert(offsetof(FileHeader, size) == 2, "Mixfile header layout changed"); /* diff --git a/code/pcx.h b/code/pcx.h index a8256d941..7dfb3bb8e 100644 --- a/code/pcx.h +++ b/code/pcx.h @@ -70,8 +70,6 @@ struct PCX_HEADER static_assert(sizeof(PCX_HEADER) == 128, "the PCX header is 128 bytes on disk"); #pragma pack(pop) -static_assert(sizeof(RGB) == 3, "PCX palette entry layout changed"); -static_assert(sizeof(PCX_HEADER) == 128, "PCX file header layout changed"); static_assert(offsetof(PCX_HEADER, x) == 4, "PCX file header layout changed"); static_assert(offsetof(PCX_HEADER, ega_palette) == 16, "PCX file header layout changed"); static_assert(offsetof(PCX_HEADER, byte_per_line) == 66, "PCX file header layout changed"); diff --git a/code/rgb.h b/code/rgb.h index d54336561..d06bdd2af 100644 --- a/code/rgb.h +++ b/code/rgb.h @@ -54,8 +54,6 @@ struct RGBStruct static_assert(sizeof(RGBStruct) == 3, "a palette entry is 3 bytes on disk"); #pragma pack() -static_assert(sizeof(RGBStruct) == 3, "Palette entry layout changed"); - /* ** Each color entry is represented by this class. It holds the values for the color diff --git a/code/shapeset.h b/code/shapeset.h index 071c864a6..2885a5833 100644 --- a/code/shapeset.h +++ b/code/shapeset.h @@ -173,7 +173,6 @@ class ShapeSet // A shape file is cast straight onto this class, so the frame records that follow the // header keep their file widths and offsets. - static_assert(sizeof(ShapeRecord) == 24, "Shape frame record layout changed"); static_assert(offsetof(ShapeRecord, Width) == 4, "Shape frame record layout changed"); static_assert(offsetof(ShapeRecord, Color) == 12, "Shape frame record layout changed"); static_assert(offsetof(ShapeRecord, Data) == 20, "Shape frame record layout changed"); @@ -198,9 +197,6 @@ class ShapeSet static_assert(sizeof(ShapeSet) == 8, "the SHP header is 8 bytes on disk"); #pragma pack(pop) -// A shape file is cast straight onto this header, so its four fields keep their file widths. -static_assert(sizeof(ShapeSet) == 8, "Shape file header layout changed"); - /*********************************************************************************************** * ShapeSet::Get_Data -- Fetches pointer to raw shape data. * diff --git a/code/vqalib/loader.cpp b/code/vqalib/loader.cpp index a59dbdec7..c4f359793 100644 --- a/code/vqalib/loader.cpp +++ b/code/vqalib/loader.cpp @@ -311,7 +311,6 @@ struct VQASN2J { static_assert(sizeof(VQASN2J) == 12, "the SN2J chunk is 12 bytes on disk"); #pragma pack(pop) -static_assert(sizeof(VQASN2J) == 12, "SN2J chunk layout changed"); static_assert(offsetof(VQASN2J, predicted2) == 8, "SN2J chunk layout changed"); @@ -3539,8 +3538,6 @@ long Load_SN2J(VQAHandleP *vqap, unsigned long iffsize) static_assert(sizeof(SNJ2Struct) == 12, "the SN2J chunk is 12 bytes on disk"); #pragma pack(pop) - static_assert(sizeof(data) == 12, "SN2J chunk layout changed"); - #if(VQAVOC_ON && VQAAUDIO_ON) if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (vqap->vocfh != -1) || (audio->Buffer == NULL)) { diff --git a/code/vqalib/vqafile.h b/code/vqalib/vqafile.h index 994fc9ee9..c041d8fac 100644 --- a/code/vqalib/vqafile.h +++ b/code/vqalib/vqafile.h @@ -116,7 +116,6 @@ static_assert(sizeof(VQAHeader) == 42, "the VQHD chunk is 42 bytes on disk"); // The VQHD chunk is 42 bytes on disk. MaxCBSize and AudioPreload were written as a 32-bit // long by the original 32-bit build, so they stay 32 bits wide here. -static_assert(sizeof(VQAHeader) == 42, "VQHD chunk layout changed"); static_assert(offsetof(VQAHeader, MaxCBSize) == 34, "VQHD chunk layout changed"); static_assert(offsetof(VQAHeader, AudioPreload) == 38, "VQHD chunk layout changed");