unit KM_UnitWorkPlan; {$I KaM_Remake.inc} interface uses KM_Defaults, KM_CommonClasses, KM_Points, KM_TerrainTypes, KM_Units, KM_ResTypes; const MAX_WORKPLAN = 24; type TKMWorkPlanAllowedEvent = function(aProduct: TKMWareType): Boolean of object; TKMUnitWorkPlan = class private fHome: TKMHouseType; fIssued: Boolean; function ChooseTree(const aLoc, aAvoid: TKMPoint; aRadius: Integer; aPlantAct: TKMPlantAct; aUnit: TKMUnit; out Tree: TKMPointDir; out PlantAct: TKMPlantAct): Boolean; procedure Clear; procedure WalkStyle(const aLoc2: TKMPointDir; aTo, aWork: TKMUnitActionType; aCycles, aDelay: Byte; aFrom: TKMUnitActionType; aScript: TKMGatheringScript); procedure SubActAdd(aAct: TKMHouseActionType; aCycles: Single); procedure ResourcePlan(Res1: TKMWareType; Qty1: Byte; Res2: TKMWareType; Qty2: Byte; Prod1: TKMWareType; Prod2: TKMWareType = wtNone); public HasToWalk: Boolean; Loc: TKMPoint; ActionWalkTo: TKMUnitActionType; ActionWorkType: TKMUnitActionType; WorkCyc: Integer; WorkDir: TKMDirection; GatheringScript: TKMGatheringScript; AfterWorkDelay: Integer; ActionWalkFrom: TKMUnitActionType; Resource1: TKMWareType; Count1: Byte; Resource2: TKMWareType; Count2: Byte; ActCount: Byte; HouseAct: array [0..MAX_WORKPLAN - 1] of record Act: TKMHouseActionType; TimeToWork: Word; end; Product1: TKMWareType; ProdCount1: Byte; Product2: TKMWareType; ProdCount2: Byte; AfterWorkIdle: Integer; ResourceDepleted: Boolean; public procedure FindPlan(aUnit: TKMUnit; aHome: TKMHouseType; aProduct: TKMWareType; aLoc: TKMPoint; aPlantAct: TKMPlantAct); function FindDifferentResource(aUnit: TKMUnit; aLoc: TKMPoint; const aAvoidLoc: TKMPoint): Boolean; property IsIssued: Boolean read fIssued; procedure Save(SaveStream: TKMemoryStream); procedure Load(LoadStream: TKMemoryStream); end; implementation uses SysUtils, KM_ResUnits, KM_Houses, KM_HouseWoodcutters, KM_ResHouses, KM_Terrain, KM_Resource, KM_CommonUtils; {Houses are only a place on map, they should not issue or perform tasks (except Training) Everything should be issued by units Where to go, which walking style, what to do on location, for how long How to go back in case success, incase bad luck What to take from supply, how much, take2, much2 What to do, Work/Cycles, What resource to add to Output, how much E.g. CoalMine: Miner arrives at home and Idles for 5sec, then takes a work task (depending on ResOut count) Since Loc is 0,0 he immidietely skips to Phase X where he switches house to Work1 (and self busy for same framecount) Then Work2 and Work3 same way. Then adds resource to out and everything to Idle for 5sec. E.g. Farmer arrives at home and Idles for 5sec, then takes a work task (depending on ResOut count, HouseType and Need to sow corn) ...... then switches house to Work1 (and self busy for same framecount) Then Work2 and Work3 same way. Then adds resource to out and everything to Idle for 5sec.} procedure TKMUnitWorkPlan.Clear; begin fIssued := False; HasToWalk := False; Loc := KMPOINT_ZERO; ActionWalkTo := uaWalk; ActionWorkType := uaWork; WorkCyc := 0; WorkDir := dirNA; GatheringScript := gsNone; AfterWorkDelay := 0; ActionWalkFrom := uaWalk; Resource1 := wtNone; Count1 := 0; Resource2 := wtNone; Count2 := 0; ActCount := 0; Product1 := wtNone; ProdCount1 := 0; Product2 := wtNone; ProdCount2 := 0; AfterWorkIdle := 0; ResourceDepleted := False; end; procedure TKMUnitWorkPlan.WalkStyle(const aLoc2: TKMPointDir; aTo, aWork: TKMUnitActionType; aCycles,aDelay: Byte; aFrom: TKMUnitActionType; aScript: TKMGatheringScript); begin Loc := aLoc2.Loc; HasToWalk := True; ActionWalkTo := aTo; ActionWorkType := aWork; WorkCyc := aCycles; AfterWorkDelay := aDelay; GatheringScript := aScript; ActionWalkFrom := aFrom; WorkDir := aLoc2.Dir; end; procedure TKMUnitWorkPlan.SubActAdd(aAct: TKMHouseActionType; aCycles: Single); begin HouseAct[ActCount].Act := aAct; HouseAct[ActCount].TimeToWork := Round(gRes.Houses[fHome].Anim[aAct].Count * aCycles); Inc(ActCount); end; procedure TKMUnitWorkPlan.ResourcePlan(Res1: TKMWareType; Qty1: Byte; Res2: TKMWareType; Qty2: Byte; Prod1: TKMWareType; Prod2: TKMWareType = wtNone); begin Resource1 := Res1; Count1 := Qty1; Resource2 := Res2; Count2 := Qty2; Product1 := Prod1; ProdCount1 := gRes.Houses[fHome].ResProductionX; if Prod2=wtNone then exit; Product2 := Prod2; ProdCount2 := gRes.Houses[fHome].ResProductionX; end; function TKMUnitWorkPlan.FindDifferentResource(aUnit: TKMUnit; aLoc: TKMPoint; const aAvoidLoc: TKMPoint): Boolean; var NewLoc: TKMPointDir; PlantAct: TKMPlantAct; Found: boolean; HW: TKMHouseWoodcutters; begin if (GatheringScript = gsWoodCutterCut) or (GatheringScript = gsWoodCutterPlant) then begin HW := TKMHouseWoodcutters(aUnit.Home); HW.ValidateFlagPoint; //Validate Cutting point. It will be set to a valid one if needed. if HW.IsFlagPointSet then aLoc := HW.FlagPoint; end; with gTerrain do case GatheringScript of gsStoneCutter: Found := FindStone(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, False, NewLoc); gsFarmerSow: Found := FindCornField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, taPlant, PlantAct, NewLoc); gsFarmerCorn: begin Found := FindCornField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, taAny, PlantAct, NewLoc); if PlantAct = taPlant then begin GatheringScript := gsFarmerSow; //Switch to sowing corn rather than cutting ActionWalkFrom := uaWalkTool; //Carry our scythe back (without the corn) as the player saw us take it out ActionWorkType := uaWork1; WorkCyc := 10; Product1 := wtNone; //Don't produce corn ProdCount1 := 0; end; end; gsFarmerWine: begin Found := FindWineField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, NewLoc); NewLoc.Dir := dirN; //The animation for picking grapes is only defined for facing north end; gsFisherCatch: Found := FindFishWater(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, False, NewLoc); gsWoodCutterCut: Found := ChooseTree(aLoc, KMGetVertexTile(aAvoidLoc, WorkDir), gRes.Units[aUnit.UnitType].MiningRange, taCut, aUnit, NewLoc, PlantAct); gsWoodCutterPlant: Found := ChooseTree(aLoc, aAvoidLoc, gRes.Units[aUnit.UnitType].MiningRange, taPlant, aUnit, NewLoc, PlantAct); else Found := False; //Can find a new resource for an unknown gathering script, so return with False end; if Found then begin Loc := NewLoc.Loc; WorkDir := NewLoc.Dir; Result := True; end else Result := False; end; function TKMUnitWorkPlan.ChooseTree(const aLoc, aAvoid: TKMPoint; aRadius: Integer; aPlantAct: TKMPlantAct; aUnit: TKMUnit; out Tree: TKMPointDir; out PlantAct: TKMPlantAct): Boolean; var I: Integer; T: TKMPoint; treeList: TKMPointDirCenteredList; bestToPlant, secondBestToPlant: TKMPointCenteredList; begin treeList := TKMPointDirCenteredList.Create(aLoc); bestToPlant := TKMPointCenteredList.Create(aLoc); secondBestToPlant := TKMPointCenteredList.Create(aLoc); gTerrain.FindTree(aLoc, aRadius, aAvoid, aPlantAct, treeList, bestToPlant, secondBestToPlant); //Convert taAny to either a Tree or a Spot if (aPlantAct in [taCut, taAny]) and ((treeList.Count > 8) //Always chop the tree if there are many or (bestToPlant.Count + secondBestToPlant.Count = 0) or ((treeList.Count > 0) and (KaMRandom({$IFDEF DBG_RNG_SPY}'TKMUnitWorkPlan.ChooseTree'{$ENDIF}) < treeList.Count / (treeList.Count + (bestToPlant.Count + secondBestToPlant.Count)/15))) ) then begin PlantAct := taCut; Result := treeList.GetWeightedRandom(Tree); end else begin PlantAct := taPlant; //First try stumps list for I := bestToPlant.Count - 1 downto 0 do if not TKMUnitCitizen(aUnit).CanWorkAt(bestToPlant[I], gsWoodCutterPlant) then bestToPlant.Delete(I); Result := bestToPlant.GetWeightedRandom(T); //Trees must always be planted facing north as that is the direction the animation uses if Result then Tree := KMPointDir(T, dirN) else begin //Try empty places list for I := secondBestToPlant.Count - 1 downto 0 do if not TKMUnitCitizen(aUnit).CanWorkAt(secondBestToPlant[I], gsWoodCutterPlant) then secondBestToPlant.Delete(I); Result := secondBestToPlant.GetWeightedRandom(T); //Trees must always be planted facing north as that is the direction the animation uses if Result then Tree := KMPointDir(T, dirN); end; end; treeList.Free; bestToPlant.Free; secondBestToPlant.Free; end; procedure TKMUnitWorkPlan.FindPlan(aUnit: TKMUnit; aHome: TKMHouseType; aProduct: TKMWareType; aLoc: TKMPoint; aPlantAct: TKMPlantAct); var I: Integer; tmp: TKMPointDir; plantAct: TKMPlantAct; HW: TKMHouseWoodcutters; begin Clear; fHome := aHome; AfterWorkIdle := gRes.Houses[aHome].WorkerRest * 10; //Now we need to fill only specific properties case aUnit.UnitType of utWoodcutter: if aHome = htWoodcutters then begin HW := TKMHouseWoodcutters(aUnit.Home); HW.ValidateFlagPoint; //Validate Cutting point. It will be set to a valid one if needed. if HW.IsFlagPointSet then aLoc := HW.FlagPoint; fIssued := ChooseTree(aLoc, KMPOINT_ZERO, gRes.Units[aUnit.UnitType].MiningRange, aPlantAct, aUnit, tmp, plantAct); if fIssued then begin case plantAct of taCut: begin //Cutting uses DirNW,DirSW,DirSE,DirNE (1,3,5,7) of uaWork ResourcePlan(wtNone,0,wtNone,0,wtTrunk); WalkStyle(tmp, uaWalkBooty,uaWork,15,20,uaWalkTool2,gsWoodCutterCut); end; taPlant: begin //Planting uses DirN (0) of uaWork WalkStyle(tmp, uaWalkTool,uaWork,12,0,uaWalk,gsWoodCutterPlant); end; else fIssued := False; end; end else case plantAct of taCut: if not gTerrain.CanFindTree(aLoc, gRes.Units[aUnit.UnitType].MiningRange) then ResourceDepleted := True; //No more trees to cut taPlant: if HW.WoodcutterMode = wmPlant then ResourceDepleted := True; //No place for trees to plant end; end; utMiner: if aHome = htCoalMine then begin fIssued := gTerrain.FindOre(aLoc, wtCoal, tmp.Loc); if fIssued then begin Loc := tmp.Loc; ResourcePlan(wtNone,0,wtNone,0,wtCoal); GatheringScript := gsCoalMiner; SubActAdd(haWork1,1); SubActAdd(haWork2,23); SubActAdd(haWork5,1); end else ResourceDepleted := True; end else if aHome = htIronMine then begin fIssued := gTerrain.FindOre(aLoc, wtIronOre, tmp.Loc); if fIssued then begin Loc := tmp.Loc; ResourcePlan(wtNone,0,wtNone,0,wtIronOre); GatheringScript := gsIronMiner; SubActAdd(haWork1,1); SubActAdd(haWork2,24); SubActAdd(haWork5,1); end else ResourceDepleted := True; end else if aHome = htGoldMine then begin fIssued := gTerrain.FindOre(aLoc, wtGoldOre, tmp.Loc); if fIssued then begin Loc := tmp.Loc; ResourcePlan(wtNone,0,wtNone,0,wtGoldOre); GatheringScript := gsGoldMiner; SubActAdd(haWork1,1); SubActAdd(haWork2,24); SubActAdd(haWork5,1); end else ResourceDepleted := True; end; utAnimalBreeder: if aHome = htSwine then begin ResourcePlan(wtCorn,1,wtNone,0,wtPig,wtSkin); GatheringScript := gsSwineBreeder; for I := 0 to 3 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); end; SubActAdd(haWork2,1); fIssued := True; end else if aHome = htStables then begin ResourcePlan(wtCorn,1,wtNone,0,wtHorse); GatheringScript := gsHorseBreeder; SubActAdd(haWork1,1); SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); SubActAdd(haWork5,1); fIssued := True; end; utFarmer: if aHome = htFarm then begin fIssued := gTerrain.FindCornField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, aPlantAct, plantAct, tmp); if fIssued then case plantAct of taCut: begin ResourcePlan(wtNone,0,wtNone,0,wtCorn); WalkStyle(tmp, uaWalkTool,uaWork,6,0,uaWalkBooty,gsFarmerCorn); end; taPlant: WalkStyle(tmp, uaWalk,uaWork1,10,0,uaWalk,gsFarmerSow); else fIssued := False; end; end else if aHome = htVineyard then begin fIssued := gTerrain.FindWineField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, tmp); if fIssued then begin ResourcePlan(wtNone,0,wtNone,0,wtWine); WalkStyle(KMPointDir(tmp.Loc,dirN), uaWalkTool2,uaWork2,5,0,uaWalkBooty2,gsFarmerWine); //The animation for picking grapes is only defined for facing north SubActAdd(haWork1,1); SubActAdd(haWork2,11); SubActAdd(haWork5,1); end; end; utCarpenter: if aHome = htSawmill then begin ResourcePlan(wtTrunk,1,wtNone,0,wtTimber); SubActAdd(haWork1,1); SubActAdd(haWork2,25); SubActAdd(haWork5,1); fIssued := True; end else if (aHome = htArmorWorkshop) and (aProduct = wtLeatherArmor) then begin ResourcePlan(wtLeather,1,wtNone,0,wtLeatherArmor); for I := 0 to 3 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork2,0.25); fIssued := True; end else if (aHome = htArmorWorkshop) and (aProduct = wtWoodenShield) then begin ResourcePlan(wtTimber,1,wtNone,0,wtWoodenShield); for I := 0 to 3 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork2,0.25); fIssued := True; end else if (aHome = htWeaponWorkshop) and (aProduct = wtAxe) then begin ResourcePlan(wtTimber,2,wtNone,0,wtAxe); SubActAdd(haWork1,1); for I := 0 to 2 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork5,1); fIssued := True; end else if (aHome = htWeaponWorkshop) and (aProduct = wtLance) then begin ResourcePlan(wtTimber,2,wtNone,0,wtLance); SubActAdd(haWork1,1); for I := 0 to 2 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork5,1); fIssued := True; end else if (aHome = htWeaponWorkshop) and (aProduct = wtBow) then begin ResourcePlan(wtTimber,2,wtNone,0,wtBow); SubActAdd(haWork1,1); for I := 0 to 2 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork5,1); fIssued := True; end; utBaker: if aHome = htMill then begin ResourcePlan(wtCorn,1,wtNone,0,wtFlour); SubActAdd(haWork2,47); fIssued := True; end else if aHome = htBakery then begin ResourcePlan(wtFlour,1,wtNone,0,wtBread); for I := 0 to 6 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); end; fIssued := True; end; utButcher: if aHome = htTannery then begin ResourcePlan(wtSkin,1,wtNone,0,wtLeather); SubActAdd(haWork1,1); SubActAdd(haWork2,29); fIssued := True; end else if aHome = htButchers then begin ResourcePlan(wtPig,1,wtNone,0,wtSausage); SubActAdd(haWork1,1); for I := 0 to 5 do begin SubActAdd(haWork2,1); SubActAdd(haWork4,1); SubActAdd(haWork3,1); end; fIssued := True; end; utFisher: if aHome = htFishermans then begin fIssued := gTerrain.FindFishWater(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, False, tmp); if fIssued then begin ResourcePlan(wtNone,0,wtNone,0,wtFish); WalkStyle(tmp,uaWalk,uaWork2,10,0,uaWalkTool,gsFisherCatch); end else //We must check again this time ignoring working units since they don't indicate the resource is depleted ResourceDepleted := not gTerrain.FindFishWater(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, True, tmp); end; utStonemason: if aHome = htQuarry then begin fIssued := gTerrain.FindStone(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, False, tmp); if fIssued then begin ResourcePlan(wtNone,0,wtNone,0,wtStone); WalkStyle(tmp, uaWalk,uaWork,8,0,uaWalkTool,gsStoneCutter); SubActAdd(haWork1,1); SubActAdd(haWork2,9); SubActAdd(haWork5,1); end else //We must check again this time ignoring working units since they don't indicate the resource is depleted ResourceDepleted := not gTerrain.FindStone(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, True, tmp); end; utSmith: if (aHome = htArmorSmithy) and (aProduct = wtIronShield) then begin ResourcePlan(wtIron,1,wtCoal,1,wtIronShield); for I := 0 to 3 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork2,1); SubActAdd(haWork5,1); fIssued := True; end else if (aHome = htArmorSmithy) and (aProduct = wtIronArmor) then begin ResourcePlan(wtIron,1,wtCoal,1,wtIronArmor); for I := 0 to 3 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork2,1); SubActAdd(haWork5,1); fIssued := True; end else if (aHome = htWeaponSmithy) and (aProduct = wtSword) then begin ResourcePlan(wtIron,1,wtCoal,1,wtSword); SubActAdd(haWork1,1); for I := 0 to 2 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork5,1); fIssued := True; end else if (aHome = htWeaponSmithy) and (aProduct = wtPike) then begin ResourcePlan(wtIron,1,wtCoal,1,wtPike); SubActAdd(haWork1,1); for I := 0 to 2 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork5,1); fIssued := True; end else if (aHome = htWeaponSmithy) and (aProduct = wtCrossbow) then begin ResourcePlan(wtIron,1,wtCoal,1,wtCrossbow); SubActAdd(haWork1,1); for I := 0 to 2 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork5,1); fIssued := True; end; utMetallurgist: if aHome = htIronSmithy then begin ResourcePlan(wtIronOre,1,wtCoal,1,wtIron); for I := 0 to 3 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); end; SubActAdd(haWork2,1); SubActAdd(haWork3,0.25); fIssued := True; end else if aHome = htMetallurgists then begin ResourcePlan(wtGoldOre,1,wtCoal,1,wtGold); for I := 0 to 3 do begin SubActAdd(haWork2,1); SubActAdd(haWork3,1); SubActAdd(haWork4,1); end; SubActAdd(haWork2,1); SubActAdd(haWork3,0.1); fIssued := True; end; else raise Exception.Create('No work plan for ' + gRes.Units[aUnit.UnitType].GUIName + ' in ' + gRes.Houses[aHome].HouseName); end; end; procedure TKMUnitWorkPlan.Load(LoadStream: TKMemoryStream); var I: Integer; begin LoadStream.CheckMarker('WorkPlan'); LoadStream.Read(fHome, SizeOf(fHome)); LoadStream.Read(fIssued); //public LoadStream.Read(HasToWalk); LoadStream.Read(Loc); LoadStream.Read(ActionWalkTo, SizeOf(ActionWalkTo)); LoadStream.Read(ActionWorkType, SizeOf(ActionWorkType)); LoadStream.Read(WorkCyc); LoadStream.Read(WorkDir); LoadStream.Read(GatheringScript, SizeOf(GatheringScript)); LoadStream.Read(AfterWorkDelay); LoadStream.Read(ActionWalkFrom, SizeOf(ActionWalkFrom)); LoadStream.Read(Resource1, SizeOf(Resource1)); LoadStream.Read(Count1); LoadStream.Read(Resource2, SizeOf(Resource2)); LoadStream.Read(Count2); LoadStream.Read(ActCount); for I := 0 to ActCount - 1 do //Write only assigned begin LoadStream.Read(HouseAct[I].Act, SizeOf(HouseAct[I].Act)); LoadStream.Read(HouseAct[I].TimeToWork); end; LoadStream.Read(Product1, SizeOf(Product1)); LoadStream.Read(ProdCount1); LoadStream.Read(Product2, SizeOf(Product2)); LoadStream.Read(ProdCount2); LoadStream.Read(AfterWorkIdle); LoadStream.Read(ResourceDepleted); end; procedure TKMUnitWorkPlan.Save(SaveStream: TKMemoryStream); var I: Integer; begin SaveStream.PlaceMarker('WorkPlan'); SaveStream.Write(fHome, SizeOf(fHome)); SaveStream.Write(fIssued); //public SaveStream.Write(HasToWalk); SaveStream.Write(Loc); SaveStream.Write(ActionWalkTo, SizeOf(ActionWalkTo)); SaveStream.Write(ActionWorkType, SizeOf(ActionWorkType)); SaveStream.Write(WorkCyc); SaveStream.Write(WorkDir); SaveStream.Write(GatheringScript, SizeOf(GatheringScript)); SaveStream.Write(AfterWorkDelay); SaveStream.Write(ActionWalkFrom, SizeOf(ActionWalkFrom)); SaveStream.Write(Resource1, SizeOf(Resource1)); SaveStream.Write(Count1); SaveStream.Write(Resource2, SizeOf(Resource2)); SaveStream.Write(Count2); SaveStream.Write(ActCount); for I := 0 to ActCount - 1 do //Write only assigned begin SaveStream.Write(HouseAct[I].Act, SizeOf(HouseAct[I].Act)); SaveStream.Write(HouseAct[I].TimeToWork); end; SaveStream.Write(Product1, SizeOf(Product1)); SaveStream.Write(ProdCount1); SaveStream.Write(Product2, SizeOf(Product2)); SaveStream.Write(ProdCount2); SaveStream.Write(AfterWorkIdle); SaveStream.Write(ResourceDepleted); end; end.