シーザーIII:ゲームループ

Caesarゲームの技術的実装のどの部分が他の部分よりも私にとって興味があるかと聞かれたら、都市生活の1日の計算を思い出します。 都市の数学的モデルの個々のコンポーネントも実装に興味がありますが、これらの「ギア」はアセンブリでのみ回転します。 ほとんどのゲームは「ゲームサイクル」内で行われ、コンポーネントパラメータが計算され、ゲームオブジェクトが移動し、新しいイベントとオブジェクトが作成されます。 1998年の最高のゲームの1つに都市シミュレーションがどのように配置されたかを学びたい場合は、Catへようこそ 説明、擬似コード、およびスキーマは、使用されているアルゴリズムをよりよく理解するのに役立ちます。







ゲームの作成者は、市内の1つの「日」の計算をいくつかのステップに分割しましたが、その主なものを以下に示します。関数自体の簡単なコードは、ネタバレの下にあります。







新しい日が始まる50ティックごと(ゲーム月の16日間)に、このような頻繁な処理を必要としない、さらにいくつかの関数が計算されます。







都市パラメーターの計算の段階
1.ムードの計算:

-神々

-ネイティブ

2.シーザー軍の侵攻のパラメーターを更新する

3.オブジェクトのグループの動きの計算

4.納屋に関する情報の収集

5.家庭で利用可能なサービスのデータを更新する

6.倉庫パラメーターの更新

7.人口アドバイザーとローマからの小麦の供給のデータを更新する

8.ワークショップでの財の消費と鉱業企業の資材の更新

9. Dockパスの更新

10.ワークショップでの商品の生産の計算

11.ローマへの道路のアクセシビリティの計算

12.住宅の人口の更新

13.過密住宅からのホームレスの外観の計算

14.企業による労働者の分布の計算、失業者、働く企業の計算

15.噴水と貯水池のカバレッジエリアの更新

16.家庭用水へのアクセスの更新

17.オブジェクトのグループのステータスの更新

18.建物にサービスを提供することによる市民の外観の計算

19.商人の外観の計算

20.都市の建物の種類と数を数え、文化的対象物のカバレッジを数える

21.上院とフォーラム間の都市の財務省の分布の計算

22.住宅の文化パラメータの減少の計算

23.家庭におけるサービスの減少の計算

24.土地の望ましさに及ぼす建物の影響の計算

25.家レベルの更新

26.解体のマークが付いた建物を地図から削除する

27.燃える廃insのパラメーターの更新

28.火災周辺の建物のステータスを更新する

29.抗議住民の作成

30.徴税パラメーターの計算

31.家庭での娯楽のレベルを更新する





都市パラメーターの計算の段階(コード)


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(); } }
      
      







新しい日が来ています


 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(); }
      
      







新しい月が来る


 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(); }
      
      







新年


 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; }
      
      









宗教







最初は、ローマ人は異教徒であり、ギリシャ人とそれほどではないがエトルリアの神々を崇拝していました。 その後、神話の時代は異教のカルトへの情熱に置き換えられました。 国家は、儀式の組織と行動を引き受けて、公式の宗教を作りました。そして、それは神に関する古い考えを変えました。 人々の生活における宗教は常に非常に重要であり、コンピューターモデルは人間の偏見から逃れることはできませんでした。神を選ぶときの偶然の要素。







神の気分と怒り/祝福の条件の計算
 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); } } } } } }
      
      









街の気分







住民自身は、都市とローマの賃金の差、都市のさまざまな製品、税のレベル、スラム街の数に反応します。 このパラメーターは家ごとに保存され、近隣の家に関係なく変わりません。







市内の気分と移住度の計算
 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; }
      
      









祭り









街のムードが大幅に向上すると、最初のフェスティバルは12か月間だけ、2回目以降は半分になります。 これは、豊かな都市では祭りを通してのみ元気づけることができなかったために行われます。 フェスティバル自体の準備にも時間がかかるため、年間に開催されるフェスティバルの数に制限があります。







祭りが都市の気分に与える影響の計算
 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; }
      
      









天皇の納税









皇帝へのオマージュ。 年末に財務省から支払われる金額

都市の利益と生活する人々の数に依存します。 最初の要因は、四半期の支払いを意味します

1年ごとに受け取るお金。ただし、一定の額以上。現在の人口に依存します。 もし都市

このお金を支払うことができない、支配者は天皇の好意の減少を支払う、そして過去

長年の不払いにより、不満は長期にわたる不払いの累積とともに蓄積されます。







昨年の皇室の計算
 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; }
      
      









ランダムイベント







ランダムイベント。 ゲームには7つのランダムなイベントが用意されており、ミッションエディターで変更できるフラグのセットがイベントの発生を担当します。 ジェネレーターがそのタイプをスローし、スクリプトで許可されている場合、ランダムイベントが発生します。 開発者は、ローマでの給与の引き下げ/引き上げ、海や土地の取引の問題、井戸の汚染、鉱山の崩壊、採石場や粘土採掘場の洪水などのタイプを作成しました。 地震はエディターで設定され、時間と発生点があり、4方向にランダムに広がります。



ランダムイベント
 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; } } }
      
      









住民の健康







開業医はローマで比較的遅れて現れました。 2世紀まで BC e。そして、社会の貧しい層で、そしてずっと後に、ローマ人は、世代から世代へと受け継がれた単純な手段で、親relativeの賢明な人生経験によって扱われました。 この民間薬は、原始的な魔法とは異質ではありませんでした。 農業のマニュアルは、人々と動物をどのように扱うかに関する多くの指示とともに、民間療法に明確に沿って編集されました。

ゲームでは、診療所と病院が同じサービスを提供しますが、高レベルの住宅が成長を続けるには病院も必要です。 住民の健康は、都市の繁栄の主要な指標の1つです。流行はブロック全体を刈ることができ、都市の人口の増加に伴い、流行中に死亡する人の数は増加するだけです。







都市の流行の健康と確率の計算
 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); } } }
      
      









徴税









市は最初は入国税で生活していますが、かなり控えめですが。 徴税人は、診療所の医師のように、定期的に住宅の建物と並んで歩く必要があります。 住宅の建物が定期的に集税者によって訪問される場合、毎月税金が支払われます。



住宅の1か月あたりの支払い額は、次の条件によって異なります。



-ファイナンシャルアドバイザーが設定した金利(0〜25%に調整可能)。

-税金の徴収時(つまり、月の変更時)に家に住んでいる人の数;

-家の開発レベル(ゲームファイル「c3_model.txt」、住宅のデータ、20列目-この数字は、このレベルの住宅の開発に住んでいる人1人あたり月額200%の税金です)



機能を分析した結果、税の円滑な増加は、迅速な変更と何ら違いはないと結論付けることができます。 税金は住民の気分に影響を与えますが、ローマの給与に比べて労働者の給与を8単位以上増やすことは意味がありません。



200%の税金とは、(おそらく)このブロックで、全体を小さくするために運用を節約したい開発者の欲求です。



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







このコードは、徴収された税金が2で割られることを示しています。必要な税金* 2が導入された以上に家が支払うことができない状況にならないように、計算では常に正しい値以下の値を取得します。



市の発展のより高いレベルでは、税金だけで生活することができます。











月ごとに受け取った税金の計算
 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 ); }
      
      









食料消費









どんな種類の食べ物を食べても、人々はX個の食べ物を食べます。食べられる食べ物の量は、家に住んでいる人の数のみに依存します(10人が月に5単位の食べ物を食べます)たとえば、20階の住宅があり、人口がいっぱいです。つまり、200人が住んでおり、3種類の食べ物が必要です。彼らは200/10 * 5 = 1か月あたり100単位に等しい合計量の食物を食べます。これらの100単位は、3つの必要なタイプの食物に分配されます。



家庭の消費量の計算
 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; }
      
      









商品の生産







時々、都市は商品を生産するために材料を輸入する必要があります。8つのワークショップは1つの倉庫で安定して動作し、2つは断続的に動作します。インポートは、ワークショップの数と取引先が提供できる材料の量に依存します。輸出は数値的に制御でき、ワークショップの要求に応じて原材料が輸入されます。したがって、ワークショップの数が少ない場合、倉庫は単に原材料を購入しません。



福祉評価







ゲームの福祉の評価は上昇するのが最も「困難」です。一方で、ルーラー側の不正行為はその減少につながります。他方では、最大レベルの増加は2ポイントです。すべての規則に従って、市は罰金とボーナスを除き、少なくとも25年間で50ポイントのマークに到達します。







都市福祉を数える
 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 MB未満のRAMテクスチャを含むアーカイブで忙しかった。以下に、物理的な意味が復元されたフィールドのテクスチャについて説明します。



(ウォーカー)動いている物体の説明
 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; //    };
      
      







(建物)固定オブジェクトの説明
 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 (     )
      
      









謝辞



都市の数学モデルに関する前回の記事のhabra-effectをきっかけに、このゲームはSteam青信号を受け取り、IndieGoGo.comで多くの寄付を受け取りました。リメイクをサポートしてくれたhabrasocietyに感謝します。



Caesar IIIファンと相談した後、変更を一時停止し、バグとバランスの修正に集中して、v0.4ブランチがオリジナルのように見えるようになりました。目立った変更のうち、マップのスケーリングと個々のユニットのロジック間の不一致のみが残りました。



リメイクには、新しいアートを描くアーティストDmitry Plotnikovが加わり





ましたありがとう、SkidanovAlexUnuntriumBickとindiegogoのテキストの翻訳を手伝ったすべての人。

多くのおかげでMennyCalaveraリメイク促進する上で彼らのサポートと支援のための

記事の翻訳のために特別な感謝アナスタシアSmolskiを12プロジェクトページで



ゲームの変更と追加情報を見ることができますそこで、最新のビルドをダウンロードして、見つかったバグについて私たちに書き込むことができます。PSこの記事は、オリジナルのソースを復元するために多くの時間とエネルギーを費やしたビアンカ・ヴァン・シャイクの助けがなければ不可能でした。











お時間をいただきありがとうございます。




All Articles