Hacking Cities:Skylines

https://github.com/yasushisakai/CitiesSkylinesTestMod

Mod 開発準備

下記が、親切に教えてくれてそう… https://community.simtropolis.com/forums/topic/73404-modding-tutorial-0-your-first-mod/

だけど、いきなり古すぎて対応していない箇所があったので、半信半疑でやったほうがよさそう。結局リバースエンジニアリングが出てくるのは避けられなそう。

https://skylines.paradoxwikis.com/Modding の公式? の方がまだアップデートされている模様

fbx の dump ができそう https://steamcommunity.com/sharedfiles/filedetails/?id=2434651215&searchtext=ModTools

ModTools をいれたほうが良い

ゲーム内のアセットの一覧や情報を取得するために便利な ModTools という Mod があるので、それを入れる。 Debug.Log もここから見えるので、必須です。

Visual Studio で開発…

https://skylines.paradoxwikis.com/User_path によると、 windows だと %LOCALAPPDATA%/Collossal Order/Cities_Skyline/Addons/Mods の中に

- Mods
    - TestMod
        - Source
            - Test.cs

に書けば、ゲームがロード時にコンパイルしてくれるらしい。 linux の場合、 ~/.steam

/home/yasushi/.local/share/Steam/steamapps/common/Cities_Skylines/Files/Mods
/home/yasushi/.local/share/Colossal Order/Cities_Skylines/Addons/Mods

のどちらかにありそうなもの。チュートリアル的には、後者のようなものだけど、未検証。コード補完の dll のリンクさえ出来れば、開発できそう。

でも結局、なんかいろいろ大変そうだったのでおとなしく、windows + visual studio で開発することがいいような気もする。くやしいけど。

あとのメモとして、Unity のバージョンは 5 らしい。最新の Unity Scripting API でつかえないものもあるかもしれない。

https://github.com/yasushisakai/CitiesSkylinesTestMod でつくり初めたけど、まあ、バッドノウハウばっかり笑

以下やれるといいこと:

さらに情報の取得

人口や、犯罪率などの数値情報と、どこに何が入っているかななど位置と属性情報がとれればいいかな。道路の情報や、エージェントの情報もとれるとなお良い。

情報の反映

人を追加したり、建物を追加したり。ケント的には混合用途の建物を追加できないかともいっていた。でも、まあ、これはそんなに心配していない。

SimulationManager.instance.SimulationPaused = !SimulationManager.instance.SimulationPaused

とすれば、シミュレーションがとまったり、はしったりする。

自動起動(と終了)、Mod のビルド

開発そのものは、vscode から、remote で windows 機のファイルをいじる、(コード補完とかも remote 側が使えるし、そもそも c#を mac で開発する気になれない。)

とにかく起動がおそく、再起動にいろいろハードルがあり、これを自動化しないと全然だめ。

以下を組みあわせれば、Mod のビルドから Cities の起動と強制終了ができる。マウスのエミュレーションがいらなくて助かる。

強制終了

taskkill /f /t /im "Cities.exe"

起動

ローカルなら以下でいいんだけど、

start /MIN /D"C:\Program Files (x86)\Steam\steamapps\common\Cities_Skylines" Cities.exe

ssh 経由だとだめらしく、 PSExec というコマンド経由で起動するらしい。これは PSTools というのを DL して、PATH に追加しておく。

その後さらに、

Ok, I’ve found the answer. There is a GPO on the domain that needs to be added. Computer Config > Windows settings > Security Settings > Local Policies > User Rights Assignment: Make sure “allow log on locally” and “allow log on through remote desktop services” both are enabled with your admin account listed. Once I did that, all PowerShell scripts on a shared folder I had could be successfully executed on all systems with PSExec.

だったり、レジストリをいじったり色々したらなんか出来た。

結局以下にしないとだめ。

psexec \\localhost -i "C:\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities.exe"

Paradox launcher をスキップする

その上で、Paradox のランチャーをスキップしたい時は下記のプログラムをいれる。

https://github.com/shusaura85/notparadoxlauncher

最後に遊んだファイルを自動ロードするオプションがあるのでそれをつける。

… Pascal…

もしかすると、Paradox Launcher もオプションつければ、自動的に最後のセーブをロードしてくれるのかもしれない。

ビルド

...Microsoft Visual Studio\2017\Community\MSBuild\15.0\MSBuild.exe" {Path to Mod}

CapsLock がかかりっぱなしになってる場合あり

複数インスタンスによる中継

https://citiesskylinemultiplayer.com

をすれば、複数インスタンスを走らせることができそう。操作しないまでも、町の様子を中継するのは出来そう。

API 深掘り

公式の API にはほとんど情報が載っていないので、ModTools と ILSpy を駆使して、リバースエンジニアリング。ILSpy でみえるものは、Decompile できるので、それをながめる。

DistrictManager

色んなデータがここからとれそう。 DistrictManager.instance.m_districts.m_buffer[0].m_populationData.m_finalCount で人口がとれた。

この m_buffer[*] の型が District.cs らしい、この中にももちろん沢山型があるんだけど、その中でクラス変数で関係ありそうなのは…

選定する必要ありだけど…

DistrictAgeData m_adultData;
DistrictAgeData m_ageAtDeathData;
DistrictAgeData m_birthData;
DistrictAgeData m_childData;
DistrictAgeData m_childHealthData;
DistrictAgeData m_childSickData;
DistrictAgeData m_deadSeniorsData;
DistrictAgeData m_deathData;
DistrictAgeData m_education1Data; // 1-3
DistrictAgeData m_elementaryEligibleData;
DistrictAgeData m_highschoolEligibleData;
DistrictAgeData m_libraryVisitorData;
DistrictAgeData m_seniorData;
DistrictAgeData m_seniorHealthData;
DistrictAgeData m_seniorSickData;
DistrictAgeData m_student1Data; // 1-3
DistrictAgeData m_teenData;
DistrictAgeData m_universityEligibleData;
DistrictAgeData m_youngData;
DistrictConsumptionData m_commercialConsumption;
DistrictConsumptionData m_industrialConsumption;
DistrictConsumptionData m_officeConsumption;
DistrictConsumptionData m_playerConsumption;
DistrictConsumptionData m_residentialConsumption;
DistrictEducationData m_educated0Data; // 0-3
DistrictGroundData m_groundData;
DistrictPopulationData m_populationData; // ****
DistrictPrivateData m_commercialData;
DistrictPrivateData m_industrialData;
DistrictPrivateData m_officeData;
DistrictPrivateData m_playerData;
DistrictPrivateData m_residentialData;
DistrictPrivateData m_visitorData;
DistrictProductionData m_productionData;
DistrictResourceData m_exportData;
DistrictResourceData m_importData;
DistrictSubCultureData m_gangstaData;
DistrictSubCultureData m_hippieData;
DistrictSubCultureData m_hipsterData;
DistrictSubCultureData m_redneckData;
DistrictTouristData m_tourist1Data;
DistrictTouristData m_tourist2Data;
DistrictTouristData m_tourist3Data;
DistrictUsageData m_usageData;
byte m_lastAverageLifespan;
byte m_finalHappiness;
byte m_finalCrimeRate;

メソッドでいうと…

void SimulationStep(byte districtID)
void AddXXXData(...)
int GetAverageLifespan()
int GetCremateCapacity()
int GetCriminalAmount()
int GetCriminalCapacity()
int GetDeadAmount()
int GetDeadCapacity()
int GetDeadCount()
int GetEducation1Capacity() // 1,2,3
int GetEducation1Need() // 1,2,3
int GetEducation1Rate() // 1,2,3
int GetElectricityCapacity()
int GetElectricityConsumption()
int GetExportAmount()
int GetExtraCriminals()
int GetGarbageAccumulation()
int GetGarbageAmount()
int GetGarbageCapacity()
int GetGarbagePiles()
int GetGroundPollution()
int GetHealCapacity()
int GetHeatingCapacity()
int GetHeatingConsumption()
int GetImportAmount()
int GetIncinerationCapacity()
int GetIncomeAccumulation()
int GetLandValue()
int GetLibraryCapacity()
int GetLibraryVisitorCount()
int GetSewageAccumulation()
int GetSewageCapacity()
int GetShelterCitizenCapacity()
int GetShelterCitizenNumber()
int GetSickCount()
int GetUnemployment()
int GetWaterCapacity()
int GetWaterConsumption()
int GetWaterPollution()
int GetWaterStorageAmount()
int GetWaterStorageCapacity()
int GetWorkerCount()
int GetWorkplaceCount()

あたりかな?

Date: 2022-07-26 Tue 16:59