diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp --- a/src/group_cmd.cpp +++ b/src/group_cmd.cpp @@ -285,6 +285,7 @@ CommandCost CmdCreateGroup(TileIndex til Group *g = new Group(_current_company); g->replace_protection = false; g->vehicle_type = vt; + g->parent = INVALID_GROUP; _new_group_id = g->index; @@ -313,6 +314,14 @@ CommandCost CmdDeleteGroup(TileIndex til /* Remove all vehicles from the group */ DoCommand(0, p1, 0, flags, CMD_REMOVE_ALL_VEHICLES_GROUP); + /* Delete sub-groups */ + Group *gp; + FOR_ALL_GROUPS(gp) { + if (gp->parent == g->index) { + DoCommand(0, gp->index, 0, flags, CMD_DELETE_GROUP); + } + } + if (flags & DC_EXEC) { /* Update backupped orders if needed */ OrderBackup::ClearGroup(g->index); @@ -352,33 +361,60 @@ static bool IsUniqueGroupNameForVehicleT } /** - * Rename a group + * Alter a group * @param tile unused * @param flags type of operation * @param p1 index of array group * - p1 bit 0-15 : GroupID - * @param p2 unused + * - p1 bit 16: 0 - Rename grouop + * 1 - Set group parent + * @param p2 parent group index * @param text the new name or an empty string when resetting to the default * @return the cost of this operation or an error */ -CommandCost CmdRenameGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +CommandCost CmdAlterGroup(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { - Group *g = Group::GetIfValid(p1); + Group *g = Group::GetIfValid(GB(p1, 0, 16)); if (g == NULL || g->owner != _current_company) return CMD_ERROR; - bool reset = StrEmpty(text); + if (!HasBit(p1, 16)) { + /* Rename group */ + bool reset = StrEmpty(text); + + if (!reset) { + if (Utf8StringLength(text) >= MAX_LENGTH_GROUP_NAME_CHARS) return CMD_ERROR; + if (!IsUniqueGroupNameForVehicleType(text, g->vehicle_type)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); + } - if (!reset) { - if (Utf8StringLength(text) >= MAX_LENGTH_GROUP_NAME_CHARS) return CMD_ERROR; - if (!IsUniqueGroupNameForVehicleType(text, g->vehicle_type)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); + if (flags & DC_EXEC) { + /* Delete the old name */ + free(g->name); + /* Assign the new one */ + g->name = reset ? NULL : strdup(text); + } + } else { + /* Set group parent */ + const Group *pg = Group::GetIfValid(GB(p2, 0, 16)); + + if (pg != NULL) { + if (pg->owner != _current_company) return CMD_ERROR; + if (pg->vehicle_type != g->vehicle_type) return CMD_ERROR; + + /* Ensure request parent isn't child of group. + * This is the only place that infinite loops are prevented. */ + const Group *looptest = pg; + while (looptest->parent != INVALID_GROUP) { + if (looptest->parent == g->index) return CMD_ERROR; + looptest = Group::Get(looptest->parent); + } + } + + if (flags & DC_EXEC) { + g->parent = (pg == NULL) ? INVALID_GROUP : pg->index; + } } if (flags & DC_EXEC) { - /* Delete the old name */ - free(g->name); - /* Assign the new one */ - g->name = reset ? NULL : strdup(text); - SetWindowDirty(WC_REPLACE_VEHICLE, g->vehicle_type); InvalidateWindowData(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack()); } @@ -542,6 +578,20 @@ CommandCost CmdRemoveAllVehiclesGroup(Ti return CommandCost(); } +/** + * Set replace protection for a group and its sub-groups. + * @param g initial group. + * @param protect 1 to set or 0 to clear protection. + */ +static void SetGroupReplaceProtection(Group *g, bool protect) +{ + g->replace_protection = protect; + + Group *pg; + FOR_ALL_GROUPS(pg) { + if (pg->parent == g->index) SetGroupReplaceProtection(pg, protect); + } +} /** * (Un)set global replace protection from a group @@ -551,6 +601,7 @@ CommandCost CmdRemoveAllVehiclesGroup(Ti * - p1 bit 0-15 : GroupID * @param p2 * - p2 bit 0 : 1 to set or 0 to clear protection. + * - p2 bit 1 : 1 to apply to sub-groups. * @param text unused * @return the cost of this operation or an error */ @@ -560,7 +611,11 @@ CommandCost CmdSetGroupReplaceProtection if (g == NULL || g->owner != _current_company) return CMD_ERROR; if (flags & DC_EXEC) { - g->replace_protection = HasBit(p2, 0); + if (HasBit(p2, 1)) { + SetGroupReplaceProtection(g, HasBit(p2, 0)); + } else { + g->replace_protection = HasBit(p2, 0); + } SetWindowDirty(GetWindowClassForVehicleType(g->vehicle_type), VehicleListIdentifier(VL_GROUP_LIST, g->vehicle_type, _current_company).Pack()); InvalidateWindowData(WC_REPLACE_VEHICLE, g->vehicle_type); @@ -639,8 +694,13 @@ void UpdateTrainGroupID(Train *v) */ uint GetGroupNumEngines(CompanyID company, GroupID id_g, EngineID id_e) { + uint count = 0; const Engine *e = Engine::Get(id_e); - return GroupStatistics::Get(company, id_g, e->type).num_engines[id_e]; + const Group *g; + FOR_ALL_GROUPS(g) { + if (g->parent == id_g) count += GetGroupNumEngines(company, g->index, id_e); + } + return count + GroupStatistics::Get(company, id_g, e->type).num_engines[id_e]; } void RemoveAllGroupsForCompany(const CompanyID company) @@ -651,3 +711,19 @@ void RemoveAllGroupsForCompany(const Com if (company == g->owner) delete g; } } + + +bool GroupIsInGroup(GroupID search, GroupID group) +{ + if (search == NEW_GROUP || + search == ALL_GROUP || + search == DEFAULT_GROUP || + search == INVALID_GROUP) return search == group; + + do { + if (search == group) return true; + search = Group::Get(search)->parent; + } while (search != INVALID_GROUP); + + return false; +}