/// <summary> /// Set up filter bar. /// We don't use Start() here as we need to access the category toggle states to set up the initial filtering list before Start() is called by UnityEngine. /// </summary> public void Setup() { // Catgegory buttons. categoryToggles = new UICheckBox[(int)BuildingCategories.numCategories]; for (int i = 0; i < (int)BuildingCategories.numCategories; i++) { // Basic setup. categoryToggles[i] = UIUtils.CreateIconToggle(this, CategoryIcons.atlases[i], CategoryIcons.spriteNames[i], CategoryIcons.spriteNames[i] + "Disabled"); categoryToggles[i].tooltip = Translations.Translate(CategoryIcons.tooltips[i]); categoryToggles[i].relativePosition = new Vector3(40 * i, 0); categoryToggles[i].isChecked = true; categoryToggles[i].readOnly = true; // Single click event handler - toggle state of this button. categoryToggles[i].eventClick += (c, p) => { // If either shift or control is NOT held down, deselect all other toggles. if (!(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift) || Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))) { for (int j = 0; j < (int)BuildingCategories.numCategories; j++) { categoryToggles[j].isChecked = false; } } // Select this toggle. ((UICheckBox)c).isChecked = true; // Trigger an update. eventFilteringChanged(this, 0); }; } // 'All categories' button. allCategories = UIUtils.CreateButton(this, 120); allCategories.text = Translations.Translate("RPR_CAT_ALL"); allCategories.relativePosition = new Vector3(405, 5); // All categories event handler. allCategories.eventClick += (c, p) => { // Select all category toggles. for (int i = 0; i < (int)BuildingCategories.numCategories; i++) { categoryToggles[i].isChecked = true; } // Trigger an update. eventFilteringChanged(this, 0); }; // Name filter. UILabel nameLabel = AddUIComponent <UILabel>(); nameLabel.textScale = 0.8f; nameLabel.padding = new RectOffset(0, 0, 8, 0); nameLabel.relativePosition = new Vector3(width - 250, 0); nameLabel.text = Translations.Translate("RPR_FIL_NAME"); nameFilter = UIUtils.CreateTextField(this, 200f, 30f); nameFilter.relativePosition = new Vector3(width - nameFilter.width, 0); // Name filter event handling - update on any change. nameFilter.eventTextChanged += (c, s) => eventFilteringChanged(this, 5); nameFilter.eventTextSubmitted += (c, s) => eventFilteringChanged(this, 5); }
/// <summary> /// Create the panel; we no longer use Start() as that's not sufficiently reliable (race conditions), and is no longer needed, with the new create/destroy process. /// </summary> public void Setup() { const int marginPadding = 10; // Generic setup. isVisible = true; canFocus = true; isInteractive = true; backgroundSprite = "UnlockingPanel"; autoLayout = false; autoLayoutDirection = LayoutDirection.Vertical; autoLayoutPadding.top = 5; autoLayoutPadding.right = 5; builtinKeyNavigation = true; clipChildren = true; // Panel title. UILabel title = this.AddUIComponent <UILabel>(); title.relativePosition = new Vector3(0, 5); title.textAlignment = UIHorizontalAlignment.Center; title.text = Translations.Translate("RPR_CUS_TITLE"); title.textScale = 1.2f; title.autoSize = false; title.width = this.width; title.height = 30; // Text field label. homeJobLabel = this.AddUIComponent <UILabel>(); homeJobLabel.relativePosition = new Vector3(marginPadding, 40); homeJobLabel.textAlignment = UIHorizontalAlignment.Left; homeJobLabel.text = Translations.Translate("RPR_LBL_HOM"); // Home or jobs count text field. homeJobsCount = UIUtils.CreateTextField(this, this.width - (marginPadding * 3) - homeJobLabel.width, 20); homeJobsCount.relativePosition = new Vector3(marginPadding + homeJobLabel.width + marginPadding, 40); // Save button. saveButton = UIUtils.CreateButton(this, 200); saveButton.relativePosition = new Vector3(marginPadding, 70); saveButton.text = Translations.Translate("RPR_CUS_ADD"); saveButton.tooltip = Translations.Translate("RPR_CUS_ADD_TIP"); saveButton.Disable(); // Delete button. deleteButton = UIUtils.CreateButton(this, 200); deleteButton.relativePosition = new Vector3(marginPadding, 110); deleteButton.text = Translations.Translate("RPR_CUS_DEL"); deleteButton.tooltip = Translations.Translate("RPR_CUS_DEL_TIP"); deleteButton.Disable(); // Save button event handler. saveButton.eventClick += (component, clickEvent) => { // Hide message. messageLabel.isVisible = false; // Don't do anything with invalid entries. if (currentSelection == null || currentSelection.name == null) { return; } // Read textfield if possible. if (int.TryParse(homeJobsCount.text, out int homesJobs)) { // Minimum value of 1. if (homesJobs < 1) { // Print warning message in red. messageLabel.textColor = new Color32(255, 0, 0, 255); messageLabel.text = Translations.Translate("RPR_ERR_ZERO"); messageLabel.isVisible = true; } else { // Homes or jobs? if (currentSelection.GetService() == ItemClass.Service.Residential) { Debugging.Message("adding custom household count of " + homesJobs + " for " + currentSelection.name); // Residential building. ExternalCalls.SetResidential(currentSelection, homesJobs); // Update household counts for existing instances of this building - only needed for residential buildings. // Workplace counts will update automatically with next call to CalculateWorkplaceCount; households require more work (tied to CitizenUnits). UpdateHouseholds(currentSelection.name); } else { Debugging.Message("adding custom workplace count of " + homesJobs + " for " + currentSelection.name); // Employment building. ExternalCalls.SetWorker(currentSelection, homesJobs); } // Refresh the display so that all panels reflect the updated settings. BuildingDetailsPanel.Panel.UpdateSelectedBuilding(currentSelection); BuildingDetailsPanel.Panel.Refresh(); } } else { // TryParse couldn't parse the data; print warning message in red. messageLabel.textColor = new Color32(255, 0, 0, 255); messageLabel.text = Translations.Translate("RPR_ERR_INV"); messageLabel.isVisible = true; } }; // Delete button event handler. deleteButton.eventClick += (component, clickEvent) => { // Hide message. messageLabel.isVisible = false; // Don't do anything with invalid entries. if (currentSelection == null || currentSelection.name == null) { return; } Debugging.Message("deleting custom entry for " + currentSelection.name); // Homes or jobs? Remove custom entry as appropriate. if (currentSelection.GetService() == ItemClass.Service.Residential) { // Residential building. ExternalCalls.RemoveResidential(currentSelection); // Update household counts for existing instances of this building - only needed for residential buildings. // Workplace counts will update automatically with next call to CalculateWorkplaceCount; households require more work (tied to CitizenUnits). UpdateHouseholds(currentSelection.name); } else { // Employment building. ExternalCalls.RemoveWorker(currentSelection); } // Refresh the display so that all panels reflect the updated settings. BuildingDetailsPanel.Panel.Refresh(); homeJobsCount.text = string.Empty; }; // Message label (initially hidden). messageLabel = this.AddUIComponent <UILabel>(); messageLabel.relativePosition = new Vector3(marginPadding, 160); messageLabel.textAlignment = UIHorizontalAlignment.Left; messageLabel.autoSize = false; messageLabel.autoHeight = true; messageLabel.wordWrap = true; messageLabel.width = this.width - (marginPadding * 2); messageLabel.isVisible = false; messageLabel.text = "No message to display"; }
/// <summary> /// Called whenever the currently selected building is changed to update the panel display. /// </summary> /// <param name="building"></param> public void SelectionChanged(BuildingInfo building) { if ((building == null) || (building.name == null)) { // If no valid building selected, then hide the calculations panel. detailsPanel.height = 0; detailsPanel.isVisible = false; return; } // Variables to compare actual counts vs. mod count, to see if there's another mod overriding counts. int appliedCount; int modCount; // Building model size, not plot size. Vector3 buildingSize = building.m_size; int floorCount; // Array used for calculations depending on building service/subservice (via DataStore). int[] array; // Default minimum number of homes or jobs is one; different service types will override this. int minHomesJobs = 1; int customHomeJobs; // Check for valid building AI. if (!(building.GetAI() is PrivateBuildingAI buildingAI)) { Debugging.Message("invalid building AI type in building details"); return; } // Residential vs. workplace AI. if (buildingAI is ResidentialBuildingAI) { // Get appropriate calculation array. array = ResidentialBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); // Set calculated homes label. homesJobsCalcLabel.text = Translations.Translate("RPR_CAL_HOM_CALC"); // Set customised homes label and get value (if any). homesJobsCustomLabel.text = Translations.Translate("RPR_CAL_HOM_CUST"); customHomeJobs = ExternalCalls.GetResidential(building); // Applied homes is what's actually being returned by the CaclulateHomeCount call to this building AI. // It differs from calculated homes if there's an override value for that building with this mod, or if another mod is overriding. appliedCount = buildingAI.CalculateHomeCount(building.GetClassLevel(), new Randomizer(0), building.GetWidth(), building.GetLength()); homesJobsActualLabel.text = Translations.Translate("RPR_CAL_HOM_APPL") + appliedCount; } else { // Workplace AI. // Default minimum number of jobs is 4. minHomesJobs = 4; // Find the correct array for the relevant building AI. switch (building.GetService()) { case ItemClass.Service.Commercial: array = CommercialBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); break; case ItemClass.Service.Office: array = OfficeBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); break; case ItemClass.Service.Industrial: if (buildingAI is IndustrialExtractorAI) { array = IndustrialExtractorAIMod.GetArray(building, (int)building.GetClassLevel()); } else { array = IndustrialBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); } break; default: Debugging.Message("invalid building service in building details"); return; } // Set calculated jobs label. homesJobsCalcLabel.text = Translations.Translate("RPR_CAL_JOB_CALC") + " "; // Set customised jobs label and get value (if any). homesJobsCustomLabel.text = Translations.Translate("RPR_CAL_JOB_CUST") + " "; customHomeJobs = ExternalCalls.GetWorker(building); // Applied jobs is what's actually being returned by the CalculateWorkplaceCount call to this building AI. // It differs from calculated jobs if there's an override value for that building with this mod, or if another mod is overriding. int[] jobs = new int[4]; buildingAI.CalculateWorkplaceCount(building.GetClassLevel(), new Randomizer(0), building.GetWidth(), building.GetLength(), out jobs[0], out jobs[1], out jobs[2], out jobs[3]); appliedCount = jobs[0] + jobs[1] + jobs[2] + jobs[3]; homesJobsActualLabel.text = Translations.Translate("RPR_CAL_JOB_APPL") + " " + appliedCount; } // Reproduce CalcBase calculations to get building area. int calcWidth = building.GetWidth(); int calcLength = building.GetLength(); floorCount = Mathf.Max(1, Mathf.FloorToInt(buildingSize.y / array[DataStore.LEVEL_HEIGHT])); // If CALC_METHOD is zero, then calculations are based on building model size, not plot size. if (array[DataStore.CALC_METHOD] == 0) { // If asset has small x dimension, then use plot width in squares x 6m (75% of standard width) instead. if (buildingSize.x <= 1) { calcWidth *= 6; } else { calcWidth = (int)buildingSize.x; } // If asset has small z dimension, then use plot length in squares x 6m (75% of standard length) instead. if (buildingSize.z <= 1) { calcLength *= 6; } else { calcLength = (int)buildingSize.z; } } else { // If CALC_METHOD is nonzero, then caluclations are based on plot size, not building size. // Plot size is 8 metres per square. calcWidth *= 8; calcLength *= 8; } // Display calculated (and retrieved) details. detailLabels[(int)Details.width].text = Translations.Translate("RPR_CAL_BLD_X") + " " + calcWidth; detailLabels[(int)Details.length].text = Translations.Translate("RPR_CAL_BLD_Z") + " " + calcLength; detailLabels[(int)Details.height].text = Translations.Translate("RPR_CAL_BLD_Y") + " " + (int)buildingSize.y; detailLabels[(int)Details.personArea].text = Translations.Translate("RPR_CAL_BLD_M2") + " " + array[DataStore.PEOPLE]; detailLabels[(int)Details.floorHeight].text = Translations.Translate("RPR_CAL_FLR_Y") + " " + array[DataStore.LEVEL_HEIGHT]; detailLabels[(int)Details.floors].text = Translations.Translate("RPR_CAL_FLR") + " " + floorCount; // Area calculation - will need this later. int calculatedArea = calcWidth * calcLength; detailLabels[(int)Details.area].text = Translations.Translate("RPR_CAL_M2") + " " + calculatedArea; // Show or hide extra floor modifier as appropriate (hide for zero or less, otherwise show). if (array[DataStore.DENSIFICATION] > 0) { detailLabels[(int)Details.extraFloors].text = Translations.Translate("RPR_CAL_FLR_M") + " " + array[DataStore.DENSIFICATION]; detailLabels[(int)Details.extraFloors].isVisible = true; } else { detailLabels[(int)Details.extraFloors].isVisible = false; } // Set minimum residences for high density. if ((building.GetSubService() == ItemClass.SubService.ResidentialHigh) || (building.GetSubService() == ItemClass.SubService.ResidentialHighEco)) { // Minimum of 2, or 90% number of floors, whichever is greater. This helps the 1x1 high density. minHomesJobs = Mathf.Max(2, Mathf.CeilToInt(0.9f * floorCount)); } // Perform actual household or workplace calculation. modCount = Mathf.Max(minHomesJobs, (calculatedArea * (floorCount + Mathf.Max(0, array[DataStore.DENSIFICATION]))) / array[DataStore.PEOPLE]); homesJobsCalcLabel.text += modCount; // Set customised homes/jobs label (leave blank if no custom setting retrieved). if (customHomeJobs > 0) { homesJobsCustomLabel.text += customHomeJobs.ToString(); // Update modCount to reflect the custom figures. modCount = customHomeJobs; } // Check to see if Ploppable RICO Revisited is controlling this building's population. if (ModUtils.CheckRICO(building)) { messageLabel.text = Translations.Translate("RPR_CAL_RICO"); messageLabel.Show(); } else { // Hide message text by default. messageLabel.Hide(); } // We've got a valid building and results, so show panel. detailsPanel.height = 270; detailsPanel.isVisible = true; }