📜 ⬆️ ⬇️

Caesar III: game loop

If I were asked what part of the technical implementation of the Caesar game is more interesting to me than others, I would remember about the calculation of one “day” of city life. Separate components of the mathematical model of the city are also interesting in implementation, but these “gears” will spin only as an assembly. Most of the game takes place inside the "game cycle", in which calculations of the parameters of components are carried out, movements of game objects are performed, new events and objects are created. If you are interested to know how the simulation of the city was arranged in one of the best games of 1998 - welcome under cat. The descriptions, pseudocode and schematics will help you to better know the algorithms used



The authors of the game split the steps of one “day” in the city into several steps, the main of which are given below, the simplified code of the function itself can be found under the spoiler:


')
Every 50 ticks starts a new day (16 days in the game month), for which several more functions are calculated that do not require such frequent processing, namely:



Stages of city parameters calculation
1. Calculation of mood:
- gods
- Aboriginal
2. Update the parameters of the invasion of Caesar's troops
3. Calculation of the movement of groups of objects
4. Collection of information on barns
5. Updating data on affordable services for homes
6. Updating warehouse parameters
7. Updating the data of the adviser on population and supply of wheat from Rome
8. Updating the consumption of goods in workshops and materials in mining enterprises
9. Updating the ways to the docks
10. Calculation of production of goods in the workshops
11. Calculation of the availability of the road to Rome
12. Renewal of the population of houses
13. Calculation of the appearance of homeless from overcrowded houses
14. The calculation of the distribution of workers in enterprises, the counting of the unemployed, working enterprises
15. Updating the coverage of fountains and tanks
16. Upgrading access to water for homes
17. Updating the status of groups of objects
18. Calculation of the appearance of citizens from serving buildings
19. Calculation of the appearance of merchants
20. Counting the types and number of buildings in the city, counting the coverage of cultural objects
21. Calculation of the distribution of the treasury of the city between the Senate and the forums
22. The calculation of the decrease of the parameters of culture in homes
23. Calculation of the decrease of services in homes
24. Calculation of the influence of buildings on the desirability of land
25. Upgrading House Levels
26. Deletion of buildings marked for demolition
27. Updating the parameters of burning ruins
28. Updating the status of buildings around fires.
29. Creation of protesting residents
30. Calculation of tax collection parameters
31. Updating the level of entertainment in homes


Stages of city parameters calculation (code)

void gameLoop() { while( game.run ) { gametime.ticks++; switch ( gametime.ticks ) { case 1: calculateGodHappiness(1); case 2: changeBackgroundMusic(); case 3: minimap_redraw = 1; case 4: tick_updateCaesarInvasion(); case 5: tick_updateFormations(0); case 6: tick_checkNativeLand(); case 7: determineRoadNetworkIds(); case 8: gatherGranaryStorageInfo(); case 9: ??? case 10: updateHighestInUseBuildingId(); case 11: ??? case 12: buildingDecayHousesCovered(); case 16: tick_resource_recalculateStock(); case 17: updateAdvisorFoodAndSupplyRomeWheat(); case 18: tick_updateCityInfoWorkshopRawMaterialsStored(); case 19: docksDetermineWaterAccess(); case 20: tick_updateIndustryProduction(); case 21: tick_checkPathingAccessToRome(); case 22: updatePopulationInHouses(); case 23: population(); case 24: evictPeopleFromOvercrowdedHouses(); case 25: calculateWorkersNeededPerCategory(); calculateUnemployment(); setBuildingWorkerPercentage(); setBuildingNumWorkersWater(); setBuildingNumWorkers(); case 27: recalculateReservoirAndFountainAccess(); case 28: gametick_updateHouseWaterAccess(); case 29: updateFormations(1); case 30: minimap_redraw = 1; case 31: generateWalkersForBuildings(); case 32: generateTraders(); case 33: countBuildingTypes(); calculateCultureCoverage(); case 34: distributeTreasuryOverForumsAndSenates(); case 35: decayService_culture(); case 36: determineHousingServicesForEvolve(); case 37: calculateDesirabilityOfBuildings(); calculateDesirabilityOfTerrain(); case 38: calculateBuildingDesirability(); case 39: evolveDevolveHouses(); case 40: clearDeletedBuildings(); case 43: updateBurningRuin(); case 44: updateCrimeFireDamage(); case 45: generateCriminal(); case 46: updateDoubleWheatProduction(); case 47: case 48: decayService_taxCollector(); case 49: gatherEntertainmentInfo(); } if( gametime.ticks >= 50 ) { gametime.ticks = 0; doGameDayTick(); } renderCity(); } } 


The advent of the new day

 void doGameDayTick() { ++gametime.totalDays; ++gametime.day; if ( gametime_day > 15 ) { gametime.day = 0; cityinfo.newcomersThisMonth = 0; ++cityinfo.monthsSinceFestival; monthHandle(); ++gametime.month; if ( gametime_month <= 11 ) { updateRatings(0); } else { startNewYear(); } recordMonthlyPopulation(); holdFestival(); } if ( !gametime.day || gametime.day == 8 ) calculateCityHappinessAndCrime(); } 


New month coming

 void monthHandle() { calculateHealthRate(); handleRandomEvents(); collectMonthlyTaxes(); payMonthlyWages(); payMonthlyInterest(); payMonthlySalary(); housesConsumeMonthlyFood(); handleDistantBattleEvent(); handleInvasionEvent(); checkRequestsEvent(); checkDemandChangesEvent(); checkPriceChangesEvent(); decreaseMonthsLeftToGovernAfterWin(); tickMonth_updateLegionMorale(); playerMessages_updateMessageDelay(); determineGraphicIdsForRoads(); determineGraphicIdsForWater(0, 0, setting_map_width - 1, setting_map_height - 1); calculateOpenGroundCitizen(); sortAndCompactPlayerMessages(); } 


The coming of the new year

 void startNewYear() { gametime.month = 0; handleExpandEmpireEvent(); ++gametime.year; gametick_requestBirthsDeaths_calculateHousingTypes(); copyFinanceTaxesToLastYear(); copyFinanceWagesToLastYear(); copyFinanceImportExportToLastYear(); copyFinanceConstructionToLastYear(); copyFinanceInterestToLastYear(); copyFinanceSalaryToLastYear(); copyFinanceSundriesToLastYear(); calculateAndPayTribute(); resetTradeAmounts(); tick_updateFireSpreadDirection(); updateRatings(1); cityinfo.blessingNeptuneDoubleTradeActive = 0; } 



Religion




At first the Romans were pagans, worshiped by Greek and to a lesser extent Etruscan gods. Later, the mythological period was replaced by a passion for pagan cults. The state, taking on the organization and conduct of rituals, created the official religion, which changed the previous ideas about the gods. Religion in people's lives has always been of great importance, and computer models have not escaped human prejudices; element of chance when choosing a deity.



Calculation of the mood of the gods and the conditions of anger
 void calculateGodHappiness(int includeBlessingsAndCurses) { maxTemples = 0; maxGod = 10; minTemples = 100000; minGod = 10; cityinfo.maxHappinessCeres = pctReligionCoverageCeres; cityinfo.maxHappinessNeptune = pctReligionCoverageNeptune; cityinfo.maxHappinessMercury = pctReligionCoverageMercury; cityinfo.maxHappinessMars = pctReligionCoverageMars; cityinfo.maxHappinessVenus = pctReligionCoverageVenus; for ( i = 0; i < 5; ++i ) { if ( i ) { switch ( i ) { case 1: numTemples = numLargeTemplesNeptune + numSmallTemplesNeptune; break; case 2: numTemples = numLargeTemplesMercury + numSmallTemplesMercury; break; case 3: numTemples = numLargeTemplesMars + numSmallTemplesMars; break; case 4: numTemples = numLargeTemplesVenus + numSmallTemplesVenus; break; } } else { numTemples = numLargeTemplesCeres + numSmallTemplesCeres; } if ( numTemples >= maxTemples ) { if ( numTemples == maxTemples ) maxGod = 10; else maxGod = i + 1; maxTemples = numTemples; } if ( numTemples <= minTemples ) { if ( numTemples == minTemples ) minGod = 10; else minGod = i + 1; minTemples = numTemples; } } for ( j = 0; j < 5; ++j ) { monthsGodSinceFestival = cityinfo.monthsGodSinceFestival[j]; if ( monthsGodSinceFestival > 40 ) monthsGodSinceFestival = 40; cityinfo.maxGodHappiness[j] += 12; cityinfo.maxGodHappiness[j] -= monthsGodSinceFestival; } if( maxGod ) { if( maxGod < 5 ) { if ( cityinfo.monthsGodSinceFestival[maxGod + 3] >= 50 ) cityinfo.monthsGodSinceFestival[maxGod + 3] = 100; else cityinfo.monthsGodSinceFestival[maxGod + 3] += 50; } } if ( minGod ) { if ( minGod < 5 ) cityinfo.monthsGodSinceFestival[minGod + 3] -= 25; } if ( cityinfo.population >= 100 ) { if ( cityinfo.population >= 200 ) { if ( cityinfo.population >= 300 ) { if ( cityinfo.population >= 400 ) { if ( cityinfo.population >= 500 ) min = 0; else min = 10; } else { min = 20; } } else { min = 30; } } else { min = 40; } } else { min = 50; } for ( k = 0; k < 5; ++k ) { if( cityinfo.maxGodHappiness[k] > 100 ) cityinfo.maxGodHappiness[k] = 100; if( cityinfo.maxHappinessCeres[k] < min ) cityinfo.maxGodHappiness[k] = min; } if ( includeBlessingsAndCurses ) { for ( l = 0; l < 5; ++l ) { if ( cityinfo.godHappiness[l] <= cityinfo.maxGodHappiness[l] ) { if ( cityinfo.godHappiness[l] < cityinfo.maxGodHappiness[l] ) ++cityinfo.godHappiness[l]; } else { --cityinfo.godHappiness[l]; } } for ( m = 0; m < 5; ++m ) { if( cityinfo.godHappiness[m] > 50 ) cityinfo.godSmallCurseDone[m] = 0; if ( cityinfo.godHappiness[m] < 50 ) cityinfo.godBlessingDone[m] = 0; } god = random_7f_1 & 7; if ( god <= 4 ) { if ( cityinfo.godHappiness[god] < 50 ) { if ( cityinfo.godHappiness[god] < 40 ) { if ( cityinfo.godHappiness[god] < 20 ) { if ( cityinfo.godHappiness[god] < 10 ) cityinfo.numBoltsGod[god] += 5; else cityinfo.numBoltsGod[god] += 2; } else { ++cityinfo.numBoltsGod[god]; } } } else { cityinfo.numBoltsGod[god] = 0; } if ( cityinfo.numBoltsGod[god] >= 50 ) cityinfo.numBoltsGod[god] = 50; } if ( !gametime.day ) { for ( n = 0; n < 5; ++n ) ++cityinfo.monthsGodSinceFestival[n]; if ( god > 4 ) { if( determineAngriestGod() ) god = cityinfo.religionAngryGod - 1; } if ( setting.godsOn ) { if ( god <= 4 ) { if( cityinfo.godHappiness[god] < 100 || cityinfo.godBlessingDone[god] ) { if ( cityinfo.numBoltsGod[god] < 20 || cityinfo.godSmallCurseDone[god] || cityinfo.monthsGodSinceFestival[god] <= 3 ) { if ( cityinfo.numBoltsGod[god] >= 50 && cityinfo.monthsGodsSinceFestival[ god ] > 3 ) { cityinfo.numBoltsGod[god] = 0; cityinfo.godHappiness[god] += 30; message.usePopup = 1; if ( god ) // large curse { switch ( god ) { case God_Neptune: if ( cityinfo.numOpenSeaTradeRoutes <= 0 ) { postMessageToPlayer(42, 0, 0); return; } postMessageToPlayer(81, 0, 0); neptuneSinkAllShips(); cityinfo.seaTradeProblemDuration = 80; cityinfo.godCurseNeptuneSankShips= 1; break; case God_Mercury: postMessageToPlayer(43, 0, 0); removeGoodsFromStorageForMercury(1); break; case God_Mars: if ( largeCurseMarsCurseFort() ) { postMessageToPlayer(82, 0, 0); startLocalUprisingFromMars(); } else { postMessageToPlayer(44, 0, 0); } break; case God_Venus: postMessageToPlayer(45, 0, 0); setCrimeRiskForAllHouses(40); increaseSentiment(-10); if( cityinfo.healthRate < 80 ) { if ( cityinfo.healthRate < 60 ) changeHealthRate(-20); else changeHealthRate(-40); } else { changeHealthRate(-50); } cityinfo.godCurseVenusActive = 1; alculateCityHappinessAndCrime(); break; } } else { postMessageToPlayer(41, 0, 0); ceresWitherCrops(1); } } } else { // small curse cityinfo.godSmallCurseDone[ god] = 1; cityinfo.numBoltsCeres[god] = 0; cityinfo.godHappiness[god] += 12; message.usePopup = 1; if ( god ) { switch ( god ) { case God_Neptune: postMessageToPlayer(92, 0, 0); neptuneSinkAllShips(); cityinfo.godCurseNeptuneSankShips = 1; break; case God_Mercury: postMessageToPlayer(93, 0, 0); removeGoodsFromStorageForMercury(0); break; case God_Mars: if ( startLocalUprisingFromMars() ) postMessageToPlayer(94, 0, 0); else postMessageToPlayer(44, 0, 0); break; case God_Venus: postMessageToPlayer(95, 0, 0); setCrimeRiskForAllHouses(50); increaseSentiment(-5); hangeHealthRate(-10); calculateCityHappinessAndCrime(); break; } } else { postMessageToPlayer(91, 0, 0); ceresWitherCrops(0); } } } else { cityinfo.godBlessingDone[god] = 1; message_usePopup = 1; if ( god ) { switch ( god ) { case God_Neptune: postMessageToPlayer(97, 0, 0); cityinfo.blessingNeptuneDoubleTradeActive = 1; break; case God_Mercury: postMessageToPlayer(98, 0, 0); smallBlessingMercuryFillGranary(); break; case God_Mars: postMessageToPlayer(99, 0, 0); cityinfo_blessingMarsEnemiesToKill = 10; break; case God_Venus: postMessageToPlayer(100, 0, 0); increaseSentiment(25); break; } } else // ceres { postMessageToPlayer(96, 0, 0); ceresBlessing(); } } } minHappiness = 100; for ( ii = 0; ii < 5; ++ii ) { if ( cityinfo.godHappiness[ii] < minHappiness ) minHappiness = cityinfo.godHappiness[ii]; } if ( cityinfo.godAngryMessageDelay ) { --cityinfo_godAngryMessageDelay; } else { if ( minHappiness < 30 ) { cityinfo.godAngryMessageDelay = 20; if ( minHappiness >= 10 ) postMessageToPlayer(55, 0, 0); else postMessageToPlayer(101, 0, 0); } } } } } } 



Moods in the city




Residents themselves respond to the wage differences between the city and Rome, the variety of products in the city, the level of taxes and the number of slums. This parameter is stored for each house and does not change regardless of the neighboring houses.



Calculation of mood and degree of migration in the city
 void calculateCityHappinessAndCrime() { totalPop = calculatePeopleInHousingTypes(); if ( totalPop < cityinfo.population ) removePeopleFromCensus(ciid, cityinfo.population - totalPop); sentimentContributionTents = 0; sentimentContributionFood = 0; sentimentContributionWages = 0; sentimentContributionTaxes = taxrate_happiness_factor[ cityinfo.taxpercentage ]; diffWage = cityinfo.wages - cityinfo.wagesRome; switch( diffWage ) { >= 7: sentimentContributionWages = 4; >= 4: sentimentContributionWages = 3; > 1: sentimentContributionWages = 2; == 1: sentimentContributionWages = 1; } if ( diffWage < 0 ) { sentimentContributionWages = -diffWage / 2; } switch( cityinfo.unemploymentPercentage ) { > 25: sentimentContributionEmployment = -3; > 17: sentimentContributionEmployment = -2; > 10: sentimentContributionEmployment = -1; < 5: sentimentContributionEmployment = 1; } if( cityinfo.populationSentiment_includeTents > 0 ) { tentPenaltyIfLessTents = getHappinessPenaltyForTentDwellers(); cityinfo.populationSentiment_includeTents = 0; } else { tentPenaltyIfLessTents = 0; cityinfo.populationSentiment_includeTents = 1; } housesNeedingFood = 0; housesCalculated = 0; totalSentimentContributionFood = 0; totalTentPenalty = 0; for( building in city.buildings ) { if ( building.inUse == 1 ) { if ( building.houseSize ) { if ( building.house_population ) { if ( cityinfo.population >= 300 ) { building.house_happiness += sentimentContributionTaxes; building.house_happiness += sentimentContributionWages; building.house_happiness += sentimentContributionEmployment; ++housesCalculated; sentimentContributionFood = 0; sentimentContributionTents = 0; if ( model.houses_foodtypes[ building.level ] > 0 ) // needs food: >= shack { ++housesNeedingFood; sentimentContributionFood = building.houseNumFoods - building.houseHaveFoods; ++totalSentimentContributionFood; } else // tent dwellers { sentimentContributionTents = tentPenaltyIfLessTents; totalTentPenalty += tentPenaltyIfLessTents; } building.house_happiness += sentimentContributionFood; building.house_happiness += sentimentContributionTents; } else { sentimentContributionFood = 0; sentimentContributionEmployment = 0; sentimentContributionTaxes = 0; sentimentContributionWages = 0; sentimentContributionTents = 0; if ( cityinfo.population >= 200 ) building.house_happiness = 50; else building.house_happiness = 60; } } else { building.house_happiness = 60; } } } } if ( housesNeedingFood ) sentimentContributionFood = totalSentimentContributionFood / housesNeedingFood; if ( housesCalculated ) sentimentContributionTents = totalTentPenalty / housesCalculated; totalHappiness = 0; totalHouses = 0; for ( building in city.buildings ) { if( building.inUse == 1 && building.houseSize && building.house_population ) { ++totalHouses; totalHappiness += building.happiness; } } if ( totalHouses > 0 ) cityinfo.citySentiment = totalHappiness / totalHouses; else cityinfo.citySentiment = 60; cityinfo.emigrationCause = 0; worstSentiment = 0; if( sentimentContributionFood < 0 ) { worstSentiment = sentimentContributionFood; cityinfo.emigrationCause = 1; } if ( sentimentContributionEmployment < worstSentiment ) { worstSentiment = sentimentContributionEmployment; cityinfo.emigrationCause = 2; } if ( sentimentContributionTaxes < worstSentiment ) { worstSentiment = sentimentContributionTaxes; cityinfo.emigrationCause = 3; } if ( sentimentContributionWages < worstSentiment ) { worstSentiment = sentimentContributionWages; cityinfo.emigrationCause = 4; } if ( sentimentContributionTents < worstSentiment ) cityinfo.emigrationCause = 5; cityinfo.citySentimentLastTime = cityinfo_citySentiment; } 



Festivals





A significant increase in the mood in the city gives only the first festival for the full 12 months, the second and subsequent only half. This was done so that in a rich city it was not possible to cheer up only through festivals. Preparation for the festival itself also takes some time, which imposes a limit on the number of festivals held during the year.



Calculation of the influence of the festival on the mood in the city
 void holdFestival() { --cityinfo.monthsSinceFirstFestival; --cityinfo.monthsSinceSecondFestival; if ( cityinfo.plannedFestival_size <= 0 ) return; --cityinfo.plannedFestival_monthsToGo; if( cityinfo.plannedFestival_monthsToGo > 0 ) return; if ( cityinfo.monthsSinceFirstFestival > 0 ) { if ( cityinfo.monthsSinceSecondFestival <= 0 ) { cityinfo.monthsSinceSecondFestival = 12; switch ( cityinfo.plannedFestival_size ) { case smallFestival: increaseSentiment(2); break; case middleFestival: increaseSentiment(3); break; case bigFestival: increaseSentiment(5); break; } } } else { cityinfo.monthsSinceFirstFestival = 12; switch ( cityinf._plannedFestival_size ) { case smallFestival: increaseSentiment(7); break; case middleFestival: increaseSentiment(9); break; case bigFestival: increaseSentiment(12); break; } } cityinfo.monthsSinceFestival = 1; switch ( cityinfo.plannedFestival_size ) { case smallFestival: postMessageToPlayer(38, 0, 0); break; case middleFestival: postMessageToPlayer(39, 0, 0); break; case bigFestival: postMessageToPlayer(40, 0, 0); break; } cityinfo.plannedFestival_size = 0; cityinfo.plannedFestival_monthsToGo = 0; } 



Paying tribute to the emperor





Payment of tribute to the emperor. The amount of money that at the end of the year is required to pay from the treasury
depends on the profit of the city and the number of people living. The first factor means paying a quarter
money received during the year, but no less than a certain amount, which depends on the current population. If the city
can not pay this money, the ruler pays a decrease in the favor of the emperor, and the past
years of non-payment, so that dissatisfaction accumulates with prolonged non-payment of tribute.



Calculation of the imperial quarter for the last year
 void calculateAndPayTribute() { cityinfo.finance_donated_lastyear = cityinfo.finance_donated_thisyear; cityinfo.finance_donated_thisyear = 0; cityinfo.tributeNotPaid = 0; income = cityinfo.finance_donated_lastyear + cityinfo.finance_taxes_lastyear + cityinfo.finance_exports_lastyear expenses = cityinfo.finance_sundries_lastyear + cityinfo.finance_salary_lastyear + cityinfo.finance_interest_lastyear + cityinfo.finance_construction_lastyear + cityinfo.finance_wages_lastyear + cityinfo.finance_imports_lastyear if ( cityinfo.treasury > 0 ) { switch( cityinfo.population ) { > 5000: cityinfo.finance_tribute_lastyear = 500; > 3000: cityinfo.finance_tribute_lastyear = 400; > 2000: cityinfo.finance_tribute_lastyear = 300; > 1001: cityinfo.finance_tribute_lastyear = 225; > 501: cityinfo.finance_tribute_lastyear = 150; > 0: cityinfo.finance_tribute_lastyear = 50; } if ( income > expenses ) { cityinfo.tributeNotPaidYears = 0; realTribute = adjustWithPercentage(income - expenses, 25); if ( realTribute > cityinfo.finance_tribute_lastyear ) cityinfo.finance_tribute_lastyear = realTribute; } } else { cityinfo.tributeNotPaid = 1; ++cityinfo.tributeNotPaidYears; cityinfo.finance_tribute_lastyear = 0; } cityinfo.treasury -= cityinfo.finance_tribute_lastyear; expenses += cityinfo.finance_tribute_lastyear; calculateTributeThisYear(); cityinfo.finance_balance_lastyear = cityinfo.treasury; cityinfo.finance_totalIncome_lastyear = income; cityinfo.finance_totalExpenses_lastyear = expenses; } 



Random events




Random events. There are 7 random events available in the game, a set of flags that can be changed in the mission editor is responsible for their occurrence. A random event occurs if the generator has thrown its type and it is allowed in the script. The developers made the following types: a reduction / increase in wages in Rome, problems with the sale of sea or land, poisoning wells, the collapse of mines and flooding of quarries and clay pits. An earthquake is set in the editor, it has time and a point of origin, it spreads randomly in four directions.

Random events
 void handleRandomEvents() { event = randomEvent.probability[random_7f_1]; if ( event > 0 ) { switch ( event ) { case 1: if ( scn_event_raiseWages ) { if ( cityinfo.wagesRome < 45 ) { cityinfo.wagesRome += (random_7f_2 & 3) + 1; if ( cityinfo.wagesRome > 45 ) cityinfo.wagesRome = 45; message_usePopup = 1; postMessageToPlayer(68, 0, 0); } } break; case 2: if ( scn_event_lowerWages ) { if ( cityinfo.wagesRome > 5 ) { cityinfo.wagesRome -= (random_7f_2 & 3) + 1; message_usePopup = 1; postMessageToPlayer(69, 0, 0); } } break; case 3: if ( scn_event_landTradeProblem ) { if ( cityinfo.numOpenLandTradeRoutes > 0 ) { cityinfo.landTradeProblemDuration = 48; message_usePopup = 1; if ( scn_climate == Climate_Desert ) postMessageToPlayer(65, 0, 0); else postMessageToPlayer(67, 0, 0); } } break; case 4: if ( scn_event_seaTradeProblem ) { if ( cityinfo.numOpenSeaTradeRoutes > 0 ) { cityinfo.seaTradeProblemDuration = 48; message_usePopup = 1; postMessageToPlayer(66, 0, 0); } } break; case 5: if ( scn_event_contaminatedWater ) { if ( cityinfo.population > 200 ) { if ( cityinfo.healthRate <= 80 ) { if ( cityinfo.healthRate <= 60 ) changeHealthRate(-25); else changeHealthRate(-40); } else { changeHealthRate(-50); } message_usePopup = 1; postMessageToPlayer(70, 0, 0); } } break; case 6: if ( scn_event_ironMineCollapse ) { gridOffsetIronmine = destroyFirstBuildingOfType(B_IronMine); if ( gridOffsetIronmine ) { message_usePopup = 1; postMessageToPlayer(71, 0, gridOffsetIronmine); } } break; case 7: if ( scn_event_clayPitFlooded ) { gridOffsetClaypit = destroyFirstBuildingOfType(B_ClayPit); if ( gridOffsetClaypit ) { message_usePopup = 1; postMessageToPlayer(72, 0, gridOffsetClaypit); } } break; } } } 



Public health




Practicing doctors appeared relatively late in Rome. Up to the II c. BC Oe., and in the low-income strata of society and much later the Romans were treated by the wise with the life experience of their relatives by simple means that were passed down from generation to generation. This traditional medicine was not alien to primitive magic. Agriculture guidelines were compiled, with a number of instructions on how to treat people and animals, clearly following folk remedies.
In the game, clinics and hospitals provide the same services, but hospitals are also needed for high-level homes for continued growth. The health of the inhabitants is one of the main indicators of the prosperity of the city: epidemics can mow entire neighborhoods, and with an increase in the city’s population, the number of people who died during the epidemic will only grow.



Calculation of health and the probability of an epidemic in the city
 void calculateHealthRate() { population = 0; populationWithDoctors = 0; if ( cityinfo.population < 200 ) { cityinfo.healthRate = 50; cityinfo.calculatedTargetHealthRate = 50; return; } for( building in city.buildings ) { if ( building.inUse == 1 && building.houseSize > 0 && building.house_population > 0 ) { population += building.house_population; if ( building.hasClinicService ) populationWithDoctors += building.house_population; else populationWithDoctors += building.house_population / 4; } } cityinfo.calculatedTargetHealthRate = getPercentage(populationWithDoctors, population); cityinfo.healthRate += sign( cityinfo.healthRate - cityinfo.calculatedTargetHealthRate ) * 2; cityinfo.healthRate = bound( 0, cityinfo.healthRate, 100 ); if ( cityinfo.healthRate >= 40 ) return; pandemicChance = 40 - cityinfo.healthRate; goodHealthPeople = random_7f_1 & 0x3F; if ( cityinfo.godCurseVenusActive ) goodHealthPeople = 0; cityinfo.godCurseVenusActive = 0; if ( goodHealthPeople > pandemicChance ) return; howPeopleCanDie = adjustWithPercentage(populationWithDoctors, (random_7f_1 & 3) + 7); if ( howPeopleCanDie > 0 ) { howPeopleCanDie = howPeopleCanDie - cityinfo.numHospitalWorkers; changeHealthRate(10); if( howPeopleCanDie > 0 ) { if ( cityinfo.numHospitalWorkers > 0 ) postMessageToPlayer(103, 0, 0); else postMessageToPlayer(104, 0, 0); for( building in city.buildings ) { if ( building.inUse == 1 && building.houseSize > 0 && building.house_population > 0 && !building.hasClinicService ) { howPeopleCanDie -= building.house_population; collapseBuildingOnFire(j, 1); if ( howPeopleCanDie <= 0 ) return; } } } else { postMessageToPlayer(102, 0, 0); } } } 



Tax collection





At first, the city lives at the expense of incoming taxes, although they are quite modest. The tax collector, as well as, for example, a doctor from the clinic, should pass near residential buildings at regular intervals. If the apartment house is visited without fail by tax collectors, it will pay tax every month.

How much a house will pay per month depends on:

- interest rate set by the financial adviser (regulated from 0 to 25%);
- the number of people living in the house at the time of tax collection (i.e., when the month changes);
- the level of development of the house (in the game file “c3_model.txt”, data on residential buildings, the 20th column - this number is a 200 percent tax per month per person living in this level of development of a residential building).

After analyzing the function, we can conclude that a smooth increase in taxes is no different from a quick change. Taxes affect the mood of your people, but it makes no sense to raise the wages of their workers by more than 8 units, in relation to the wages that Rome pays.

What is a 200 percent tax is (probably) the desire of developers to save on a reduction operation to a smaller whole, in this block:

  collectedPatricians = adjustWithPercentage( cityinfo.monthlyCollectedTaxFromPatricians / 2, cityinfo.taxpercentage ); 


the code shows that the collected taxes are divided by 2. In order not to get a situation where the house can pay more than the tax was imposed * 2, and in the calculations we will always get a value less than or equal to the correct one.

At higher levels of the city’s development, it can only live on taxes:





Calculation of taxes received during the month
 void __cdecl fun_collectMonthlyTaxes() { cityinfo.numPlebsTaxed = 0; cityinfo.numPatriciansTaxed = 0; cityinfo.numPlebsNotTaxed = 0; cityinfo.numPatriciansNotTaxed = 0; cityinfo.monthlyUncollectedTaxFromPlebs = 0; cityinfo.monthlyCollectedTaxFromPlebs = 0; cityinfo.monthlyUncollectedTaxFromPatricians = 0; cityinfo.monthlyCollectedTaxFromPatricians = 0; for ( i = 0; i < 20; ++i ) cityinfo.societyGraph[ i ] = 0; for ( house in city.houses ) { isPatrician = house.level >= 12; trm = adjustWithPercentage( model_houses.tax[ house.level ], difficulty.moneypct[setting.difficulty] ); cityinfo.societyGraph[ house.level ] += house.population; if (house.taxcollector > 0 ) { if ( isPatrician ) cityinfo.numPatriciansTaxed += house.population; else cityinfo.numPlebsTaxed += house.population; tax = house.population * trm; house.taxIncomeThisYear += tax; if ( isPatrician ) cityinfo.monthlyCollectedTaxFromPatricians += tax; else cityinfo.monthlyCollectedTaxFromPlebs += tax; } else { if ( isPatrician ) cityinfo.numPatriciansNotTaxed += house.population; else cityinfo.numPlebsNotTaxed += house.population; if ( isPatrician ) cityinfo.monthlyUncollectedTaxFromPatricians += house.population * trm; else cityinfo.monthlyUncollectedTaxFromPlebs += house.population * trm; } } collectedPatricians = adjustWithPercentage( cityinfo.monthlyCollectedTaxFromPatricians / 2, cityinfo.taxpercentage ); cityinfo.yearlyTaxFromPatricians += collectedPatricians; collectedPatricians2 = collectedPatricians; collectedPlebs = adjustWithPercentage( cityinfo.monthlyCollectedTaxFromPlebs / 2, cityinfo.taxpercentage ); cityinfo.yearlyTaxFromPlebs += collectedPlebs; totalCollectedTax = collectedPlebs + collectedPatricians2; cityinfo.yearlyUncollectedTaxFromPatricians += adjustWithPercentage( cityinfo.monthlyUncollectedTaxFromPatricians/ 2, cityinfo.taxpercentage); cityinfo.yearlyUncollectedTaxFromPlebs += adjustWithPercentage( cityinfo.monthlyUncollectedTaxFromPlebs / 2, cityinfo.taxpercentage); cityinfo.treasury += totalCollectedTax; cityinfo.percentagePlebsRegisteredForTax = getPercentage( cityinfo.numPlebsTaxed, cityinfo.numPlebsNotTaxed + cityinfo.numPlebsTaxed ); cityinfo.percentagePatriciansRegisteredForTax = getPercentage( cityinfo.numPatriciansTaxed, cityinfo.numPatriciansNotTaxed + cityinfo.numPatriciansTaxed ); cityinfo.percentageRegisteredForTax = getPercentage( cityinfo.numPlebsTaxed + cityinfo_numPatriciansTaxed, cityinfo.numPlebsNotTaxed + cityinfo.numPlebsTaxed + cityinfo.numPatriciansNotTaxed + cityinfo.numPatriciansTaxed ); } 



Food consumption





People eat X quantities of food, no matter how many kinds of food they have. The amount of food eaten depends only on the number of people living in the house (10 people eat 5 units of food per month) For example: there is a dwelling house of the 20th level, fully populated, that is, 200 people live there they need 3 types of food. In a month they will eat up the total amount of food equal to 200/10 * 5 = 100 units, these 100 units will be distributed between the three necessary types of food, probably equally, that is, 100/3 = 33.

Calculate consumption for homes
 void housesConsumeMonthlyFood() { gatherFoodInformation(); cityinfo.foodTypesEaten = 0; totalConsumed = 0; for ( building in city.houses ) { numTypes = model_houses.foodtypes[ building.level ]; foodToConsumePerType = adjustWithPercentage( building.population, 50); if ( numTypes > 1 ) foodToConsumePerType /= numTypes; building.houseNumFoods = 0; if ( scn_romeSuppliesWheat ) { cityinfo.foodTypesEaten = 1; cityinfo.foodTypesAvailable = 1; building.foodstocks[0] = foodToConsumePerType; building.houseNumFoods = 1; } else { if ( numTypes > 0 ) { for ( j = 0; ; ++j ) { if ( j < 4 ) { if (building.foodstocks[j] < foodToConsumePerType ) { if ( building.foodstocks[j] ) { building.foodstocks[j] = 0; ++building.houseNumFoods; totalConsumed += foodToConsumePerType; } } else { building.foodstocks[j] -= foodToConsumePerType; ++building.houseNumFoods; totalConsumed += foodToConsumePerType; } if ( building.houseNumFoods > cityinfo.foodTypesEaten ) cityinfo.foodTypesEaten = building.houseNumFoods; if ( building.houseNumFoods < numTypes ) continue; } break; } } } } cityinfo.foodConsumedLastMonth = totalConsumed; cityinfo_foodStoredLastMonth = cityinfo_foodStoredSoFarThisMonth; cityinfo_foodStoredSoFarThisMonth = 0; } 



Production of goods




Sometimes the city needs to import materials to produce goods. Eight workshops work stably with one warehouse, two more intermittently: imports, depends on the number of workshops and on the number of materials that a trading partner can provide. Exports can be adjusted numerically, and raw materials are imported in proportion to the demand of the workshops. Therefore, with a small number of workshops, the warehouse simply will not buy raw materials in reserve.

Welfare rating calculation




«» , , — 2 , .. 50 25 , .



 void updateProsperityRating() { labor = 0; if ( cityinfo.unemploymentPercentage >= 5 ) { if ( cityinfo.unemploymentPercentage >= 15 ) labor = -1; // -1 Unemployment rate is above 15% } else { labor = 1; // +1 Less than 5% unemployment } if ( cityinfo.finance_construction_lastyear + cityinfo.treasury <= cityinfo.treasury_lastyear_prosperity ) increase = labor - 1; // -1 Losing money else increase = labor + 5; // +5 Making a profit cityinfo.treasury_lastyear_prosperity = cityinfo.treasury; if ( cityinfo.foodTypesEaten >= 2 )// == grand insula or better ++increase; // +1 There is at least one Grand Insula or better avgWage = cityinfo.wageRatePaid_lastYear / 12; if ( avgWage <= cityinfo.wagesRome + 1 ) { if ( avgWage < cityinfo.wagesRome ) --increase; // -1 Your wages are below Rome's } else { ++increase; // You pay at least 2 Dn more than Rome's wage } poor = getPercentage(cityinfo_peopleInTentsAndShacks, cityinfo_population); rich = getPercentage(cityinfo_peopleInVillasAndPalaces, cityinfo_population); if ( poor > 30 ) --increase; if ( rich > 10 ) ++increase; // +1 10% or more of your population lives in villas if ( cityinfo.tributeNotPaid ) --increase; if ( cityinfo_hippodromeShows > 0 ) ++increase; // +1 Active Hippodrome cityinfo_prosperityRating += increase; if ( cityinfo.prosperityRating > cityinfo.maxProsperity ) cityinfo.prosperityRating = cityinfo.maxProsperity; if ( cityinfo.prosperityRating < 0 ) cityinfo.prosperityRating = 0; if ( cityinfo.prosperityRating > 100 ) cityinfo.prosperityRating = 100; setProsperityRatingExplanation(); } 







Caesar III , , , , . , 2000, (, , , ) 50. - 32, . , .

(Walker)
 struct Walker { int gridOffset; //   (y * mapWidth + x) char inUse; //   short nextIdOnSameTile; //     unsigned char actionState; //   (, , , ) int tradeCityId; //  ,     int direction; //  int buildingId; // ,    unsigned char y; //   unsigned char x; unsigned char byte_7FA360; //dst_x ??? unsigned char byte_7FA361; //dst_y ??? int progressOnTile; //   ,     int tilePosition_y; //    int tilePosition_x; int destination_x; //  int destination_y; WalkerType type; //  int word_7FA344; char byte_7FA34C; char speed; //  char byte_7FA3A6; int state; //   short baseWorkingBuildingId; //   ,   short formationId; // ,     short word_7FA346; char byte_7FA39B; short word_7FA366; short tradeCaravanNextId; //    ,        short itemCollecting; char byte_7FA341; short migrantDestinationHome; // ,    short word_7FA374; short destinationpathId; // ,    ,    char byte_7FA376; char lastDirection; //   short word_7FA3B0; short wlk_ID_mm; short word_7FA3B4; short word_7FA3B6; short word_7FA372; short word_7FA35E; char cartPusherGoodType; //    char byte_7FA39C; char byte_7FA39D; char byte_7FA393; char reachedLastStep; //   (  0/1) char maxLevelOrRiskSeen; //  ,      (0\1) char byte_7FA3B8; char byte_7FA342; char byte_7FA3A5; char byte_7FA3A2; char isBoat; //  char byte_7FA34D; char byte_7FA39F; char byte_7FA3A7; char byte_7FA3A9; short word_7FA384; short wlk_ID_pp; //   ,   ,      char migrantNumPeopleCarried; //     char mood; /// char byte_7FA389; char byte_7FA3A3; char byte_7FA370; char ruler; //  ,        char simpleDirection; //      (0 - , 1 -   ) char byte_7FA39A; char byte_7FA3B9; char at_dest_x; //     char at_dest_y; short word_7FA3BA; short word_7FA3BC; char prevActionState; //   short destinationPathCurrent; //    }; 


(Building)
 struct Building { BuildingType type; //  int storageId; //  (, , ) int x; //   int y; unsigned char inUse; //  int house_crimeRisk; //  ,   int house_size; // ()    int house_population; //()  int walkerServiceAccess; //   (0-100) int laborCategory; //  (,   ) int word_94BDAC[2]; char byte_94BDB8; int level_resourceId; //     () int grow_value_house_foodstocks[8]; //()   short house_roomForPeople; //()    short haveRomeroad; //   short house_maxPopEver; //()   short noContactWithRome; //     char enter_x; //  char enter_y; short walkerId; // /      short laborSeekerId; // ,     short immigrantId; // ()  ,      short towerBallistaId; // ()  ,     char walkerSpawnDelay; // ()     char byte_94BD6C; char hasFountain; //       char waterDep; // (, )      short warehouse_prevStorage; //()   short warehouse_nextStorage; //()   ( ) short industry_unitsStored; // ()     char house_hasWell; // ()    short num_workers; // ()    short fireRisk; //  short damageRisk; //   short industry_outputGood; // ()     short house_theater_amphi_wine; //    short house_amphiGlad_colo; //()    short house_coloLion_hippo; // ()    short house_school_library; //()  / short house_academy_barber; // ()   / short granary_capacity[4]; //  -   short house_wheat; // ()   short gridOffset; //     ( ) short wharf_hasBoat_house_evolveStatusDesir; //  -    /  -   short house_pottery; // ()   short house_oil; // ()   short house_furniture; // ()   short house_wine; // ()   short house_vegetables; // ()   short size; //    short formationId; //  ,      short placedSequenceNumber; //     (, ) char byte_always0; //??? short cityId; // ,     (   2) short workersEffectivity; //    short burningRuinStep; //     char house_bathhouse_dock_numships_entert_days; char byte_94BDBB; char haveProblems; //    char house_entertainment; // ()   char house_numGods; // ()   char house_education; //   char house_clinic; // ()   char house_hospital_entert_days2; //()       char house_mercury; //()    char house_neptune; char house_mars; char house_venus; char byte_94BDB9; char hasRoadAccess; //    char haveRoadnet; //   char house_isMerged; //     char desirability; //  (-50  100) char adjacentToWater; //    char byte_94BD84; char byte_94BD85; char house_health; //   char house_ceres; // char house_taxcollector; //    char byte_94BD7D; }; 


(EmpireObject)
 struct EmpireObject { char inUse; //   char type; // (, , , ) char currentAnimationIndex; //  __int16 xCoord; //   __int16 yCoord; __int16 width; __int16 height; __int16 graphicID; //  __int16 graphicID_exp; ///  char distBattleTravelMonths; // ( )         __int16 xCoord_exp; //    __int16 yCoord_exp; char cityType; // , , ,  char cityNameId; //  char tradeRouteId; //      char tradeRouteOpen; //  __int16 tradeCostToOpen[10]; char citySells[16]; //    char ownerCityIndex; //,     char f990D29[10]; char cityBuys[16]; //    char invasionPathId; //  char invasionYears; //   ,      __int16 trade40; __int16 trade25; __int16 trade15; }; 


(TradeRoute)
 struct TradeRoute { char inUse; //  char cityType; //  char cityNameId; //  char routeId[16]; char isOpen; //  char buysFlag[16]; //  char sellsFlag[16]; //  char sellsFlag_wine; //     __int16 costToOpen; //   __int16 unknown10; __int16 walkerEntryDelay; //    __int16 unknown0; __int16 empireObjectId; //  char isSeaTrade; //  ,      __int16 walkerId1; //  __int16 walkerId2; __int16 walkerId3; int quotas[16]; //   }; 


 Walker walkers[1000]; //     Building buildings[2000]; //    Formation formations[50]; //  EmpireObject empireObjects[100]; //    ModelHouse model_houses[20]; //  Storage storages[200]; //    TradeRoute tradeRoutes[200]; //  CityInfo city_inform[8]; //   2 (     ) 



Thanks


« » , IndieGoGo.com . .

Caesar III, , v0.4 . .

,


SkidanovAlex , Ununtrium , Bick indiegogo.
MennyCalavera
1 2 .

. .



PS Bianca van Schaik, .

Source: https://habr.com/ru/post/239013/


All Articles