Browse Source

first live test

Karathan 5 years ago
parent
commit
8c27f2206f

BIN
assets/animation_text_rez.gif View File


BIN
assets/animation_tileset.gif View File


BIN
assets/favicon.ico View File


+ 2
- 2
config/config.yml View File

@@ -1,8 +1,8 @@
1 1
 ##
2 2
 # Basic
3 3
 #
4
-site_title: Stellar Dev                    # The title of your website
5
-base_url: ~                         # Pico will try to guess its base URL, if this fails, override it here
4
+site_title: Karathan                # The title of your website
5
+base_url: https://dev.karathan.at   # Pico will try to guess its base URL, if this fails, override it here
6 6
                                     #     Example: http://example.com/pico/
7 7
 rewrite_url: true                   # A boolean (true or false) indicating whether URL rewriting is forced
8 8
 timezone: UTC                       # Your PHP installation might require you to manually specify a timezone

+ 9
- 0
content/404.md View File

@@ -0,0 +1,9 @@
1
+---
2
+Title: Error 404
3
+Robots: noindex,nofollow
4
+---
5
+
6
+Error 404
7
+=========
8
+
9
+Woops. Looks like this page doesn't exist.

+ 5
- 10
content/_meta.md View File

@@ -3,15 +3,10 @@ social:
3 3
     - title: "Gitea"
4 4
       url: "https://gitlab.karathan.at"
5 5
       icon: "fa-github alt"
6
-    - title: "Facebook"
7
-      url: "https://facebook.com"
8
-      icon: "fa-facebook alt"
9
-AddressStreet: "Strozzigasse 36"
10
-AddressTown: "Wien"
11
-AddressZip: "1080"
12
-AddressCountry: "Österreich"
13
-Phone: "+43 650 55 96 688"
14 6
 Email: "philipp@karathan.at"
15
-SloganTitle: "Slogan"
16
-Slogan: "Eisiger Wind trägt mein Lied über's Feld"
7
+FooterTitle: "Support"
8
+FooterText: "Maybe add some buy me a coffee button idk"
9
+Slogan: "Eisiger Wind trägt mein Lied übers Feld"
10
+Copyright: "Karathan"
11
+Branch: "dev"
17 12
 ---

+ 9
- 0
content/about-me.md View File

@@ -0,0 +1,9 @@
1
+---
2
+Title: Über mich
3
+Main: True
4
+Section: aboutSection
5
+---
6
+
7
+Mein Name ist Philipp "Karathan" Auer. Ich bin ein Student der technischen Informatik an der TU Wien. Ich bin leidenschaftlicher Entwickler, Bastler und Hobbyschriftsteller. Auf dieser Seite findest du ein paar Projekte von mir, für die ich mir die Zeit genommen habe, ein paar Zeilen über sie zu verlieren. Viel Spaß beim Stöbern!
8
+
9
+Ich bin selbstständiger Web-Entwickler, respektive fand ich auch Gefallen daran, meine eigene Seite zu gestalten. Falls es dich interessiert, im Backend läuft eine Instanz von PicoCMS, den gesamten Code findest du bei meinen restlichen Git Repositories, auf meiner Gitea Instanz.

+ 34
- 0
content/gamedesign/about-procedural.md View File

@@ -0,0 +1,34 @@
1
+---
2
+Title: "Über: Procedural Generation"
3
+Description: Eine Mechanik, die wir alle nun schon allzu gut kennen. Eine Karte, erschaffen von einem Algorithmus, eine Welt, designed von deiner CPU. In diesem Artikel nur ein paar Gedanken über diese weit verbreitete Technik.
4
+Template: article
5
+ReleaseIndex: 0
6
+---
7
+
8
+Ein paar Worte über Procedural Generation, ein mächtiges Werkzeug. Eine Insel, eine Weltkarte oder ein ganzes Universum, immer wieder neu generiert von einem Computer, in Rekordgeschwindigkeit. Das alles ist Procedural Generation. Dieser Artikel soll nicht davon handeln wie genau eine Welt generiert werden kann, wenngleich die Mathematik hinter einem solchen algorithmischen Monster sicherlich äußerst interessant ist. Ich will verschiedenste Anwendungen diskutieren und dabei eine Grundfrage beantworten: Wann ist es sinnvoll, seine Welt von einem Computer generieren zu lassen?
9
+
10
+Sicherlich wird der ein oder andere Entwickler eine andere Antwort auf diese Frage geben. Ich bin mir vorweg sicher, eine eindeutige Antwort wird man nicht finden können, aber einige Extrembeispiele zeigen, wie es (nicht) geht.
11
+
12
+## Was nun eigentlich?
13
+
14
+Fangen wir damit an, uns einen Rahmen zu bauen. Was genau meinen wir eigentlich mit Procedural Generation? Nun, gerade um diesen Artikel auf einen Kernpunkt zu beschränken: Eine Spielwelt, generiert von einem Computer, grundsätzlich ohne Hilfe eines Leveldesigners. Erzeugt werden kann eine nicht näher definierte, sehr große Anzahl an Welten, jedenfalls ohne dass der Leveldesigner seine magischen Fähigkeiten an jeder einzelnen von ihnen anwenden muss. Natürlich ist es in der Praxis oft so, dass eine Anzahl an im vorhinein erzeugten Strukturen und Parametern dem Generator dabei hilft, sein Ziel zu erreichen. Diese Parameter könnten unter anderem festlegen, wie viel Wald auf einer Karte sein soll, wie groß die Berge sein sollen, etc.
15
+
16
+## Sounds good, does it work?
17
+
18
+Ans Eingemachte: Nicht gerade wenige Videospiele vertrauen darauf, Welten „ex nihilo“ zu erzeugen. Also ja, es funktioniert. Wir nehmen also einfach einen Algorithmus und schnallen ihn auf all die Spiele, die wir kennen und lieben, und schwupps: Endloser Spielspaß… Naja. Dass es so nicht geht, sollte wohl auch jedem klar sein. Aber wo genau ist denn nun die Grenze? Wann wird ein Spiel besser, wann vielleicht gar schlechter? Ein Grundgedanke bei diesem ganzen Konzept ist es, eine Welt zu erschaffen, deren Rahmenbedingungen man festgelegt hat. Man hat sie nicht erschaffen, aber man war doch maßeblich am Designprozess beteiligt.
19
+
20
+> A world, not of my making, yet a world of my design
21
+
22
+Ein Zitat aus einem alten [64K Video](https://www.youtube.com/watch?v=ZfuierUvx1A) (Zumindest konnte ich es soweit zurück verfolgen) – Das impliziert aber auch, dass gerade eben keine Details in solche Welten einfließen können. Dieses Haus am See, das von einer alten Hexe bewohnt wird, welche einen umfassenden Dialog mit dem Spieler führt, eine faszinierende Geschichte hat und obendrein noch verdammt viele Warzen im Gesicht? Sie wird nicht existieren in einer Welt, die eben nicht von einem Designer erstellt wurde. (Es sei denn, der Algorithmus weiß genau bescheid von unserer Hexe, aber das wäre ja geschummelt, sie wäre dann ja nicht mehr generiert worden)
23
+
24
+Worauf will ich hier also hinaus, eine Maschine wird uns keine Geschichten erzählen. Die Kunst dessen, des Erzählens, ist etwas, das Maschinen uns Menschen noch nicht abnehmen können. Ja allein die semantischen Eigenschaften unserer Sprachen sind oftmals für Maschinen noch schwer zu verstehen. Das schließt also aus, ein Spiel, welches viele und umfangreiche Geschichten zu erzählen hat von einer Maschine generieren zu lassen (Beziehungsweise dessen Welt und somit auch die Geschichte selbst)
25
+
26
+Was macht nun ein Spiel aus, dass man sagen kann, eine generierte Welt ist besser, als eine von einem Designer erstellte. Nun, meine einfache, und möglicherweise umstrittene, Antwort lautet: Nichts. Ein Leveldesigner wird immer eine bessere Welt erschaffen können als ein Algorithmus. Nun, ich bin leidenschaftlicher Mathematiker, ein Beweis für diese These könnte wie folgt aussehen: Man nehme eine beliebige, von einem Algorithmus generierte Welt. Findet man nun etwas an dieser Welt, was man als Menschlicher Designer verbessern hätte können (Beurteilt von einer externen Referenzgruppe) – So ist das Level schlechter als das neue, verbesserte Level. Im großen und ganzen, wird man so etwas immer finden. Dieses Dorf in Minecraft, in dem das Haus eines Bewohners auf einer Säule aus Stein steht, weil der Ausgangspunkt auf dem anliegenden Berg liegt? Jap, und das ist nur ein einfaches Beispiel, man stelle sich vor jede Karte in Minecraft würde aussehen wie eine von den vielen, von Spielern gebauten Karten, mit ihren eigenen kleinen Geschichten und Gimmicks – Nur leider ist dem nicht so, und es ist an der Zeit, für die Generatoren ihren Schein auf die Welten fallen zu lassen, die sie erschaffen.
27
+
28
+## Die Grenzen von Design
29
+
30
+Wir alle haben unsere Grenzen. Die Grenzen eines Computers, sind, in einem Aspekt, Geschichten zu erzählen, aber auch andere Designentscheidungen zu treffen. Und die Grenzen eines Menschen sind, nun, Geschwindigkeit. Eine von einem Menschen erstellte Karte in Minecraft kommt wohl kaum mit einer Produktionszeit von unter einem Monat aus, vielleicht einem Jahr oder noch länger. Ähnliche Zeitspannen gelten natürlich auch für jede andere Spielwelt, die von einem Menschen geschaffen wurde. Und ein Spiel, dass sich eben darauf verlässt, eine so große Zahl an Welten sein Eigen nennen zu dürfen, muss sich auch Zwangsweise auf seine Algorithmik verlassen. Es erhöht die Replayability eines Spiels maßgeblich, wenn die Welt bei jedem Versuch eine andere ist. Nur muss sich der Entwickler dann eben auch im Klaren sein, was damit verbunden ist und wie man manche Probleme am Effektivsten löst (Wobei es auf manche Probleme wohl überhaupt keine Lösung gibt)
31
+
32
+## Fazit
33
+
34
+Nun gut, keine Lore und Geschichte mit einem Procedural Generation Algorithmus, dann ist alles gut? Naja, ja und nein. Zum einen muss ein Spiel, wenn es überhaupt keine Geschichte erzählen kann, umso mehr mit seinem Gameplay glänzen. Zum anderen gibt es auch Titel, die sowohl mit Geschichte, als auch mit endlosen Welten prahlen können. Man nehme als Beispiel die Pokémon Spin-Off Reihe „Mystery Dungeon“ oder „Diablo“ aus dem Hause Blizzard. Wie funktioniert das hier, wenn ich doch vorhin gesagt habe, dass so ein Algorithmus keine Geschichten erzählen kann. Nun, die Geschichte wird hier einfach von den Inhalten erzählt, die eben von Hand gemacht wurden. Cinematics, von Hand erstellte Karten und scripted Szenen bauen eine Geschichte auf, während das eigentliche Gameplay in einer anderen, nicht vom Designer erstellten Welt stattfindet. Noch dazu sind die meisten dieser Welten Höhlen, welche sehr einfach generiert und verschönert werden können. Man hat mit Procedural Generation also ein mächtiges Werkzeug, muss sich aber im Klaren sein, dass man selbst immer noch Entwickler eines Spiels sein muss, der sein Werkzeug verwenden kann. Das Werkzeug wird die Arbeit nicht von selbst verrichten.

+ 6
- 0
content/gamedesign/index.md View File

@@ -0,0 +1,6 @@
1
+---
2
+Title: Game Design
3
+Template: generic
4
+---
5
+
6
+Eine nicht unwesentliche Frage, wenngleich auch unbeantwortet, ist jene, ob ich mich selbst eigentlich als Game Developer bezeichnen würde. Nun, zumindest beschäftige ich mich in meiner Freizeit viel damit. Videospiele waren lange Zeit Teil meines Lebens und werden das wohl noch ein wenig bleiben. Als „Hobbyist“ hat man viele Freiheiten, insofern ist es wohl alles andere als ein Problem ein solcher zu sein. In diesen Artikeln finden sich daher meine Gedanken, als Entwickler, aber auch als Spieler, die hoffentlich dazu beitragen werden auch andere auf den Kurs eines Entwicklers zu lenken, oder ein paar Perspektiven zu ergründen, die man vielleicht als typisches, sich der „Gamer“ Subkultur zuzuordnendes Individuum nicht unbedingt wahrnimmt.

+ 22
- 6
content/index.md View File

@@ -1,11 +1,27 @@
1 1
 ---
2
-Title: Welcome
3
-Main: True
2
+Title: Willkommen
3
+Main: False
4 4
 Section: WelcomeSection
5 5
 Image: pic02.jpg
6
-ActionName: Email Me
7
-ActionTarget: literature
8
-ActionPrimary: True
9 6
 ---
10 7
 
11
-Just some more text to have more than just a single line going y'all dev bastards know your stuff! No hate tho' - I'm a dev too so it's cool, amiright, lel.
8
+<ul class="features">
9
+    <li>
10
+        <span class="icon major style1 fa-pencil"></span>
11
+        <h3>Literatur</h3>
12
+        <p>Das geschriebene Wort ist eine der mächtigsten Waffen des Menschen. Lerne die Gedanken von Autoren kennen, und finde deine eigene Inspiration in ihnen wieder.</p>
13
+        <a href="%base_url%/literature" class="button primary">Erfahre</a>
14
+    </li>
15
+    <li>
16
+        <span class="icon major style4 fa-gamepad"></span>
17
+        <h3>Game Design</h3>
18
+        <p>Die Kunst Welten zu erschaffen, ihre Bewohner, gesellschaftlichen Strukturen, Flora, Fauna und anschließend zum Leben zu erwecken. All das und noch viel mehr ist Game Design. Lerne die andere Seite der vierten Wand kennen.</p>
19
+        <a href="%base_url%/gamedesign" class="button primary">Erschaffe</a>
20
+    </li>
21
+    <li>
22
+        <span class="icon major style3 fa-code"></span>
23
+        <h3>Programmierung</h3>
24
+        <p>Eine der Wohl kreativsten Arten, seine Zeit zu verbringen ist das Lösen von Problemen. Programmieren ist eine vielseitige Kunst. Lerne hier die Sprache der Maschinen kennen.</p>
25
+        <a href="%base_url%/programming" class="button primary">Erfinde</a>
26
+    </li>
27
+</ul>

+ 2
- 9
content/literature/index.md View File

@@ -1,13 +1,6 @@
1 1
 ---
2
-Title: Test
3
-Description: Testing the description
4
-Main: True
5
-Section: testSection
6
-Image: pic04.jpg
7
-ActionName: Email Me
8
-ActionTarget: mailto:karathan@karathan.at
9
-ActionPrimary: True
2
+Title: Literatur
10 3
 Template: generic
11 4
 ---
12 5
 
13
-Some more testing
6
+Die Literatur als Kunstform entdeckt fast ein jeder einmal für sich. Ein Buch, ein Gedicht, der Text zu einem Lied. Die Geschichte eines Videospiels, all das ist Literatur. Hier findet sich meine Welt dessen, in Form eines breiten Fächers dessen, was sie ist. Konzepte, Ideen, Gedanken, zu meinen eigenen Werken, zu denen anderer.

+ 12
- 0
content/literature/trank.md View File

@@ -0,0 +1,12 @@
1
+---
2
+Title: "Der Trank"
3
+Description: Ein Text aus einer nächtlichen Laune, und ich glaube entstanden aus einer dieser "One Line Premisses" entstanden, wenn ich mich nicht irre.
4
+Template: article
5
+ReleaseIndex: 1
6
+---
7
+
8
+Spinnennetze zieren den Raum, der Geruch von Moder und Chemie liegt in der Luft. Der Boden knarzt bei jedem Schritt. Ein Lichtstrahl lässt den Staub neben dem Fenster tanzen. Die Eingangstür ist verschlossen und mit einer großen Eisenstange verriegelt. Die Wände sind geschmückt mit Bücherregalen voller Wälzer, die Seiten vom Licht gebräunt, alt. In der Mitte steht ein Tisch, einsam und alleine. Eine Phiole sitzt auf ihm, sie ist eckig und im Licht blitzen ein paar Kratzer aus dem Glas. Ein Korken steckt im Hals des Gefäßes, an seiner Vorderseite fehlt ein Splitter. Um den Flaschenhals ist eine Schnur gebunden, sie hängt an der Vorderseite der Phiole herunter, an ihrem Ende ist eine violette Glasperle befestigt, die ausgefranste Schnur hängt darüber.
9
+
10
+Die Phiole ist gefüllt mit einer orangefarbenen Flüssigkeit, sie leuchtet leicht, und lässt den Staub um sich herum ebenfalls tanzen. Bei näherer Betrachtung erkennt man um die tanzenden Staubkörner auch orange leuchtende Glühwürmchen, sie umkreisen die Flasche, unermüdlich. Auf der Tischplatte sind Aderförmige Kerben zu sehen, sie bilden ein Muster um den Trank, und pulsieren in demselben Orange wie auch schon der Rest des Spektakels.
11
+
12
+Auf der Rückseite der Phiole klebt ein Ettiket, ganz oben ziert ein Kreis, violett, wie schon die Perle vorne, gefüllt mit einem violetten Punkt. Darunter steht in roten Lettern: „Trink mich, ich bin dein Heil, und du wirst meines sein.“

+ 20
- 0
content/literature/welt-aus-asche.md View File

@@ -0,0 +1,20 @@
1
+---
2
+Title: "Eine Welt aus Asche"
3
+Description: Eine weitere kurze Charaktergeschichte, für einen weiteren Rollenspielcharakter. Der Text ist schon etwas älter und lag bei mir auf der Platte herum, es geht um Yalena, eine Zauberin ("Sorceress") aus Dungeons & Dragons, die sich auf einer Reise vor ihrer Kampagne einen anderen Begleiter gefunden hat. (Und eine Portion Wahnsinn)
4
+Template: article
5
+ReleaseIndex: 2
6
+---
7
+
8
+Die blauen Kristalle leuchten den Weg durch die düstere Höhle, die Yalena erkundet. Nebel reflektiert das Licht durch den schmalen Gang und durch die Löcher in der Decke des Felsens strahlt der Mond herunter. Lange schon hat kein Wesen mehr diesen Ort betreten, Yalena spürt wie die Energie der Alten hier schlummert, nach der sie sich sehnt. Was hier wohl versteckt liegt – Welche Macht – Der Gedanke kommt plötzlich, und verschwindet alsbald in einem blauen Nebelschwall. Die Geheimnisse der Welt könnten unter den Füßen des jungen Mädchens liegen, ihre Augen funkeln bei dem Gedanken an all das, was man hier verschlossen hielt.
9
+
10
+Ein Gefühl von Freiheit strömt durch Yalenas Körper, als könnte man sie durch die Nase atmen. Freiheit, zu tun was sie will, ein schönes Gefühl. Tiefer hinein in der Höhle wird das Gefühl noch stärker, der Nebel dichter, und das blaue Licht der Kristalle, es schwindet. Mit einer Hand zieht das Mädchen eine mit Pech getränkte Fackel aus ihrem kleinen Rucksack, umfasst den Kopf mit einer Hand und entfacht eine lodernde Flamme, die den Raum wieder erhellt. Das Licht, vom Nebel wieder durch den Raum geworfen, hüllt die Felsen in ein beruhigendes, flammendes rot. Wieder funkeln Yalenas Augen bei dem Anblick – Mein Feuer, es brennt so hell. Kleine Funken sprangen wie Glühwürmchen von der Fackel durch den Raum, doch bald war auch die Fackel in ihrer Hand nicht mehr stark genug, den dichten Nebel zu durchdringen, der diesen Ort heimsucht – So hell, und doch bin ich so schwach.
11
+
12
+*"Dein Feuer, es zeigt dir den Weg, den Weg in die Freiheit"* – Die Stimme ist klar verständlich, als wären es Yalenas eigene Gedanken, die durch ihren Kopf sausen wie die Funken erst durch den Nebel, aber dachte sie das wirklich? 
13
+
14
+Das Feuer ihrer Fackel begann in einer Stichflamme emporzusteigen, gegen die der Nebel keine Chance mehr hatte. War sie das? Die Kristalle der Höhle wurden wieder klarer, ihre Farben erheben sich wieder über die Nebelschwaden, diese schönen – blutroten – Kristalle an der Wand. Wie konnte Yalena einst ohne sie sehen? Das Blut beginnt in ihren Adern zu pochen, sie fühlt die Macht, die Freiheit, zu tun was sie wollte, so schön und rein, wie das Feuer ihrer Fackel, das schöne, reine Feuer ihrer Fackel.
15
+
16
+*"Komm zu mir, ich bin was du suchst, diese Macht, du spürst sie in dir, ich kann sie dir zeigen!"* – Das sind nicht ihre Gedanken, aber das Ziel scheint so nahe, durch einen Bogen aus blutroten Steinen, Yalenas Sicht füllt sich mit ihnen, diesen Steinen. Ist das ihre Fackel dort am Boden? Sie braucht keine Fackel mehr, das Licht der Kristalle ist hell genug, hell genug die Schrecken der Nacht zu vertreiben, und diesen Nebel, den schrecklichen Nebel.
17
+
18
+*"Du hast mich gefunden, du wusstest, wer ich bin, oder? Du konntest nicht anders, als der Macht zu folgen, die du hier glaubst zu finden. Ich will sie dir zeigen, deine Begierde. Du sollst mein Segen sein, und ich dein Schutzpatron. Du hast es erkannt oder? Die Welt ist hässlich, bewohnt von Wesen, die ihre Gunst nicht verdienen. Ich habe es erkannt, du kannst es sehen oder? Du willst sie befreien, von all den Parasiten, von denen sie befallen ist. Ich kann dir helfen, helfen eine neue Welt zu bauen. Wenn du mich willst, mit all meiner Macht die du gespürt hast, dann sprich mir nach!"*
19
+
20
+Und Yalenas stimme einte sich mit der Stimme in ihrem Kopf: *"Eine Welt aus Asche!"* – *"Eine Welt aus Asche!"*

+ 19
- 0
content/literature/winds-of-conquest.md View File

@@ -0,0 +1,19 @@
1
+---
2
+Title: "English Corner: Winds of Conquest"
3
+Description: This is an abstract story about Aaron Ironheart, glorious captain of his own ship, the White Widow, and possible dwarven fighter in a sleepless night of dungeons & dragons. I created this piece of text in order to give myself a better idea of my characters ideals and emotions, but also, to set the scene for the start of an epidramatic adventure. (Well, if that one ever happens that is)
4
+Template: article
5
+ReleaseIndex: 0
6
+---
7
+
8
+Dusk laid his hands upon the bay of Aegisgard, its people buried beneath in peaceful, silent slumber. Aaron Ironheart set foot to meet his crew at the dock of the “White Widow”,of which he was captain. The days of glory had come, or so he thought. Come dawn they hissed their banners high, the warbanners of Aegisgard in their respective colors: A burning red cannonball on a shiny silver dwarven shield, drawn upon a blue starfield.
9
+
10
+“The days have come, my comrades, it is time to prove our courage. Why do we fight, they asked me once, if all we want is peace? Because there are people out there, who want a war, and a war is what they’ll get.” – “For the glory of Aegisgard, long live the king, may the dwarves never succumb!”
11
+
12
+*Dear Maira, if you have received this letter, I have not yet returned, and I am likely to be a floating corpse deep down in the damned blue sea – bugger! Do not mourn me my love, for I gave my life, so you can live on in peace. Smile, for the world is now a better one, take a deep breath of the air that smells from the lavender fields you always took me too, aye, I can smell them even now as I am writing this letter, I’ll make sure to bring you a couple before I go home. Harro must have finished his training by now, he’ll be sure to keep you safe and fed with the weapons he’s crafting for the armies. I never liked that you know me. Water under my ship is all I need to stay alive – well… The sea has me now, it won’t give me back, stay safe Maira…*
13
+
14
+*With never ending love,*  
15
+*Aaron*
16
+
17
+A small tear washed over the ink of the letter before it lit up into flames before a weeping girl, kneeling in the ashes of her hometown, the city that shall never fall, Aegisgard.
18
+
19
+The text was inspired by a majestic piece of music written by BrunuhVille: [Winds of Conquest](https://www.youtube.com/watch?v=772Xg4X70IA). It consists of a short introduction, a letter written by Aaron, and, well, the last 2 lines. It is held short on purpose, so everyone can get a picture of who Aaron Ironheart is, without going too much into detail, it is meant as a basis for roleplaying in the end.

+ 6
- 0
content/programming/index.md View File

@@ -0,0 +1,6 @@
1
+---
2
+Title: Programmierung
3
+Template: generic
4
+---
5
+
6
+Es ist schon faszinierend, wie die Welt am heutigen Tag nicht ohne Computer funktionieren kann. Sie sind die Technik, die unsere Zeit vermutlich stärker verändert hat, als die industrielle Revolution. Wir haben heute ein weltweites Netzwerk, ein E-Mail Mitteleuropa, nach Australien, in ein paar Millisekunden. Die Faszination dafür ist groß. Hier soll es also um diese Technik gehen, im eigentlichen Sinn, wie auch im Metaphysischen – Komischerweise sind die Techniker selbst auch immer die größten Kritiker ihrer eigenen Werke.

+ 121
- 0
content/programming/inside-sovereign.md View File

@@ -0,0 +1,121 @@
1
+---
2
+Title: "Inside Sovereign: Tileset-Animationen"
3
+Template: article
4
+Description: Ein kleiner Auszug aus der Welt des Romhacking, wie funktionieren Tileset Animationen in Pokémon Feuerrot?
5
+ReleaseIndex: 0
6
+---
7
+
8
+Inside Sovereign ist ein Format, in dem ich über einige aktuelle Entwicklungen meines Romhacks „Pokémon Sovereign of the Skies“ berichte, oder aktuelle Geschehnisse berichte. Die entnommenen Codebeispiele sind dem Source Code von Pokémon Sovereign of the Skies oder dem Sovereign of the Skies g3headers Fork entnommen. Es handelt sich bei Pokémon Sovereign of the Skies um einen Romhack des Spiels Pokémon Feuerrot (U) [BPRE v1.0]. Etwaige Adressierung und Funktionalität ist möglicherweise nicht auf andere Versionen des Spiels übertragbar. Es werden desweiteren Begriffe aus Romhacking, Programmierung und anderen technischen Disziplinen verwendet, die für das Verständnis dieses Artikels möglicherweise essentiell sind.
9
+
10
+___
11
+
12
+Ein, nun, nicht unbedingt ungelöstes, Mysterium der Pokémon Romhacker waren die Animationen, die auf verschiedenen Maps angezeigt werden. Im Grunde ist die Materie simpel, nur gab man sich damit zufrieden, die Animationen mit dem „Tileset Animation Editor“ zu bearbeiten, einem Tool von Lu-Ho. Das Tool ist alt, aber in der Lage, Animationen wie sie im Originalspiel vorkommen, zu erstellen:
13
+
14
+<span style="display:block;text-align:center">![Einfache Tileset Animation](%base_url%/assets/animation_tileset.gif#center)</span>
15
+
16
+Das Tool überschreibt dabei die Bedeutung des function Feldes in der MapBlockset Struktur des Spiels:
17
+
18
+```C
19
+/**
20
+ * Blocks from which the map is constructed.
21
+ */
22
+struct MapBlockset {
23
+    /**
24
+     * Whether the tiles are compressed or not.
25
+     */
26
+    bool compressed;
27
+
28
+    /**
29
+     * Whether this tileset is to be used as a secondary tileset or primary tileset.
30
+     */
31
+    bool secondary;
32
+    u16 padding;
33
+ 
34
+    /**
35
+     * Tiles used to build blocks.
36
+     */
37
+    void* tiles;
38
+ 
39
+    /**
40
+     * Palettes for the blockset.
41
+     */
42
+    void* palettes;
43
+ 
44
+    /**
45
+     * Block description.
46
+     */
47
+    struct MapBlock* blocks;
48
+ 
49
+    /**
50
+     * Tileset initialization function. Called to set up animation functions.
51
+     */
52
+    void (*function)(void);
53
+ 
54
+    /**
55
+     * Block behaviours.
56
+     */
57
+    struct MapBlockBehavior* behaviors;
58
+};
59
+```
60
+
61
+In einem Vanilla Rom ist dieses Feld einfach ein wie oben beschriebener Funktionspointer, welcher die Animationen des aktuell geladenen Blocksets initialisieren soll. Das geschieht zum Beispiel, wenn eine Map betreten wird, oder ein komplexes Menü geladen wird. (Sprich: Wenn die Tilesets vorher entladen wurden, weil die Character Base im VRAM für andere Grafiken benötigt wird)
62
+
63
+### Die Grenzen des Animations Editors
64
+
65
+Der „Tileset Animation Editor“ überschreibt einen Teil des Loaders mit eigenem Assembly Code, und sorgt so dafür, dass dieses Feld keine Funktion mehr, sondern eine eigene Struktur darstellt, welche einfach ein Framework für Standard Animationen wie oben gegeben bietet. Dadurch verschließt sich natürlich die Möglichkeit, sich selbst eine Animation zu bauen, die nicht nur einfach Frame für Frame im Tileset agiert. (Wie eine GIF Animation)
66
+
67
+Wenn man diesen Animations Editor nicht benutzt, kann man sich natürlich trotzdem eine Methode schreiben, die nach einer Standard Struktur Tiles im VRAM ersetzt. Das zu tun, ist grundsätzlich die Hauptaufgabe dieses Konstrukts. Wenn man dafür nicht einfach das function Feld der MapBlockset Struktur überschreibt, hält man sich aber die Möglichkeit für ein paar mehr Animationen offen, die man sonst nicht hätte.
68
+
69
+### Die Alternative: Handarbeit
70
+
71
+Grundsätzlich geht es also zuerst darum, einen Initializer zu haben. Dieser bereitet etwaige Grafiken vor, und wird immer ausgeführt, wenn das Tileset neu geladen werden muss. Dort werden ein paar Konstanten, so wie der Handler für das jeweilige Blockset festgelegt. Die Konstanten sind `blockset_???_current_frame (current_frame)` sowie `blockset_???_max_frame (max_frame)`. Hierbei handelt es sich um Frame Counter, sie zählen jeweils hinauf bis max_frame und resetten anschließend wieder auf 0. So kann man relativ einfach seine Animationstimings lösen. Für max_frame wählt man am besten einen Wert, der relativ viele Frequenzteiler der später definierten Animationen enthält. Optimal währe das kleinste gemeinsame Vielfache jener Frequenzen, die über den Frame Counter abgewickelt werden. Das hängt ganz einfach damit zusammen, dass man später seine Animationen vermutlich über eine Modulo Operation in die verschiedenen Frames einteilen will. Wenn die Frequenz einer Animation also kein Teiler des max_frame ist, kann es passieren, dass Ungenauigkeiten entstehen, wenn der Frame Counter sein Maximum erreicht. In dieser Beispielmethode, welche den Main Initializer von Pokémon Sovereign of the Skies darstellt, wird max_frame einfach auf 0x280 gesetzt. Dieser Wert enthält relativ viele Teiler, und etwaige Ungenauigkeiten sind kaum sichtbar, wenn das Wasser eines Sees einmal 1-2 Frames früher aktualisiert wird (Das sind Zeiten von bis zu 2/60 Sekunden, da der GBA im Optimalfall mit 60 FPS läuft)
72
+
73
+```C
74
+void main_animator_init(void) {
75
+    blockset_one_current_frame = 0;
76
+    blockset_one_max_frame = 0x280;
77
+    blockset_one_animator = main_animator;
78
+}
79
+```
80
+
81
+Der `blockset_???_animator` (Handler) ist nun verantwortlich, die Tiles auch wirklich zu aktualisieren. Er folgt dem Muster `void main_animator(u16 current_frame);`. `current_frame` wird der globalen Variable `blockset_???_current_frame` entnommen, je nachdem welcher Handler gerade ausgeführt wird. (Für zwei Tilesets existieren natürlich zwei Handler)
82
+
83
+Es bietet sich an, eine deskriptive Struktur für seine Animation zu verwenden:
84
+
85
+```C
86
+struct TilesetAnimation {
87
+    u16 tile_start;
88
+    u16 frame_length;
89
+    u16 tile_length;
90
+    u16 frame_count;
91
+    const void *image;
92
+};
93
+```
94
+
95
+So oder so ähnlich, geschieht das auch im Animations Editor. (Nur dass wir sie dann verwenden müssen) Ein Beispiel für eine solche Standardmethode könnte so aussehen, sie wird dann einfach vom main_animator mit den entsprechenden Parametern ausgeführt:
96
+
97
+```C
98
+void animate_from_structure(const struct TilesetAnimation *anim, u16 tile_skip, u16 current_frame) {
99
+    void *vram_address = (void *)(0x06000000 + (tile_skip * 0x20));
100
+    u8 current_animation = 0;
101
+    while (anim[current_animation].image != (void *)0xFFFFFFFF) {
102
+        void *current_vram = vram_address + (0x20 * anim[current_animation].tile_start);
103
+        u16 max_frame = anim[current_animation].frame_length * anim[current_animation].frame_count;
104
+        u16 used_frame = current_frame % max_frame;
105
+        used_frame /= anim[current_animation].frame_length;
106
+        memcpy(current_vram, anim[current_animation].image + (0x20 * anim[current_animation]
107
+            .tile_length * used_frame), anim[current_animation].tile_length * 0x20);
108
+        current_animation++;
109
+    }
110
+}
111
+```
112
+
113
+Hierbei wird zuerst eine Zieladresse berechnet (Je nachdem ob wir gerade im ersten, oder im zweiten Tileset agieren) – Danach durchforsten wir unsere Liste an Animationen und kopieren jeweils mit memcpy den aktuell zu verwendenden Tile Block. Hier wird jeden Frame etwaig alles kopiert, was nicht unbedingt für die Performance der Routine spricht, aber an Memory spart. Mit den hier gewonnenen Informationen, sollte es aber ein Leichtes sein, eine eigene Methode zu schreiben, die ein Tileset animiert.
114
+
115
+### Vorteile der Methode
116
+
117
+Ich habe gezeigt, wie man die Funktionalität eines alten Tools mit eigenem Code nachbauen kann, aber natürlich ist es jetzt auch möglich, Animationen zu erzeugen, die früher nicht möglich gewesen werden. Ein kleines Beispiel sind Anzeigetafeln aus Pokémon Sovereign of the Skies, welche den Weg oder aus Städten weisen:
118
+
119
+<span style="display:block;text-align:center">![Schriftzug Animation](%base_url%/assets/animation_text_rez.gif#center)</span>
120
+
121
+Ein Codebeispiel dazu, lässt sich auch im Github Repository von Pokémon Sovereign of the Skies finden: [Text Animator](https://github.com/SBird1337/source_of_the_sovereign/blob/master/src/overworld/tileset_animation/text_animator.c#L103)

+ 106
- 0
content/programming/wow-auction-tracking.md View File

@@ -0,0 +1,106 @@
1
+---
2
+Title: "Blizzard API: Tracking WoW Auctions"
3
+Template: article
4
+Description: Spiele- Entwickler und Publisher Blizzard Entertainment stellt mittlerweile die umfangreiche [Battle.net API](https://dev.battle.net/) zur Verfügung. Über sie kann mittels REST ein Teil der Spieldatenbank, sowie statische Information, gelesen werden. In diesem Artikel stelle ich euch ein kleines Projekt vor, den AuctionTracker, welches meine Auktionen überwachen und mich mittels Push Notification über Updates informieren kann.
5
+ReleaseIndex: 1
6
+---
7
+
8
+World of Warcraft besitzt mit seinem Umfangreichen Auktionshaus eine Handelsplattform für alle Goblins von Azeroth, auch jenen, die unter dem blauen Banner der Allianz Handel betreiben. Seit einiger Zeit bietet die Battle.net API auch eine REST Schnittstelle für uns Goblins, man kann, zumindest mit einiger Zeitverzögerung, das Auktionshaus seines Realms auslesen, und so einen Überblick bekommen, welche Auktionen gerade eingestellt sind. Eine Idee begann mir im Kopf herum zu schwirren: Wieso nicht einfach die eigenen Auktionen auslesen, filtern und mit diesen Rohdaten eventuell Visualisierungen, oder Analysen des eigenen Marktverhaltens zu erstellen, so zumindest der erste Gedanke. Weiter dachte ich mir, es sei von Vorteil, seine Auktionen auch außerhalb des Spiels verfolgen zu können, und v.a. die Information über abgelaufene Items zu erhalten, sodass man diese im Zweifel schnell wieder in den Markt bringen kann.
9
+
10
+### AuctionTracker
11
+
12
+Der `AuchtionTracker` tut genau das, er analysiert die Auktionsdatenbank, und sendet alle abgelaufenen, sowie alle verkauften Auktionen an einen Push Server, welcher Nachrichten an meine Endgeräte verteilt. Im Grunde synchronisiert der `AuctionTracker` seine interne Datenbank mit der Blizzard Auction DB, und erzeugt so zu je zwei Zeitpunkten einen Snapshot. Wenn eine Auktion fehlt, ist diese entweder abgelaufen, oder verkauft worden. Was genau passiert ist, ermittelt das Tool aufgrund der letzten Verfügbaren Zeitspanne der respektiven Auktion. Ist diese `SHORT`, also hat eine Lebensdauer von 30 Minuten oder weniger, so geht das Tool davon aus, dass die Autkion ausgelaufen ist, ansonsten wurde sie verkauft. Theoretisch könnte eine Auktion auch in den letzten 30 Minuten verkauft worden sein. Ein akurateres Kriterium wäre vermutlich, zu schauen, ob es noch andere Auktionen der gleichen Zeitspanne gab, und ob diese ebenfalls verschwunden sind. Denn es ist unwarscheinlich, dass alle Auktionen in den letzten 30 Minuten verkauft werden, und meine Auktionen haben meist, in Gruppen, die gleich Zeitspanne, da ich sie zum selben Zeitpunkt erstelle.
13
+
14
+Der `AuctionTracker` basiert auf einer, ebenfalls selbst geschriebenen, minimalistischen .net Core Implementierung der Battle.Net Api, welche den Namen `BlizzSharp` trägt und ebenfalls selbst geschrieben wurde. Die Entwicklung ist jeweils sehr rudimentär, aber wer sich für die Tools interessiert kann sie auf meinem Git Server finden: [AuctionTracker](https://gitlab.karathan.at/Karathan/AuctionTracker)
15
+
16
+### BlizzSharp
17
+
18
+BlizzSharp kommuniziert mit der REST Api, und cached die Auktionen bereits rudimentär, da es sich bei dem Tool im aktuellen Zustand nur um ein Proof of Concept handelt, werden weder Fehler abgefangen, noch behandelt. Hier beispielhaft, der Zugriff auf die Auktions API mittels REST:
19
+
20
+```csharp
21
+public async Task<AuctionResponse> GetAuctionsAsync(string realm, string locale)
22
+{
23
+    HttpResponseMessage response = await _http.GetAsync($"auction/data/{realm}?locale={locale}&apikey={_key}");
24
+    if(response.IsSuccessStatusCode)
25
+    {
26
+        string auctionsLocatorResponse = await response.Content.ReadAsStringAsync();
27
+        AuctionLocator locator = JsonConvert.DeserializeObject<Data.AuctionLocatorList>(auctionsLocatorResponse).Locators[0];
28
+        if((locator.Timestamp > _latestTimeStamp) || CurrentAuctionResponse == null)
29
+        {
30
+            using(HttpClient auctionClient = new HttpClient())
31
+            {
32
+                HttpResponseMessage auctionResponse = await auctionClient.GetAsync(locator.Url);
33
+                if(auctionResponse.IsSuccessStatusCode)
34
+                {
35
+                    string auctionsResponse = await auctionResponse.Content.ReadAsStringAsync();
36
+                    PreviousAuctionResponse = CurrentAuctionResponse;
37
+                    CurrentAuctionResponse = JsonConvert.DeserializeObject<AuctionResponse>(auctionsResponse);
38
+                    return CurrentAuctionResponse;
39
+                }
40
+            }
41
+
42
+        }
43
+    }
44
+    return null;
45
+}
46
+```
47
+
48
+Wie man sieht wird zuerst die aktuelle Datenbankressource abgefragt, diese ändert sich etwa jede halbe Stunde. Sollte der Zeitstempel der Datenbank neuer sein, als das, was bereits im Cache liegt, so wird die Datenbank heruntergeladen und deserialisiert. Hierzu verwende ich die [Newtonsoft.JSON](https://www.newtonsoft.com/json) Library, da die REST Api JSON Objekte liefert.
49
+
50
+### Datenverarbeitung
51
+
52
+Die von der Battle.net Api gewonnenen Daten werden anschließend wieder von `AuctionTracker` ausgewertet. Dabei durchläuft das Tool in einem fixen Zeitintervall ein Update, welches versucht neue Auktionsdaten zu ziehen, sofern vorhanden, und diese mit den Daten des letzten Updates gegenprüft:
53
+
54
+```csharp
55
+private static async Task RunUpdate()
56
+{
57
+    _logger.Info($"Running Update for {_character} on {_realm}");
58
+    AuctionResponse response = await _client.GetAuctionsAsync(_realm, _locale);
59
+    IEnumerable<Auction> auctions = response.Auctions.Where(a => a.Owner == _character);
60
+    if(lastAuctions != null)
61
+    {
62
+        HashSet<Auction> expiredAuctions = new HashSet<Auction>();
63
+        bool removedFound = false;
64
+        foreach(Auction a in lastAuctions)
65
+        {
66
+            if(!auctions.Any(b => b.Id == a.Id))
67
+            {
68
+                //the auction is now gone, but why?
69
+                Item item = await _client.GetItemInfoAsync(a.Item, _locale);
70
+                if(a.TimeLeft == SHORT_DURATION)
71
+                {
72
+                    //the auction probably expired
73
+                    expiredAuctions.Add(a);
74
+                }
75
+                else
76
+                {
77
+                    //the auction was sold
78
+                    await _pushClient.SendNotification($"Your Auction of {a.quantity}x {item.Name} was sold for {FormatCurrency(a.Buyout)}.", "cashregister");
79
+                }
80
+                removedFound = true;
81
+            }
82
+        }
83
+        if(!removedFound)
84
+        {
85
+            _logger.Info("No Expired/Sold Auctions found");
86
+        }
87
+        if(expiredAuctions.Any())
88
+        {
89
+            StringBuilder sb = new StringBuilder();
90
+            foreach(Auction a in expiredAuctions)
91
+            {
92
+                string name = (await _client.GetItemInfoAsync(a.Item, _locale)).Name;
93
+                sb.AppendLine($"{a.quantity}x {name}");
94
+            }
95
+            await _pushClient.SendNotification($"Your Auctions have expired:\n{sb.ToString()}", "none");
96
+        }
97
+    }
98
+    lastAuctions = auctions;
99
+}
100
+```
101
+
102
+Sollte eine Differenz gefunden werden wird noch eine Push Notification an die Push API gesendet, welche die Nachricht an alle Subscriber weiterleitet.
103
+
104
+### Nachwort
105
+
106
+Sollte ein anderer WoW Goblin Interesse an dem Tool haben, kann es gerne unter der gegebenen Lizenz (GPLv3) verwendet werden. Ich plane aktuell nicht, ein öffentliches Service dafür aufzuziehen. Dafür habe ich nicht die nötige Infrastruktur und kann vermutlich auch nicht genug Support geben, damit das auf langfristiger Basis funktionieren könnte.

+ 0
- 12
content/test.md View File

@@ -1,12 +0,0 @@
1
----
2
-Title: Test
3
-Description: Testing the description
4
-Main: True
5
-Section: testSection
6
-Image: pic02.jpg
7
-ActionName: Email Me
8
-ActionTarget: mailto:karathan@karathan.at
9
-ActionPrimary: True
10
----
11
-
12
-Some more testing

+ 103
- 0
themes/stellar/article.twig View File

@@ -0,0 +1,103 @@
1
+<!DOCTYPE HTML>
2
+<!--
3
+	Stellar by HTML5 UP
4
+	html5up.net | @ajlkn
5
+	Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
6
+-->
7
+<html>
8
+	<head>
9
+		<title>{{site_title}}</title>
10
+		<meta charset="utf-8" />
11
+		<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
12
+		<link rel="stylesheet" href="{{ theme_url }}/assets/css/main.css" />
13
+		<noscript><link rel="stylesheet" href="{{ theme_url }}/assets/css/noscript.css" /></noscript>
14
+	</head>
15
+	<body class="is-preload">
16
+
17
+		<!-- Wrapper -->
18
+			<div id="wrapper">
19
+
20
+				<!-- Header -->
21
+					<header id="header" class="alt">
22
+						<span class="logo"><img src="{{ theme_url }}/images/logo.svg" alt="" /></span>
23
+						<h1>{{site_title}} | {{current_page.title}}</h1>
24
+						<p>{{pages._meta.meta.Slogan}}
25
+						{% if pages._meta.meta.Branch %}
26
+							<br />
27
+							<span class="icon fa-code-fork"></span> {{pages._meta.meta.Branch}}
28
+						{% endif %}
29
+						</p>
30
+					</header>
31
+
32
+					<nav id="nav">
33
+						<ul>
34
+							<li><a href="{{pages.index|link}}">Startseite</a></li>
35
+							<li><a href="{{"literature"|link}}">Literatur</a></li>
36
+							<li><a href="{{"gamedesign"|link}}">Game Design</a></li>
37
+							<li><a href="{{"programming"|link}}">Programmierung</a></li>
38
+						</ul>
39
+					</nav>
40
+
41
+				<!-- Main -->
42
+					<div id="main">
43
+						<!-- Content -->
44
+							<section id="content" class="main">
45
+								{% if current_page.meta.Image %}
46
+								<span class="image main"><img src="{{ theme_url }}/images/{{current_page.meta.Image}}" alt="" /></span>
47
+								{% endif %}
48
+								<header class="major">
49
+									<h2>{{current_page.title}}</h2>
50
+								</header>
51
+								<p>{{current_page.id|content}}</p>
52
+							</section>
53
+					</div>
54
+
55
+				<!-- Footer -->
56
+					<footer id="footer">
57
+						<section>
58
+							{% if pages._meta.meta.FooterTitle and pages._meta.meta.FooterText %}
59
+							<h2>{{pages._meta.meta.FooterTitle}}</h2>
60
+							<p>{{pages._meta.meta.FooterText}}</p>
61
+							{% endif %}
62
+							<ul class="actions">
63
+								<li><a href="" class="button" onclick="topFunction()"><span class="icon fa-angle-up"></span></a></li>
64
+							</ul>
65
+						</section>
66
+						<section>
67
+							<h2>Kontakt</h2>
68
+							<dl class="alt">
69
+								{% if pages._meta.meta.AddressStreet and pages._meta.meta.AddressTown and pages._meta.meta.AddressZip and pages._meta.meta.AddressCountry %}
70
+								<dt>Address</dt>
71
+								<dd>{{pages._meta.meta.AddressStreet}} &bull; {{pages._meta.meta.AddressTown}}, {{pages._meta.meta.AddressZip}} &bull; {{pages._meta.meta.AddressCountry}}</dd>
72
+								{% endif %}
73
+								{% if pages._meta.meta.Phone %}
74
+								<dt>Phone</dt>
75
+								<dd>{{pages._meta.meta.Phone}}</dd>
76
+								{% endif %}
77
+								{% if pages._meta.meta.Email %}
78
+								<dt>Email</dt>
79
+								<dd><a href="mailto:{{pages._meta.meta.Email}}">{{pages._meta.meta.Email}}</a></dd>
80
+								{% endif %}
81
+							</dl>
82
+							<ul class="icons">
83
+								{% for social in pages._meta.meta.social %}
84
+								<li><a href="{{social.url}}" class="icon {{social.icon}}"><span class="label">{{social.title}}</span></a></li>
85
+								{% endfor %}
86
+							</ul>
87
+						</section>
88
+						<p class="copyright">&copy; {{ pages._meta.meta.Copyright }}. Design: <a href="https://html5up.net">HTML5 UP</a>. Ported to Pico by Karathan</p>
89
+					</footer>
90
+
91
+			</div>
92
+
93
+		<!-- Scripts -->
94
+			<script src="{{ theme_url }}/assets/js/jquery.min.js"></script>
95
+			<script src="{{ theme_url }}/assets/js/jquery.scrollex.min.js"></script>
96
+			<script src="{{ theme_url }}/assets/js/jquery.scrolly.min.js"></script>
97
+			<script src="{{ theme_url }}/assets/js/browser.min.js"></script>
98
+			<script src="{{ theme_url }}/assets/js/breakpoints.min.js"></script>
99
+			<script src="{{ theme_url }}/assets/js/util.js"></script>
100
+			<script src="{{ theme_url }}/assets/js/main.js"></script>
101
+
102
+	</body>
103
+</html>

+ 36
- 9
themes/stellar/generic.twig View File

@@ -18,35 +18,62 @@
18 18
 			<div id="wrapper">
19 19
 
20 20
 				<!-- Header -->
21
-					<header id="header">
22
-						<h1>{{current_page.title}}</h1>
23
-						<p>{{current_page.description}}</p>
21
+					<header id="header" class="alt">
22
+						<span class="logo"><img src="{{ theme_url }}/images/logo.svg" alt="" /></span>
23
+						<h1>{{site_title}} | {{current_page.title}}</h1>
24
+						<p>{{pages._meta.meta.Slogan}}
25
+						{% if pages._meta.meta.Branch %}
26
+							<br />
27
+							<span class="icon fa-code-fork"></span> {{pages._meta.meta.Branch}}
28
+						{% endif %}
29
+						</p>
24 30
 					</header>
25 31
 
26 32
 					<nav id="nav">
27 33
 						<ul>
28 34
 							<li><a href="{{pages.index|link}}">Startseite</a></li>
29 35
 							<li><a href="{{"literature"|link}}">Literatur</a></li>
36
+							<li><a href="{{"gamedesign"|link}}">Game Design</a></li>
37
+							<li><a href="{{"programming"|link}}">Programmierung</a></li>
30 38
 						</ul>
31 39
 					</nav>
32 40
 
33 41
 				<!-- Main -->
34 42
 					<div id="main">
35 43
 						<!-- Content -->
36
-							<section id="content" class="main">
44
+							<section id="content" class="main special">
37 45
 								{% if current_page.meta.Image %}
38 46
 								<span class="image main"><img src="{{ theme_url }}/images/{{current_page.meta.Image}}" alt="" /></span>
39 47
 								{% endif %}
40
-								{{current_page.content}}
48
+								<header class="major">
49
+									<p>{{current_page.content}}</p>
50
+								</header>
41 51
 							</section>
52
+							
53
+							{% if current_page.tree_node.children %}
54
+							{% set childPages = current_page.tree_node.children|map("page") %}
55
+							{% for child in childPages|sort_by(['meta','ReleaseIndex'])|reverse if child %}
56
+								{% if child.title %}
57
+								<section id="content" class="main">
58
+									<header class="major">
59
+										<h2>{{child.title}}</h2>
60
+									</header>
61
+									<p>{{child.description}}</p>
62
+									<ul class="actions">
63
+										<li><a href="{{child.url}}" class="button">Weiterlesen</a></li>
64
+									</ul>
65
+								</section>
66
+							{% endif %}
67
+							{% endfor %}
68
+							{% endif %}
42 69
 					</div>
43 70
 
44 71
 				<!-- Footer -->
45 72
 					<footer id="footer">
46 73
 						<section>
47
-							{% if pages._meta.meta.SloganTitle and pages._meta.meta.Slogan %}
48
-							<h2>{{pages._meta.meta.SloganTitle}}</h2>
49
-							<p>{{pages._meta.meta.Slogan}}</p>
74
+							{% if pages._meta.meta.FooterTitle and pages._meta.meta.FooterText %}
75
+							<h2>{{pages._meta.meta.FooterTitle}}</h2>
76
+							<p>{{pages._meta.meta.FooterText}}</p>
50 77
 							{% endif %}
51 78
 							<ul class="actions">
52 79
 								<li><a href="" class="button" onclick="topFunction()"><span class="icon fa-angle-up"></span></a></li>
@@ -74,7 +101,7 @@
74 101
 								{% endfor %}
75 102
 							</ul>
76 103
 						</section>
77
-						<p class="copyright">&copy; {{ site_title }}. Design: <a href="https://html5up.net">HTML5 UP</a>. Ported to Pico by Karathan</p>
104
+						<p class="copyright">&copy; {{ pages._meta.meta.Copyright }}. Design: <a href="https://html5up.net">HTML5 UP</a>. Ported to Pico by Karathan</p>
78 105
 					</footer>
79 106
 
80 107
 			</div>

+ 0
- 98
themes/stellar/index.html.txt View File

@@ -1,98 +0,0 @@
1
-						<!-- Introduction -->
2
-							<section id="intro" class="main">
3
-								<div class="spotlight">
4
-									<div class="content">
5
-										<header class="major">
6
-											<h2>Ipsum sed adipiscing</h2>
7
-										</header>
8
-										<p>Sed lorem ipsum dolor sit amet nullam consequat feugiat consequat magna
9
-										adipiscing magna etiam amet veroeros. Lorem ipsum dolor tempus sit cursus.
10
-										Tempus nisl et nullam lorem ipsum dolor sit amet aliquam.</p>
11
-										<ul class="actions">
12
-											<li><a href="generic.html" class="button">Learn More</a></li>
13
-										</ul>
14
-									</div>
15
-									<span class="image"><img src="{{ theme_url }}/images/pic01.jpg" alt="" /></span>
16
-								</div>
17
-							</section>
18
-
19
-						<!-- First Section -->
20
-							<section id="first" class="main special">
21
-								<header class="major">
22
-									<h2>Magna veroeros</h2>
23
-								</header>
24
-								<ul class="features">
25
-									<li>
26
-										<span class="icon major style1 fa-code"></span>
27
-										<h3>Ipsum consequat</h3>
28
-										<p>Sed lorem amet ipsum dolor et amet nullam consequat a feugiat consequat tempus veroeros sed consequat.</p>
29
-									</li>
30
-									<li>
31
-										<span class="icon major style3 fa-copy"></span>
32
-										<h3>Amed sed feugiat</h3>
33
-										<p>Sed lorem amet ipsum dolor et amet nullam consequat a feugiat consequat tempus veroeros sed consequat.</p>
34
-									</li>
35
-									<li>
36
-										<span class="icon major style5 fa-diamond"></span>
37
-										<h3>Dolor nullam</h3>
38
-										<p>Sed lorem amet ipsum dolor et amet nullam consequat a feugiat consequat tempus veroeros sed consequat.</p>
39
-									</li>
40
-								</ul>
41
-								<footer class="major">
42
-									<ul class="actions special">
43
-										<li><a href="generic.html" class="button">Learn More</a></li>
44
-									</ul>
45
-								</footer>
46
-							</section>
47
-
48
-						<!-- Second Section -->
49
-							<section id="second" class="main special">
50
-								<header class="major">
51
-									<h2>Ipsum consequat</h2>
52
-									<p>Donec imperdiet consequat consequat. Suspendisse feugiat congue<br />
53
-									posuere. Nulla massa urna, fermentum eget quam aliquet.</p>
54
-								</header>
55
-								<ul class="statistics">
56
-									<li class="style1">
57
-										<span class="icon fa-code-fork"></span>
58
-										<strong>5,120</strong> Etiam
59
-									</li>
60
-									<li class="style2">
61
-										<span class="icon fa-folder-open-o"></span>
62
-										<strong>8,192</strong> Magna
63
-									</li>
64
-									<li class="style3">
65
-										<span class="icon fa-signal"></span>
66
-										<strong>2,048</strong> Tempus
67
-									</li>
68
-									<li class="style4">
69
-										<span class="icon fa-laptop"></span>
70
-										<strong>4,096</strong> Aliquam
71
-									</li>
72
-									<li class="style5">
73
-										<span class="icon fa-diamond"></span>
74
-										<strong>1,024</strong> Nullam
75
-									</li>
76
-								</ul>
77
-								<p class="content">Nam elementum nisl et mi a commodo porttitor. Morbi sit amet nisl eu arcu faucibus hendrerit vel a risus. Nam a orci mi, elementum ac arcu sit amet, fermentum pellentesque et purus. Integer maximus varius lorem, sed convallis diam accumsan sed. Etiam porttitor placerat sapien, sed eleifend a enim pulvinar faucibus semper quis ut arcu. Ut non nisl a mollis est efficitur vestibulum. Integer eget purus nec nulla mattis et accumsan ut magna libero. Morbi auctor iaculis porttitor. Sed ut magna ac risus et hendrerit scelerisque. Praesent eleifend lacus in lectus aliquam porta. Cras eu ornare dui curabitur lacinia.</p>
78
-								<footer class="major">
79
-									<ul class="actions special">
80
-										<li><a href="generic.html" class="button">Learn More</a></li>
81
-									</ul>
82
-								</footer>
83
-							</section>
84
-
85
-						<!-- Get Started -->
86
-							<section id="cta" class="main special">
87
-								<header class="major">
88
-									<h2>Congue imperdiet</h2>
89
-									<p>Donec imperdiet consequat consequat. Suspendisse feugiat congue<br />
90
-									posuere. Nulla massa urna, fermentum eget quam aliquet.</p>
91
-								</header>
92
-								<footer class="major">
93
-									<ul class="actions special">
94
-										<li><a href="generic.html" class="button primary">Get Started</a></li>
95
-										<li><a href="generic.html" class="button">Learn More</a></li>
96
-									</ul>
97
-								</footer>
98
-							</section>

+ 11
- 6
themes/stellar/index.twig View File

@@ -20,7 +20,12 @@
20 20
 					<header id="header" class="alt">
21 21
 						<span class="logo"><img src="{{ theme_url }}/images/logo.svg" alt="" /></span>
22 22
 						<h1>{{site_title}}</h1>
23
-						<p>{{pages._meta.meta.Slogan}}</p>
23
+						<p>{{pages._meta.meta.Slogan}}
24
+						{% if pages._meta.meta.Branch %}
25
+							<br />
26
+							<span class="icon fa-code-fork"></span> {{pages._meta.meta.Branch}}
27
+						{% endif %}
28
+						</p>
24 29
 					</header>
25 30
 
26 31
 				<!-- Nav -->
@@ -52,7 +57,7 @@
52 57
 											{{page.id|content}}
53 58
 											{% if page.meta.ActionTarget and page.meta.ActionName %}
54 59
 												<ul class="{{page.meta.Main ? 'actions' : 'actions special'}}">
55
-													<li><a href="{{page.meta.ActionTarget}}" class="{{ page.meta.ActionPrimary ? 'button primary' : 'button'}}">{{page.meta.ActionName}}</a></li>
60
+													<li><a href="{{page.meta.ActionTarget|link}}" class="{{ page.meta.ActionPrimary ? 'button primary' : 'button'}}">{{page.meta.ActionName}}</a></li>
56 61
 												</ul>
57 62
 											{% endif %}
58 63
 										</div>
@@ -68,9 +73,9 @@
68 73
 				<!-- Footer -->
69 74
 					<footer id="footer">
70 75
 						<section>
71
-							{% if pages._meta.meta.SloganTitle and pages._meta.meta.Slogan %}
72
-							<h2>{{pages._meta.meta.SloganTitle}}</h2>
73
-							<p>{{pages._meta.meta.Slogan}}</p>
76
+							{% if pages._meta.meta.FooterTitle and pages._meta.meta.FooterText %}
77
+							<h2>{{pages._meta.meta.FooterTitle}}</h2>
78
+							<p>{{pages._meta.meta.FooterText}}</p>
74 79
 							{% endif %}
75 80
 							<ul class="actions">
76 81
 								<li><a href="" class="button" onclick="topFunction()"><span class="icon fa-angle-up"></span></a></li>
@@ -98,7 +103,7 @@
98 103
 								{% endfor %}
99 104
 							</ul>
100 105
 						</section>
101
-						<p class="copyright">&copy; {{ site_title }}. Design: <a href="https://html5up.net">HTML5 UP</a>. Ported to Pico by Karathan</p>
106
+						<p class="copyright">&copy; {{ pages._meta.meta.Copyright }}. Design: <a href="https://html5up.net">HTML5 UP</a>. Ported to Pico by Karathan</p>
102 107
 					</footer>
103 108
 
104 109
 			</div>