C# Interpreter Console for SimCity Societies

C# Interpreter Console for SimCity SocietiesSimCity Societies is a PC game that allows you to write mods using C# and XML. The game was developed by Tilted Mill and published by EA Games, and an expansion pack is also available, titled SimCity Societies Destinations. However, there is very little information available on the C# application programming interface (API), which makes the coding of a mod somewhat difficult. To enable one to explore the game’s programming environment more easily through an interactive console, I’ve adapted the code from my C# interpreter for Unity 3D to develop a similar plug-in for SimCity Societies (SCS for short), and this resulting rework also works with SCS Destinations. The interpreter mod/plugin opens a separate console window for input and output interactions. Without the interpreter console, you may often need to restart the game after making small changes to scripts in an attempt to figure out what an API member actually does, which tends to be a slow and tedious process; but with the console, the experimentation process becomes instantaneous. Furthermore, the interpreter can serve as an interactive debug tool for your actual mods, or you can even use it as a cheat console to do things such as adding Simoleons to your treasury with the “CS_Player.ModifyCurrentMoney” method (see example further down). As with the earlier C# interpreter, the rework is released as open-source software under the MIT License and is available as a free download. The rest of this article describes how to install and use the software.

Download and Install the Plugin

The current incarnation of the C# Interpreter (a.k.a. CSI) is distributed as a ZIP file containing the four files that need to be copied to the data-scripts directory-structure of your SimCity Societies installation. For example, you need to decompress the ZIP file’s content into your folder that is named something like “C:\Program Files\Electronic Arts\SimCity™ Societies\Data\Scripts” for SimCity Societies or “…\SimCity™ Societies\DataXP1\scripts” for SCS Destinations (or in both locations, if you want to use CSI from both). The four files to be installed are:

  • csi-host.cs
  • csi-include.txt
  • csi-interpreter.cs
  • csi-prepro.cs

The first file, “csi-host.cs”, is the primary script that implements the hosting environment for the C# Interpreter. The host still needs to be enabled so that the CSI window would show up, but this is covered in the next section.

The “csi-include.txt” file gets included during start-up to configure the interpreter environment. It specifies additional namespaces and assembly references that must be included, as well as macros (such as print) that can save you some typing. You can edit this text file to tweak the interpreter environment to your liking – each line in this file is effectively processed as if it were provided manually as input to the interpreter, one step at a time. If your .NET Framework installation is an older version, you might also need to remove (or comment out) some of the lines that reference newer assemblies, such as “System.Core.dll” and the ones for LINQ. Also note that the newer C# language features that you can use in CSI may not be available to mod scripts, since the latter are essentially limited to C# 2.0 functionality and the .NET base classes contained within the “mscorlib.dll” and “System.dll” assemblies. However, within the C# console you may use additional base classes, including LINQ, as shown in an example later on. If you are having problems with this include file, you can even delete it completely; the interpreter would then setup a simple environment that automatically includes the “TMill.SC5M” and “BuildingScripts” namespaces. The interpreter also always includes the SCS Managed API assembly (either “SCSLib.dll” for SimCity Societies or “SCSLibxp1.dll” for SCS Destinations), as well as the assembly containing the compiled scripts of mods and default game logic (i.e., the dynamically compiled DLL named “MasterScripts.asm”).

The last two files, “csi-interpreter.cs” and “csi-prepro.cs”, contain the underlying CSI interpreter and pre-processor code that provide support for the CSI host. (They are almost identical to the “interpreter.cs” and “prepro.cs” files from the Unity 3D incarnation, except for minor enhancements.)

As a side note, I discovered the hard way that one should actually not install SimCity Societies in its default directory on Windows Vista, such as “C:\Program Files\Electronic Arts\SimCity™ Societies”. Rather specify a different path during game installation, for example “C:\Games\Electronic Arts\SimCity™ Societies”. Otherwise you might end up with errors like the following when you start the game, even without any mod: “A required security module cannot be activated”. Installing games under the “Program Files” folder seems to be a general problem with a number of games. Furthermore, do not run developer tools like Sysinternals Process Explorer when starting the game, or you might also get this error message. From a similar report about the Spore game, I infer that SecuROM is also used for copy protection of SimCity Societies — it really just feels like they penalize legitimate users who have to work around frustrating bugs.

Bootstrapping — Enable the Plugin

Once you have the files installed, the runtime user-interface needs to be enabled through a bootstrapping procedure. One way to accomplish this is by adding some code to an existing script file named “BuildingAbilities.cs”, which can be found in the same scripts directory where you installed the CSI files. For example, insert the indicated lines of code, shown below, right after the line that reads “public static readonly int k24HourAtHour = -1;” (which can be found near the top of the file)…

class BuildingAbilities : IBuildingAbilityScript
{
public static readonly int k24HourAtHour = -1;

// =====================================================
// TODO: Add the next 4 lines to "BuildingAbilities.cs"
static BuildingAbilities()
{
CSI.Host.Spawn(); // Start the C# Interpreter
}
// =====================================================

private List<AbilityScriptData> mStaticAbilityList = ...

With the change applied, the CSI console window would show up the next time when you run the game, as soon as you buy your first building or load a game with a building. (If you have more detail about the game’s script-initialization logic and can suggest a better way to activate the console, perhaps without needing a building, please comment.)

Using the CSI Console

When you have the C# Interpreter set up and running within SimCity Societies, its runtime user-interface will display as separate console window, similar to the screenshot below. It does help to run the game in windowed mode, instead of full-screen mode, to allow quick switching between the console window and the game’s main window.

Screenshot of CSI Simple C# Interpreter on SimCity Societies

The user interface works very much like the CSI for Unity (as discussed in its Runtime User Interface section), but the input and output sections are now always visible (that is, no toggle buttons) and they are combined into a single window in the console version for SimCity Societies. (A couple of minor features, such as the auto-selection of results, are also not available.) However, the rudimentary auto-completion or help system behaves the same, which can be triggered with the Alt+F1 shortcut. Also, one still uses the dollar-sign syntax for session variables, but the query mechanism now obviously expose different global values within the game. For example, $money retrieves the player’s current treasury, which is a shortcut for “CS_Player.GetCurrentMoney”. Also, $buildings, $houses and $sims (and so on) can be used as shortcuts to get the results of the “BuildingLists.GetUnitList” methods that operate on a CS_UnitBuildingList, CS_UnitHouseList and CS_UnitSimList, respectively. If you want to discover the names of additional shortcuts, refer to the “CSI.Host.OnGetUnknownItem” method in the “csi-host.cs” file. To get an idea of some of the possible console interactions, have a look at the example CSI session below.

CSI Simple C# Interpreter v.0.8.23.1 from Tiaan.com in CLR v.2.0.50727.4200
>>> DisasterControl.LaunchFireworks()
>>> DisasterControl.IsFireworksOccuring()
(bool) True
>>> CS_Player.IsInDebt()
(bool) False
>>> CS_Player.GetCurrentMoney()
(int) 78401
>>> $tt = TTreasuryType.kTreasuryTypeNone
>>> $tt
(TMill.SC5M.TTreasuryType) kTreasuryTypeNone
>>> CS_Player.ModifyCurrentMoney(125000, true, $tt)
>>> CS_Player.GetCurrentMoney()
(int) 203401
>>> $money
(int) 203401
>>> CS_CameraControlManaged.ZoomToHeight(42)
>>> CS_CameraControlManaged.SetWorldTranslation(90, 123, false, true)
>>> Messaging.SendMiniMapPing(90, 123, 5000)
(TMill.SC5M.IDataID) TMill.SC5M.IDataID
>>> Game.Instance().Population
(uint) 180
>>> BuildingLists.GetNumSimsHappierThanIncl(new DataID("Happy"))
(int) 165
>>> BuildingLists.GetNumSimsHappierThanIncl(${@Happy})
(int) 165
>>> ${@Happy}
(DataID) DataID
>>> new DataID("Happy") == (IDataID)${@Happy}
(bool) True
>>> BuildingLists.GetNumSimsByMood(${@Happy})
(int) 12
>>> $id = BuildingScripts.Utilities.SimStateFlagIDs.kNormalSim
>>> BuildingLists.GetNumSimsByFlag($id)
(int) 180
>>> foreach (System.Reflection.FieldInfo mi in $type.GetFields()) {
... int i = BuildingLists.GetNumSimsByFlag((ISimStateFlagID)mi.GetValue(null));
... if (i == 0) continue;
... Console.WriteLine("{1,4} : {0}", mi.Name, i); }
180 : kBreadwinner
180 : kNormalSim
117 : kEcstatic
36 : kElated
12 : kHappy
13 : kContent
2 : kSad
>>> $buildings.Count
(int) 87
>>> $buildings[0]
(TMill.SC5M.CS_UnitDecoration) TMill.SC5M.CS_UnitDecoration
>>> $buildings[0].OpenToday
(bool) True
>>> $buildings[0].Collapse()
>>> $buildings[0].GetX()
(float) 22.50001
>>> $buildings[0].GetY()
(float) 202.5
>>> $buildings[0].GetZ()
(float) 4.912768
>>> DisplayTimeControl.GetHour()
(float) 20.03082
>>> UserData.GetIsLowMemMode()
(bool) False
>>> UIManager.GetGhostUnitID()
(TMill.SC5M.IDataID) TMill.SC5M.IDataID
>>> UIManager.GetSelectedUnitID()
(uint) 4294967295
>>> UIManager.GetSelectedUnitID() == CS_Unit.GetInvalidID()
(bool) True
>>> DisasterControl.StartSound("dis_meteor_impact_03")
>>> CS_FireSystemManaged.IgniteRandomBuilding()
>>>

The SCS API often uses a data identifier (ID) involving the IDataID interface to refer to a corresponding well-known item. The scripts that come with SCS provide the DataID class that implements the IDataID interface, and there are also predefined DataID instances that can be used. If you know the textual name that corresponds with an ID, you can also create a DataID instance from that string, as was done in the example with “new DataID("Happy")”. However, the interpreter also provides a shortcut mechanism, involving the @-symbol, such that “${@Happy}” can instead be used to create the same ID. Also note that, in the previous example towards the end of the session, the predefined invalid numerical ID was returned when querying for the currently selected item in the game, since nothing was selected at the time.

If you were to select an item (a.k.a. SCS unit) in the game window, such as a building or a sim figure, the “UIManager.GetSelectedUnitID” method would return a valid identifier; this is equivalent to using the $selected shortcut variable. For example, if you select a building in the game, it would be possible to run a C# Interpreter session similar to the one below. The next example also introduces another shortcut mechanism, involving a hash notation, which can be used to look up the actual game object corresponding with a specific unit’s numerical ID. Furthermore, when omitting the numerical ID after the hash character (which results in “${#}”), the currently selected unit is returned; when a building is selected, this is equivalent to executing the “BuildingLists.GetUnit” method with “(int)UIManager.GetSelectedUnitID()” for the input parameter, along with an output parameter of type CS_UnitBuilding.

>>> UIManager.GetSelectedUnitID()
(uint) 7057
>>> UIManager.GetSelectedUnitID() == CS_Unit.GetInvalidID()
(bool) False
>>> $selected
(uint) 7057
>>> ${#7057}
(TMill.SC5M.CS_UnitBuilding) TMill.SC5M.CS_UnitBuilding
>>> ${#7057}.GetID()
(uint) 7057
>>> ${#}
(TMill.SC5M.CS_UnitBuilding) TMill.SC5M.CS_UnitBuilding
>>> $building = ${#}
>>> $building.GetID()
(uint) 7057
>>> $building.GetIsOnFire()
(bool) False
>>>

Unless your .NET Framework installation is outdated, you should also be able to use LINQ and lambda expressions in the C# Interpreter, as illustrated below.

>>> $capacities = $venues.Select(venue => venue.GetCapacity())
>>> $capacities
(IEnumerable<uint>)
{9,9,13,6,49,34,3,9,6,9,19,34,19,24}
>>> $capacities = $capacities.Select(capacity => (int)capacity).Reverse()
>>> $capacities
(IEnumerable<int>)
{24,19,34,19,9,6,9,3,34,49,6,13,9,9}
>>> $capacities.OrderBy(capacity => capacity)
(Linq.IOrderedEnumerable<int>)
{3,6,6,9,9,9,9,13,19,19,24,34,34,49}
>>> $capacities.Sum()
(int) 243
>>> $capacities.Average()
(double) 17.3571428571429
>>>

The final example uses the technique discussed in one of my earlier post to create an XML representation of a house object. The first line references the “System.Web.Routing” DLL, which only needs to be done manually once per console session or placed in the “csi-include.txt” file for automatic inclusion. The second line includes the namespace, which could also have been done in shorthand form with “/n System.Web.Routing” (again, this is needed only once per session). Then the RouteValueDictionary is abused to extract the property values, which is eventually turned into XML.

>>> /r System.Web.Routing.dll
>>> using System.Web.Routing;
>>> $dict = new RouteValueDictionary($houses[0])
>>> $dict
(Web.Routing.RouteValueDictionary)
{[Damaged, False],[AbilityDataWeight, 0],[ClosingHour, 23.9],
[OpeningHour, 0],[VisitDuration, 2],[HappinessEffects, -2],
[SimCashPay, 0],[Mothballed, False],[OpenToday, True],
[AllowRevenue, True],[Functional, True]}
>>> $elements = $dict.Select(item => new XElement(item.Key, item.Value))
>>> $elements
(IEnumerable<Xml.Linq.XElement>)
{<Damaged>false</Damaged>,<AbilityDataWeight>0</AbilityDataWeight>,
<ClosingHour>23.9</ClosingHour>,<OpeningHour>0</OpeningHour>,
<VisitDuration>2</VisitDuration>,
<HappinessEffects>-2</HappinessEffects>,<SimCashPay>0</SimCashPay>,
<Mothballed>false</Mothballed>,<OpenToday>true</OpenToday>,
<AllowRevenue>true</AllowRevenue>,<Functional>true</Functional>}
>>> new XElement("House", $elements)
(Xml.Linq.XElement) <House>
<Damaged>false</Damaged>
<AbilityDataWeight>0</AbilityDataWeight>
<ClosingHour>23.9</ClosingHour>
<OpeningHour>0</OpeningHour>
<VisitDuration>2</VisitDuration>
<HappinessEffects>-2</HappinessEffects>
<SimCashPay>0</SimCashPay>
<Mothballed>false</Mothballed>
<OpenToday>true</OpenToday>
<AllowRevenue>true</AllowRevenue>
<Functional>true</Functional>
</House>
>>>

That's all Folks!

Be aware that the C# Interpreter console is executing on a separate thread while the game is running, which might interfere with the actual game logic. SimCity Societies may not have been designed with such an interactive console and liberal threading model in mind; the SCS API may not be thread-safe at all times. So, don’t be too surprised if the game hangs, crashes, doesn’t start, or behaves strangely now and again while executing your interactive C# code — luckily that does not seem to happen very often, though.

I think it would be neat if more games would expose .NET APIs and allow modding using C#, similar to SimCity Societies. But hopefully the developers or publishers of such games would put in a bit more effort to actually document those APIs than what has happened with SCS. Nevertheless, the C# Interpreter presented here can now be used to lessen the pain of exploring the SCS .NET API. And perhaps SimCity Societies is indeed as customizable as it was touted to be; for one thing, it allowed me to extend the game with this interpreter quite easily. Maybe time would tell for sure…

Download: