Die Asset-Bibliothek - Einleitung

Stell Dir dein Spiel am besten so vor (sehr vereinfacht):
Assets + Grafiken → Binary (Anno1800.exe) → Anzeige auf dem Bildschirm
An der Binary können wir nichts verändern. Was wir allerdings tun können, ist der Binary veränderte oder neue Assets und Grafiken zu füttern, die das Spiel interpretieren kann. Das bedeutet, wir müssen uns an dessen Regeln halten – Nur wenn man Code schreibt, den Anno versteht, wird es auch tun, was man will.

(Vielleicht ein kleines Update) Was sollte man können, um loszulegen?

Zuerst, nein, man muss nicht unbedingt programmieren können, obwohl das immer ein Pluspunkt ist.
Wenn Du nicht programmieren kannst, solltest du dich aber auf jeden Fall mit den Konzepten Datentyp, Variable, Arrays, Vererbung und (generischen) Dictionaries und Vectors vertraut machen. Code musst du damit nicht schreiben, aber diese Konzepte zu verstehen ist eine große Hilfe beim Modding.
Die meisten Dateien in Anno benutzen die XML-Scriptsprache. Dessen Syntax werde ich nicht erklären, und ein Basisverständnis wird vorausgesetzt. Wenn Du noch nie XML gesehen hast, solltest Du dich zuerst dort etwas einarbeiten. Du solltest ebenfalls ein bisschen Xpath können, da dies für den Modloader gebraucht wird.

Die Assets-Bibliothek verstehen

Zuerst, entschuldigt bitte den trockenen Start, es wird leider ein wenig theoretisch, aber für die interessanten Teile wird es später sehr wertvoll sein, besonders, wenn Du neue Funktionen in das Spiel einbauen möchtest.
Für die Assets-Bibliothek verwendet Anno ein paar Konzepte:

  • Assets sind Objekte im Spiel – Ein Gebäude, ein Schiff, oder das Handwerker-Baumenü
  • Templates sind Blaupausen für Assets, die Standardwerte für jedes Asset mit dieser Template festlegen. Außerdem gibt die Template an, welche Properties geladen werden sollen.
  • Properties sind Container, die direkt in einem Asset enthalten sind, Properties können Container enthalten, aber nicht umgekehrt!
  • Container sind Sammlungen an Values, Vectors oder anderen Containern.
  • Vectors sind Listen aus Variablen oder Containern
  • Dictionaries sind wie Vectors, nur dass jedes Element einen einzigartigen Namen bekommt, die vom Spiel vordefiniert sind. Deswegen ist die Größe eines Dictionaries limitiert auf die Anzahl vordefinierter Hardcodes.
  • Values sind simple Variablen oder Arrays
Im folgenden werden Values als Variablen bezeichnet, um Verwirrung zwischen Variablenname und dem Wert zu vermeiden.

Properties

Die Properties.xml ist wahrscheinlich am schwersten zu verstehen, aber sobald man diese versteht, wird man wenige Probleme haben, sich in jeder Template und jedem Asset zurechtzufinden.
Die Datei besteht aus zwei Teilen: DefaultContainerValues und DefaultValues. Für bessere Übersicht in den Entwicklertools sind sie gruppiert, aber generell könnte man es auf die beiden runterbrechen.
DefaultContainerValues gibt an, welche Container eine Property besitzt und definiert Standardwerte für Variablen in diesen Containern. Im Fall, dass der Container einen Vector oder ein Dictionary enthält, ist der Datentyp des Inhalts angegeben.
DefaultValues definiert Standardwerte für Variablen, die direkt in der Property selbst enthalten sind, und erbt die Container aus DefaultContainerValues.

Die Standardwerte der Variablen können geändert werden, aber wir können dem Spiel nicht sagen, dass es andere Container oder Variablen laden soll. Die Properties ist ein direkter Export, was das Spiel von uns erwartet.
In seltenen Fällen ist dieser Export nicht komplett, in diesem Fall ist die Information aber auf jeden Fall in der assets.xml zu finden.
Wenn Du Informationen brauchst, was man in einer Property so alles eintragen kann, solltest Du hier als erstes gucken. Das folgende Beispiel zeigt die Property „IrrigationConfig“

Beispiel: Property IrrigationConfig[ Codeblock kopieren ]
<Properties> <DefaultContainerValues> <Building> <IrrigationConfig> <IrrigationSpreading> <!-- Variable in Container in Property: Standardwert für Variable am selben Ort definiert --> <IrrigationSpreadRule>MaxAbsDist</IrrigationSpreadRule> </IrrigationSpreading> <!-- IrrigationIslandSeeds ist ein Container, der einen Vector enthält --> <IrrigationIslandSeeds> <!-- Island ist der ContentType dieses Vectors--> <Island>0</Island> </IrrigationIslandSeeds> </IrrigationConfig> </Building> </DefaultContainerValues> <DefaultValues> <IrrigationConfig> <!-- Container werden vererbt --> <IrrigationSpreading /> <IrrigationIslandSeeds /> <!-- Variable direkt in der Property: Standardwert für Variable wird in DefaultValues definiert--> <StaticIrrigationRandomizerFrequency>2</StaticIrrigationRandomizerFrequency> </IrrigationConfig> </DefaultValues> </Properties>
Code ausklappen

Im Fall, dass Container A einen anderen Container B enthält, wird B in „ContainerValues“ definiert und dann an A vererbt. Wenn B Variablen enthält, werden diese an derselben Stelle definiert.
Das folgende Beispiel zeigt einen anderen Teil der „IrrigationConfig“ Property

Beispiel: Container in Container[ Codeblock kopieren ]
<Properties> <DefaultContainerValues> <IrrigationConfig> <VisualsAndEffects> <!-- Vegetation-Container wird definiert--> <ContainerValues> <Vegetation> <!-- Variablen in Container in Container in Property: Standartwerte für Variablen werden am selben Ort definiert --> <FadingSpeed>0.05</FadingSpeed> <BlurRadius>1</BlurRadius> </Vegetation> </ContainerValues> <!-- Der Vegetation Container wird an VisualsAndEffects vererbt. --> <!-- Wichtig: In unserem Fall ist Vegetation nur ein einfacher Container --> <!-- es könnte aber auch der ContentType eines Vectors oder Dictionaries sein. --> <Vegetation /> </VisualsAndEffects> </IrrigationConfig> </DefaultContainerValues> <DefaultValues> <IrrigationConfig /> </DefaultValues> </Properties>
Code ausklappen

Die volle Definition der Property „IrrigationConfig“ sieht dann ungefähr so aus:

Beispiel: IrrigationConfig Komplett[ Codeblock kopieren ]
<Properties> <DefaultContainerValues> <Building> <IrrigationConfig> <IrrigationSpreading> <IrrigationSpreadRule>MaxAbsDist</IrrigationSpreadRule> </IrrigationSpreading> <IrrigationIslandSeeds> <Island>0</Island> </IrrigationIslandSeeds> <VisualsAndEffects> <ContainerValues> <Vegetation> <FadingSpeed>0.05</FadingSpeed> <BlurRadius>1</BlurRadius> </Vegetation> </ContainerValues> <Vegetation /> </VisualsAndEffects> </IrrigationConfig> </Building> </DefaultContainerValues> <DefaultValues> <IrrigationConfig> <IrrigationSpreading /> <IrrigationIslandSeeds /> <VisualsAndEffects /> <StaticIrrigationRandomizerFrequency>2</StaticIrrigationRandomizerFrequency> </IrrigationConfig> </DefaultValues> </Properties>
Code ausklappen

Datentypen

Die folgenden Datentypen werden für Variablen benutzt:

  • int → meist signed 16 oder 32 bit integers
  • float → amerikanische schreibart 0.1305
  • boolean → notiert als 0 oder 1
  • String → ohne Anführungszeichen
  • Hardcode → benutzt wie String, nur vorgegebene Namen werden akzeptiert.
Der Datentyp jeder Variable kann leider nur mit Datamining und Probieren herausgefunden werden. Es gibt leider keine Liste davon. Vorherige Spiele wie 2205 hatten dafür eine datasets.xml, die diese Information enthielt, in 1800 fehlt sie aber, und es wäre toll, wenn das von Ubisoft veröffentlicht werden könnte.

Templates

Eine Template besteht aus einem einzigartigen Namen sowie einer Reihe an Properties die Objekte mit dieser Template haben.
Wie mit der Properties.xml auch, sind Gruppen nur für Übersicht in den Devtools zuständig.

Sehen wir uns nun die Template „UnlockableAsset“ an:

Simple Asset[ Codeblock kopieren ]
<Templates> <Template> <Name>UnlockableAsset</Name> <Properties> <Standard /> <Locked /> </Properties> </Template> </Templates>

Diese hat die Properties „Standard“ und „Locked“.
Templates können Standardwerte für jede Variable in einer Property geben, die dann für alle Assets, die diese Template benutzen, geladen werden, und das ist auch eigentlich schon alles.

Wie können wir die Werte aber überschreiben? An dem Punkt sollten wir uns der Vererbung zuwenden.

Vererbung

Dies ist meine eigens erstellte Template „CustomIrrigationConfig“.

CustomIrrigationConfig Base[ Codeblock kopieren ]
<Templates> <Template> <Name>CustomIrrigationConfig</Name> <Properties> <Standard /> <IrrigationConfig /> </Properties> </Template> </Templates>

In der Property „IrrigationConfig“ von oben, wurde StaticIrrigationRandomizerFrequency mit einem Standardwert von 2 versehen. Wir wollen das jetzt auf 5 ändern, aber den Rest so lassen, wie er in der Property ist.
Nein, wir müssen nicht die ganze Property nochmal abschreiben, wir können einfach nur diese eine Variable abändern, und den Rest weglassen. In der templates.xml und assets.xml werden Container automatisch vererbt, Für properties gilt das hingegen nicht.
Wir können Container auch manuell vererben, aber müssen das nicht und können sie weglassen, solange wir nichts darin ändern wollen.

CustomIrrigationConfig Changed Variable[ Codeblock kopieren ]
<Templates> <Template> <Name>CustomIrrigationConfig</Name> <Properties> <Standard /> <IrrigationConfig> <StaticIrrigationRandomizerFrequency>5</StaticIrrigationRandomizerFrequency> </IrrigationConfig> </Properties> </Template> </Templates>

Wir wollen jetzt noch den FadingSpeed in VisualsAndEffects ändern. Dafür vererben wir zuerst VisualsAndEffects manuell, darin dann Vegetation, wieder manuell, und darin überschreiben wir den FadingSpeed.

CustomIrrigationConfig Changed FadingSpeed[ Codeblock kopieren ]
<Templates> <Template> <Name>CustomIrrigationConfig</Name> <Properties> <Standard /> <IrrigationConfig> <VisualsAndEffects> <Vegetation> <FadingSpeed>0.05</FadingSpeed> </Vegetation> </VisualsAndEffects> <StaticIrrigationRandomizerFrequency>5</StaticIrrigationRandomizerFrequency> </IrrigationConfig> </Properties> </Template> </Templates>

Für Dictionaries werden die einzigartigen Namen wie Container behandelt. Für Vectors muss man manuell vererben, was so nervig ist, dass das ganze ein ausgelagertes Tutorial ergibt. Dieses findet ihr im Maug-Forum

Okay, genug Trockenfleisch für heute, wir erstellen jetzt eine Fabrik.

Ein Asset besteht aus Template und Values. Unser Asset beginnen wir mit der Template, in unserem Fall „FactoryBuilding7“. Für die Values, schreiben wir die Properties auf, die diese Template besitzt, erstmal ohne Variablen oder Container zu ändern.

Template[ Codeblock kopieren ]
Asset> <Template>FactoryBuilding</Template> <Values> <Standard /> <Building /> <Blocking /> <Cost /> <Object /> <Mesh /> <Selection /> <Text /> <Constructable /> <Locked /> <SoundEmitter /> <FeedbackController /> <Infolayer /> <UpgradeList /> <Factory7 /> <FactoryBase /> <LogisticNode /> <AmbientMoodProvider /> <Maintenance /> <Attackable /> <Pausable /> <IncidentInfectable /> <Industrializable /> <Culture /> <QuestObject /> <Electrifiable /> </Values> </Asset>
Code ausklappen

Dann machen wir die Values.
Ich habe für die Fabrik die GUID 1337733142 ausgesucht. Die wird in der „Standard“-Property als Variable angegeben. Außerden können wir einen Namen (Name) vergeben, der aber nur für unsere Übersicht ist, das Spiel macht damit nichts. Weitere Informationen zu GUIDs findet ihr hier.

Theoretisch haben wir damit ein valides Asset auf Basis der Template FactoryBuilding7. Praktisch produziert es nichts aus nichts, kostet keine Arbeitskraft oder Credits, und Baumaterial braucht es auch nicht.
Also rein damit.
Wir machen eine Fabrik, die Dosenfutter aus Zink und Kartoffeln herstellt, und nur weil wir können, lassen wir in der Neuen Welt Obreros dafür arbeiten.

Werte zuweisen[ Codeblock kopieren ]
<Asset> <Template>FactoryBuilding</Template> <Values> <Standard> <GUID>1337733142</GUID> <Name>Canned Food Factory 02 - Variant uses Potatoes and Zinc</Name> </Standard> <Building> <!-- Gebäude für Colony01 (Südamerika) --> <AssociatedRegions>Colony01</AssociatedRegions> <!-- Zufällige Rotation beim bauen --> <BuildModeRandomRotation>90</BuildModeRandomRotation> </Building> <Blocking> <!-- Radius in Feldern, in dem Gras versteckt wird, wenn das Gebäude gebaut wird--> <HidePropsRadius>0</HidePropsRadius> <DeletePropsRadius>0</DeletePropsRadius> </Blocking> <Cost> <Costs> <Item> <!-- Referenziert Geld --> <Ingredient>1010017</Ingredient> <Amount>10000</Amount> </Item> <Item> <!-- Referenziert Holz --> <Ingredient>1010196</Ingredient> <Amount>50</Amount> </Item> <Item> <!-- Referenziert Ziegel --> <Ingredient>1010205</Ingredient> <Amount>30</Amount> </Item> <Item> <Ingredient>1010218</Ingredient> </Item> <Item> <Ingredient>1010207</Ingredient> </Item> <Item> <Ingredient>1010202</Ingredient> </Item> </Costs> </Cost> <Object /> <Mesh /> <Selection> <!-- Hardcode, der den Participant (Charakter) im Gebäudemenü angibt. --> <!-- in unserem Fall ist es die Obrera --> <ParticipantMessageArcheType>SA_Resident_tier02_atWork</ParticipantMessageArcheType> </Selection> <Text /> <Constructable /> <Locked /> <SoundEmitter /> <FeedbackController /> <Infolayer /> <UpgradeList /> <Factory7 /> <FactoryBase> <FactoryInputs> <Item> <!-- Product referenziert die GUID von Kartoffeln --> <Product>1010195</Product> <Amount>1</Amount> <!-- Internes Kartoffel-Lager --> <StorageAmount>6</StorageAmount> </Item> <Item> <!-- Product referenziert die GUID von Zink--> <Product>1010229</Product> <Amount>1</Amount> <!-- Internes Zink-Lager --> <StorageAmount>3</StorageAmount> </Item> </FactoryInputs> <FactoryOutputs> <Item> <!-- Product referenziert GUID von Dosenfutter--> <Product>1010217</Product> <Amount>1</Amount> <!-- Internes Dosenfutter-Lager --> <StorageAmount>4</StorageAmount> </Item> </FactoryOutputs> <!-- Zeit in sekunden, in der 1t produziert wird --> <CycleTime>45</CycleTime> </FactoryBase> <LogisticNode /> <AmbientMoodProvider /> <Maintenance> <!-- Unterhaltskosten --> <Maintenances> <Item> <!-- Geld --> <Product>1010017</Product> <Amount>40</Amount> <InactiveAmount>20</InactiveAmount> </Item> <Item> <!-- Obrero Arbeitskraft --> <Product>1010367</Product> <Amount>25</Amount> </Item> </Maintenances> </Maintenance> <Attackable /> <Pausable /> <IncidentInfectable /> <Industrializable> <!-- Boost durch Strom unmöglich machen, da es in der neuen Welt keine Kraftwerke gibt. --> <BoostedByIndustrialization>0</BoostedByIndustrialization> </Industrializable> <Culture /> <QuestObject /> <Electrifiable /> </Values> </Asset>
Code ausklappen

Nur eines fehlt noch – das visuelle. Anno trennt mehr oder weniger Grafik von Logik. Alles mit Grafiken wird separat gebastelt, wir geben nur den Dateipfad vom Ordner „data“ zur .cfg an (Der Hauptdatei einer Grafik, die alles zusammensetzt), bzw. zum Icon.

Grafiken zuweisen[ Codeblock kopieren ]
<Asset> <Template>FactoryBuilding7</Template> <Values> <Standard> <GUID>1337733142</GUID> <Name>Canned Food Factory 02 - Variant uses Potatoes and Zinc</Name> <IconFilename>data/ui/2kimages/main/3dicons/icon_canned_goulash.png</IconFilename> </Standard> <Building> <AssociatedRegions>Colony01</AssociatedRegions> <BuildModeRandomRotation>90</BuildModeRandomRotation> </Building> <Blocking> <HidePropsRadius>0</HidePropsRadius> <DeletePropsRadius>0</DeletePropsRadius> </Blocking> <Cost> <Costs> <Item> <Ingredient>1010017</Ingredient> <Amount>10000</Amount> </Item> <Item> <Ingredient>1010196</Ingredient> <Amount>50</Amount> </Item> <Item> <Ingredient>1010205</Ingredient> <Amount>30</Amount> </Item> <Item> <Ingredient>1010218</Ingredient> </Item> <Item> <Ingredient>1010207</Ingredient> </Item> <Item> <Ingredient>1010202</Ingredient> </Item> </Costs> </Cost> <Object> <Variations> <!-- Mehrere .cfg bedeuten, dass man mit Shift+V durchschalten kann --> <Item> <Filename>data/graphics/buildings/production/workshop_colony01_01/workshop_colony01_01.cfg</Filename> </Item> </Variations> </Object> <Mesh /> <Selection> <ParticipantMessageArcheType>SA_Resident_tier02_atWork</ParticipantMessageArcheType> </Selection> <Text /> <Constructable /> <Locked /> <SoundEmitter /> <FeedbackController /> <Infolayer /> <UpgradeList /> <Factory7 /> <FactoryBase> <FactoryInputs> <Item> <Product>1010195</Product> <Amount>1</Amount> <StorageAmount>6</StorageAmount> </Item> <Item> <Product>1010229</Product> <Amount>1</Amount> <StorageAmount>3</StorageAmount> </Item> </FactoryInputs> <FactoryOutputs> <Item> <Product>1010217</Product> <Amount>1</Amount> <StorageAmount>4</StorageAmount> </Item> </FactoryOutputs> <CycleTime>45</CycleTime> </FactoryBase> <LogisticNode /> <AmbientMoodProvider /> <Maintenance> <Maintenances> <Item> <Product>1010017</Product> <Amount>40</Amount> <InactiveAmount>20</InactiveAmount> </Item> <Item> <Product>1010367</Product> <Amount>25</Amount> </Item> </Maintenances> </Maintenance> <Attackable /> <Pausable /> <IncidentInfectable /> <Industrializable> <BoostedByIndustrialization>0</BoostedByIndustrialization> </Industrializable> <Culture /> <QuestObject /> <Electrifiable /> </Values> </Asset>
Code ausklappen

Schau dir die letzten beiden Beispiele nochmal genau an. Besonders achten solltest du auf Building, Maintenance, Object und FactoryBase, diese Container wirst du oft brauchen.

Jetzt haben wir auch ein voll funktionstüchtiges Asset, das Anno korrekt interpretieren und darstellen kann.
Beim nächsten Mal basteln wir unser Asset in das Baumenü, setzen dafür einen Text und schalten es frei.