/// <summary> /// Adds footer buttons to the panel. /// </summary> /// <param name="yPos">Relative Y position for buttons</param> protected virtual void FooterButtons(float yPos) { // Reset button. UIButton resetButton = UIControls.AddButton(panel, Margin, yPos, Translations.Translate("RPR_OPT_RTD"), 150f); resetButton.eventClicked += ResetDefaults; // Revert button. UIButton revertToSaveButton = UIControls.AddButton(panel, (Margin * 2) + 150f, yPos, Translations.Translate("RPR_OPT_RTS"), 150f); revertToSaveButton.eventClicked += ResetSaved; }
/// <summary> /// Called by the game when level loading is complete. /// </summary> /// <param name="mode">Loading mode (e.g. game, editor, scenario, etc.)</param> public override void OnLevelLoaded(LoadMode mode) { base.OnLevelLoaded(mode); // Check to see that Harmony 2 was properly loaded. if (!harmonyLoaded) { // Harmony 2 wasn't loaded; display warning notification and exit. ListMessageBox harmonyBox = MessageBoxBase.ShowModal <ListMessageBox>(); // Key text items. harmonyBox.AddParas(Translations.Translate("ERR_HAR0"), Translations.Translate("RPR_ERR_HAR"), Translations.Translate("RPR_ERR_FAT"), Translations.Translate("ERR_HAR1")); // List of dot points. harmonyBox.AddList(Translations.Translate("ERR_HAR2"), Translations.Translate("ERR_HAR3")); // Closing para. harmonyBox.AddParas(Translations.Translate("MES_PAGE")); } // Check to see if a conflicting mod has been detected. if (conflictingMod) { // Mod conflict detected - display warning notification and exit. ListMessageBox modConflictBox = MessageBoxBase.ShowModal <ListMessageBox>(); // Key text items. modConflictBox.AddParas(Translations.Translate("ERR_CON0"), Translations.Translate("RPR_ERR_CON0"), Translations.Translate("RPR_ERR_FAT"), Translations.Translate("ERR_CON1")); // Add conflicting mod name(s). modConflictBox.AddList(ModUtils.conflictingModNames.ToArray()); // Closing para. modConflictBox.AddParas(Translations.Translate("RPR_ERR_CON1")); } // Don't do anything further if mod hasn't activated for whatever reason (mod conflict, harmony error, something else). if (!isModEnabled) { // Disable keystrokes. UIThreading.operating = false; return; } // Show legacy choice message box if this save hasn't been flagged as being from Realistic Population 2. if (!ModSettings.isRealPop2Save) { MessageBoxBase.ShowModal <LegacyChoiceMessageBox>(); } // Record initial (default) school settings and apply ours over the top. SchoolData.instance.OnLoad(); // IF a legacy file exists, flag it for writing. if (File.Exists(DataStore.currentFileLocation)) { Logging.KeyMessage("found legacy settings file"); XMLUtilsWG.writeToLegacy = true; } // Add button to building info panels. BuildingDetailsPanel.AddInfoPanelButton(); Logging.KeyMessage("loading complete"); // Display update notification. WhatsNew.ShowWhatsNew(); // Set up options panel event handler. OptionsPanel.OptionsEventHook(); // Check and record CitizenUnits count. Logging.KeyMessage("citizen unit count is currently ", ColossalFramework.Singleton <CitizenManager> .instance.m_unitCount.ToString()); }
/// <summary> /// Adds a slider. /// </summary> /// <param name="parent">Parent component</param> /// <param name="xPos">Relative X position</param> /// <param name="yPos">Relative Y position</param> /// <param name="width">Slider width</param> /// <param name="tooltipKey">Tooltip translation key</param> /// <param name="isPercent">True if this slider should display percentage values, false to display absolute values</param> /// <returns>New slider</returns> protected UISlider AddSlider(UIComponent parent, float xPos, float yPos, float width, string tooltipKey, bool isPercent = true) { // Layout constants. const float SliderPanelHeight = 20f; const float SliderHeight = 6f; const float ValueLabelWidth = 45f; const float OffsetX = (SliderPanelHeight - SliderHeight) / 2f; // Mutiplier slider panel. UIPanel sliderPanel = parent.AddUIComponent <UIPanel>(); sliderPanel.autoSize = false; sliderPanel.autoLayout = false; sliderPanel.size = new Vector2(width, SliderPanelHeight); sliderPanel.relativePosition = new Vector2(xPos, yPos); // Mutiplier slider value label. UILabel valueLabel = sliderPanel.AddUIComponent <UILabel>(); valueLabel.name = "ValueLabel"; valueLabel.verticalAlignment = UIVerticalAlignment.Middle; valueLabel.textAlignment = UIHorizontalAlignment.Center; valueLabel.textScale = 0.7f; valueLabel.autoSize = false; valueLabel.color = new Color32(91, 97, 106, 255); valueLabel.size = new Vector2(ValueLabelWidth, 15f); valueLabel.relativePosition = new Vector2(sliderPanel.width - ValueLabelWidth - Margin, (SliderPanelHeight - valueLabel.height) / 2f); // Mutiplier slider control. UISlider newSlider = sliderPanel.AddUIComponent <UISlider>(); newSlider.size = new Vector2(sliderPanel.width - ValueLabelWidth - (Margin * 3), SliderHeight); newSlider.relativePosition = new Vector2(0f, OffsetX); // Mutiplier slider track. UISlicedSprite sliderSprite = newSlider.AddUIComponent <UISlicedSprite>(); sliderSprite.autoSize = false; sliderSprite.size = new Vector2(newSlider.width, newSlider.height); sliderSprite.relativePosition = new Vector2(0f, 0f); sliderSprite.atlas = TextureUtils.InGameAtlas; sliderSprite.spriteName = "ScrollbarTrack"; // Mutiplier slider thumb. UISlicedSprite sliderThumb = newSlider.AddUIComponent <UISlicedSprite>(); sliderThumb.atlas = TextureUtils.InGameAtlas; sliderThumb.spriteName = "ScrollbarThumb"; sliderThumb.height = 20f; sliderThumb.width = 10f; sliderThumb.relativePosition = new Vector2(0f, -OffsetX); newSlider.thumbObject = sliderThumb; // Tooltip. newSlider.tooltipBox = TooltipUtils.TooltipBox; newSlider.tooltip = Translations.Translate(tooltipKey); // Mutiplier slider value range. newSlider.stepSize = 1f; newSlider.minValue = 1f; newSlider.maxValue = 100f; // Event handler to update text. if (isPercent) { newSlider.eventValueChanged += PercentSliderText; } else { newSlider.eventValueChanged += AbsSliderText; } return(newSlider); }
/// <summary> /// Called whenever the currently selected building is changed to update the panel display. /// </summary> /// <param name="building">Newly selected building</param> internal void SelectionChanged(BuildingInfo building) { // Set current building. currentBuilding = building; // Safety first! if (currentBuilding != null) { string buildingName = building.name; // Get available calculation packs for this building. popPacks = PopData.instance.GetPacks(building); floorPacks = FloorData.instance.Packs; // Get current and default packs for this item. currentPopPack = (PopDataPack)PopData.instance.ActivePack(building); currentFloorPack = (FloorDataPack)FloorData.instance.ActivePack(building); PopDataPack defaultPopPack = (PopDataPack)PopData.instance.CurrentDefaultPack(building); FloorDataPack defaultFloorPack = (FloorDataPack)FloorData.instance.CurrentDefaultPack(building); // Update multiplier before we do any other calcs. multCheck.isChecked = Multipliers.instance.HasOverride(buildingName); currentMult = Multipliers.instance.ActiveMultiplier(building); // Build pop pack menu. popMenu.items = new string[popPacks.Length]; for (int i = 0; i < popMenu.items.Length; ++i) { popMenu.items[i] = popPacks[i].displayName; // Check for default name match, if (popPacks[i].name.Equals(defaultPopPack.name)) { popMenu.items[i] += Translations.Translate("RPR_PCK_DEF"); } // Set menu selection to current pack if it matches. if (popPacks[i].name.Equals(currentPopPack.name)) { popMenu.selectedIndex = i; } } // Set population pack to current pack. UpdatePopSelection(currentPopPack); // Build floor pack menu. floorMenu.items = new string[floorPacks.Length]; for (int i = 0; i < floorPacks.Length; ++i) { floorMenu.items[i] = floorPacks[i].displayName; // Check for default name match, if (floorPacks[i].name.Equals(defaultFloorPack.name)) { floorMenu.items[i] += Translations.Translate("RPR_PCK_DEF"); } // Set menu selection to current pack if it matches. if (floorPacks[i].name.Equals(currentFloorPack.name)) { floorMenu.selectedIndex = i; // Force pack selection update. UpdateFloorSelection(i); } } // Update legacy panel for private building AIs (volumetric panel is updated by menu selection change above). if (building.GetAI() is PrivateBuildingAI) { legacyPanel.SelectionChanged(building); } // Is this a school building (need to do school building after pop and floor packs are updated)? if (building.GetAI() is SchoolAI) { // Yes - school building. Set current pack. currentSchoolPack = (SchoolDataPack)SchoolData.instance.ActivePack(building); // Are we using custom school settings? if (ModSettings.enableSchoolProperties) { // Yes - extend panel height and show school panel. volumetricPanel.relativePosition = new Vector2(0f, SchoolCalcY); applyButton.relativePosition = new Vector2(ApplyX, SchoolSaveY); // Get available school packs for this building. schoolPacks = SchoolData.instance.GetPacks(building); // Get current and default packs for this item. currentSchoolPack = (SchoolDataPack)SchoolData.instance.ActivePack(building); SchoolDataPack defaultSchoolPack = (SchoolDataPack)SchoolData.instance.CurrentDefaultPack(building); // Build school pack menu. schoolMenu.items = new string[schoolPacks.Length]; for (int i = 0; i < schoolMenu.items.Length; ++i) { schoolMenu.items[i] = schoolPacks[i].displayName; // Check for default name match, if (schoolPacks[i].name.Equals(defaultSchoolPack.name)) { schoolMenu.items[i] += Translations.Translate("RPR_PCK_DEF"); } // Set menu selection to current pack if it matches. if (schoolPacks[i].name.Equals(currentSchoolPack.name)) { schoolMenu.selectedIndex = i; // Force pack selection update. UpdateSchoolSelection(i); } } // Set multiplier value. multSlider.value = currentMult; schoolPanel.Show(); } else { // It's a school, but we're not using custom school settings, so use the non-school layout. volumetricPanel.relativePosition = new Vector2(0f, BaseCalcY); applyButton.relativePosition = new Vector2(ApplyX, BaseSaveY); schoolPanel.Hide(); } } else { // Not a school building - use non-school layout. currentSchoolPack = null; volumetricPanel.relativePosition = new Vector2(0f, BaseCalcY); applyButton.relativePosition = new Vector2(ApplyX, BaseSaveY); schoolPanel.Hide(); } } }
/// <summary> /// Updates the population calculation pack selection to the selected pack. /// </summary> /// <param name="index">Index number (from menu) of selection pack</param> private void UpdatePopSelection(PopDataPack popPack) { // Update selected pack. currentPopPack = popPack; // Update description. popDescription.text = currentPopPack.description; // Check if we're using legacy or volumetric data. if (currentPopPack is VolumetricPopPack) { // Volumetric pack. Are we coming from a legacy setting? if (usingLegacy) { // Reset flag. usingLegacy = false; // Restore floor rendering. BuildingDetailsPanel.Panel.HideFloors = false; // Update override label text. floorOverrideLabel.text = Translations.Translate("RPR_CAL_FOV"); // Set visibility. legacyPanel.Hide(); volumetricPanel.Show(); } // Update panel with new calculations. LevelData thisLevel = CurrentLevelData; volumetricPanel.UpdatePopText(thisLevel); volumetricPanel.CalculateVolumetric(currentBuilding, thisLevel, currentFloorOverride ?? currentFloorPack, currentSchoolPack, currentMult); // Set visibility. if (currentFloorOverride == null) { floorOverrideLabel.Hide(); } else { floorOverrideLabel.Show(); } floorPanel.Show(); } else { // Using legacy calcs = set flag. usingLegacy = true; // Set visibility. volumetricPanel.Hide(); floorPanel.Hide(); legacyPanel.Show(); // Set override label and show. floorOverrideLabel.text = Translations.Translate("RPR_CAL_FLG"); floorOverrideLabel.Show(); // Cancel any floor rendering. BuildingDetailsPanel.Panel.HideFloors = true; } }
/// <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 = UIControls.AddButton(this, 445f, 5f, Translations.Translate("RPR_CAT_ALL"), 120f); // 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. nameFilter = UIControls.BigLabelledTextField(this, width - 200f, 0, Translations.Translate("RPR_FIL_NAME")); // Name filter event handling - update on any change. nameFilter.eventTextChanged += (control, text) => EventFilteringChanged(this, 5); nameFilter.eventTextSubmitted += (control, text) => EventFilteringChanged(this, 5); // Settings filter label. UILabel filterLabel = SettingsFilterLabel(55f, Translations.Translate("RPR_FIL_SET")); // Settings filter checkboxes. settingsFilter = new UICheckBox[(int)FilterCategories.NumCategories]; for (int i = 0; i < (int)FilterCategories.NumCategories; ++i) { settingsFilter[i] = this.AddUIComponent <UICheckBox>(); settingsFilter[i].width = 20f; settingsFilter[i].height = 20f; settingsFilter[i].clipChildren = true; settingsFilter[i].relativePosition = new Vector3(AnyX + (FilterSpacing * i), 45f); // Checkbox sprites. UISprite sprite = settingsFilter[i].AddUIComponent <UISprite>(); sprite.spriteName = "ToggleBase"; sprite.size = new Vector2(20f, 20f); sprite.relativePosition = Vector3.zero; settingsFilter[i].checkedBoxObject = sprite.AddUIComponent <UISprite>(); ((UISprite)settingsFilter[i].checkedBoxObject).spriteName = "ToggleBaseFocused"; settingsFilter[i].checkedBoxObject.size = new Vector2(20f, 20f); settingsFilter[i].checkedBoxObject.relativePosition = Vector3.zero; // Tooltip. settingsFilter[i].tooltip = Translations.Translate(FilterTooltipKeys[i]); // Special event handling for 'any' checkbox. if (i == (int)FilterCategories.Any) { settingsFilter[i].eventCheckChanged += (control, isChecked) => { if (isChecked) { // Unselect all other checkboxes if 'any' is checked. settingsFilter[(int)FilterCategories.HasOverride].isChecked = false; settingsFilter[(int)FilterCategories.HasNonDefault].isChecked = false; } }; } else { // Non-'any' checkboxes. // Unselect 'any' checkbox if any other is checked. settingsFilter[i].eventCheckChanged += (control, isChecked) => { if (isChecked) { settingsFilter[0].isChecked = false; } }; } // Trigger filtering changed event if any checkbox is changed. settingsFilter[i].eventCheckChanged += (control, isChecked) => { EventFilteringChanged(this, 0); }; } }
/// <summary> /// Create the mod calcs 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> internal void Setup() { // Basic setup. clipChildren = true; // Title. title = this.AddUIComponent <UILabel>(); title.relativePosition = new Vector3(0, 0); title.textAlignment = UIHorizontalAlignment.Center; title.text = Translations.Translate("RPR_CAL_MOD"); title.textScale = 1.2f; title.autoSize = false; title.width = this.width; // Column titles. UILabel densityTitle = ColumnLabel(this, Translations.Translate("RPR_CAL_DEN"), Margin, ColumnLabelY); UILabel floorTitle = ColumnLabel(this, Translations.Translate("RPR_CAL_BFL"), RightColumnX, ColumnLabelY); // Volumetric calculations panel. volumetricPanel = this.AddUIComponent <UIVolumetricPanel>(); volumetricPanel.relativePosition = new Vector2(0f, BaseCalcY); volumetricPanel.height = this.height - title.height + 80f; volumetricPanel.width = this.width; volumetricPanel.Setup(); // Legacy calculations panel - copy volumetric calculations panel. legacyPanel = this.AddUIComponent <UILegacyCalcs>(); legacyPanel.relativePosition = volumetricPanel.relativePosition; legacyPanel.height = volumetricPanel.height; legacyPanel.width = volumetricPanel.width; legacyPanel.Setup(); legacyPanel.Hide(); // Floor dropdown panel - set size manually to avoid invisible overlap of calculations panel (preventing e.g. tooltips). floorPanel = this.AddUIComponent <UIPanel>(); floorPanel.relativePosition = new Vector2(RightColumnX, MenuY); floorPanel.autoSize = false; floorPanel.width = RightColumnX - this.width; floorPanel.height = BaseCalcY - MenuY; floorPanel.autoLayout = false; floorPanel.clipChildren = false; floorPanel.Show(); // Floor override label (for when floor dropdown menu is hidden). floorOverrideLabel = UIControls.AddLabel(this, RightColumnX, MenuY, Translations.Translate("RPR_CAL_FOV"), this.width - RightColumnX, 0.7f); floorOverrideLabel.Hide(); // Pack dropdowns. popMenu = UIControls.AddDropDown(this, Margin, MenuY, ComponentWidth); floorMenu = UIControls.AddDropDown(floorPanel, 0f, 0f, ComponentWidth); // School dropdown panel. schoolPanel = this.AddUIComponent <UIPanel>(); schoolPanel.relativePosition = new Vector2(Margin, Row2LabelY); schoolPanel.autoSize = false; schoolPanel.autoLayout = false; schoolPanel.clipChildren = false; schoolPanel.height = ApplyX - Row2LabelY; schoolPanel.width = this.width - (Margin * 2); // School panel title and dropdown menu. UILabel schoolTitle = ColumnLabel(schoolPanel, Translations.Translate("RPR_CAL_SCH_PRO"), 0, 0); schoolMenu = UIControls.AddDropDown(schoolPanel, 0f, LabelHeight, ComponentWidth); schoolPanel.Hide(); // Pack descriptions. popDescription = Description(this, Margin, DescriptionY); floorDescription = Description(floorPanel, 0f, DescriptionY - MenuY); schoolDescription = Description(schoolPanel, 0f, LabelHeight + DescriptionY - MenuY); // Apply button. applyButton = UIControls.AddButton(this, ApplyX, BaseSaveY, Translations.Translate("RPR_OPT_SAA"), ButtonWidth); applyButton.eventClicked += (control, clickEvent) => ApplySettings(); // Dropdown event handlers. popMenu.eventSelectedIndexChanged += (component, index) => UpdatePopSelection(index); floorMenu.eventSelectedIndexChanged += (component, index) => UpdateFloorSelection(index); schoolMenu.eventSelectedIndexChanged += (component, index) => UpdateSchoolSelection(index); // Add school multiplier slider (starts hidden). multSlider = AddSliderWithMultipler(schoolPanel, string.Empty, 1f, 5f, 0.5f, ModSettings.DefaultSchoolMult, (value) => UpdateMultiplier(value), ComponentWidth); multSlider.parent.relativePosition = new Vector2(RightColumnX, 10f); multSlider.parent.Hide(); // Muliplier checkbox. multCheck = UIControls.LabelledCheckBox(schoolPanel, RightColumnX, 18f, Translations.Translate("RPR_CAL_CAP_OVR")); // Multiplier default label. multDefaultLabel = UIControls.AddLabel(schoolPanel, RightColumnX + 21f, 40f, Translations.Translate("RPR_CAL_CAP_DEF") + " x" + ModSettings.DefaultSchoolMult, textScale: 0.8f); // Multplier checkbox event handler. multCheck.eventCheckChanged += (control, isChecked) => MultiplierCheckChanged(isChecked); }
/// <summary> /// Adds control buttons to the bottom of the panel. /// </summary> /// <param name="panel">UI panel instance</param> protected void AddButtons(UIPanel panel) { // Add extra space. currentY += Margin; // Reset button. UIButton resetButton = UIControls.AddButton(panel, Margin, currentY, Translations.Translate("RPR_OPT_RTD"), 150f); resetButton.eventClicked += (component, clickEvent) => ResetToDefaults(); // Revert button. UIButton revertToSaveButton = UIControls.AddButton(panel, (Margin * 2) + 150f, currentY, Translations.Translate("RPR_OPT_RTS"), 150f); revertToSaveButton.eventClicked += (component, clickEvent) => { ConfigUtils.LoadSettings(); PopulateFields(); }; // Save button. UIButton saveButton = UIControls.AddButton(panel, (Margin * 3) + 300f, currentY, Translations.Translate("RPR_OPT_SAA"), 150f); saveButton.eventClicked += (component, clickEvent) => ApplyFields(); }
/// <summary> /// Adds a sub-service field group to the panel. /// </summary> /// <param name="panel">UI panel instance</param> /// <param name="subService">Subservice reference number</param> /// <param name="isExtract">Set this to true (and label to null) to add extractor/processor labels (default false, which is plain level labels)</param> /// <param name="label">Text label base for each row; null (default) to use level numbers or extractor/prcessor</param> protected void AddSubService(UIPanel panel, bool _, int subService, bool isExtract = false, string label = null) { // Add a row for each level within this subservice. for (int i = 0; i < powerFields[subService].Length; ++i) { // Row label. RowLabel(panel, currentY, label ?? (isExtract ? Translations.Translate(i == 0 ? "RPR_CAT_EXT" : "RPR_CAT_PRO") : Translations.Translate("RPR_OPT_LVL") + " " + (i + 1).ToString())); // Textfields. powerFields[subService][i] = AddTextField(panel, ColumnWidth, PowerX, currentY, powerLabel); waterFields[subService][i] = AddTextField(panel, ColumnWidth, WaterX, currentY, waterLabel); garbageFields[subService][i] = AddTextField(panel, ColumnWidth, GarbageX, currentY, garbageLabel); sewageFields[subService][i] = AddTextField(panel, ColumnWidth, SewageX, currentY, sewageLabel); pollutionFields[subService][i] = AddTextField(panel, ColumnWidth, PollutionX, currentY, pollutionLabel); noiseFields[subService][i] = AddTextField(panel, ColumnWidth, NoiseX, currentY, noiseLabel); mailFields[subService][i] = AddTextField(panel, ColumnWidth, MailX, currentY, mailLabel); incomeFields[subService][i] = AddTextField(panel, WideColumnWidth, IncomeX, currentY, wealthLabel); // Increment Y position. currentY += RowHeight; } // Add an extra bit of space at the end. currentY += Margin; }
/// <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() { // 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, TitleY); 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; // Checkboxes. popCheck = UIControls.LabelledCheckBox(this, 20f, PopCheckY, Translations.Translate("RPR_EDT_POP"), textScale: 1.0f); floorCheck = UIControls.LabelledCheckBox(this, 20f, FloorCheckY, Translations.Translate("RPR_EDT_FLR"), textScale: 1.0f); // Text fields. homeJobsCount = AddLabelledTextfield(HomeJobY, "RPR_LBL_HOM"); firstFloorField = AddLabelledTextfield(FirstFloorY, "RPR_LBL_OFF"); floorHeightField = AddLabelledTextfield(FloorHeightY, "RPR_LBL_OFH"); homeJobLabel = homeJobsCount.label; // Save button. saveButton = UIControls.AddButton(this, MarginPadding, SaveY, Translations.Translate("RPR_CUS_ADD")); saveButton.tooltip = Translations.Translate("RPR_CUS_ADD_TIP"); saveButton.Disable(); // Delete button. deleteButton = UIControls.AddButton(this, MarginPadding, DeleteY, Translations.Translate("RPR_CUS_DEL")); deleteButton.tooltip = Translations.Translate("RPR_CUS_DEL_TIP"); deleteButton.Disable(); // Message label (initially hidden). messageLabel = this.AddUIComponent <UILabel>(); messageLabel.relativePosition = new Vector3(MarginPadding, MessageY); 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"; // Checkbox event handlers. popCheck.eventCheckChanged += (component, isChecked) => { // If this is now selected and floorCheck is also selected, deselect floorCheck. if (isChecked && floorCheck.isChecked) { floorCheck.isChecked = false; } }; floorCheck.eventCheckChanged += (component, isChecked) => { // If this is now selected and popCheck is also selected, deselect popCheck. if (isChecked && popCheck.isChecked) { popCheck.isChecked = false; } }; // Save button event handler. saveButton.eventClick += (component, clickEvent) => SaveAndApply(); // Delete button event handler. deleteButton.eventClick += (component, clickEvent) => DeleteOverride(); }
/// <summary> /// Saves and applies settings - save button event handler. /// </summary> private void SaveAndApply() { // Hide message. messageLabel.isVisible = false; // Don't do anything with invalid entries. if (currentSelection == null || currentSelection.name == null) { return; } // Are we doing population overrides? if (popCheck.isChecked) { // Read total floor count textfield if possible; ignore zero values if (int.TryParse(homeJobsCount.textField.text, out int homesJobs) && homesJobs != 0) { // 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 { // Set overide. PopData.instance.SetOverride(currentSelection, homesJobs); // Update CitizenUnits for existing building instances. CitizenUnitUtils.UpdateCitizenUnits(currentSelection.name, ItemClass.Service.None, currentSelection.GetSubService(), false); // Repopulate field with parsed value. homeJobLabel.text = homesJobs.ToString(); } } else { // TryParse couldn't parse any data; print warning message in red. messageLabel.textColor = new Color32(255, 0, 0, 255); messageLabel.text = Translations.Translate("RPR_ERR_INV"); messageLabel.isVisible = true; } } else { // Population override checkbox wasn't checked; remove any custom settings. PopData.instance.DeleteOverride(currentSelection); // Remove any legacy file settings to avoid conflicts. OverrideUtils.RemoveResidential(currentSelection); OverrideUtils.RemoveWorker(currentSelection); } // Are we doing floor overrides? if (floorCheck.isChecked) { // Attempt to parse values into override floor pack. FloorDataPack overrideFloors = TryParseFloors(); // Were we successful?. if (overrideFloors != null) { // Successful parsing - add override. FloorData.instance.SetOverride(currentSelection, overrideFloors); // Save configuration. ConfigUtils.SaveSettings(); // Update panel override. BuildingDetailsPanel.Panel.OverrideFloors = overrideFloors; // Repopulate fields with parsed values. UpdateFloorTextFields(overrideFloors.firstFloorMin.ToString(), overrideFloors.floorHeight.ToString()); } else { // Couldn't parse values; print warning message in red. messageLabel.textColor = new Color32(255, 0, 0, 255); messageLabel.text = Translations.Translate("RPR_ERR_INV"); messageLabel.isVisible = true; } } else { // Floor override checkbox wasn't checked; remove any floor override. FloorData.instance.DeleteOverride(currentSelection); } // Refresh the display so that all panels reflect the updated settings. BuildingDetailsPanel.Panel.Refresh(); }
/// <summary> /// Called whenever the currently selected building is changed to update the panel display. /// </summary> /// <param name="building"></param> public void SelectionChanged(BuildingInfo building) { string buildingName = building.name; // Hide message. messageLabel.isVisible = false; // Set current selecion. currentSelection = building; // Blank all textfields and deselect checkboxes to start with. homeJobsCount.textField.text = string.Empty; UpdateFloorTextFields(string.Empty, string.Empty); popCheck.isChecked = false; floorCheck.isChecked = false; // Disable buttons and exit if no valid building is selected. if (building == null || building.name == null) { saveButton.Disable(); deleteButton.Disable(); return; } // Set label by building type. if (building.GetService() == ItemClass.Service.Residential) { // Residential building - homes. homeJobLabel.text = Translations.Translate("RPR_LBL_HOM"); } else if (building.GetService() == ItemClass.Service.Education) { // Schoool building - students. homeJobLabel.text = Translations.Translate("RPR_LBL_STU"); } else { // Workplace building - jobs. homeJobLabel.text = Translations.Translate("RPR_LBL_JOB"); } // Get any population override. int homesJobs = PopData.instance.GetOverride(buildingName); // If custom settings were found (return value was non-zero), then display the result, rename the save button, and enable the delete button. if (homesJobs != 0) { // Valid custom settings found; display the result, rename the save button, and enable the delete button. homeJobsCount.textField.text = homesJobs.ToString(); saveButton.text = Translations.Translate("RPR_CUS_UPD"); deleteButton.Enable(); // Select the 'has population override' check. popCheck.isChecked = true; } else { // No population override - check for custom floor override. FloorDataPack overridePack = FloorData.instance.HasOverride(buildingName); if (overridePack != null) { // Valid custom settings found; display the result, rename the save button, and enable the delete button. UpdateFloorTextFields(overridePack.firstFloorMin.ToString(), overridePack.floorHeight.ToString()); saveButton.text = Translations.Translate("RPR_CUS_UPD"); deleteButton.Enable(); // Select the 'has floor override' check. floorCheck.isChecked = true; } else { // No valid selection - rename the save button, and disable the delete button. saveButton.text = Translations.Translate("RPR_CUS_ADD"); deleteButton.Disable(); } // Communicate override to panel. BuildingDetailsPanel.Panel.OverrideFloors = overridePack; } // We've at least got a valid building, so enable the save button. saveButton.Enable(); }
/// <summary> /// Perform and display volumetric calculations for the currently selected building. /// </summary> /// <param name="building">Selected building prefab</param> /// <param name="levelData">Population (level) calculation data to apply to calculations</param> /// <param name="floorData">Floor calculation data to apply to calculations</param> /// <param name="schoolData">School calculation data to apply to calculations</param> /// <param name="schoolData">Multiplier to apply to calculations</param> internal void CalculateVolumetric(BuildingInfo building, LevelData levelData, FloorDataPack floorData, SchoolDataPack schoolData, float multiplier) { // Safety first! if (building == null) { return; } // Reset message label. messageLabel.text = string.Empty; // Perform calculations. // Get floors and allocate area an number of floor labels. SortedList <int, float> floors = PopData.instance.VolumetricFloors(building.m_generatedInfo, floorData, out float totalArea); floorAreaLabel.text = totalArea.ToString("N0", LocaleManager.cultureInfo); numFloorsLabel.text = floors.Count.ToString(); // Get total units. int totalUnits = PopData.instance.VolumetricPopulation(building.m_generatedInfo, levelData, floorData, multiplier, floors, totalArea); // Floor labels list. List <string> floorLabels = new List <string>(); // What we call our units for this building. string unitName; switch (building.GetService()) { case ItemClass.Service.Residential: // Residential - households. unitName = Translations.Translate("RPR_CAL_UNI_HOU"); break; case ItemClass.Service.Education: // Education - students. unitName = Translations.Translate("RPR_CAL_UNI_STU"); break; default: // Default - workplaces. unitName = Translations.Translate("RPR_CAL_UNI_WOR"); break; } // See if we're using area calculations for numbers of units, i.e. areaPer is at least one. if (levelData.areaPer > 0) { // Determine area percentage to use for calculations (inverse of empty area percentage). float areaPercent = 1 - (levelData.emptyPercent / 100f); // Create new floor area labels by iterating through each floor. for (int i = 0; i < floors.Count; ++i) { // StringBuilder, because we're doing a fair bit of manipulation here. StringBuilder floorString = new StringBuilder("Floor "); // Floor number floorString.Append(i + 1); floorString.Append(" " + Translations.Translate("RPR_CAL_VOL_ARA") + " "); floorString.Append(floors[i].ToString("N0")); // See if we're calculating units per individual floor. if (!levelData.multiFloorUnits) { // Number of units on this floor - always rounded down. int floorUnits = (int)((floors[i] * areaPercent) / levelData.areaPer); // Adjust by multiplier (after rounded calculation above). floorUnits = (int)(floorUnits * multiplier); // Add extra info to label. floorString.Append(" ("); floorString.Append(floorUnits.ToString("N0")); floorString.Append(" "); floorString.Append(unitName); floorString.Append(")"); } // Add new floor label item with results for this calculation. floorLabels.Add(floorString.ToString()); } } // Do we have a current school selection, and are we using school property overrides? if (schoolData != null && ModSettings.enableSchoolProperties) { // Yes - calculate and display school worker breakdown. int[] workers = SchoolData.instance.CalcWorkers(schoolData, totalUnits); schoolWorkerLabel.Show(); schoolWorkerLabel.text = workers[0] + " / " + workers[1] + " / " + workers[2] + " / " + workers[3]; // Calculate construction cost to display. int cost = SchoolData.instance.CalcCost(schoolData, totalUnits); ColossalFramework.Singleton <EconomyManager> .instance.m_EconomyWrapper.OnGetConstructionCost(ref cost, building.m_class.m_service, building.m_class.m_subService, building.m_class.m_level); // Calculate maintenance cost to display. int maintenance = SchoolData.instance.CalcMaint(schoolData, totalUnits) * 100; ColossalFramework.Singleton <EconomyManager> .instance.m_EconomyWrapper.OnGetMaintenanceCost(ref maintenance, building.m_class.m_service, building.m_class.m_subService, building.m_class.m_level); float displayMaint = Mathf.Abs(maintenance * 0.0016f); // And display school cost breakdown. costLabel.Show(); costLabel.text = cost.ToString((!(displayMaint >= 10f)) ? Settings.moneyFormat : Settings.moneyFormatNoCents, LocaleManager.cultureInfo) + " / " + displayMaint.ToString((!(displayMaint >= 10f)) ? Settings.moneyFormat : Settings.moneyFormatNoCents, LocaleManager.cultureInfo); // Enforce school floors list position. ResetFloorListPosition(); } else { // No - hide school worker breakdown and cost labels. schoolWorkerLabel.Hide(); costLabel.Hide(); // Enforce default floors list position. ResetFloorListPosition(); } // Allocate our new list of labels to the floors list (via an interim fastlist to avoid race conditions if we 'build' manually directly into floorsList). FastList <object> fastList = new FastList <object>() { m_buffer = floorLabels.ToArray(), m_size = floorLabels.Count }; floorsList.rowsData = fastList; // Display total unit calculation result. switch (building.GetService()) { case ItemClass.Service.Residential: // Residential building. totalJobsLabel.Hide(); totalStudentsLabel.Hide(); totalHomesLabel.Show(); totalHomesLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo); break; case ItemClass.Service.Education: // School building. totalHomesLabel.Hide(); totalJobsLabel.Hide(); totalStudentsLabel.Show(); totalStudentsLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo); break; default: // Workplace building. totalHomesLabel.Hide(); totalStudentsLabel.Hide(); totalJobsLabel.Show(); totalJobsLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo); break; } // Display commercial visit count, or hide the label if not commercial. if (building.GetAI() is CommercialBuildingAI) { visitCountLabel.Show(); visitCountLabel.text = RealisticVisitplaceCount.PreviewVisitCount(building, totalUnits).ToString(); } else { visitCountLabel.Hide(); } // Display production count, or hide the label if not a production building. if (building.GetAI() is PrivateBuildingAI privateAI && (privateAI is OfficeBuildingAI || privateAI is IndustrialBuildingAI || privateAI is IndustrialExtractorAI)) { productionLabel.Show(); productionLabel.text = privateAI.CalculateProductionCapacity(building.GetClassLevel(), new ColossalFramework.Math.Randomizer(), building.GetWidth(), building.GetLength()).ToString(); }
/// <summary> /// Adds editing options tab to tabstrip. /// </summary> /// <param name="tabStrip">Tab strip to add to</param> /// <param name="tabIndex">Index number of tab</param> internal FloorPanel(UITabstrip tabStrip, int tabIndex) : base(tabStrip, tabIndex) { // Add title. float currentY = PanelUtils.TitleLabel(panel, TabTooltipKey); // Initialise arrays floorHeightField = new UITextField(); firstMinField = new UITextField(); firstExtraField = new UITextField(); firstEmptyCheck = new UICheckBox(); // Pack selection dropdown. packDropDown = UIControls.AddPlainDropDown(panel, Translations.Translate("RPR_OPT_CPK"), new string[0], -1); packDropDown.parent.relativePosition = new Vector3(20f, currentY); packDropDown.eventSelectedIndexChanged += PackChanged; // Headings. currentY += 140f; PanelUtils.ColumnLabel(panel, FloorHeightX, currentY, ColumnWidth, Translations.Translate("RPR_CAL_VOL_FLH"), Translations.Translate("RPR_CAL_VOL_FLH_TIP"), 1.0f); PanelUtils.ColumnLabel(panel, FirstMinX, currentY, ColumnWidth, Translations.Translate("RPR_CAL_VOL_FMN"), Translations.Translate("RPR_CAL_VOL_FMN_TIP"), 1.0f); PanelUtils.ColumnLabel(panel, FirstMaxX, currentY, ColumnWidth, Translations.Translate("RPR_CAL_VOL_FMX"), Translations.Translate("RPR_CAL_VOL_FMX_TIP"), 1.0f); PanelUtils.ColumnLabel(panel, FirstEmptyX, currentY, ColumnWidth, Translations.Translate("RPR_CAL_VOL_IGF"), Translations.Translate("RPR_CAL_VOL_IGF_TIP"), 1.0f); // Add level textfields. currentY += RowHeight; floorHeightField = UIControls.AddTextField(panel, FloorHeightX + Margin, currentY, width: TextFieldWidth, tooltip: Translations.Translate("RPR_CAL_VOL_FLH_TIP")); floorHeightField.eventTextChanged += (control, value) => PanelUtils.FloatTextFilter((UITextField)control, value); floorHeightField.tooltipBox = TooltipUtils.TooltipBox; firstMinField = UIControls.AddTextField(panel, FirstMinX + Margin, currentY, width: TextFieldWidth, tooltip: Translations.Translate("RPR_CAL_VOL_FMN_TIP")); firstMinField.eventTextChanged += (control, value) => PanelUtils.FloatTextFilter((UITextField)control, value); firstMinField.tooltipBox = TooltipUtils.TooltipBox; firstExtraField = UIControls.AddTextField(panel, FirstMaxX + Margin, currentY, width: TextFieldWidth, tooltip: Translations.Translate("RPR_CAL_VOL_FMX_TIP")); firstExtraField.eventTextChanged += (control, value) => PanelUtils.FloatTextFilter((UITextField)control, value); firstExtraField.tooltipBox = TooltipUtils.TooltipBox; firstEmptyCheck = UIControls.AddCheckBox(panel, FirstEmptyX + (ColumnWidth / 2), currentY, tooltip: Translations.Translate("RPR_CAL_VOL_IGF_TIP")); firstEmptyCheck.tooltipBox = TooltipUtils.TooltipBox; // Move to next row. currentY += RowHeight; // Add footer controls. PanelFooter(currentY); // Populate pack menu and set onitial pack selection. packDropDown.items = PackList(); packDropDown.selectedIndex = 0; }