Example #1
0
 public BillGiverTracker(ManagerJob_Production job)
 {
     manager            = job.manager;
     Recipe             = job.Bill.recipe;
     _job               = job;
     _assignedBills     = new Dictionary <Bill_Production, Building_WorkTable>();
     SpecificBillGivers = new List <Building_WorkTable>();
 }
 public BillGiverTracker( ManagerJob_Production job )
 {
     manager = job.manager;
     Recipe = job.Bill.recipe;
     _job = job;
     _assignedBills = new Dictionary<Bill_Production, Building_WorkTable>();
     SpecificBillGivers = new List<Building_WorkTable>();
 }
 public Trigger_Threshold( ManagerJob_Production job )
     : base(job.manager)
 {
     Op = Ops.LowerThan;
     MaxUpperThreshold = job.MainProduct.MaxUpperThreshold;
     // TODO: Better way of setting sensible defaults?
     Count = MaxUpperThreshold / 20;
     ThresholdFilter = new ThingFilter();
     ThresholdFilter.SetDisallowAll();
     if ( job.MainProduct.ThingDef != null )
     {
         ThresholdFilter.SetAllow( job.MainProduct.ThingDef, true );
     }
     if ( job.MainProduct.CategoryDef != null )
     {
         ThresholdFilter.SetAllow( job.MainProduct.CategoryDef, true );
     }
 }
 public Trigger_Threshold( ManagerJob_Production job )
     : base(job.manager)
 {
     Op = Ops.LowerThan;
     MaxUpperThreshold = job.MainProduct.MaxUpperThreshold;
     // TODO: Better way of setting sensible defaults?
     Count = MaxUpperThreshold / 20;
     ThresholdFilter = new ThingFilter();
     ThresholdFilter.SetDisallowAll();
     if ( job.MainProduct.ThingDef != null )
     {
         ThresholdFilter.SetAllow( job.MainProduct.ThingDef, true );
     }
     if ( job.MainProduct.CategoryDef != null )
     {
         ThresholdFilter.SetAllow( job.MainProduct.CategoryDef, true );
     }
 }
Example #5
0
            public void AddBills()
            {
                // only proceed if we selected an ingredient/thingdef (recipeSelector != null), and there is a recipe selected.
                if (recipeSelector?.selectedRecipe == null)
                {
                    return;
                }

                // try to get a job with our recipe
                RecipeDef             curRecipe = recipeSelector.selectedRecipe;
                ManagerJob_Production curJob    = manager.JobStack.FullStack <ManagerJob_Production>()
                                                  .FirstOrDefault(job => job.Bill.recipe == curRecipe);

                // if there is a job for the recipe, add our job's count - any settings beyond that are user responsibility.
                if (curJob != null && curJob.Trigger.Count < targetCount)
                {
                    curJob.Trigger.Count = targetCount;
                    Messages.Message("FMP.IncreasedThreshold".Translate(curRecipe.LabelCap, targetCount),
                                     MessageSound.Benefit);
                }
                // otherwise create a new job.
                else
                {
                    curJob = new ManagerJob_Production(manager, curRecipe);
                    // make sure the trigger is valid (everything else is user responsibility).
                    if (curJob.Trigger.IsValid)
                    {
                        curJob.Managed = true;
                        manager.JobStack.Add(curJob);
                        Messages.Message("FMP.AddedJob".Translate(curRecipe.LabelCap), MessageSound.Benefit);
                    }
                    else
                    {
                        Messages.Message("FMP.CouldNotAddJob".Translate(curRecipe.LabelCap), MessageSound.RejectInput);
                    }
                }

                // finally, call this method on all of our children
                foreach (IngredientSelector child in recipeSelector.children)
                {
                    child.AddBills();
                }
            }
        public void DoContent( Rect canvas )
        {
            Widgets.DrawMenuSection( canvas );
            GUI.BeginGroup( canvas );
            canvas = canvas.AtZero();

            if ( _selected != null )
            {
                // bottom buttons
                Rect buttonRect = new Rect( canvas.xMax - _button.x, canvas.yMax - _button.y, _button.x - _margin,
                                            _button.y - _margin );
                Rect ingredientCheck = new Rect( buttonRect.xMin - 300f - _margin, buttonRect.yMin, 300f,
                                                 buttonRect.height );

                // add / remove to the stack
                if ( Source == SourceOptions.Current )
                {
                    if ( Widgets.TextButton( buttonRect, "FM.Delete".Translate() ) )
                    {
                        _selected.Delete();
                        _selected = null;
                        Refresh();
                        return; // just skip to the next tick to avoid null reference errors.
                    }
                    TooltipHandler.TipRegion( buttonRect, "FMP.DeleteBillTooltip".Translate() );
                }
                else
                {
                    if ( _selected.Trigger.IsValid )
                    {
                        Widgets.LabelCheckbox(ingredientCheck, "FMP.IngredientDialogTitle".Translate(), ref _selected._createIngredientBills, !_selected._hasMeaningfulIngredientChoices);

                        if ( Widgets.TextButton( buttonRect, "FM.Manage".Translate() ) )
                        {
                            _selected.Managed = true;
                            Manager.Get.JobStack.Add( _selected );

                            // refresh source list so that the next added job is not an exact copy.
                            Refresh();

                            if ( _selected._hasMeaningfulIngredientChoices &&
                                 _selected._createIngredientBills )
                            {
                                Find.WindowStack.Add( new Dialog_CreateJobsForIngredients( _selected.Bill.recipe, _selected.Trigger.Count ) );
                            }

                            Source = SourceOptions.Current;
                            Refresh();
                            SourceFilter = "";
                        }
                        TooltipHandler.TipRegion( buttonRect, "FMP.ManageBillTooltip".Translate() );
                    }
                    else
                    {
                        GUI.color = new Color( .6f, .6f, .6f );
                        Widgets.DrawBox( buttonRect );
                        Utilities.Label( buttonRect, "FMP.NoThreshold".Translate(), "FMP.NoThresholdTooltip".Translate(),
                                         TextAnchor.MiddleCenter );
                        GUI.color = Color.white;
                    }
                }

                // options
                Rect optionsColumnRect = new Rect( _margin / 2,
                                                   _topAreaHeight,
                                                   canvas.width / 2 - _margin,
                                                   canvas.height - _topAreaHeight - _margin - _button.y );
                Rect recipeColumnRect = new Rect( optionsColumnRect.xMax + _margin,
                                                _topAreaHeight,
                                                canvas.width / 2 - _margin,
                                                canvas.height - _topAreaHeight - _margin - _button.y );

                Rect optionsColumnTitle = new Rect( optionsColumnRect.xMin,
                                                    0f,
                                                    optionsColumnRect.width,
                                                    _topAreaHeight );
                Rect recipeColumnTitle = new Rect( recipeColumnRect.xMin,
                                                        0f,
                                                        recipeColumnRect.width,
                                                        _topAreaHeight );

                // backgrounds
                GUI.DrawTexture( optionsColumnRect, Resources.SlightlyDarkBackground );
                GUI.DrawTexture( recipeColumnRect, Resources.SlightlyDarkBackground );

                // titles
                Utilities.Label( optionsColumnTitle, "FMP.Options".Translate(),
                                 anchor: TextAnchor.LowerLeft, lrMargin: _margin * 2, font: GameFont.Tiny );
                Utilities.Label( recipeColumnTitle, "FMP.Recipe".Translate(),
                                 anchor: TextAnchor.LowerLeft, lrMargin: _margin * 2, font: GameFont.Tiny );

                // options
                GUI.BeginGroup( optionsColumnRect );
                Vector2 cur = Vector2.zero;
                float width = optionsColumnRect.width;

                // keep track of optionIndex for shading purposes (lazy way to avoid having to redo this all the damn time).
                int optionindex = 0;

                // suspended
                Rect suspendedRect = new Rect( cur.x, cur.y, width, _entryHeight );
                if (optionindex++ % 2 == 0) Widgets.DrawAltRect( suspendedRect );
                Utilities.DrawToggle( suspendedRect, "Suspended".Translate(), _selected.Suspended,
                                      delegate { _selected.Suspended = !_selected.Suspended; } );
                cur.y += _entryHeight;

                // store mode 
                Rect takeToStockRect = new Rect( cur.x, cur.y, width, _entryHeight );
                if( optionindex++ % 2 == 0 ) Widgets.DrawAltRect( takeToStockRect );
                Utilities.DrawToggle( takeToStockRect, "BillStoreMode_BestStockpile".Translate(),
                                      _selected.Bill.storeMode == BillStoreMode.BestStockpile,
                                      delegate { _selected.Bill.storeMode = BillStoreMode.BestStockpile; },
                                      delegate { _selected.Bill.storeMode = BillStoreMode.DropOnFloor; } );
                cur.y += _entryHeight;

                // ingredient search radius (3)
                Rect searchRadiusLabelRect = new Rect( cur.x, cur.y, width, _entryHeight );
                if( optionindex % 2 == 0 ) Widgets.DrawAltRect( searchRadiusLabelRect );
                Utilities.Label( searchRadiusLabelRect,
                                 "IngredientSearchRadius".Translate() + ": " +
                                 _selected.Bill.ingredientSearchRadius.ToString( " #####0" ),
                                 anchor: TextAnchor.MiddleLeft, lrMargin: _margin );
                cur.y += _entryHeight;

                Rect searchRadiusRect = new Rect( cur.x, cur.y, width, Utilities.SliderHeight );
                if( optionindex++ % 2 == 0 ) Widgets.DrawAltRect( searchRadiusRect );
                _selected.Bill.ingredientSearchRadius =
                    (int)GUI.HorizontalSlider( searchRadiusRect, _selected.Bill.ingredientSearchRadius, 0f, 250f );
                cur.y += Utilities.SliderHeight;

                // prioritize over manually set jobs (4)
                Rect prioritizeRect = new Rect( cur.x, cur.y, width, _entryHeight );
                if( optionindex++ % 2 == 0 ) Widgets.DrawAltRect( prioritizeRect );
                Utilities.DrawToggle(prioritizeRect, "FMP.PrioritizeManual".Translate(), ref ManagerJob_Production.prioritizeManual);
                cur.y += _entryHeight;
                
                // min skill (5)
                if ( _selected.Bill.recipe.workSkill != null )
                {
                    Rect skillLabelRect = new Rect( cur.x, cur.y, width, _entryHeight );
                    if( optionindex % 2 == 0 ) Widgets.DrawAltRect( skillLabelRect );
                    Utilities.Label( skillLabelRect,
                                     "MinimumSkillLevel".Translate( _selected.Bill.recipe.workSkill.label.ToLower() )
                                     + ": " + _selected.Bill.minSkillLevel.ToString( "#####0" ),
                                     anchor: TextAnchor.MiddleLeft, lrMargin: 6f );
                    cur.y += _entryHeight;

                    Rect skillRect = new Rect( cur.x, cur.y, width, Utilities.SliderHeight );
                    if( optionindex % 2 == 0 ) Widgets.DrawAltRect( skillRect );
                    _selected.Bill.minSkillLevel =
                        (int)GUI.HorizontalSlider( skillRect, _selected.Bill.minSkillLevel, 0f, 20f );
                    cur.y += Utilities.SliderHeight;

                    Rect snapToHighestRect = new Rect( cur.x, cur.y, width, _entryHeight );
                    if( optionindex++ % 2 == 0 ) Widgets.DrawAltRect( snapToHighestRect );
                    Utilities.DrawToggle( snapToHighestRect, "FMP.SnapToHighestSkill".Translate(), ref _selected.maxSkil );
                    cur.y += _entryHeight;
                    
                }

                // draw threshold and billgiver config (6, 7)
                _selected.Trigger.DrawTriggerConfig( ref cur, optionsColumnRect.width, _entryHeight, optionindex++ % 2 == 0 );
                _selected.BillGivers.DrawBillGiverConfig( ref cur, optionsColumnRect.width, _entryHeight, optionindex++ % 2 == 0 );

                // add a better recipe available notification with corresponding float menu if other recipe options are available.
                if( _selected.Managed && _selected.OtherRecipeAvailable() )
                {
                    Rect otherRecipeAvailableRect = new Rect(cur.x, cur.y, width, _entryHeight);
                    Utilities.Label( otherRecipeAvailableRect, "FMP.OtherRecipeAvailable".Translate(), "FMP.OtherRecipeAvailableTooltip".Translate() );
                    Widgets.DrawHighlightIfMouseover( otherRecipeAvailableRect );
                    if ( optionindex++ % 2 == 0 ) Widgets.DrawAltRect(otherRecipeAvailableRect);

                    // add a little icon to mark interactivity
                    Rect searchIconRect = new Rect( otherRecipeAvailableRect.xMax - Utilities.Margin - _entryHeight, cur.y, _entryHeight, _entryHeight );
                    if( searchIconRect.height > Utilities.SmallIconSize )
                    {
                        // center it.
                        searchIconRect = searchIconRect.ContractedBy( ( searchIconRect.height - Utilities.SmallIconSize ) / 2 );
                    }
                    GUI.DrawTexture( searchIconRect, Resources.Search );

                    // draw a floatmenu on click
                    if( Widgets.InvisibleButton( otherRecipeAvailableRect ) )
                    {
                        List<FloatMenuOption> options = new List<FloatMenuOption>();
                        string curLabel = "Current: " + _selected.Label +
                                           " (<i>" + string.Join( ", ", _selected.Targets ) + "</i>)";
                        options.Add(new FloatMenuOption( curLabel, null));

                        foreach( RecipeDef recipe in _selected.OtherRecipeDefs )
                        {
                            string label = recipe.LabelCap +
                                           " (<i>" + string.Join( ", ", recipe.GetRecipeUsers().Select( td => td.LabelCap ).ToArray()) + "</i>)";
                            Action action = delegate
                            {
                                _selected.SetNewRecipe( recipe );
                                _selected.ForceRecacheOtherRecipe();
                            };
                            options.Add( new FloatMenuOption( label, action ) );
                        }

                        Find.WindowStack.Add( new FloatMenu( options ) );
                    }

                    cur.y += _entryHeight;
                }

                GUI.EndGroup(); // options

                // bill
                GUI.BeginGroup( recipeColumnRect );
                cur = Vector2.zero;
                width = recipeColumnRect.width;

                // bill information
                Rect infoRect = new Rect( cur.x, cur.y, width, ( recipeColumnRect.height - cur.y ) / 2 );
                string text = GetInfoText();
                float actualHeight = Text.CalcHeight( text, infoRect.width );

                // if required height is small, cull info area
                if ( infoRect.height > actualHeight )
                {
                    infoRect.height = actualHeight;
                }

                // set up scrolling region
                Rect infoViewRect = infoRect;
                if ( actualHeight > infoRect.height )
                {
                    infoViewRect.width -= 16f; // scrollbar
                    infoViewRect.height = Text.CalcHeight( text, infoViewRect.width );
                }

                Widgets.BeginScrollView( infoRect, ref _infoScrollPosition, infoViewRect );
                Utilities.Label( infoRect, text, anchor: TextAnchor.UpperLeft, lrMargin: _margin );
                Widgets.EndScrollView();

                // if there is one or more products known to us (so not smelting, ugh!) display an infocard button
                if ( _selected.Bill.recipe.products.Count > 0 )
                {
                    Widgets.InfoCardButton( infoRect.xMax - Widgets.InfoCardButtonSize - _margin,
                                            infoRect.yMin + _margin, _selected.Bill.recipe.products[0].thingDef );
                }
                cur.y += infoRect.height;

                // ingredients label
                Rect ingredientsLabelRect = new Rect( cur.x, cur.y, width, _entryHeight );
                Utilities.Label( ingredientsLabelRect, "FMP.AllowedIngredients".Translate(),
                                 anchor: TextAnchor.MiddleLeft, lrMargin: _margin );
                cur.y += _entryHeight;

                // ingredients picker, fill available space
                Rect ingredientsRect = new Rect( cur.x, cur.y, width, recipeColumnRect.height - cur.y );
                filterUI.DoThingFilterConfigWindow( ingredientsRect, ref IngredientsScrollPosition,
                                                    _selected.Bill.ingredientFilter,
                                                    _selected.Bill.recipe.fixedIngredientFilter, 4 );

                GUI.EndGroup(); // bill
            }

            GUI.EndGroup(); // window
        }
        public void DoLeftRow( Rect canvas )
        {
            Widgets.DrawMenuSection( canvas, false );

            // filter
            Rect filterRect = new Rect( 10f, canvas.yMin + 5f, canvas.width - _leftRowEntryHeight, _entryHeight );

            GUI.SetNextControlName( "filterTextfield" );
            SourceFilter = Widgets.TextField( filterRect, SourceFilter );

            if ( !_postOpenFocus )
            {
                GUI.FocusControl( "filterTextfield" );
                _postOpenFocus = true;
            }

            if ( SourceFilter != "" )
            {
                Rect clearFilter = new Rect( filterRect.width + 10f, filterRect.yMin, _entryHeight, _entryHeight );
                if ( Widgets.ImageButton( clearFilter, Widgets.CheckboxOffTex ) )
                {
                    SourceFilter = "";
                }
                TooltipHandler.TipRegion( clearFilter, "FMP.ClearFilterDesc".Translate() );
            }
            TooltipHandler.TipRegion( filterRect, "FMP.FilterDesc".Translate() );

            // tabs
            List<TabRecord> list = new List<TabRecord>();
            TabRecord availableTabRecord = new TabRecord( "FMP.Available".Translate(), delegate
            {
                Source = SourceOptions.Available;
                Refresh();
            }, Source == SourceOptions.Available );
            list.Add( availableTabRecord );
            TabRecord currentTabRecord = new TabRecord( "FMP.Current".Translate(), delegate
            {
                Source = SourceOptions.Current;
                Refresh();
            }, Source == SourceOptions.Current );
            list.Add( currentTabRecord );
            TabDrawer.DrawTabs( canvas, list );

            // content
            Rect scrollCanvas = canvas;
            scrollCanvas.yMin = scrollCanvas.yMin + _entryHeight + _margin;
            float height = SourceListHeight;
            Rect scrollView = new Rect( 0f, 0f, scrollCanvas.width, height );
            if ( height > scrollCanvas.height )
            {
                scrollView.width -= 16f;
            }

            Widgets.BeginScrollView( scrollCanvas, ref LeftRowScrollPosition, scrollView );
            Rect scrollContent = scrollView;

            GUI.BeginGroup( scrollContent );
            float y = 0;
            int i = 0;

            foreach ( ManagerJob_Production current in from job in SourceList
                                                       where
                                                           job.Bill.recipe.label.ToUpper()
                                                              .Contains( SourceFilter.ToUpper() ) ||
                                                           job.MainProduct.Label.ToUpper()
                                                              .Contains( SourceFilter.ToUpper() )
                                                       select job )
            {
                Rect row = new Rect( 0f, y, scrollContent.width, Utilities.LargeListEntryHeight );
                Widgets.DrawHighlightIfMouseover( row );
                if ( _selected == current )
                {
                    Widgets.DrawHighlightSelected( row );
                }

                if ( i++ % 2 == 1 )
                {
                    Widgets.DrawAltRect( row );
                }

                Rect jobRect = row;

                if ( Source == SourceOptions.Current )
                {
                    if ( ManagerTab_Overview.DrawOrderButtons(
                        new Rect( row.xMax - _leftRowEntryHeight, row.yMin, _leftRowEntryHeight, _leftRowEntryHeight ),
                        current ) )
                    {
                        Refresh();
                    }
                    jobRect.width -= _leftRowEntryHeight;
                }

                current.DrawListEntry( jobRect, false, Source == SourceOptions.Current );
                if ( Widgets.InvisibleButton( jobRect ) )
                {
                    _selected = current;
                }

                y += Utilities.LargeListEntryHeight;
            }
            SourceListHeight = y;
            GUI.EndGroup();
            Widgets.EndScrollView();
        }
        public void DoLeftRow(Rect canvas)
        {
            Widgets.DrawMenuSection(canvas, false);

            // filter
            var filterRect = new Rect(10f, canvas.yMin + 5f, canvas.width - _leftRowEntryHeight, _entryHeight);

            GUI.SetNextControlName("filterTextfield");
            SourceFilter = Widgets.TextField(filterRect, SourceFilter);

            if (!_postOpenFocus)
            {
                GUI.FocusControl("filterTextfield");
                _postOpenFocus = true;
            }

            if (SourceFilter != "")
            {
                var clearFilter = new Rect(filterRect.width + 10f, filterRect.yMin, _entryHeight, _entryHeight);
                if (Widgets.ButtonImage(clearFilter, Widgets.CheckboxOffTex))
                {
                    SourceFilter = "";
                }
                TooltipHandler.TipRegion(clearFilter, "FMP.ClearFilterDesc".Translate());
            }
            TooltipHandler.TipRegion(filterRect, "FMP.FilterDesc".Translate());

            // tabs
            var list = new List <TabRecord>();
            var availableTabRecord = new TabRecord("FMP.Available".Translate(), delegate
            {
                Source =
                    SourceOptions.Available;
                Refresh();
            },
                                                   Source == SourceOptions.Available);

            list.Add(availableTabRecord);
            var currentTabRecord = new TabRecord("FMP.Current".Translate(), delegate
            {
                Source = SourceOptions.Current;
                Refresh();
            }, Source == SourceOptions.Current);

            list.Add(currentTabRecord);
            TabDrawer.DrawTabs(canvas, list);

            // content
            Rect scrollCanvas = canvas;

            scrollCanvas.yMin = scrollCanvas.yMin + _entryHeight + _margin;
            float height     = SourceListHeight;
            var   scrollView = new Rect(0f, 0f, scrollCanvas.width, height);

            if (height > scrollCanvas.height)
            {
                scrollView.width -= 16f;
            }

            Widgets.BeginScrollView(scrollCanvas, ref LeftRowScrollPosition, scrollView);
            Rect scrollContent = scrollView;

            GUI.BeginGroup(scrollContent);
            float y = 0;
            var   i = 0;

            foreach (ManagerJob_Production current in from job in SourceList
                     where
                     job.Bill.recipe.label.ToUpper()
                     .Contains(SourceFilter.ToUpper()) ||
                     job.MainProduct.Label.ToUpper()
                     .Contains(SourceFilter.ToUpper())
                     select job)
            {
                var row = new Rect(0f, y, scrollContent.width, Utilities.LargeListEntryHeight);
                Widgets.DrawHighlightIfMouseover(row);
                if (_selected == current)
                {
                    Widgets.DrawHighlightSelected(row);
                }

                if (i++ % 2 == 1)
                {
                    Widgets.DrawAltRect(row);
                }

                Rect jobRect = row;

                if (Source == SourceOptions.Current)
                {
                    if (ManagerTab_Overview.DrawOrderButtons(
                            new Rect(row.xMax - _leftRowEntryHeight, row.yMin,
                                     _leftRowEntryHeight, _leftRowEntryHeight),
                            manager,
                            current))
                    {
                        Refresh();
                    }
                    jobRect.width -= _leftRowEntryHeight;
                }

                current.DrawListEntry(jobRect, false, Source == SourceOptions.Current);
                if (Widgets.ButtonInvisible(jobRect))
                {
                    _selected = current;
                }

                y += Utilities.LargeListEntryHeight;
            }

            SourceListHeight = y;
            GUI.EndGroup();
            Widgets.EndScrollView();
        }
        public void DoContent(Rect canvas)
        {
            Widgets.DrawMenuSection(canvas);
            GUI.BeginGroup(canvas);
            canvas = canvas.AtZero();

            if (_selected != null)
            {
                // bottom buttons
                var buttonRect = new Rect(canvas.xMax - _button.x, canvas.yMax - _button.y, _button.x - _margin,
                                          _button.y - _margin);
                var ingredientCheck = new Rect(buttonRect.xMin - 300f - _margin, buttonRect.yMin, 300f,
                                               buttonRect.height);

                // add / remove to the stack
                if (Source == SourceOptions.Current)
                {
                    if (Widgets.ButtonText(buttonRect, "FM.Delete".Translate()))
                    {
                        _selected.Delete();
                        _selected = null;
                        Refresh();
                        return; // just skip to the next tick to avoid null reference errors.
                    }

                    TooltipHandler.TipRegion(buttonRect, "FMP.DeleteBillTooltip".Translate());
                }
                else
                {
                    if (_selected.Trigger.IsValid)
                    {
                        Widgets.CheckboxLabeled(ingredientCheck, "FMP.IngredientDialogTitle".Translate(),
                                                ref _selected._createIngredientBills,
                                                !_selected._hasMeaningfulIngredientChoices);

                        if (Widgets.ButtonText(buttonRect, "FM.Manage".Translate()))
                        {
                            _selected.Managed = true;
                            manager.JobStack.Add(_selected);

                            // refresh source list so that the next added job is not an exact copy.
                            Refresh();

                            if (_selected._hasMeaningfulIngredientChoices &&
                                _selected._createIngredientBills)
                            {
                                Find.WindowStack.Add(new Dialog_CreateJobsForIngredients(manager,
                                                                                         _selected.Bill.recipe,
                                                                                         _selected.Trigger.Count));
                            }

                            Source = SourceOptions.Current;
                            Refresh();
                            SourceFilter = "";
                        }
                        TooltipHandler.TipRegion(buttonRect, "FMP.ManageBillTooltip".Translate());
                    }
                    else
                    {
                        GUI.color = new Color(.6f, .6f, .6f);
                        Widgets.DrawBox(buttonRect);
                        Utilities.Label(buttonRect, "FMP.NoThreshold".Translate(), "FMP.NoThresholdTooltip".Translate(),
                                        TextAnchor.MiddleCenter);
                        GUI.color = Color.white;
                    }
                }

                // options
                var optionsColumnRect = new Rect(_margin / 2,
                                                 _topAreaHeight,
                                                 canvas.width / 2 - _margin,
                                                 canvas.height - _topAreaHeight - _margin - _button.y);
                var recipeColumnRect = new Rect(optionsColumnRect.xMax + _margin,
                                                _topAreaHeight,
                                                canvas.width / 2 - _margin,
                                                canvas.height - _topAreaHeight - _margin - _button.y);

                var optionsColumnTitle = new Rect(optionsColumnRect.xMin,
                                                  0f,
                                                  optionsColumnRect.width,
                                                  _topAreaHeight);
                var recipeColumnTitle = new Rect(recipeColumnRect.xMin,
                                                 0f,
                                                 recipeColumnRect.width,
                                                 _topAreaHeight);

                // backgrounds
                GUI.DrawTexture(optionsColumnRect, Resources.SlightlyDarkBackground);
                GUI.DrawTexture(recipeColumnRect, Resources.SlightlyDarkBackground);

                // titles
                Utilities.Label(optionsColumnTitle, "FMP.Options".Translate(),
                                anchor: TextAnchor.LowerLeft, lrMargin: _margin * 2, font: GameFont.Tiny);
                Utilities.Label(recipeColumnTitle, "FMP.Recipe".Translate(),
                                anchor: TextAnchor.LowerLeft, lrMargin: _margin * 2, font: GameFont.Tiny);

                // options
                GUI.BeginGroup(optionsColumnRect);
                Vector2 cur   = Vector2.zero;
                float   width = optionsColumnRect.width;

                // keep track of optionIndex for shading purposes (lazy way to avoid having to redo this all the damn time).
                var optionindex = 0;

                // suspended
                var suspendedRect = new Rect(cur.x, cur.y, width, _entryHeight);
                if (optionindex++ % 2 == 0)
                {
                    Widgets.DrawAltRect(suspendedRect);
                }
                Utilities.DrawToggle(suspendedRect, "Suspended".Translate(), _selected.Suspended,
                                     delegate
                                     { _selected.Suspended = !_selected.Suspended; });
                cur.y += _entryHeight;

                // store mode
                var takeToStockRect = new Rect(cur.x, cur.y, width, _entryHeight);
                if (optionindex++ % 2 == 0)
                {
                    Widgets.DrawAltRect(takeToStockRect);
                }
                Utilities.DrawToggle(takeToStockRect, "BillStoreMode_BestStockpile".Translate(),
                                     _selected.Bill.storeMode == BillStoreMode.BestStockpile,
                                     delegate
                                     { _selected.Bill.storeMode = BillStoreMode.BestStockpile; },
                                     delegate
                                     { _selected.Bill.storeMode = BillStoreMode.DropOnFloor; });
                cur.y += _entryHeight;

                // ingredient search radius (3)
                var searchRadiusLabelRect = new Rect(cur.x, cur.y, width, _entryHeight);
                if (optionindex % 2 == 0)
                {
                    Widgets.DrawAltRect(searchRadiusLabelRect);
                }
                Utilities.Label(searchRadiusLabelRect,
                                "IngredientSearchRadius".Translate() + ": " +
                                _selected.Bill.ingredientSearchRadius.ToString(" #####0"),
                                anchor: TextAnchor.MiddleLeft, lrMargin: _margin);
                cur.y += _entryHeight;

                var searchRadiusRect = new Rect(cur.x, cur.y, width, Utilities.SliderHeight);
                if (optionindex++ % 2 == 0)
                {
                    Widgets.DrawAltRect(searchRadiusRect);
                }
                _selected.Bill.ingredientSearchRadius =
                    (int)GUI.HorizontalSlider(searchRadiusRect, _selected.Bill.ingredientSearchRadius, 0f, 250f);
                cur.y += Utilities.SliderHeight;

                // prioritize over manually set jobs (4)
                var prioritizeRect = new Rect(cur.x, cur.y, width, _entryHeight);
                if (optionindex++ % 2 == 0)
                {
                    Widgets.DrawAltRect(prioritizeRect);
                }
                Utilities.DrawToggle(prioritizeRect, "FMP.PrioritizeManual".Translate(),
                                     ref ManagerJob_Production.prioritizeManual);
                cur.y += _entryHeight;

                // skill range (5)
                if (_selected.Bill.recipe.workSkill != null)
                {
                    var skillLabelRect = new Rect(cur.x, cur.y, width, _entryHeight);
                    if (optionindex % 2 == 0)
                    {
                        Widgets.DrawAltRect(skillLabelRect);
                    }
                    Utilities.Label(skillLabelRect,
                                    "FMP.AllowedSkillRange".Translate()
                                    + ": " + _selected.Bill.allowedSkillRange);
                    cur.y += _entryHeight;

                    var skillRect = new Rect(cur.x, cur.y, width, Utilities.SliderHeight);
                    if (optionindex % 2 == 0)
                    {
                        Widgets.DrawAltRect(skillRect);
                    }
                    Widgets.IntRange(skillRect, 2134112311, ref _selected.Bill.allowedSkillRange, 0, 20);
                    cur.y += Utilities.SliderHeight;

                    var snapToHighestRect = new Rect(cur.x, cur.y, width, _entryHeight);
                    if (optionindex++ % 2 == 0)
                    {
                        Widgets.DrawAltRect(snapToHighestRect);
                    }
                    Utilities.DrawToggle(snapToHighestRect, "FMP.SnapToHighestSkill".Translate(),
                                         ref _selected.restrictToMaxSkill);
                    cur.y += _entryHeight;
                }

                // draw threshold and billgiver config (6, 7)
                _selected.Trigger.DrawTriggerConfig(ref cur, optionsColumnRect.width, _entryHeight,
                                                    optionindex++ % 2 == 0);
                _selected.BillGivers.DrawBillGiverConfig(ref cur, optionsColumnRect.width, _entryHeight,
                                                         optionindex++ % 2 == 0);

                // add a better recipe available notification with corresponding float menu if other recipe options are available.
                if (_selected.Managed && _selected.OtherRecipeAvailable())
                {
                    var otherRecipeAvailableRect = new Rect(cur.x, cur.y, width, _entryHeight);
                    Utilities.Label(otherRecipeAvailableRect, "FMP.OtherRecipeAvailable".Translate(),
                                    "FMP.OtherRecipeAvailableTooltip".Translate());
                    Widgets.DrawHighlightIfMouseover(otherRecipeAvailableRect);
                    if (optionindex++ % 2 == 0)
                    {
                        Widgets.DrawAltRect(otherRecipeAvailableRect);
                    }

                    // add a little icon to mark interactivity
                    var searchIconRect = new Rect(otherRecipeAvailableRect.xMax - Utilities.Margin - _entryHeight,
                                                  cur.y, _entryHeight, _entryHeight);
                    if (searchIconRect.height > Utilities.SmallIconSize)
                    {
                        // center it.
                        searchIconRect =
                            searchIconRect.ContractedBy((searchIconRect.height - Utilities.SmallIconSize) / 2);
                    }
                    GUI.DrawTexture(searchIconRect, Resources.Search);

                    // draw a floatmenu on click
                    if (Widgets.ButtonInvisible(otherRecipeAvailableRect))
                    {
                        var    options  = new List <FloatMenuOption>();
                        string curLabel = "Current: " + _selected.Label +
                                          " (<i>" + string.Join(", ", _selected.Targets) + "</i>)";
                        options.Add(new FloatMenuOption(curLabel, null));

                        foreach (RecipeDef recipe in _selected.OtherRecipeDefs)
                        {
                            string label = recipe.LabelCap +
                                           " (<i>" +
                                           string.Join(", ",
                                                       recipe.GetRecipeUsers().Select(td => td.LabelCap).ToArray()) +
                                           "</i>)";
                            Action action = delegate
                            {
                                _selected.SetNewRecipe(recipe);
                                _selected.ForceRecacheOtherRecipe();
                            };
                            options.Add(new FloatMenuOption(label, action));
                        }

                        Find.WindowStack.Add(new FloatMenu(options));
                    }

                    cur.y += _entryHeight;
                }

                GUI.EndGroup(); // options

                // bill
                GUI.BeginGroup(recipeColumnRect);
                cur   = Vector2.zero;
                width = recipeColumnRect.width;

                // bill information
                var    infoRect     = new Rect(cur.x, cur.y, width, (recipeColumnRect.height - cur.y) / 2);
                string text         = GetInfoText();
                float  actualHeight = Text.CalcHeight(text, infoRect.width);

                // if required height is small, cull info area
                if (infoRect.height > actualHeight)
                {
                    infoRect.height = actualHeight;
                }

                // set up scrolling region
                Rect infoViewRect = infoRect;
                if (actualHeight > infoRect.height)
                {
                    infoViewRect.width -= 16f; // scrollbar
                    infoViewRect.height = Text.CalcHeight(text, infoViewRect.width);
                }

                Widgets.BeginScrollView(infoRect, ref _infoScrollPosition, infoViewRect);
                Utilities.Label(infoRect, text, anchor: TextAnchor.UpperLeft, lrMargin: _margin);
                Widgets.EndScrollView();

                // if there is one or more products known to us (so not smelting, ugh!) display an infocard button
                if (_selected.Bill.recipe.products.Count > 0)
                {
                    Widgets.InfoCardButton(infoRect.xMax - Widgets.InfoCardButtonSize - _margin,
                                           infoRect.yMin + _margin, _selected.Bill.recipe.products[0].thingDef);
                }
                cur.y += infoRect.height;

                // ingredients label
                var ingredientsLabelRect = new Rect(cur.x, cur.y, width, _entryHeight);
                Utilities.Label(ingredientsLabelRect, "FMP.AllowedIngredients".Translate(),
                                anchor: TextAnchor.MiddleLeft, lrMargin: _margin);
                cur.y += _entryHeight;

                // ingredients picker, fill available space
                var ingredientsRect = new Rect(cur.x, cur.y, width, recipeColumnRect.height - cur.y);
                filterUI.DoThingFilterConfigWindow(ingredientsRect, ref IngredientsScrollPosition,
                                                   _selected.Bill.ingredientFilter,
                                                   _selected.Bill.recipe.fixedIngredientFilter, 4);

                GUI.EndGroup(); // bill
            }

            GUI.EndGroup(); // window
        }
            public void AddBills()
            {
                // only proceed if we selected an ingredient/thingdef (recipeSelector != null), and there is a recipe selected.
                if ( recipeSelector?.selectedRecipe == null )
                {
                    return;
                }

                // try to get a job with our recipe
                RecipeDef curRecipe = recipeSelector.selectedRecipe;
                ManagerJob_Production curJob = manager.JobStack.FullStack<ManagerJob_Production>()
                                                      .FirstOrDefault( job => job.Bill.recipe == curRecipe );

                // if there is a job for the recipe, add our job's count - any settings beyond that are user responsibility.
                if ( curJob != null && curJob.Trigger.Count < targetCount )
                {
                    curJob.Trigger.Count = targetCount;
                    Messages.Message( "FMP.IncreasedThreshold".Translate( curRecipe.LabelCap, targetCount ),
                                      MessageSound.Benefit );
                }
                // otherwise create a new job.
                else
                {
                    curJob = new ManagerJob_Production( manager, curRecipe );
                    // make sure the trigger is valid (everything else is user responsibility).
                    if ( curJob.Trigger.IsValid )
                    {
                        curJob.Managed = true;
                        manager.JobStack.Add( curJob );
                        Messages.Message( "FMP.AddedJob".Translate( curRecipe.LabelCap ), MessageSound.Benefit );
                    }
                    else
                    {
                        Messages.Message( "FMP.CouldNotAddJob".Translate( curRecipe.LabelCap ), MessageSound.RejectInput );
                    }
                }

                // finally, call this method on all of our children
                foreach ( IngredientSelector child in recipeSelector.children )
                {
                    child.AddBills();
                }
            }
        /// <summary>
        ///     Amount per worker to satsify the bill.
        /// </summary>
        /// <param name="job"></param>
        /// <param name="workerIndex"></param>
        /// <returns></returns>
        public static int CountPerWorker(this ManagerJob_Production job, int workerIndex)
        {
            int n    = job.BillGivers.CurBillGiverCount;
            int diff = job.Trigger.Count - job.Trigger.CurCount;

            int bills;

            // if diff is negative we can assume this is a destructive job.
            if (diff < 0)
            {
                // default input count set to 1
                float inputCount = 1;
                IEnumerable <ThingDef> filterThingDefs = job.Trigger.ThresholdFilter.AllowedThingDefs;

                // if the filter is a single thingdef, try to figure out what the input count of it is for the managed recipe.
                if (filterThingDefs?.Count() == 1)
                {
                    // only one thingdef in the filter, so get the first.
                    ThingDef filterThingDef = filterThingDefs.First();

                    // the ingredient list of the managed recipe.
                    List <IngredientCount> ingredients = job.Bill.recipe.ingredients;

                    // get the total input count of any ingredients that allow our thingdef (probably 0 or 1, but who knows - could be a stuff + specific listing).
                    if (ingredients != null)
                    {
                        float?recipeInput =
                            (ingredients?.Where(
                                 ing =>
                                 ing
                                 .filter
                                 .AllowedThingDefs.Contains(filterThingDef))).Select(ing =>
                                                                                     ing
                                                                                     .CountRequiredOfFor
                                                                                         (filterThingDef,
                                                                                         job
                                                                                         .Bill
                                                                                         .recipe))
                            .Sum();

                        // if this wasn't null or close to zero, set the reduction count per bill to this number.
                        if (recipeInput != null &&
                            Math.Abs(recipeInput.Value) > 1)
                        {
                            inputCount = recipeInput.Value;
                        }
                    }
                }

                // divide by negative reduction amount per bill to get a positive number of bills.
                bills = Mathf.CeilToInt(diff / -inputCount);
            }
            else
            {
                // this one is a bit simpler - mainly because we already did the complicated stuff in the MainProduct class.
                bills = Mathf.CeilToInt(diff / job.MainProduct.Count);
            }

            // naive number of bills per worker (float)
            float naive = bills / (float)n;

            // round up or down to get a total that matches the desired total exactly
            if (bills % n > workerIndex)
            {
                return((int)Math.Floor(naive));
            }

            return((int)Math.Ceiling(naive));
        }