DevLog #55 - IndustrySystem refactored and FINALLY DONE !!!! Big milestone! Biggest system finished!!

 ******IndustrySystem finished! ****** I will go deep into how it works and how it differs from other systems we had.****** 



This is it!!! The system I have been building since the start of this game development, now its done! 

Of course there were amny issues, sturcture wise, constant refactoring, optimising, and trying to solve the whole puzzle in my mind before i do it in code.

Lets get started! : )


First, reminder why it is if not the most complex system in game: 

For industry, we have few different databases(scriptable objects ) that interact with each other. For example, officeSystem has a single database of officeTierDatabase, which contains differnt sizes of offices, which , uponupgrade it takes all the other data , like max workers , upgrade cost, efficiency per worker etc.. ( feels like i have talked about this haha), anyhow, the industrySystem  has one industryTierDatabase, plus it has another database which is industryTypeDatabase, which contains directly settings for a certrain products, recepies, upkeep modifiers, construction etc. So then the question was, how do I add necessary resources? I was thinking and thinking, what is the best way, then after having different ideas, i simply came to conclusion, perhaps i can make a list, that contains the products we want to have as a recepy. The problem, every list in cityMarkets, truckSystem and everywhere ( warehouse) basically is a list of all products from enum productType. So, in this case we would need to have a whole list to be in sync. why? that is a bit of an issue with the structure itself. Lets imagine, 

product 1 - position 0,
product 2 - position 1,
product 3 -position 2,
product 4 -position 3, 


so now, how the truck works, it says, take product 1 ( position0)  and deliver it to the position 0 in the other place. 

but now, if we dont make the list of 50 products (including raw materials and everything) , then we cant say truck take product 30(steel for example) ( position 29) , and deliver it to the positioion 1 in the industry input (list that has only 1 or two products). See? that is the issue because the truck doesnt actually knows what is on the position, it only delivers the product based on the productType enum location which is in the sync. 

The solution was: First, use the list/array only with 1 or two products, but in the list we make( like we had fo storeStateslot etc) industrySlotState, which contains productType, so it would then compare the productType and then know where the truck should deliver. For example:


[System.Serializable]
public struct IndustrySlotState
{
    public string listLabel;
    public int warehouseStock;

    public float goodsRatioForProduct;
    public float primePrice;
    public int incomingOrderAmount;
    public ProductType productType;

}




So here is the example in the inspector.

Now then we come to the second issue, that i have also been thinking (nervously haha), how do i actually know ( how does industry knows) how much it can produce.

First I was thinking, if we have a ratio, for a single product, had some brainstorming ideas late in the night, and then it came to me one morning, You take ratio ( which i called ratio..could be anything actually, resourceForSingleProduct? 

Anyhow, then i say, ok, i have 1000 wheat in warehouse, and i say it takes 2:1 wheat -bread ratio .

Then i say, ok float maxProducedAmount = warehouseStock / productRatio 

and thats it, i get 500 bread. simple.

Then i said ok, how do we calculate if there are mor eproducts or we say, lets say cotton and planks from the screenshot above (btw that is totally wrong now i see it hahah, it should be cotton and planks not fabric haha) anyhow,

Then we have to take the data from the list(array) , go through the list, and calculate for each of the items maximum amount of producable goods  per amount of the warehousestock per product.

So , for example, 

cotton  we have 3000 , 

planks we have 2000,

we go through cotton , divide it by 3, we get total amount is 1000, then we go to planks and ask the same, then we say ok 2000/5 = 400, then we use mathf.Min and we get the minimum, which is 400, so that is how much we can produce.

NOTE: we also use in that equation max possible amount to be produced based upon current amount of workers and workers efficiency (which depends on tier) . so then we add that to the mathf.min, and thats it!! Beautiful!

Also, problem hjere is that we are getting info from the list/array..so with using for int , problem is how to be universal, no matter if there are 5 products or just 1..
And here it is :

  float maxProducedGoods = industry.industryMaxProductionRate;

        for (int p = 0; p < industry.resourcesInventory.Length; p++)
        {
            IndustrySlotState slot = industry.resourcesInventory[p];

            float maxProducedGoodsPerItem = slot.warehouseStock / slot.goodsRatioForProduct;

            float maxProducedGoodsByAmountOfWorkers = industry.currentWorkers * industry.baseProductionRatePerTick;
            //upored i izvuci manji broj da bi imao maks koliko se proizvelo
            maxProducedGoods = Mathf.Min(maxProducedGoods, maxProducedGoodsPerItem, maxProducedGoodsByAmountOfWorkers);
        }

see? It goes once through it, then instead of adding the value we use producedGoods = mathf.min, while keeping hte last value for comparison. in the end we get the maxProduced Goods filtered also with the max amount of workers. 

Next question, how do we write back, we cant just do it right there, its a list, in a struct, so we have to get into it, change stuff, writeback. 



        int totalProducedGoods = Mathf.RoundToInt(maxProducedGoods);

        // Debug.Log($"totalProduced goods {totalProducedGoods} ");

        industry.currentProductionRate = totalProducedGoods;

        //writeback promenu stock-a
        for (int p = 0; p < industry.resourcesInventory.Length; p++)
        {
            IndustrySlotState slot = industry.resourcesInventory[p];
            slot.warehouseStock -= Mathf.RoundToInt(totalProducedGoods * slot.goodsRatioForProduct);

            //writeback
            industry.resourcesInventory[p] = slot;

        }

and that is how its done, then we go through the list, again, and take away the goods that we actually produced. 

Since we didnt know how much it will be produced based upon the max amount of produced items per product ratio, we just take that what is produced and multiply it back with the actual ratio. BEAUTIFULLLL aagh. yes!! A small reward for all those thinking. 

Do you like it? 

I know there is probably a better solution, but for me this is awesome. its universal, it can have lots of products. so who cares! 


________________________________________________________________________________

Next small step, truckSystem, how it knows which product to aim?

Take a look at this: 

   int productIndex = -1;
                //provera proizvoda sa liste, i da li se kamion poklapa po ProductType unutar IndeustrySlotState-a
                //jer koristimo malu listu i nemamo univerzalni raspored proizvoda kao city, store..
                for (int p = 0; p < industry.resourcesInventory.Length; p++)
                {
                    IndustrySlotState findSlot = industry.resourcesInventory[p];

                    if (findSlot.productType == truck.productType)
                    {
                        productIndex = p;
                        break;
                    }

                }

Sorry for Serbian notes, but sometimes i write englihs sometimes serbian // . Anyhow, now first we find a slot, based upon the truckProductType, then we get that product index. which could be 30, but on a list we have 2. if we would have taken the index from those it would be wrong,b ecause they would be 0 and 1, and if we kept the whole list, it would be so complex and annoying to go through the complete list of products just to select 2..and hell to change later.


________________________________________________________________________________


So this was the biggest and final nail for the industrySystem!! Tthe rest was structularly similar to previous. 


Build industry  - take the industryType, take the tier data, combine it, make a GHOST industry, untill its constructed.
Charge the adjusted price for averageCitySalary where it is built, the cost modifier of per product industry (bakery and microchip doesnt cost the same) , and then take the tier cost for construction, then that is the final price for construction. 



Its something you have already seen before, only as i said, why it was so tricky, and i have had so many bugs  writing htis, is because it uses 3 different data locations, and i couldn't find any better solution unless to have tier settings for each of the industryType, which would mean around 40 types x 6, 240 combinations. Then it would be way easier to write this. But to sustain.........

this way, we have 40 types, and 6 tiers, so any of the 40 we take, has default values then we just override with tier data. Way better. 

Everything else is straighforward, we have ConstructionTick, upgradeTick, which is like bifore for office,hubs, stores.. it counts down, then changes bool to isUnderConstruction = false; this way it is not ghost anymore,a nd its active, and it appears within the game.  And thats it!  

______________________________________________________________________________

oh yeah, another tricky one was SELL INDUSTRY  hhahah, same it was with the store, calculate assets, calculate products that are within, but also firing workers, that was free. not anymore! hahah now that is also being calculated. So as before, we calculate all of that, then we calculate amount of workers, calculate how much is to fire all of them, add an expense, (single expense), and then we change current workers = 0, and ghost the industry. So its still ont he list because of the index numbers, charts etc..truck locations, its crucial not to be deleted as we discussed before. 

For example:


        //charge all the workers expenses for firing, penalty,
        float firingCost = CalculateWorkersFiringCosts(industry, city);

        WorldManager.Instance.RemoveMoney(firingCost, MoneyEventType.IndustryFiringCost,
 entityName: $"{city.cityName} - {industry.industryName} for {industry.currentWorkers} workers.",
 description: "Industry worker Firing Cost");


 private static float CalculateWorkersFiringCosts(IndustryState industry, CityState city)
    {
        float firingCost = 0f;
        for (int w = 0; w < industry.currentWorkers; w++)
        {
            firingCost += city.averageSalary;

        }

        return firingCost;
    }

See? Simple, but very neat! haha, clean, and now it calculates, and shows only a single expense, which is different then firing one by one, then the ledger list is overburden? if thats the word.

Anyhow!


next 
___________________________________________________________________________________

public struct IndustrySaleValue
{
    public float buildingValue;
    public float inventoryValue;
    public float totalValue;
    public float workersFiringCost;
}

This is the new struct, its same as store, only it has workersFiring Cost, so this is something that is filled upon pressing the sell button.

It calculates assets, invenory, then also calculates the amount that it will cost to fire all workers, and when confirmed, it does all of that. but here, this struct is used only to show as a confirm button and know how much you get money back.

That also was supposed to be NON TAXABLE hahaha, so that i fixed as well but adding it to the nonTAxable , so the taxSystem wouldn't charge selling industry. haha


       //truckSale, Storesale, industry sale, non taxable.
            if (evt.type == MoneyEventType.TruckSale | evt.type ==
MoneyEventType.StoreBuildingSale | evt.type == MoneyEventType.IndustryBuildingSale)
            {
                nonTaxable += evt.amount;
                continue;
            }



There is so much more to cover, very interesting! But the most important part was discussed here. 

If anyone is wondering, now its simple, you can deliver goods only to which are in the blueprint list, also, if you use this industry as source, you can only pickup from the main industry.warehouseStock. Otherwise as we said, if you use it as destination, you can only deliver to the inventoryList/array. Why i say list, is because in inspector it looks like a list hahah and easier to store in my mind visually.

   [Header("Product - needed resources")]
    public IndustrySlotState[] resourcesInventory;



Thank you!!! 








Comments

Popular posts from this blog

Devlog #47 - TaxSystem/FinanceSystem ,Update-FIx -LoanFixes-- TruckSystem Update - Upgrade/BuyNEwTruck - Costs Prices introduced / OfficeSystem CreateNewOffice, upgrade office - other Fixes

Devlog #46 - HubSystem - CreatNewHub added, HubBuild Ui + INdustrySystem Update - -INdustryTierData to be combined with the IndustryData and IndustryState struct -

Devlog #49-50 - Asset System-NEW system, *Game Design* -construction costs update - LoanSystem- fix-redesign-upgrade,Bug fixes-storeSystem,AutoAdvance Day, Market system, Truck System cost redesign, refactored, Ui bug fixes