diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 000000000..98396d6f2 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,16 @@ +bin/ +Debug/ +Release/ +*~ +*.user +*.pdb +*.suo +*.sdf +*.opensdf +*.ipch +*.sh +*.tmp +src/*.vcproj.*.user +tags +build-vs2010 +config diff --git a/core/LICENSE b/core/LICENSE new file mode 100644 index 000000000..00ab17b3d --- /dev/null +++ b/core/LICENSE @@ -0,0 +1,15 @@ +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/core/res/armor/chainmail.xml b/core/res/armor/chainmail.xml new file mode 100644 index 000000000..81152f89e --- /dev/null +++ b/core/res/armor/chainmail.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/res/armor/laenmail.xml b/core/res/armor/laenmail.xml new file mode 100644 index 000000000..bdcf6b7e1 --- /dev/null +++ b/core/res/armor/laenmail.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/res/armor/laenshield.xml b/core/res/armor/laenshield.xml new file mode 100644 index 000000000..f5eb2d783 --- /dev/null +++ b/core/res/armor/laenshield.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/res/armor/plate.xml b/core/res/armor/plate.xml new file mode 100644 index 000000000..eb10f6359 --- /dev/null +++ b/core/res/armor/plate.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/res/armor/rustychainmail.xml b/core/res/armor/rustychainmail.xml new file mode 100644 index 000000000..a52b4db7b --- /dev/null +++ b/core/res/armor/rustychainmail.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/res/armor/rustyshield.xml b/core/res/armor/rustyshield.xml new file mode 100644 index 000000000..4c34f7ee1 --- /dev/null +++ b/core/res/armor/rustyshield.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/res/armor/shield.xml b/core/res/armor/shield.xml new file mode 100644 index 000000000..9a72baf9a --- /dev/null +++ b/core/res/armor/shield.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/res/calendar.xml b/core/res/calendar.xml new file mode 100644 index 000000000..42f83d58b --- /dev/null +++ b/core/res/calendar.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/common/armor.xml b/core/res/common/armor.xml new file mode 100644 index 000000000..58abae47f --- /dev/null +++ b/core/res/common/armor.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/res/common/buildings.xml b/core/res/common/buildings.xml new file mode 100644 index 000000000..63ac3e650 --- /dev/null +++ b/core/res/common/buildings.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/common/construction.xml b/core/res/common/construction.xml new file mode 100644 index 000000000..95dcb6a33 --- /dev/null +++ b/core/res/common/construction.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/res/common/herbs.xml b/core/res/common/herbs.xml new file mode 100644 index 000000000..d7d0ef2e6 --- /dev/null +++ b/core/res/common/herbs.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/common/items.xml b/core/res/common/items.xml new file mode 100644 index 000000000..d867a3126 --- /dev/null +++ b/core/res/common/items.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/common/luxuries.xml b/core/res/common/luxuries.xml new file mode 100644 index 000000000..bdf9c110b --- /dev/null +++ b/core/res/common/luxuries.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/common/potions.xml b/core/res/common/potions.xml new file mode 100644 index 000000000..f444e5a94 --- /dev/null +++ b/core/res/common/potions.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/common/resources.xml b/core/res/common/resources.xml new file mode 100644 index 000000000..91d0e8afc --- /dev/null +++ b/core/res/common/resources.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/core/res/common/weapons.xml b/core/res/common/weapons.xml new file mode 100644 index 000000000..e91b15e69 --- /dev/null +++ b/core/res/common/weapons.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/de/strings.xml b/core/res/de/strings.xml new file mode 100644 index 000000000..bb16c92fe --- /dev/null +++ b/core/res/de/strings.xml @@ -0,0 +1,7456 @@ + + + + + Wirbel + vortex + remous + + + Ein Wirbel aus reinem Chaos zieht über die Region + A vortex of pure chaos energy pulls over the region + + + Wabernde grüne Schwaden treiben durch den Nebel und + verdichten sich zu einer unheimlichen Kreatur, die nur aus einem langen + Ruderschwanz und einem riesigen runden Maul zu bestehen scheint. + Wobbling green vapours drift through the mists to form an eldritch creature that seems to be entirely made up of huge jaws and a long tail. + + + + Keine Informationen über diese Rasse verfügbar. + No information available for this race. + + + Singdrachen sind von der Größe eines ausgewachsenden Tigers. Ihre Färbung reicht von schillerndem Rot, über dunkles Grün bis hin zu tiefem Schwarz. Alle bekannten Drachen dieser Art weisen eine hohe Intelligenz und ein hohes Maß an magischen Fähigkeiten auf. Wie Ihre großen Verwandten verfügen sie über einen Feuerodem. Sie lieben den Gesang und das üppige Mahl. Von Zeit zu Zeit gehen sie eine engen magisches Bund zu einem Magier ein. Wenn dies geschieht, so steht dem Magier ein äußerst loyaler und lohnender Vertrauter zur Seite. + + Song Dragons are roughly the size of a fully grown tiger. Their coloring ranges from bright red, through a dark green shade to a deep black. All known dragons of this species display a high level of intelligence and highly developed magical skills. Like their larger cousins, Song Dragons posess a firegland. They love singing and a good meal. From time to time one of these magnificent creatures will bond with a mage. When this happens, the mage is assured of a most loyal and useful familiar at his side. + + + Dieses mystische Wesen lebt bevorzugt in den tiefsten Wäldern und + vermag sich hervorragend vor den Augen anderer zu verbergen. Nur + selten schließt sich ein Einhorn einem Magier an, jedoch wenn das + geschieht ist es ein mächtiger Verbündeter, der auch über eigene Magie + verfügt. + + + Der Adler ist ein ausgezeichneter Späher, fliegend überquert er sogar + kurze Meerengen, doch ist er hoch oben am Himmel auch sehr exponiert, + was ihn beim Rasten zu einem leichten Ziel macht. + + + Der Luchs ist bekannt für seine Geschicklichkeit im Verbergen und + Beobachten. Mit ein wenig Geduld kann er zu einem hervorragenden + Späher ausgebildet werden. Im Kampf verteidigt er sich mit seinen + scharfen Krallen und weiß seine Gewandheit zu nutzen. + + + Diese großen Wölfe sind nicht alle so wild und böse wie in den + Legenden berichtet, und einige von ihnen schließen sich auch guten + Magiern bereitwillig an und sind ihnen dann treue Gefährten. + + + Diese aus den Tiefen Eresseas stammende gigantische Geschöpf ist dem + Leben im Untergrund hervorragend angepasst. Blind, taub und nicht + besonders intelligent, aber mit seinen gewaltigen Kräften kann es + ganze Berge versetzen oder Wälder roden. + + + + + Das Horn eines Trolles. Kein Troll würde sich lebend davon trennen. + The horn of an adult troll. No troll would ever part with this while he's alive. + + + Beim Barte des Proheten! Ach nein, Zwergen. Irgendetwas riecht hier ranzig. + Sniff... Bleah. Don't they ever wash these? + + + Diese Amulett ist ein hervorragender Fokus für alle Heilzauber. Ein + mit diesem Fokus gewirkter Heilzauber wird mit größerer + Warscheinlichkeit Erfolgreich sein und doppelt so viele Leute heilen + können. + + + Der Kopf eines toten Drachens oder Wyrms. Man sagt, es ruhen magische Kräfte darin. + The head of a dead dragon or wyrm. They say that it has magical powers. + + + Munition für Katapulte. + Ammunition for catapults. + + + Ein Elfenpferd wird sich nur den wenigsten jemals anschließen. Hat es + jedoch seine Scheu überwunden ist es ein sehr wertvoller Gefährte. Ein + Elfenpferd ist schneller als ein Pferd. Zudem hilft es seinem Reiter + im Kampf und unterstützt ihn mit seiner Magie. Es sind schwarze + Elfenpferde bekannt, die sich sogar Orks angeschlossen haben. + + + Die rötlich glühende Klinge dieser furchterregenden magischen Waffe + ist mit dunklen Runen bedeckt. Nur die erfahrendsten Schwertkämpfer + vermögen ihre Kraft zu zähmen, doch in ihrer Hand vermag dem + Runenschwert nichts zu widerstehen - selbst magische Rüstungen + durchdringt es ohne Schwierigkeiten - und den Geist des Kämpfers füllt + es mit unterschütterlicher Zuversicht. + + + This enchanted dragon-eye has to be eaten by the leader of your forces + on the eve before battle. During the night he gains insight into the + dreams of the enemy leaders and may potentially glean a decisive + advantage. + Dieses verzauberte Drachenauge muß vor dem Abend einer Schlacht vom + Heerführer verzehrt werden. Während der Nacht wird er dann Einblick in + die Träume der feindlichen Heerführer erhalten und so möglicherweise + einen entscheidenden Vorteil im kommenden Gefecht erlangen. + + + This artifact grants its wearer the strength of a cavetroll. He will + be able to carry fifty times as much as normal and also in combat his + enhanced strength and tough troll skin will serve him well. + Dieses magische Artefakt verleiht seinem Träger die Stärke eines + ausgewachsenen Höhlentrolls. Seine Tragkraft erhöht sich auf das + 50fache und auch im Kampf werden sich die erhöhte Kraft und die + trollisch zähe Haut positiv auswirken. + + + It may look like just another quartz, but your magician will tell you + tha great power emenates from these crystals. Using it at the begining + of a week will release a strong negative energy that reduce the + power of all spells cast in the region during that week. + Für den ungeübten Betrachter mag der Antimagiekristall wie ein + gewöhnlicher Quarzkristall ausschauen, doch ein Magier spürt, das ihm + ganz besondere Kräfte innewohnen. Durch spezielle Rituale antimagisch + aufgeladen wird der Kristall, wenn er zu feinem Staub zermahlen und + verteilt wird, die beim Zaubern freigesetzten magischen Energien + aufsaugen und die Kraft aller Zauber reduzieren, welche in der betreffenden + Woche in der Region gezaubert werden. + + + A ring of power increases a magician's power. The level of all the + spells + he casts will be increased by one without increasing their costs. + Ein Ring der Macht verstärkt die Kraft des Magiers. Jeder Zauber wird, + ohne das sich die Kosten erhöhen, so gezaubert als hätte der Magier + eine Stufe mehr. + + + Herbs stored in this bag will be much better preserved. + Kräuter, die in diesem Beutelchen aufbewahrt werden, sind erheblich + besser konserviert. + + + This bag encloses a dimensional fold, which can store up to 200 + stones of weight without any extra burden on the bearer. Large items + such as horses or carts cannot be placed inside. + Dieser Beutel umschließt eine kleine Dimensionsfalte, in der bis + zu 200 Gewichtseinheiten transportiert werden können, ohne dass + sie auf das Traggewicht angerechnet werden. Pferde und andere + Lebewesen sowie besonders sperrige Dinge (Wagen und Katapulte) können + nicht in dem Beutel transportiert werden. Auch ist es nicht möglich, + einen Zauberbeutel in einem anderen zu transportieren. Der Beutel + selber wiegt 1 GE. + + + These leather boots are embroidere with unicorn hair and allow + their wearer to walk at twice his normal speed. + Diese aus Leder gefertigten und mit Einhornfell verzierten Stiefel + ermöglichen es ihrem Träger, sich mit der doppelten Geschwindigkeit + fortzubewegen, wenn er zu Fuß reist. + + + The flaming sword gives its bearer an attack of 3d6+10 plus + an additional fireball causing 2d6 damage to 1-10 victims. + Using a flaming sword requires a minimum skill of 7. It grants an + additional +1 to your skill and your resistance to magic will be + increased. + Ein Flammenschwert gibt dem Träger, der kein Magier sein muß, + zusätzlich zu seinem normalen Angriff (3d6+10) einen kleinen + Feuerballangriff, der bei 1-10 Opfern 2d6 magischen Schaden + verursacht. Um ein Flammenschwert führen zu können, muss man + mindestens Hiebwaffen 7 haben, dann verleiht es einem auch + einen zusätzlichen Kampfbonus von +1. Ein Flammenschwert + erhöht die Magieresistenz seines Trägers wie ein Laenschwert. + + + The magic in this ring makes the fingers ten times more nimble. a + craftsman can produce ten times his normal quota, and other abilities + might also be improved. + Der Zauber in diesem Ring bewirkt eine um das zehnfache verbesserte + Geschicklichkeit und Gewandheit der Finger. Handwerker können somit + das zehnfache produzieren, und bei einigen anderen Tätigkeiten könnte + dies ebenfalls von Nutzen sein. + + + This magical artifact has been used since ancient times by Elves to + conceal themselves from their enemies. Other races have also learned + the value of these rings after encountering Elves - after all the ring + makes its wearer invisible to normal eyes, and only magical methods + enable the wearer to be discovered. + Dieses magische Artefakt wurde seit Urzeiten von den Elfen benutzt, + auf der Jagd oder um sich vor Feinden zu verbergen. Doch auch andere + Rassen haben nach der Begegnung mit den Elfenvölkern den Wert des Rings + schnell schätzen gelernt - schließlich macht er den Träger für jedes + noch so scharfe Auge unsichtbar - nur mit magischen Mitteln ist der + Verborgene noch zu entdecken. + + + Das Amulett erlaubt es dem Träger, alle Einheiten, die durch einen + Ring der Unsichtbarkeit geschützt sind, zu sehen. Einheiten allerdings, + die sich mit ihrem Tarnungs-Talent verstecken, bleiben weiterhin + unentdeckt. Die Herstellung des Amulettes kostet 3000 Silber. + + + Dieser Tiegel enthält die seltenste alchemistische Substanz + Eresseas, den Krötenschleim. Angeblich soll der Krötenschleim eine + aussergewöhnlich hohe magische Absorbtionskraft besitzen und deswegen + in obskuren magischen Ritualen Verwendung finden. + + + Die Kröte ist eine der seltensten Rassen Eresseas. Man munkelt, + sie würde nur auf magische Weise entstehen. In einer uralten + Abhandlung über Magie aus der Bibliothek der Akademie von Xontormia + wird die Theorie aufgestellt, das die Kröte die ins morphische Feld + des Magiers übertragene Manifestation eines implodierten + Zauberfeldes sein könnte. Vieleicht deswegen ist die Kröte auch + gegen Zauber weitaus widerstandsfähiger als die normalen Rassen + Eresseas, leider aber auch weitaus unmagischer als diese. Die + Kröte kann schon aufgrund ihrer Größe und der fehlenden Hände + nur unter Schwierigkeiten normale Tätigkeiten ausüben. Der + einzige Vorteil ihrer geringen Größe ist, dass sie sich leichter + verstecken kann. + + + Dieses magische Szepter, ein Geschenk Igjarjuks, sorgt für große + Verwirrung und Gedächtnisverlust. Syntax: BENUTZE "Szepter der + Tränen" + + + Setzt eine Einheit dieses Segel auf einem Schiff, so erhöht + sich dessen Reichweite permanent um 1 Region. + A unit setting this sail on a ship temporarily will permanently + increase the ship's range by 1. + + + Im Mistelzweig ruht eine magische + Kraft der besonderer Art. Der Anwender wird von seinen + Feinden in Frieden gelassen, eine Woche lang läßt jeder + Kämpfer ihn unbeschadet seines Weges ziehen. + The magical misteltoe has a wonderous + property: It's use will make one person able to escape + unharmed from every conflict, no enemy will lay hand on + the bearer for one week. + + + + + + Kein Magiegebiet + no magic school + + + Illaun + Illaun + + + Tybied + Tybied + + + Cerddor + Cerddor + + + Gwyrrd + Gwyrrd + + + Draig + Draig + + + + + Tresen + counter + + + + wenige + few + + + + viele + many + + + + relativ viele + rather many + + + + sehr wenige + very few + + + + sehr viele + a great many + beaucoup de + + + + (trauernd) + (in mourning) + + + Beschreibung: + Description: + + + Art: + Type: + + + Komponenten: + Components: + + + Modifikationen: + Modifications: + + + Stufe: + Level: + + + Rang: + Rank: + + + Syntax: + Syntax: + + + + Geboten wird für + Traders can sell + + + und für + and + + + . + . + + + , für + , + + + + , im + , to the + + + und im + and to the + + + Einheit + Unit + + + Partei + Faction + + + hier_passwort_eintragen + insert_your_password_here + + + + Baum + tree + + + + Bäume + trees + + + + Mallornbaum + mallorn tree + + + + Mallornbäume + mallorn trees + + + + + + Nordwestküste + + + Nordostküste + + + Ostküste + + + Südostküste + + + Südwestküste + + + Westküste + + + + + AUSWERTUNG + + + COMPUTER + + + ZUGVORLAGE + + + SILBERPOOL + + + STATISTIK + + + DEBUG + + + ZIPPED + + + ZEITUNG + + + MATERIALPOOL + + + ADRESSEN + + + BZIP2 + + + PUNKTE + + + TALENTVERSCHIEBUNGEN + + + + + ein fliegender Teppich + + + ein Ballon + + + eine Karavelle + + + ein Boot + + + ein Langboot + + + ein Drachenschiff + + + eine Trireme + + + + fliegender Teppich + flying carpet + + + Ballon + + + Karavelle + + + Boot + + + Langboot + + + Drachenschiff + + + Trireme + + + + + Mahlstrom + + + Ozean + + + Ebene + + + Wald + + + Sumpf + + + Wüste + + + Hochland + + + Berge + + + Gletscher + + + Gletscher + + + Feuerwand + + + Vulkan + + + Nebel + + + Eisberg + + + Dichter Nebel + + + Ebene aus Feuer und Dunkelheit + + + Aktiver Vulkan + + + Halle + + + Gang + + + Wand + + + Magischer Sturm + + + + ein %s + + + %s + + + die Ebene von %s + + + der Wald von %s + + + der Sumpf von %s + + + die Wüste von %s + + + das Hochland von %s + + + das Bergland von %s + + + der Gletscher von %s + + + eine %s + + + der Vulkan von %s + + + ein %s + + + der Eisberg von %s + the glacier of %s + + + der Gletscher von %s + the glacier of %s + + + %s + + + eine %s + + + der Vulkan von %s + + + die %s + + + die %s + + + eine mächtige Mauer + + + ein %s + + + + Krater + + + Pforte + + + Kohlenstück + + + Kohlenstücke + + + + + Westen + + + Nordwesten + + + Nordosten + + + Osten + + + Südwesten + + + Südosten + + + + NW + + + NO + + + Ost + + + SO + + + SW + + + West + + + + + ein unbekanntes Gebäude + an unknown building + + + ein unbekannter zauber + an unknown spell + + + ein unbekanntes Schiff + an unknown ship + + + eine unbekannte Einheit + an unknown unit + + + einer unbekannten Einheit + an unknown unit + + + + + Ereignisse + + + Botschaften + + + Warnungen und Fehler + + + Wirtschaft und Handel + + + Rohstoffe und Produktion + + + Magie und Artefakte + + + Reisen und Bewegung + + + Lehren und Lernen + + + Kämpfe + + + Verschiedenes + + + Neue Zauber + + + Neue Tränke + New Potions + + + + + Burg + + + Leuchtturm + + + Wurmloch + wormhole + + + Bergwerk + + + Steinbruch + + + Hafen + + + Akademie + + + Magierturm + + + Schmiede + + + Sägewerk + + + Pferdezucht + + + Monument + + + Damm + + + Karawanserei + + + Tunnel + + + Taverne + + + Steinkreis + + + Gesegneter Steinkreis + + + Traumschlößchen + + + Struktur + + + Akademie der Künste + academy of arts + + + Skulptur + sculpture + + + + + Zauberstab + + + Zauberstäbe + + + + + Grundmauern + + + Handelsposten + + + Befestigung + + + Turm + + + Burg + + + Festung + + + Zitadelle + + + + + Pyramide + pyramid + + + Pyramide + pyramid + + + + + Sphäre der Unsichtbarkeit + sphere of invisibility + + + Sphären der Unsichtbarkeit + spheres of invisibility + + + Kraut + + + Kräuterbeutel + + + Kräuterbeutel + + + Phiole + + + Phiolen + + + Katapultmunition + ammunition + + + Katapultmunition + ammunition + + + Sonnensegel + solar sail + + + Sonnensegel + solar sails + + + Weihnachtsbaum + christmas tree + + + Weihnachtsbäume + christmas trees + + + Sternenstaub + stardust + + + Sternenstaub + stardust + + + Papyrus + papyrus + + + Papyri + papyri + + + Elfenohr + elven ear + + + Elfenohren + elven ears + + + Dämonenblut + demon blood + + + Dämonenblut + demon blood + + + Goblinkopf + goblin head + + + Goblinköpfe + goblinheads + + + Zwergenbart + dwarven beard + + + Zwergenbärte + dwarven beards + + + Halblingfuß + halfling foot + + + Halblingfüße + halfling feet + + + Menschenskalp + human scalp + + + Menschenskalpe + human scalps + + + Meermenschschuppe + aquarian scale + + + Meermenschschuppen + aquarian scales + + + Insektenfühler + insect antenna + + + Insektenfühler + insect antenna + + + Katzenschwanz + cat tail + + + Katzenschwänze + cat tails + + + Orkhauer + orc tusk + + + Orkhauer + orc tusks + + + Trollhorn + troll horn + + + Trollhörner + troll horns + + + Feder des Phönix + feather of the phoenix + + + Federn des Phönix + feathers of the phoenix + + + + + Silber + + + Silber + + + Trefferpunkt + + + Trefferpunkte + + + Aura + + + Aura + + + permanente Aura + + + permanente Aura + + + Bauer + + + Bauern + + + Einheit + + + Einheiten + + + Person + + + Personen + + + + + Runenschwert + + + Runenschwerter + + + Eisen + + + Eisen + + + Holz + + + Holz + + + Stein + + + Steine + + + Wagen + + + Wagen + + + Katapult + + + Katapulte + + + Schwert + + + Schwerter + + + Speer + + + Speere + + + Mallornspeer + + + Mallornspeere + + + Armbrust + + + Armbrüste + + + Mallornarmbrust + + + Mallornarmbrüste + + + Bogen + + + Bögen + + + Mallornbogen + + + Mallornbögen + + + + Kettenhemd + + + Kettenhemden + + + Schuppenpanzer + + + Schuppenpanzer + + + Plattenpanzer + + + Plattenpanzer + + + Balsam + + + Balsam + + + Gewürz + + + Gewürze + + + Juwel + + + Juwelen + + + Myrrhe + + + Myrrhe + + + Öl + + + Öl + + + Seide + + + Seide + + + Weihrauch + + + Weihrauch + + + Flammenschwert + + + Flammenschwerter + + + Bihänder + + + Bihänder + + + Kriegsaxt + axe + + + Kriegsäxte + axes + + + Elfenbogen + + + Elfenbögen + + + Laenschwert + + + Laenschwerter + + + Laenschild + + + Laenschilde + + + Laenkettenhemd + + + Laenkettenhemden + + + Laen + + + Laen + + + Schild + + + Schilde + + + Hellebarde + + + Hellebarden + + + Lanze + + + Lanzen + + + Mallornlanze + + + Mallornlanzen + + + Mallorn + + + Mallorn + + + Keks + + + Kekse + + + Apfel + + + Äpfel + + + Nuß + + + Nüsse + + + Mandelkern + + + Mandelkerne + + + Drachenblut + + + Drachenblut + + + Feenstiefel + + + Feenstiefel + + + Heiltrank + + + Heiltränke + + + Antimagiekristall + + + Antimagiekristalle + + + Tiegel mit Krötenschleim + + + Tiegel mit Krötenschleim + + + Amulett + + + Amulette + + + Amulett der Keuschheit + + + Amulette der Keuschheit + + + Amulett der Heilung + + + Amulette der Heilung + + + Amulett des Treffens + + + Amulette des Treffens + + + Amulett des wahren Sehens + + + Amulette des wahren Sehens + + + Katzenamulett + + + Katzenamulette + + + Ring der Unsichtbarkeit + + + Ringe der Unsichtbarkeit + + + Ring der Macht + + + Ringe der Macht + + + Ring der flinken Finger + + + Ringe der flinken Finger + + + Pferd + + + Pferde + + + Magischer Kräuterbeutel + + + Magische Kräuterbeutel + + + Silberbeutel + + + Silberkassette + + + Drachenhort + + + Drachenkopf + + + Drachenköpfe + + + Auge des Drachen + + + Augen des Drachen + + + Schartiges Schwert + + + Schartige Schwerter + + + Rostiger Schild + + + Rostige Schilde + + + Rostige Hellebarde + rusty halberd + + + Rostige Hellebarden + rusty halberds + + + Rostige Kriegsaxt + rusty axe + + + Rostige Kriegsäxte + rusty axes + + + Rostiger Zweihänder + rusty claymore + + + Rostige Zweihänder + rusty claymores + + + Rostiges Kettenhemd + + + Rostige Kettenhemden + + + Beutel des negativen Gewichts + + + Beutel des negativen Gewichts + + + Ring der Regeneration + + + Ringe der Regeneration + + + Amulett der Dunkelheit + + + Amulette der Dunkelheit + + + Zauberbeutel + + + Zauberbeutel + + + Traumauge + + + Traumaugen + + + Seeschlangenkopf + + + Seeschlangenköpfe + + + Aurafocus + + + Aurafocuse + + + Akkredition des Xontormia-Expreß + + + Akkreditionen des Xontormia-Expreß + + + Szepter der Tränen + wand of tears + + + Szepter der Tränen + wands of tears + + + Schneeball + snowball + + + Schneebälle + snowball + + + Schneemann + snowman + + + Schneemänner + snowmen + + + Gürtel der Trollstärke + + + Gürtel der Trollstärke + + + Elfenpferd + + + Elfenpferde + + + Pegasus + + + Pegasi + + + Delphin + + + Delphine + + + Eintrittskarte des Großen Museum + + + Eintrittskarten des Großen Museum + + + Rückkehrticket des Großen Museum + + + Rückkehrtickets des Großen Museum + + + Astralkristall + + + Astralkristalle + + + Talenttrunk + + + Talenttrünke + + + Same + + + Samen + + + Mallornsame + + + Mallornsamen + + + Feuerwerk + + + Feuerwerke + + + Lebkuchenherz mit der Aufschrift 'Erz und + Stein, das ist fein' + + + Lebkuchenherzen mit der Aufschrift 'Erz und + Stein, das ist fein' + + + Schlüssel + key + + + Schlüssel + keys + + + Achatener Schlüssel + agate key + + + Achatene Schlüssel + agate keys + + + Saphirner Schlüssel + sapphire key + + + Saphirne Schlüssel + sapphire keys + + + + + Flachwurz + + + Würziger Wagemut + + + Eulenauge + + + Grüner Spinnerich + + + Blauer Baumringel + + + Elfenlieb + + + Gurgelkraut + + + Knotiger Saugwurz + + + Blasenmorchel + + + Wasserfinder + + + Kakteenschwitz + + + Sandfäule + + + Windbeutel + + + Fjordwuchs + + + Alraune + + + Steinbeißer + + + Spaltwachs + + + Höhlenglimm + + + Eisblume + + + Weißer Wüterich + + + Schneekristall + + + + + Flachwurz + + + Würzige Wagemut + + + Eulenaugen + + + Grüne Spinneriche + + + Blaue Baumringel + + + Elfenlieb + + + Gurgelkräuter + + + Knotige Saugwurze + + + Blasenmorcheln + + + Wasserfinder + + + Kakteenschwitze + + + Sandfäulen + + + Windbeutel + + + Fjordwuchse + + + Alraunen + + + Steinbeißer + + + Spaltwachse + + + Höhlenglimme + + + Eisblumen + + + Weiße Wüteriche + + + Schneekristalle + + + + Siebenmeilentee + + + Goliathwasser + + + Wasser des Lebens + + + Schaffenstrunk + + + Wundsalbe + + + Bauernblut + + + Gehirnschmalz + + + Dumpfbackenbrot + + + Nestwärme + + + Pferdeglück + + + Berserkerblut + + + Bauernlieb + + + Trank der Wahrheit + + + Elixier der Macht + + + Heiltrank + + + + Siebenmeilentees + + + Goliathwasser + + + Wasser des Lebens + + + Schaffenstrünke + + + Wundsalben + + + Bauernblut + + + Gehirnschmalz + + + Dumpfbackenbrote + + + Nestwärme + + + Pferdeglück + + + Berserkerblut + + + Bauernlieb + + + Tränke der Wahrheit + + + Elixiere der Macht + + + Heiltränke + + + + + Gürtel der Heldentaten + + + Gürtel der Heldentaten + + + + + AGGRESSIV + + + JEDEM + + + ALLES + + + ANZAHL + + + AURA + + + BÄUME + + + BAUERN + + + BEISTAND + + + BEWACHEN + + + BURG + + + DEFENSIV + + + EINHEIT + + + ERESSEA + + + FLIEHE + + + FREMDES + + + GEBÄUDE + + + GEGENSTÄNDE + + + GIB + + + GNADE + + + HELFE + + + HINTEN + + + HINTER + + + KOMMANDO + + + KRÄUTER + + + DURCHREISE + TRAVEL + + + KÄMPFE + + + NICHT + + + NÄCHSTER + + + PARTEI + + + PARTEITARNUNG + + + PAUSE + + + PERSONEN + + + PRIVAT + + + REGION + + + SCHIFF + + + SILBER + + + STRAßEN + + + STUFE + + + TEMPORÄRE + + + TRÄNKE + + + UM + + + VOR + + + VORNE + + + ZAUBER + + + + XETRANK + XEPOTION + + + XEBALLON + XEBALLOON + + + XELAEN + XELAEN + + + + + Alchemie + + + Armbrustschießen + + + Ausdauer + + + Bergbau + + + Bogenschießen + + + Burgenbau + + + Handeln + + + Hiebwaffen + + + Holzfällen + + + Katapultbedienung + + + Kräuterkunde + + + Magie + + + Pferdedressur + + + Reiten + + + Rüstungsbau + + + Schiffbau + + + Segeln + + + Spionage + + + Stangenwaffen + + + Steinbau + + + Steuereintreiben + + + Straßenbau + + + Taktik + + + Tarnung + + + Unterhaltung + + + Waffenbau + + + Waffenloser Kampf + + + Wagenbau + + + Wahrnehmung + + + + + + // + + + BANNER + + + ARBEITEN + + + WARTEN + WAIT + + + ATTACKIEREN + + + BIETEN + + + BEANSPRUCHEN + CLAIM + + + BEKLAUEN + + + BELAGERE + + + BENENNEN + + + BENUTZEN + + + BESCHREIBEN + + + BETRETEN + + + BEWACHEN + + + BOTSCHAFT + + + ENDE + + + FAHREN + + + NUMMER + + + FRIEDEN + PEACE + + + KRIEG + WAR + + + FOLGEN + + + FORSCHEN + + + HELFEN + + + KÄMPFEN + + + KAMPFZAUBER + + + KAUFEN + + + KONTAKTIEREN + + + LEHREN + + + LERNEN + + + MACHEN + + + NACH + + + XONTORMIA + XONTORMIA + + + ALLIANZ + ALLIANCE + + + BEFÖRDERUNG + PROMOTION + + + BEZAHLEN + PAY + + + PFLANZEN + + + PRÄFIX + + + SYNONYM + + + INFO + + + PASSWORT + + + REKRUTIEREN + + + RESERVIEREN + + + ROUTE + + + SABOTIEREN + + + OPTION + + + SPIONIEREN + + + STIRB + + + TARNEN + + + TRANSPORTIEREN + + + TREIBEN + + + UNTERHALTEN + + + VERKAUFEN + + + VERLASSEN + + + VERGESSEN + + + ZAUBERE + + + ZEIGEN + + + ZERSTÖREN + + + ZÜCHTEN + + + DEFAULT + + + REPORT + + + XML + XML + + + URSPRUNG + + + EMAIL + + + PIRATERIE + + + LOCALE + + + NEUSTART + + + GRUPPE + + + OPFERE + + + BETEN + + + SORTIEREN + + + JIHAD + + + GM + + + WERWESEN + + + + + Optionen + + + Stufe + + + Aktueller Status + + + Benötigte Kräuter + + + im Bau + + + beschädigt + + + Unglücklicherweise wurde deine Partei + ausgelöscht. Du kannst gerne an einer anderen Stelle wieder + einsteigen. Melde Dich einfach wieder an. + + + Talente + + + hat + + + Größe + + + Zauber + + + Kampfzauber + + + keiner + + + Liste aller Adressen + + + anonym + + + Angriff + + + Verteidigung + + + Rüstung + + + Schaden + + + + + Deine Partei hat letzte Runde keinen Zug + abgegeben! + + + + + Schneemann + snowman + + + Schneemänner + snowmen + + + Klon + clone + + + Klone + clones + + + Klonen + clones + + + Klonen + clone + + + + Schablone + template + + + Schablonen + templates + + + Schablonen + templates + + + Schablonen + template + + + + Gnom + gnome + + + Gnome + gnomes + + + Gnomen + gnomes + + + Gnomen + gnome + + + + Museumsgeist + museumghost + + + Museumsgeister + museumghosts + + + Museumsgeistern + museumghosts + + + Museumsgeister + museumghost + + + + Ghast + ghast + + + Ghaste + ghasts + + + Ghasten + ghasts + + + Ghast + ghast + + + + Ghoul + ghoul + + + Ghoule + ghouls + + + Ghoulen + ghouls + + + Ghoul + ghoul + + + + Juju-Zombie + juju-zombie + + + Juju-Zombies + juju-zombies + + + Juju-Zombies + juju-zombies + + + Juju-Zombie + juju-zombie + + + + Zombie + zombie + + + Zombies + zombies + + + Zombies + zombies + + + Zombie + zombie + + + + Skelettherr + skeleton lord + + + Skelettherren + skeleton lords + + + Skelettherren + skeleton lords + + + Skelettherren + skeleton lord + + + + Skelett + skeleton + + + Skelette + skeletons + + + Skeletten + skeletons + + + Skelett + skeleton + + + + Zentaur + centaur + + + Zentauren + centaurs + + + Zentauren + centaurs + + + Zentauren + centaur + + + + Schattenritter + shadow knight + + + Schattenritter + shadow knight + + + Schattenrittern + shadow knights + + + Schattenritter + shadow knight + + + + Seeschlange + sea serpent + + + Seeschlangen + sea serpents + + + Seeschlangen + sea serpents + + + Seeschlangen + sea serpent + + + + Krake + kraken + + + Kraken + krakens + + + Kraken + krakens + + + Kraken + kraken + + + + Riesenschildkröte + giant turtle + + + Riesenschildkröten + giant turtles + + + Riesenschildkröten + giant turtles + + + Riesenschildkröten + giant turtle + + + + Delphin + dolphin + + + Delphine + dolphins + + + Delphinen + dolphins + + + Delphin + dolphin + + + + Tiger + tiger + + + Tiger + tiger + + + Tigern + tigers + + + Tiger + tiger + + + + Höllenkatze + hellcat + + + Höllenkatzen + hellcats + + + Höllenkatzen + hellcats + + + Höllenkatzen + hellcat + + + + Eule + owl + + + Eulen + owls + + + Eulen + owls + + + Eulen + owl + + + + Fee + fairy + + + Feen + fairies + + + Feen + fairies + + + Feen + fairy + + + + Traumkatze + dreamcat + + + Traumkatzen + dreamcats + + + Traumkatzen + dreamcats + + + Traumkatzen + dreamcat + + + + Teufelchen + imp + + + Teufelchen + imps + + + Teufelchen + imps + + + Teufelchen- + imp + + + + Geist + ghost + + + Geister + ghosts + + + Geistern + ghosts + + + Geister + ghost + + + + Warg + direwolf + + + Warge + direwolves + + + Wargen + direwolves + + + Warg + direwolf + + + + Einhorn + unicorn + + + Einhörner + unicorns + + + Einhörnern + unicorns + + + Einhorn + unicorn + + + + Nymphe + nymph + + + Nymphen + nymphs + + + Nymphen + nymphs + + + Nymphen + nymph + + + + Singdrache + song dragon + + + Singdrachen + song dragons + + + Singdrachen + song dragons + + + Singdrachen + song dragon + + + + Ratte + rat + + + Ratten + rats + + + Ratten + rats + + + Ratten + rat + + + + Adler + eagle + + + Adler + eagles + + + Adlern + eagles + + + Adler + eagle + + + + Tunnelwurm + tunnelworm + + + Tunnelwürmer + tunnelworms + + + Tunnelwürmern + tunnelworms + + + Tunnelwurm + tunnelworm + + + + Luchs + lynx + + + Luchse + lynx + + + Luchsen + lynx + + + Luchs + lynx + + + + Wolf + wolf + + + Wölfe + wolves + + + Wölfen + wolves + + + Wolfs + wolf + + + + Bauer + peasant + + + Bauern + peasants + + + Bauern + peasants + + + Bauern + peasant + + + + Hirntöter + braineater + + + Hirntöter + braineaters + + + Hirntöter + braineaters + + + Hirntöter + braineater + + + + Schlumpf + smurf + + + Schlümpfe + smurfs + + + Schlumpf + smurf + + + Schlümpfen + smurfs + + + + Kröte + toad + + + Kröten + toads + + + Kröten + toads + + + Kröten + toad + + + + Alp + nightmare + + + Alps + nightmaress + + + Alps + nightmares + + + Alp + nightmare + + + + Bergwächter + mountainguard + + + Bergwächter + mountainguard + + + Bergwächtern + mountainguards + + + Bergwächter + mountainguard + + + + Schattenmeister + shadowmaster + + + Schattenmeister + shadowmaster + + + Schattenmeistern + shadowmasters + + + Schattenmeister + shadowmaster + + + + Schattendämon + shadowdemon + + + Schattendämonen + shadowdemons + + + Schattendämonen + shadowdemons + + + Schattendämon + shadowdemon + + + + Steingolem + stone golem + + + Steingolems + stone golems + + + Steingolems + stone golems + + + Steingolem + stone golem + + + + Eisengolem + irongolem + + + Eisengolems + irongolems + + + Eisengolems + irongolems + + + Eisengolem + irongolem + + + + Zauber + spell + + + Zauber + spell + + + Zauber + spell + + + Zauber + spell + + + + Spezial + special + + + Spezial + special + + + Spezial + special + + + Spezial + special + + + + Dracoid + dracoid + + + Dracoide + dracoids + + + Dracoiden + dracoids + + + Dracoiden + dracoid + + + + Katzendrache + catdragon + + + Katzendrachen + catdragons + + + Katzendrachen + catdragons + + + Katzendrachen + catdragon + + + + Ent + ent + + + Ents + ents + + + Ents + ents + + + Ent + ent + + + + Schattendrache + shadow dragon + + + Schattendrachen + shadow dragons + + + Schattendrachen + shadow dragons + + + Schattendrachen + shadow dragon + + + + Todesflatter + darkbat + + + Todesflattern + darkbats + + + Todesflattern + darkbats + + + Todesflatter + darkbat + + + + Alptraum + nightmare + + + Alpträume + nightmares + + + Alpträumen + nightmares + + + Alptraum + nightmare + + + + Nachteinhorn + vampiric unicorn + + + Nachteinhörner + vampiric unicorns + + + Nachteinhörnern + vampiric unicorns + + + Nachteinhorn + vampiric unicorn + + + + Wyrm + wyrm + + + Wyrme + wyrms + + + Wyrmen + wyrms + + + Wyrm + wyrm + + + + Drache + dragon + + + Drachen + dragons + + + Drachen + dragons + + + Drachen + dragon + + + + Jungdrache + young dragon + + + Jungdrachen + young dragons + + + Jungdrachen + young dragons + + + Jungdrachen + young dragon + + + + Phönix + phoenix + + + Phönixe + phoenixes + + + Phönixen + phoenixes + + + Phönix + phoenix + + + + Illusion + illusion + + + Illusionen + illusions + + + Illusions + illusions + + + Illusions + illusion + + + + Spinx + sphinx + + + Spinxen + sphinxs + + + Spinxen + sphinx + + + Spinx + sphinx + + + + kleiner Scarabäus + little scarab + + + kleine Scarabäen + little scarab + + + kleinen Scarabäen + little scarab + + + kleine Scarabäen + little scarab + + + + grüner Scarabäus + green scarab + + + grüne Scarabäen + green scarab + + + grünen Scarabäen + green scarab + + + grünen Scarabäen + green scarab + + + + blauer Scarabäus + blue scarab + + + blaue Scarabäen + blue scarabs + + + blauen Scarabäen + blue scarab + + + blaue Scarabäen + blue scarab + + + + roter Scarabäus + red scarab + + + rote Scarabäen + red scarabs + + + roten Scarabäen + red scarab + + + rote Scarabäen + red scarab + + + + Untoter Pharao + undead Pharaoh + + + Untoter Pharaonen + undead Pharaohs + + + Untoten Pharao + undead Pharaoh + + + Untote Pharaonen + undead Pharaoh + + + + Mumie + mummy + + + Mumien + mummys + + + Mumien + mummy + + + Mumien + mummy + + + + Untoter + undead + + + Untote + undead + + + Untoten + undead + + + Untoten + undead + + + + Apepschlange + apepsnake + + + Apepschlangen + apepsnakes + + + Apepschlangen + apepsnakes + + + Apepschlange + apepsnake + + + + Apophis + apophis + + + Apophis + apophis + + + Apophis + apophis + + + Apophis + apophis + + + + Meermensch + aquarian + + + Meermenschen + aquarians + + + Meermenschen + aquarians + + + Meermenschen + aquarian + + + + Katze + cat + + + Katzen + cats + + + Katzen + cats + + + Katzen + cats + + + + Halbling + halfling + + + Halblinge + halflings + + + Halblingen + halflings + + + Halblings + halfling + + + + Insekt + insect + + + Insekten + insects + + + Insekten + insects + + + Insekten + insect + + + + Dämon + demon + + + Dämonen + demons + + + Dämonen + demons + + + Dämonen + demon + + + + Troll + troll + + + Trolle + trolls + + + Trollen + trolls + + + Troll + troll + + + + Mensch + human + + + Menschen + humans + + + Menschen + humans + + + Menschen + human + + + + Goblin + goblin + + + Goblins + goblins + + + Goblins + goblins + + + Goblin + goblin + + + + Ork + orc + + + Orks + orcs + + + Orks + orcs + + + Ork + orc + + + + Snotling + snotling + + + Snotlinge + snotlings + + + Snotlingen + snotlings + + + Snotling + snotling + + + + Snotling + snotling + + + Snotlinge + snotlings + + + Snotlingen + snotlings + + + Snotling + snotling + + + + Elf + elf + + + Elfen + elves + + + Elfen + elves + + + Elfen + elves + + + + Zwerg + dwarf + + + Zwerge + dwarves + + + Zwergen + dwarves + + + Zwergen + dwarf + + + + + + sehr stark + critically wounded + + + stark + heavily wounded + + + schwer verwundet + badly wounded + + + verwundet + wounded + + + erschöpft + exhausted + + + + + For Seven Mile Tea, boil up a Cobalt Fungus and pour the resulting brew into a Windbag. Catch and filter the liquid that drips out and administer it. This tea allows up to ten men to move as fast as a horse. + Für den Siebenmeilentee koche man einen Blauen Baumringel auf und gieße dieses Gebräu in einen Windbeutel. Das heraustropfende Wasser fange man auf, filtere es und verabreiche es alsdann. Durch diesen Tee können bis zu zehn Menschen schnell wie ein Pferd laufen. + + + 'First roast the Gurgelkraut quickly and add some Fjordwuchs to spice it up. Let it all boil slowly until almost all liquid has evaporated. Leave the mash overnight and finally squeeze it the next morning until a thick fluid drips out.' The liquid thus produced, 'Goliath Water' as we call it, is enough for 10 men and gives each man the carrying capacity of a horse for one week. + Zuerst brate man das Gurgelkraut leicht an und würze das Zeug mit ein wenig Fjordwuchs. Man lasse alles so lange kochen, bis fast alle Flüssigkeit verdampft ist. Diesen Brei stelle man über Nacht raus. Am nächsten Morgen presse man den Brei aus. Die so gewonnene Flüssigkeit, Goliathwasser genannt, verleiht bis zu zehn Männern die Tragkraft eines Pferdes. + + + The "Water of Life" allows living trees to be created from logs. A Knotroot and Elvendear are heated until one can just still keep one's finger in. This is then poured into a jar and allowed to cool slowly. The extract is sufficient for 10 pieces of wood. + Das 'Wasser des Lebens' ist in der Lage, aus gefällten Baumstämmen wieder lebende Bäume zu machen. Dazu wird ein knotiger Saugwurz zusammen mit einem Elfenlieb erwärmt, so daß man gerade noch den Finger reinhalten kann. Dies gieße man in ein Gefäß und lasse es langsam abkühlen. Der Extrakt reicht für 10 Holzstämme. + + + Allow a Tangy Temerity to simmer for three hours in a litre of water, then add a grated Mandrake, and sprinkle in a Gapgrowth harvested at full moon. The whole brew should then be allowed to stew for three days in a warm place. This potion increases the strength and endurance of ten men so that they can achieve twice as much in a week. + Man lasse einen Würzigen Wagemut drei Stunden lang in einem Liter Wasser köcheln. Dann gebe man eine geriebene Alraune dazu und bestreue das ganze mit bei Vollmond geerntetem Spaltwachs. Nun lasse man den Sud drei Tage an einem dunklen und warmen Ort ziehen und seie dann die Flüssigkeit ab. Dieser Schaffenstrunk erhöht die Kraft und Ausdauer von zehn Männern, so daß sie doppelt soviel schaffen können wie sonst. + + + When one is severely wounded after a hard battle it is advisable to have some Ointment to hand. Applied to wounds, this magical paste closes them in the blink of an eye. For the preparation the alchemist requires a cobalt fungus, tangy temerity, and white hemlock. A dose of the potion heals up to 400 hitpoints. + Ist man nach einem einem harten Kampf schwer verwundet, ist es ratsam, etwas Wundsalbe parat zu haben. Streicht man diese magische Paste auf die Wunden, schließen sich diese augenblicklich. Für die Herstellung benötigt der Alchemist nebst einem Blauen Baumringel einen Würzigen Wagemut und einen Weißen Wüterich. Eine solche Portion heilt bis zu 400 Lebenspunkte. + + + Knowledge of this potion is amongst the most dangerous and secret wisdom of the alchemist. Snatched from the darkest hells, the knowledge of this formula enables the production of an elixer which serves Demons as nourishment. If used by normal beings it leads to a swift death and eternal undeath. The creation requires Fjord Fungus together with some Cave Lichen and Cobalt Fungus, and an unfortunate peasant from the region, who is killed in the bloody days-long ritual. One vial of the potion satisfies the hunger of 100 Demons for a week. + Zu den gefährlichsten und geheimsten Wissen der Alchemisten zählt die Kenntnis um diesen Trank. Den finstersten Höllen entrissen, ermöglicht die Kenntnis dieser Formel die Herstellung eines Elixiers, welches Dämonen als Nahrung dient. Von normalen Lebewesen eingenommen, führt es zu schnellem Tod und ewigen Nichtleben. Die Herstellung benötigt nebst Fjordwuchs, etwas Höhlenglimm und einem Blauen Baumringel auch einen Bauern aus der Region, welcher in einem tagelangen blutigen Ritual getötet wird. Ein Fläschchen des Tranks kann den Hunger von 100 Dämonen für eine Woche stillen. + + + To create the brain wax potion, mix the juice of a waterfinder with quite a bit of grated windbag and a pinch of bugleweed. Let this steep for just a minute. When the liquid is only lukewarm, add some rock weed. Using a large spoon, stirr exactly seven times clockwise and then seven times counterclockwise. Fill the vial when the liquid has gone still. The juice gives ten people a 33% chance of an additional attempt at learning a skill. + Für das Gehirnschmalz verrühre man den Saft eines Wasserfinders mit recht viel geriebenem Windbeutel und ein wenig Gurgelkraut. Dies lasse man kurz aufwallen. Wenn die Flüssigkeit nur noch handwarm ist, gebe man etwas Steinbeißer dazu. Das ganze muß genau siebenmal rechtsherum und siebenmal linksherum mit einem großen Löffel gerührt werden. Wenn keine Bewegung mehr zu erkennen ist, fülle man den Saft ab. Der Saft gibt mit einer Chance von 1/3 bis zu zehn Personen einen zusätzlichen Lernversuch. + + + A duncebun is a nasty piece of work, negating any attempt at learning a skill, or even causing the subject to forget things! For ten servings knead a rasped fjord fungus, an abraded owlsgaze and a finely sliced spider ivy to a smooth dough. Bake for an hour at moderate heat and brush the result with some cave lichen. Who eats this bread will not learn what he's attempting to learn, and, in case there is no attempt to learn anything, will forget a week's worth of study in his best skill. + Das Dumpfbackenbrot ist eine sehr gemeine Sache, macht es doch jeden Lernerfolg zunichte oder läßt einen gar Dinge vergessen! Für zehn Portionen verknete man einen geriebenen Fjordwuchs, einen zerstoßenes Eulenauge und einen kleingeschnittenen Grünen Spinnerich zu einem geschmeidigen Teig. Diesen backe man eine Stunde lang bei guter Hitze und bestreiche das Ergebnis mit etwas Höhlenglimm. Wer dieses Brot gegessen hat, kann eine Woche lang nichts lernen, und so er nichts zu lernen versucht, wird er gar eine Woche seiner besten Fähigkeit vergessen. + + + A potion of nest warmth allows an insect to recruit outside of a desert region in winter. The learned alchemist prepares this by taking a peyote, mixing it with a portion of gapgrowth which has been gathered during a clear, starry night. To dispell winter, add some blossoms of the ice begonia in the mix, and stirr everything together with a spider ivy until it turns a nice shade of violet. One vial supplies an entire region for a whole week. + Nestwärme erlaubt es einem Insekt, im Winter außerhalb von Wüsten neue Rekruten anzuwerben. Zur Zubereitung nimmt der geübte Alchemist einen Kakteenschwitz, vermischt ihn mit einer Portion Spaltwachs, die in einer sternklaren Nacht gesammelt wurde, gibt zur Vertreibung des Winters einige Blütenblätter der Eisblume in den Sud, und rührt alles mit einem grünen Spinnerich bis es eine violette Farbe annimmt. Ein Trank reicht eine Woche lang für eine ganze Region. + + + To make a horsepower potion, chop a peyote, a cobalt fungus and some knotroot, and boil it in a bucketful of water. Then add some sand reeker and let the mixture steep for three days. Finally one gives this to the horses to drink, to double their procreation. + Für das Pferdeglück zerhacke man einen Kakteenschwitz, einen blauen Baumringel und etwas knotigen Saugwurz und koche das ganze mit einem Eimer Wasser auf. Dann füge man etwas Sandfäule dazu und lasse diesen Sud drei Tage lang ziehen. Letztlich gebe man es den Pferden zu trinken, auf daß sie sich doppelt so schnell vermehren. + + + The use of the berserkers blood potion is advised to increase one's warriors abilities to new heights. To create this, one needs a white hemlock, some flatroot, sand reeker and a mandrake. All ingredients have to be sliced as finely as possible, after which it is boiled for two hours. The cooled brew is strained through a cloth. The resulting juice is enough to improve up to ten warriors. + Will man seine Krieger zu Höchstleistungen antreiben, sei das Berserkerblut empfohlen. Um es herzustellen, braucht man einen Weißen Wüterich, etwas Flachwurz, Sandfäule und eine Alraune. Alle Zutaten müssen möglichst klein geschnitten und anschließend zwei Stunden lang gekocht werden. Den abgekühlten Brei gebe man in ein Tuch und presse ihn aus. Der so gewonnene Saft reicht aus, um zehn Kämpfer besser angreifen zu lassen. + + + The peasant love potion enamors both Man and Woman to the same degree and results in a strong wish for children. For a big portion scoop out a mandrake, fill it with finely chopped bubblemorel, elvendear and snowcrystal petal, sprinkle grated rock weed on top and let it simmer on low heat for twenty hours. The potion can grant up to 1000 peasants the happiness of twins. + Das Bauernlieb betört Mann und Frau gleichwohl und läßt in ihnen den Wunsch nach Kindern anwachsen. Für eine große Portion höhle man eine Alraune aus, gebe kleingehackten Blasenmorchel, Elfenlieb und Schneekristall dazu, streue ein wenig geriebenen Steinbeißer darüber und lasse dieses zwanzig Stunden lang auf kleiner Flamme kochen. Bis zu 1000 Bauern vermag der Trank das Glück von Zwillinge zu bescheren. + + + This simple but very potent brew sharpens the senses of anyone that drinks of it and makes him able to see through even the most complex illusions for one week. + Dieses wirkungsvolle einfache Gebräu schärft die Sinne des Trinkenden derart, daß er in der Lage ist, eine Woche lang auch die komplexesten Illusionen zu durchschauen. + + + One of the most rare and prized of all alchemist elixers, this potion grants the user a dragon's power for a few weeks. The potion increases the life-energy of a maximum of ten people fivefold. The effect is strongest right after drinking and slowly decreases over time. To brew this potion the alchemist needs an elvendear, a windbag, a piece of waterfinder and a spider ivy. Finally he dusts it with some minced bubblemorel and stirrs the powder into some dragon's blood. + Eines der seltensten und wertvollsten alchemistischen Elixiere, verleiht dieser Trank dem Anwender für einige Wochen die Kraft eines Drachen. Der Trank erhöht die Lebensenergie von maximal zehn Personen auf das fünffache. Die Wirkung ist direkt nach der Einnahme am stärksten und klingt danach langsam ab. Zur Herstellung benötigt der Alchemist ein Elfenlieb, einen Windbeutel, ein Stück Wasserfinder und einen Grünen Spinnerich. Über dieses Mischung streue er schließlich einen zerriebenen Blasenmorchel und rühre dieses Pulver unter etwas Drachenblut. + + + For a healing potion one takes the peel of a windbag and some bugleweed, stirr in some chopped elvendear and sprinkle it with the blossoms of an ice begonia. This has to cook through for four days, while a gapgrowth has to be added on the second day. Then one carefully scoops off the top layer of liquid. One such potion gives four men (or one man four times) a 50% chance to survive otherwise lethal wounds. The potion is automatically used in case of injury. + Für einen Heiltrank nehme man die Schale eines Windbeutels und etwas Gurgelkraut, rühre eine kleingehacktes Elfenlieb dazu und bestreue alles mit den Blüten einer Eisblume. Dies muß vier Tage lang gären, wobei man am zweiten Tag einen Spaltwachs dazutun muß. Dann ziehe man vorsichtig den oben schwimmenden Saft ab. Ein solcher Trank gibt vier Männern (oder einem Mann vier mal) im Kampf eine Chance von 50%, sonst tödliche Wunden zu überleben. Der Trank wird von ihnen automatisch bei Verletzung angewandt. + + + + + Erschaffe einen Ring der Macht + Create A Ring Of Power + + + Schild des Fisches + Shield Of The Fish + + + Runen des Schutzes + Protective Runes + + + Ruf der Realität + Call Of Reality + + + Astraler Ruf + Astral Call + + + Magiefresser + Destroy Magic + + + Mauern der Ewigkeit + Eternal Walls + + + Stehle Aura + Steal Aura + + + Schutzzauber + Resist Magic + + + Astraler Blick + Astral Gaze + + + Auratransfer + Transfer Aura + + + Monster friedlich stimmen + Calm Monster + + + Luftschiff + Airship + + + Lied der Verführung + Song Of Seduction + + + Aushorchen + sound_out + + + Kriegsgesang + Song Of War + + + Gesang der Angst + Song Of Fear + + + Lied des Ortes analysieren + Analysis + + + Schockwelle + Shockwave + + + Fluch brechen + Negate Curse + + + Erschaffe ein Amulett der Keuschheit + Create An Amulet Of Chastity + + + Beschleunigung + Acceleration + + + Großer Drachenodem + Powerful Dragonbreath + + + Opfere Kraft + Sacrifice Strength + + + Belebtes Gestein + Living Rock + + + Gesang der Melancholie + Song of Melancholy + + + Gesang des wachen Geistes + Song Of The Youthful Spirit + + + Gesang des schwachen Geistes + Song Of The Aging Spirit + + + Gesang der Friedfertigkeit + Song Of Peace + + + Gesang der Versklavung + Song Of Slavery + + + Hohe Kunst der Überzeugung + Song Of Slavery + + + Zeitdehnung + Double Time + + + Rüstschild + Shield Shine + + + Wyrmtransformation + Wyrmtransformation + + + Schattenodem + Shadowbreath + + + Feuersturm + Firestorm + + + Feuerwalze + Immolation + + + Eisnebel + Coldfront + + + Säurenebel + Acid Rain + + + Furchteinflößende Aura + Panic + + + Meteorregen + Meteor Shower + + + Schattenruf + Shadow Call + + + Erschaffe einen Ring der Regeneration + Create A Ring Of Regeneration + + + Mob aufwiegeln + Mob Rule + + + Aufruhr beschwichtigen + Calm Riot + + + Aufruhr verursachen + Riot + + + Blick in die Realität + Gaze Upon Reality + + + Störe Astrale Integrität + Astral Disruption + + + Eisiger Drachenodem + Icy Dragonbreath + + + Eisiger Drachenodem + Icy Dragonbreath + + + Erschaffe ein Runenschwert + Create A Runesword + + + Erschaffe einen Beutel des Negativen Gewichts + Create A Bag Of Holding + + + Erschaffe einen Aurafocus + Create An Aurafocus + + + Erschaffe Antimagiekristall + Create An Antimagic Crystal + + + Astrale Schwächezone + Antimagic + + + Astraler Ausgang + Astral Exit + + + Astraler Weg + Astral Path + + + Beute Bewahren + Save Spoils + + + Schutz vor Magie + Protection from Magic + + + Wunderdoktor + Miracle Doctor + + + Schleieraura + Concealing Aura + + + Magie analysieren + Analyze Magic + + + Hohes Lied der Gaukelei + Song of Generosity + + + Gesang des Werbens + Song of Courting + + + Schleieraura + Veil + + + Lied der Heilung + Blessed Harvest + + + Gesang der Furcht + Song of Terror + + + Segen der Erde + Blessed Harvest + + + Heldengesang + Epic Heroes + + + Gesang des Lebens analysieren + Analyze Song of Life + + + Bannlied + Countersong + + + Gesang des Auratransfers + Hymn of Aura Sharing + + + Gesang der Verwirrung + Song of Confusion + + + Plappermaul + Blabbermouth + + + Regentanz + Rain Dance + + + Gaukeleien + Jugglery + + + Friedenslied + Appeasing Song + + + Viehheilung + Cattle Healing + + + Erschaffe Steingolems + Create Stone Golems + + + Erschaffe Eisengolems + Create Iron Golems + + + Hainzauber + Grove Of Oak Trees + + + Rostregen + Rain Of Rust + + + Firuns Fell + Firun's Coat + + + Hagel + Hail + + + Seelenkopie + Doppelganger + + + Irrlichter + Wisps + + + Schlechte Träume + Bad Dreams + + + Bergwächter + Mountain Guardian + + + Magischer Pfad + Magic Path + + + Tor in die Ebene der Hitze + Great Drought + + + Wurzeln der Magie + Roots Of Magic + + + Mahlstrom + Maelstrom + + + Windschild + Air Shield + + + Segne Mallornstecken + Bless Mallorn Logs + + + Beschwörung eines + Wasserelementares + Summon Water Elemental + + + Heilung + Heal + + + Wirbelwind + Whirlwind + + + Astralschutzgeister + Astral Guardian Spirits + + + Meditation + Meditate + + + Beschwöre einen Erdelementar + Summon Earth Elemental + + + Beschwöre einen Sturmelementar + Summon Storm Elemental + + + Erschaffe ein Amulett des wahren + Sehens + Create An Amulet Of True Sight + + + Erschaffe einen Ring der + Unsichtbarkeit + Create A Ring Of Invisibility + + + Miriams flinke Finger + Quick Fingers + + + Heimstein + Homestone + + + Wolfsgeheul + Timber Wolves + + + Blick des Basilisken + Gaze Of The Basilisk + + + Starkes Tor und feste Mauer + Strong Wall And Sturdy Gate + + + Geister bannen + Banish Spirits + + + Lebenslied festigen + Silence Dissonance + + + Ritual der Aufnahme + Rit of Acceptance + + + Weg der Bäume + Path Of Trees + + + Sog des Lebens + Ties Of Life + + + Heiliger Boden + Sacred Ground + + + Erschaffe einen magischen + Kräuterbeutel + Create A Magical Herb Pouch + + + Erwecke Ents + Awakening Of The Ents + + + Segne Steinkreis + Bless Stone Circle + + + Rindenhaut + Barkskin + + + Verwünschung + Hex + + + Kleine Flüche + Minor Curses + + + Feuerball + Fireball + + + Gabe des Chaos + Chaos Gift + + + Kleines Blutopfer + Lesser Sacrifice + + + Blutrausch + Blood Frenzy + + + Chaosfluch + Chaos Curse + + + Mächte des Todes + Animate Dead + + + Rosthauch + Winds Of Rust + + + Machtübertragung + Transfer Power + + + Feuerwand + Wall Of Fire + + + Fluch der Pestilenz + Curse Of Pestilence + + + Wahnsinn des Krieges + Madness of War + + + Beschwöre Schattendämonen + Summon Shadowdemons + + + Beschwörung eines Hitzeelementar + Summon Fire Elemental + + + Untote Helden + Undead Heroes + + + Erschaffe einen Gürtel der + Trollstärke + Create A Belt Of Troll + Strength + + + Astraler Riss + Astral Leak + + + Astrales Chaos + Astral Chaos + + + Feuerteufel + Fire Fiend + + + Pentagramm + Pentagram + + + Unheilige Kraft + Unholy Strength + + + Todeswolke + Death Cloud + + + Drachenruf + Call Dragons + + + Beschwöre Schattenmeister + Summon Shadowmasters + + + Erschaffe ein Flammenschwert + Create A Flamesword + + + Vertrauten rufen + Summon Familiar + + + Chaossog + Chaos Gate + + + Traumsenden + Dream + + + Wahrsagen + Divination + + + Schattenritter + Shadow Knights + + + Grauen der Schlacht + Unspeakable Horrors + + + Seelenfrieden + Eternal Rest + + + Traumschlößchen + Castle Of Illusion + + + Traum der Magie + Dream Of Magic + + + Gestaltwandlung + Shapeshift + + + Traumlesen + Read Dreams + + + Schwere Glieder + Tiredness + + + Wiederbelebung + Resurrection + + + Schlechter Schlaf + Insomnia + + + Schlaf + Sleep + + + Traumdeuten + Mind Probe + + + Alp + Nightmare + + + Erschaffe ein Traumauge + Create a Visioneye + + + Erschaffe eine Sphäre der Unsichtbarkeit + Create a Sphere of Invisbility + + + Schöne Träume + Pleasant Dreams + + + Traumbilder entwirren + Remove Dreams + + + Tod des Geistes + Mental Death + + + Süße Träume + Sweet Dreams + + + Traum von den Göttern + Dream of the gods + + + Göttliches Netz + Web of the Gods + + + Kraft der Natur + force of nature + + + Gesang der Götter + Song of the Gods + + + Göttliche Macht + Power of the Gods + + + + + Tötet die Feinde mit Säure. + Kills enemies with acid. + + + Tötet die Feinde mit Kälte. + Kills enemies with cold. + + + Tötet die Feinde mit Feuer. + Kills enemies with fire. + + + Verletzt alle Gegner. + Injures all enemies. + + + Ruft Schattenwesen. + Calls beings from shadow. + + + Panik. + Panic. + + + Entzieht Talentstufen und macht Schaden wie Großer Odem. + + + Dieser Zauber bewirkt eine schwere Störung des Astralraums. Innerhalb eines astralen Radius von Stufe/5 Regionen werden alle Astralwesen, die dem Zauber nicht wiederstehen können, aus der astralen Ebene geschleudert. Der astrale Kontakt mit allen betroffenen Regionen ist für Stufe/3 Wochen gestört. + + + + Diese vor dem Kampf zu zaubernde Ritual gibt den eigenen Truppen + einen zusätzlichen Bonus auf ihre Rüstung. Jeder Treffer + reduziert die Kraft des Zaubers, so dass der Schild sich irgendwann + im Kampf auflösen wird. + + + + + Dieser Zauber beschleunigt einige Kämpfer auf der eigenen Seite + so, dass sie während des gesamten Kampfes in einer Kampfrunde zweimal + angreifen können. + + + + + Dieser Zauber vermag dem Gegner ein geringfügig versetztes Bild der + eigenen Truppen vorzuspiegeln, so wie der Fisch im Wasser auch nicht + dort ist wo er zu sein scheint. Von jedem Treffer kann so die Hälfte + des Schadens unschädlich abgeleitet werden. Doch hält der Schild nur + einige Hundert Schwerthiebe aus, danach wird er sich auflösen. + Je stärker der Magier, desto mehr Schaden hält der Schild aus. + + + + + Zeichnet man diese Runen auf die Wände eines Gebäudes oder auf die + Planken eines Schiffes, so wird es schwerer durch Zauber zu + beeinflussen sein. Jedes Ritual erhöht die Widerstandskraft des + Gebäudes oder Schiffes gegen Verzauberung um 20%. + Werden mehrere Schutzzauber übereinander gelegt, so addiert + sich ihre Wirkung, doch ein hundertprozentiger Schutz läßt sich so + nicht erreichen. Der Zauber hält mindestens drei Wochen an, je nach + Talent des Magiers aber auch viel länger. + + + + + Mit Hilfe dieses Zauber kann der Magier eigene Aura im Verhältnis + 2:1 auf einen anderen Magier des gleichen Magiegebietes oder im + Verhältnis 3:1 auf einen Magier eines anderen Magiegebietes + übertragen. + + + + + Der Magier kann kurzzeitig in die Astralebene blicken und erfährt + so alle Einheiten innerhalb eines astralen Radius von Stufe/5 Regionen. + + + + + Mit Hilfe dieses magischen Gesangs kann der Magier eine Region in + Aufruhr wieder beruhigen. Die Bauernhorden werden sich verlaufen + und wieder auf ihre Felder zurückkehren. + + + + Aus 'Wanderungen' von Firudin dem Weisen: + 'In Weilersweide, nahe dem Wytharhafen, liegt ein kleiner Gasthof, der + nur wenig besucht ist. Niemanden bekannt ist, das dieser Hof + bis vor einigen Jahren die Bleibe des verbannten Wanderpredigers Grauwolf + war. Nachdem er bei einer seiner berüchtigten flammenden Reden fast die + gesammte Bauernschaft angeworben hatte, wurde er wegen Aufruhr verurteilt + und verbannt. Nur zögerlich war er bereit mir das Geheimniss seiner + Überzeugungskraft zu lehren.' + + + + Dieser mächtige Bann raubt dem Opfer seinen freien Willen + und unterwirft sie den Befehlen des Barden. Für einige Zeit wird das Opfer + sich völlig von seinen eigenen Leuten abwenden und der Partei des Barden + zugehörig fühlen. + + + + + Dieser mächtige Bann verhindert jegliche Attacken. Niemand in der + ganzen Region ist fähig seine Waffe gegen irgendjemanden zu erheben. + Die Wirkung kann etliche Wochen andauern. + + + + + Dieses Lied, das in die magische Essenz der Region gewoben wird, + schwächt die natürliche Widerstandskraft gegen eine + Verzauberung einmalig um 15%. Nur die Verbündeten des Barden + (HELFE BEWACHE) sind gegen die Wirkung des Gesangs gefeit. + + + + + Mit diesem Gesang verbreitet der Barde eine melancholische, traurige + Stimmung unter den Bauern. Einige Wochen lang werden sie sich in ihre + Hütten zurückziehen und kein Silber in den Theatern und Tavernen lassen. + + + + + Dieses magische Lied wird, einmal mit Inbrunst gesungen, sich in der + Region fortpflanzen, von Mund zu Mund springen und eine Zeitlang + überall zu vernehmen sein. Nach wie vielen Wochen der Gesang aus dem + Gedächnis der Region entschwunden ist, ist von dem Geschick des Barden + abhängig. Bis das Lied ganz verklungen ist, wird seine Magie allen + Verbündeten des Barden (HELFE BEWACHE), und natürlich auch seinen + eigenem Volk, einen einmaligen Bonus von 15% + auf die natürliche Widerstandskraft gegen eine Verzauberung + verleihen. + + + + + Mit Hilfe dieses magischen Gesangs überzeugt der Magier die Bauern + der Region, sich ihm anzuschließen. Die Bauern werden ihre Heimat jedoch + nicht verlassen, und keine ihrer Besitztümer fortgeben. Jede Woche + werden zudem einige der Bauern den Bann abwerfen und auf ihre Felder + zurückkehren. Wie viele Bauern sich dem Magier anschließen hängt von der + Kraft seines Gesangs ab. + + + + + Dieses Ritual ermöglicht es, eine Einheit, egal welcher Art, in die + eigene Partei aufzunehmen. Der um Aufnahme Bittende muss dazu willig + und bereit sein, seiner alten Partei abzuschwören. Dies bezeugt er + durch KONTAKTIEREn des Magiers. Auch wird er die Woche über + ausschliesslich mit Vorbereitungen auf das Ritual beschäftigt sein. + Das Ritual wird fehlschlagen, wenn er zu stark an seine alte Partei + gebunden ist, dieser etwa Dienst für seine teuere Ausbildung + schuldet. Der das Ritual leitende Magier muss für die permanente + Bindung des Aufnahmewilligen an seine Partei naturgemäß auch + permanente Aura aufwenden. Pro Stufe und pro 1 permanente Aura kann + er eine Person aufnehmen. + + + + + Jede Verzauberung beeinflußt das Lebenslied, schwächt und verzerrt es. + Der kundige Barde kann versuchen, das Lebenslied aufzufangen und zu + verstärken und die Veränderungen aus dem Lied zu tilgen. + + + + + Wie Lebewesen, so haben auch Schiffe und Gebäude und sogar Regionen + ihr eigenes Lied, wenn auch viel schwächer und schwerer zu hören. + Und so, wie wie aus dem Lebenslied einer Person erkannt werden kann, + ob diese unter einem Zauber steht, so ist dies auch bei Burgen, + Schiffen oder Regionen möglich. + + + + + Dieser Kriegsgesang sät Panik in der Front der Gegner und schwächt + so ihre Kampfkraft erheblich. Angst wird ihren Schwertarm schwächen + und Furcht ihren Schildarm lähmen. + + + + + Wie viele magischen Gesänge, so entstammt auch dieser den altem + Wissen der Katzen, die schon immer um die machtvolle Wirkung der + Stimme wussten. Mit diesem Lied wird die Stimmung der Krieger + aufgepeitscht, sie gar in wilde Raserrei und Blutrausch versetzt. + Ungeachtet eigener Schmerzen werden sie kämpfen bis zum + Tode und niemals fliehen. Während ihre Attacke verstärkt ist + achten sie kaum auf sich selbst. + + + + + Erliegt die Einheit dem Zauber, so wird sie dem Magier alles erzählen, + was sie über die gefragte Region weiß. Ist in der Region niemand + ihrer Partei, so weiß sie nichts zu berichten. Auch kann sie nur das + erzählen, was sie selber sehen könnte. + + + + + Mit diesem Lied kann eine Einheit derartig betört werden, so dass + sie dem Barden den größten Teil ihres Bargelds und ihres Besitzes + schenkt. Sie behält jedoch immer soviel, wie sie zum Überleben + braucht. + + + + + Dieser einschmeichelnde Gesang kann fast jedes intelligente Monster + zähmen. Es wird von Angriffen auf den Magier absehen und auch seine + Begleiter nicht anrühren. Doch sollte man sich nicht täuschen, es + wird dennoch ein unberechenbares Wesen bleiben. + + + + + Dieser Zauber ermöglicht es dem Träumer, den Schlaf aller nichtaliierten + Einheiten (HELFE BEWACHE) in der Region so stark zu stören, das sie + vorübergehend einen Teil ihrer Erinnerungen verlieren. + + + + + Dieser mächtige Zauber kann einen Magier vor dem sicheren Tod + bewahren. Der Magier erschafft anhand einer kleinen Blutprobe einen + Klon von sich, und legt diesen in ein Bad aus Drachenblut und verdünntem + Wasser des Lebens. + Anschließend transferiert er in einem aufwändigen Ritual einen Teil + seiner Seele in den Klon. Stirbt der Magier, reist seine Seele in den + Klon und der erschaffene Körper dient nun dem Magier als neues Gefäß. + Es besteht allerdings eine geringer Wahrscheinlichkeit, dass die Seele + nach dem Tod zu schwach ist, das neue Gefäß zu erreichen. + + + + + Der Zauberer spricht eine Beschwörung über einen Teil der Region, + und in der Folgewoche entstehen dort Irrlichter. + Wer durch diese Nebel wandert, wird von Visionen geplagt und + in die Irre geleitet. + + + + + Dieses mächtige Ritual öffnet ein Tor in die Elementarebene der + Hitze. Eine grosse Dürre kommt über das Land. Bauern, Tiere und + Pflanzen der Region kämpfen um das nackte Überleben, aber eine + solche Dürre überlebt wohl nur die Hälfte aller Lebewesen. + Der Landstrich kann über Jahre hinaus von den Folgen einer + solchen Dürre betroffen sein. + + + + + Mit Hilfe dieses aufwändigen Rituals läßt der Druide einen Teil seiner Kraft + dauerhaft in den Boden und die Wälder der Region fliessen. Dadurch wird + das Gleichgewicht der Natur in der Region für immer verändert, und in + Zukunft werden nur noch die anspruchsvollen, aber kräftigen + Mallorngewächse in der Region gedeihen. + + + + + Dieses Ritual beschört einen großen Wasserelementar aus den + Tiefen des Ozeans. Der Elementar erzeugt einen gewaltigen + Strudel, einen Mahlstrom, welcher alle Schiffe, die ihn passieren, + schwer beschädigen kann. + + + + + Mit Hilfe dieses Zaubers kann sich der Magier permanent in einen + mächtigen Wyrm verwandeln. Der Magier behält seine Talente und + Möglichkeiten, bekommt jedoch die Kampf- und Bewegungseigenschaften + eines Wyrms. Der Odem des Wyrms wird sich mit steigendem Magie-Talent + verbessern. Der Zauber ist sehr kraftraubend und der Wyrm wird einige + Zeit brauchen, um sich zu erholen. + + + + + Mit Hilfe dieses magischen Gesangs versetzt der Magier eine ganze + Region in Aufruhr. Rebellierende Bauernhorden machen jedes Besteuern + unmöglich, kaum jemand wird mehr für Gaukeleien Geld spenden und + es können keine neuen Leute angeworben werden. Nach einigen Wochen + beruhigt sich der Mob wieder. + + + + + Der Magier kann mit Hilfe dieses Zaubers aus der Astral- in die + materielle Ebene blicken und die Regionen und Einheiten genau + erkennen. + + + + + Dieses kräftezehrende Ritual beschwört mit Hilfe einer Kugel aus + konzentriertem Laen einen gewaltigen Erdelementar und bannt ihn + in ein Gebäude. Dem Elementar kann dann befohlen werden, das + Gebäude mitsamt aller Bewohner in eine Nachbarregion zu tragen. + Die Stärke des beschworenen Elementars hängt vom Talent des + Magiers ab: Der Elementar kann maximal [Stufe-12]*250 Größeneinheiten + große Gebäude versetzen. Das Gebäude wird diese Prozedur nicht + unbeschädigt überstehen. + + + + Dieses Amulett in Gestalt einer orkischen Matrone + unterdrückt den Fortpflanzungstrieb eines einzelnen Orks sehr + zuverlässig. + Ein Ork mit Amulett der Keuschheit wird sich nicht mehr vermehren. + + + Dieser Zauber ermöglicht dem Magier, gezielt eine + bestimmte Verzauberung einer Einheit, eines Schiffes, Gebäudes oder auch + der Region aufzulösen. + This spell allows a magician to remove a specific + enchantment from a unit, ship, bulding or region. + + + Ein Schauer von Meteoren regnet über das Schlachtfeld. + A meteor shower rains down on the battlefield. + + + Mit Hilfe dieses Zaubers kann der Magier einen Teil + seiner magischen Kraft permanent auf einen anderen Magier übertragen. + Auf einen Tybied-Magier kann er die Hälfte der eingesetzten Kraft + übertragen, auf einen Magier eines anderen Gebietes ein Drittel. + This spell allows the magician to transfer part of + his magical powers to another magician. Tybied magicians will receive + half the power invested, magicians of another school will receive one + third. + + + Mit dieser Formel bindet der Magier auf ewig die + Kräfte + der Erde in die Mauern des Gebäudes. Ein solchermaßen verzaubertes + Gebäude + ist gegen den Zahn der Zeit geschützt und benötigt keinen Unterhalt + mehr. + With this spell, the magician binds the power of the + earth into the walls of a building for all eternity. Such a building is + immune to the sands of time and needs no maintenance cost. + + + Ein Magier, der sich in der astralen Ebene befindet, + kann mit Hilfe dieses Zaubers andere Einheiten zu sich holen. Der Magier + kann (Stufe-3)*15 GE durch das kurzzeitig entstehende Tor schicken. Ist + der Magier erfahren genug, den Zauber auf Stufen von 13 oder mehr zu + zaubern, kann er andere Einheiten auch gegen ihren Willen auf die + andere + Ebene zwingen. + A magician in the astral plane can summon units from + the + material world. The magician can bring (level-3)*15 GE through the + temporary portal. If he is experienced enough to cast the spell at at + least level 13, he can even summon units against their will. + + + Ein Magier, welcher sich in der materiellen Welt + befindet, kann er mit Hilfe dieses Zaubers Einheiten aus der + angrenzenden + Astralwelt herbeiholen. Ist der Magier erfahren genug, den Zauber auf + Stufen von 13 oder mehr zu zaubern, kann er andere Einheiten auch gegen + ihren Willen in die materielle Welt zwingen. + A magician in the material world can summon units from + the adjacent part of the astral plane. If he is experienced enough to + cast + the spell at at least level 13, he can even summon units against their + will. + + + Mit Hilfe dieses Zaubers kann der Magier einem anderen + Magier seine Aura gegen dessen Willen entziehen und sich selber + zuführen. + Aided by this spell, a magician can steal another + magician's aura against his will. + + + Diese magischen Runen bringen ein Boot oder Langboot + für + eine Woche zum fliegen. Damit kann dann auch Land überquert werden. Die + Zuladung von Langbooten ist unter der Einwirkung dieses Zaubers auf 100 + Gewichtseinheiten begrenzt. Für die Farbe der Runen muss eine spezielle + Tinte aus einem Windbeutel und einem Schneekristall angerührt werden. + These magic runes allow a boat or longboat to fly for + a + week. The boat can only carry 100 weight units. The enchanted ink's + components include a windbag and a snowcrystal petal. + + + Diese praktische Anwendung des theoretischen Wissens um + Raum und Zeit ermöglicht es, den Zeitfluß für einige Personen zu + verändern. Auf diese Weise veränderte Personen bekommen für einige + Wochen doppelt soviele Bewegungspunkte und doppelt soviele Angriffe + pro Runde. + Abstract theories of space and time at last find + practical application in this spell which warps the very fabric of + time around a person. Such a person has twice as many movement points + and doubles their attacks per round for a few weeks. + + + Dieser Zauber läßt eine Welle aus purer Kraft über die + gegnerischen Reihen hinwegfegen. Viele Kämpfer wird der Schock so + benommen machen, daß sie für einen kurzen Moment nicht angreifen + können. + A wave of pure force spreads out from the magician, + crashing into the enemy ranks. Many warriors are thrown off balance and + are briefly unable to attack. + + + Mit diesem Zauber kann der Magier eine Zone der + astralen + Schwächung erzeugen, ein lokales Ungleichgewicht im Astralen Feld. + Dieses + Zone wird bestrebt sein, wieder in den Gleichgewichtszustand zu + gelangen. + Dazu wird sie jedem in dieser Region gesprochenen Zauber einen Teil + seiner Stärke entziehen, die schwächeren gar ganz absorbieren. + This spell allows a magician to create a local + instability in the astral field. This zone needs to return to its + equilibrium, soaking up part of the power of all spells cast in the + region + - or even all of some of the weaker ones. + + + Dieser Zauber ermöglicht dem Magier, Verzauberungen + einer Einheit, eines Schiffes, Gebäudes oder auch der Region aufzulösen. + This spell lets a magician destroy spells on a ship, + building or region. + + + Dieser Zauber verstärkt die natürliche + Widerstandskraft + gegen Magie. Eine so geschützte Einheit ist auch gegen Kampfmagie + weniger + empfindlich. Pro Stufe reicht die Kraft des Magiers aus, um 5 Personen + zu + schützen. + This spell enhances natural magic resistence. + Protected + units are less vulnerable to battle magic. The spell protects 5 people + per + level. + + + Alte arkane Formeln ermöglichen es dem Magier, sich + und + andere in die astrale Ebene zu schicken. Der Magier kann (Stufe-3)*15 GE + durch das kurzzeitig entstehende Tor schicken. Ist der Magier erfahren + genug, den Zauber auf Stufen von 11 oder mehr zu zaubern, kann er andere + Einheiten auch gegen ihren Willen auf die andere Ebene zwingen. + Ancient arcane formulae permit the magician to + transport + himself or other units into the astral plane. The magician can transport + (level-3) * 15 GE through the transient portal. If the magician is + experienced enough to cast level 11 spells, he can also transport units + against their will. + + + Der Magier konzentriert sich auf die Struktur der + Realität und kann so die astrale Ebene verlassen. Er kann insgesamt + (Stufe-3)*15 GE durch das kurzzeitig entstehende Tor schicken. Ist der + Magier erfahren genug, den Zauber auf Stufen von 11 oder mehr zu + zaubern, + kann er andere Einheiten auch gegen ihren Willen auf die andere Ebene + zwingen. + By concentrating on the structure of reality, the + magician can breach it and thus briefly make a gateway to leave the + astral + plane. He can transport up to (level-3)*15 GE through the portal. If the + magician is able to cast at at least level 11, he can even transport + other + units against their will. + + + Mit diesem Spruch kann der Magier versuchen, die + Verzauberungen + eines einzelnen angegebenen Objekts zu erkennen. Von + allen Sprüchen, + die seine eigenen Fähigkeiten nicht überschreiten, wird + er einen + Eindruck ihres Wirkens erhalten können. Bei stärkeren + Sprüchen + benötigt er ein wenig Glück für eine gelungene Analyse. + With this spell the magician can try to identify the + enchantments of + a single object. He will get an impression of the + operation of all + spells that don't exceed his own capabilities. For more + powerful + spells he will need some luck for a successful analysis. + + + Dieser Zauber wird die gesamte Ausrüstung der + Zieleinheit für + einige Zeit vor den Blicken anderer verschleiern. Der + Zauber + schützt nicht vor Dieben und Spionen. + This spell will hide the whole equipment of a target + unit from the + looks of others. It will not protect against thieves or + spies. + + + Dieser Zauber legt ein antimagisches Feld um die Magier der Feinde + und behindert ihre Zauber erheblich. Nur wenige werden die Kraft + besitzen, das Feld zu durchdringen und ihren Truppen in der Schlacht + zu helfen. + This spell creates an antimagic field around the mages of the enemies + and considerably hinders their spells. Only few will have the power to + break through the field and be able to help their troops in battle. + + + Dieser Zauber verhindert, dass ein Teil der sonst im Kampf zerstörten + Gegenstände beschädigt wird. Die Verluste reduzieren sich um 5% pro + Stufe des Zaubers bis zu einem Minimum von 25%. + This spell prevents damage to a portion of the items that would + otherwise be lost in battle. The loss of items is reduced by 5% for + every level of the spell, up to a minimum of 25%. + + + + Dieses Lied zähmt selbst den wildesten + Ork und macht ihn friedfertig und sanftmütig. Jeder + Gedanke, dem Sänger zu schaden, wird ihm entfallen. + Unbehelligt kann der Magier in eine Nachbarregion + ziehen. + This little melody calms even the + wildest orc to a gentle and serene creature who will not + even think about putting the singer to harm. The magician + may travel to a neighboring region without being + harassed by annoying troublemakers. + + + Nicht nur der Feldscher kann den + Verwundeten einer Schlacht helfen. Die Barden kennen + verschiedene Lieder, die die Selbstheilungskräfte des + Körpers unterstützen. Dieses Lied vermag Wunden zu + schließen, gebrochene Knochen zu richten und selbst + abgetrennte Glieder wieder zu regenerieren. + The field medic isn't the only one + capable of tending the wounds of battle. The bards know + a number of magic melodies to enhance the natural + healing process of the body. This song is able to close + wounds, mend fractured bones and even regenerate lost + lims. + + + Ein gar machtvoller Gesang aus den + Überlieferungen der Katzen, der tief in die Herzen der + Feinde dringt und ihnen Mut und Hoffnung raubt. Furcht + wird sie zittern lassen und Panik ihre Gedanken + beherrschen. Voller Angst werden sie versuchen, den + gräßlichen Gesängen zu entrinnen und fliehen. + This antique, powerful song, passed + down by the cats, will penetrate the hearts of the enemy + and bereave them of courage and hope. Both their minds + and bodies will be ruled by panic. Shivering with fear, + they will flee from the dreadful chants and try to make + their escape. + + + Aus den uralten Gesängen der Katzen + entstammt dieses magisches Lied, welches vor einem + Kampfe eingesetzt, einem entscheidende strategische + Vorteile bringen kann. Wer unter den Einfluss dieses + Gesangs gelangt, der wird seiner Umgebung nicht achtend + der Melodie folgen, sein Geist wird verwirrt und + sprunghaft plötzlichen Eingebungen nachgeben. So sollen + schon einst wohlgeordnete Heere plötzlich ihre Schützen + weit vorne und ihre Kavallerie bei den Lagerwachen + kartenspielend wiedergefunden haben (oder ihren Anführer + schlafend im lange verlassenen Lager, wie es in den + Großen Kriegen der Alten Welt wirklich geschehen sein + soll). + If is used before battle, this chant, + taken from the ancient tunes of the cats, might give you + the critical tactical advantage. Those under the spell's + influence will act uncoordinated and inconsequent due to + the nonsensical ideas planted into their minds through + the melody. So it is supposed to have come to pass that + well-organized armies found their archers up at the + front (while the cavalry was back at the camp playing + cards) or that even a famous general overslept a battle + in his tent, as tale-tellers claim it really happened + during the Great Wars in the Old World. + + + Dieser alte Schlachtengesang hebt die + Moral der eigenen Truppen und und hilft ihnen auch der + angsteinflößenden Aura dämonischer und untoter Wesen zu + widerstehen. Ein derartig gefestigter Krieger wird auch + in schwierigen Situationen nicht die Flucht ergreifen + und sein überlegtes Verhalten wird ihm manch Vorteil in + der Verteidigung geben. + This ancient battle chant lifts the + spirit of your troops and helps them withstand even the + fear-inspiring aura of demonic and undead beings. A + fighter thus fortified against evil will not flee even + in the face of terror, and his defenses will be strengthened. + + + Mit Hilfe dieses Zaubers kann der + Magier eigene Aura im Verhältnis 2:1 auf einen anderen + Magier des gleichen Magiegebietes übertragen. + This spell enables the wizard to + transfer aura at a rate of 2:1 to another sorcerer of + the same school of magic. + + + Alle lebenden Wesen haben ein eigenes + individuelles Lebenslied. Nicht zwei Lieder gleichen + sich, auch wenn sich alle Lieder einer Art ähneln. Jeder + Zauber verändert dieses Lied auf die eine oder andere + Art und gibt sich damit zu erkennen. Dieser Gesang + hilft, jene Veränderungen im Lebenslied einer Person zu + erlauschen, welche magischer Natur sind. Alle + Verzauberungen, die nicht stärker maskiert sind als Eure + Fähigkeit, werdet Ihr so entschlüsseln und demaskieren + können. + Each and every living being has its + own, individual 'life-song'. No two of these songs are + alike, even though songs of creatures of the same + species are similar. Every spell alters this song of + life in one way or the other and this can be identified. + By casting this spell, the bard can detect all those + magic variations in a person's 'life-song'. You will be + able to decipher all enchantments or spells, which + aren't disguised beyond your capability. + + + Dieser schrille Gesang hallt über das + ganze Schlachtfeld. Die besonderen Dissonanzen in den + Melodien machen es Magier fast unmöglich, sich auf ihre + Zauber zu konzentrieren. + The screeching sounds of this melody + can be heard across the whole battlefield. Wizards + exposed to these special dissonances find it nearly + impossible to concentrate on their spells. + + + Die verzauberte Einheit beginnt + hemmungslos zu plappern und erzählt welche Talente sie + kann, was für Gegenstände sie mit sich führt und sollte + sie magisch begabt sein, sogar welche Zauber sie + beherrscht. Leider beeinflußt dieser Zauber nicht das + Gedächnis, und so wird sie sich im nachhinein wohl + bewußt werden, dass sie zuviel erzählt hat. + The persons of the bewitched unit + starts to babble without control about what it is said, + speaking about their talents, the objects they carry or + wear and if the unit is a magician, he or she will even list + the spells they know. Unfortunately, this spell does not + influence the memory of the subjects and afterwards, the + enchanted will realize that they probably talked too + much. + + + Man befeuchte einen kluftfreien Block + aus feinkristallinen Gestein mit einer Phiole des + Lebenswassers bis dieses vollständig vom Gestein + aufgesogen wurde. Sodann richte man seine Kraft auf die + sich bildende feine Aura des Lebens und forme der + ungebundenen Kraft ein Gehäuse. Je mehr Kraft der Magier + investiert, desto mehr Golems können geschaffen werden, + bevor die Aura sich verflüchtigt. Jeder Golem hat jede + Runde eine Chance von 10 Prozent zu Staub zu zerfallen. + Gibt man den Golems die Befehle MACHE BURG oder MACHE + STRASSE, so werden pro Golem 4 Steine verbaut und der + Golem löst sich auf. + 'Take a flawless block of crystaline + stone and humidify it with a vial of Water Of Life until + the potion has been soaked up completely. Then focus + your power on the forming aura of life and shape a + container for the unbound forces'. The more power a magician + invests, the more golems can be created before the aura + dissipates. Every week, there is a 10 percent chance + that the golem will crumble to dust. If you command a + golem to 'MAKE CASTLE' or 'MAKE ROAD', it will turn + itself into 4 stones that it uses in construction, and + disintegrate afterwards. + + + Je mehr Kraft der Magier investiert, + desto mehr Golems können geschaffen werden. Jeder Golem + hat jede Runde eine Chance von 15 Prozent zu Staub zu + zerfallen. Gibt man den Golems den Befehl MACHE + SCHWERT/BIHÄNDER oder MACHE + SCHILD/KETTENHEMD/PLATTENPANZER, so werden pro Golem 4 + Eisenbarren verbaut und der Golem löst sich auf. + The more power a magician invests, the + more golems can be created before the aura dissipates. + Each golem has a 15% chance per week to turn to dust. If + you command a golem to 'MAKE SWORD/MAKE CLAYMORE' or + 'MAKE SHIELD/CHAINMAIL/PLATEMAIL',it will work 5 iron + ingots and disintegrate afterwards. + + + Wo sonst aus einem + Stecken nur ein Baum sprießen konnte, so treibt nun jeder + Ast Wurzeln. + Every branch becomes a sturdy + oak where before only one could be grown from a log. + + + Mit diesem Ritual wird eine dunkle + Gewitterfront beschworen, die sich + unheilverkündend über der Region auftürmt. Der + magische Regen wird alles Erz rosten lassen. + Eisenwaffen und Rüstungen werden schartig und rostig. + Die Zerstörungskraft des + Regens ist von der investierten Kraft des + Magiers abhängig. Für jede Stufe können bis zu + 10 Eisenwaffen betroffen werden. Ein Ring der + Macht verstärkt die Wirkung wie eine zusätzliche + Stufe. + This ritual conjurs up a dark + thunderstorm that affects a whole region. The + magic rain will let rust any ore. Iron weapons and + armor will get rusty. The exact number of + items affected by the rain depends on the + ammount of power invested by the magician. Up to ten + weapons can be destroyed per level - a Ring Of + Power increases the effect like an additional + level. + + + Dieser Zauber ermöglicht es dem Magier + Insekten auf magische Weise vor der lähmenden + Kälte der Gletscher zu bewahren. Sie können + Gletscher betreten und dort normal agieren. Der + Spruch wirkt auf Stufe*10 Insekten. Ein Ring der + Macht erhöht die Menge der verzauberbaren + Insekten zusätzlich um 10. + This spell enables the druid to + magically protect insects from the paralysing + cold of a glacier. Under the effect of this + spell, insects are able to enter glaciers and + act normally there. Ten insects per level can be + protected in this way. A Ring Of Power increases + the number by additional ten. + + + Im Kampf ruft der Magier die + Elementargeister der Kälte an und bindet sie an + sich. Sodann kann er ihnen befehlen, den Gegner + mit Hagelkörnern und Eisbrocken zuzusetzen. + During a battle the druid calls the + Elemental Spirits Of Cold and binds them to + himself. Then he commands them to attack his + foes with hail and ice missiles. + + + Erschafft einen Wächtergeist, der + in Gletschern und Bergen Eisen- und Laenabbau durch + nichtalliierte Parteien (HELFE BEWACHE) verhindert, + solange er die Region bewacht. Der Bergwächter ist + an den Ort der Beschwörung gebunden. + Creates a guardian spirit on a + mountain or glacier that keeps all factions that + are not allied (HELP GUARD) from mining iron or + laen as long as it guards the region. The + Mountain Guardian is bound to the location where + it has been summoned. + + + Durch Ausführung dieser Rituale ist + der Magier in der Lage einen mächtigen + Erdelementar zu beschwören. Solange dieser in + den Boden gebannt ist, wird kein Regen die Wege + aufweichen und kein Fluß Brücken zerstören + können. Alle Reisende erhalten damit die + gleichen Vorteile, die sonst nur ein ausgebautes + gepflastertes Straßennetz bietet. Selbst Sümpfe + und Gletscher können so verzaubert werden. Je + mehr Kraft der Magier in den Bann legt, desto + länger bleibt die Straße bestehen. + By performing these rituals the druid + is able to summon a powerful earth elemental. As + long as this elemental remains bound to a + region, no rain can turn a path into mud and no + river can destroy a bridge. All travelers in + this region gain the same advantages as if they + were travelling on a road. Even swamps and + glaciers can be enchanted in this way. The more + power the druid invests, the longer the roads + remain intact. + + + Die Anrufung der Elementargeister des + Windes beschwört plötzliche Windböen, kleine + Windhosen und Luftlöcher herauf, die die + gegnerischen Schützen behindern werden. + Calling the Elemental Spirits Of Wind + conjurs up sudden breezes, small whirlwinds and + minor turbulences that will hinder enemy + archers. + + + Diese Ritual verstärkt die Wirkung des + magischen Trankes um ein vielfaches. Wo sonst aus einem + Stecken nur ein Baum sprießen konnte, so treibt nun jeder + Ast Wurzeln. + This ritual greatly increases the + effect of the potion. Now every branch becomes a mallorn + tree where before only one could be grown from a log. + + + Der Magier zwingt mit diesem Ritual + die Elementargeister des Wassers in seinen + Dienst und bringt sie dazu, das angegebene + Schiff schneller durch das Wasser zu tragen. + Zudem wird das Schiff nicht durch ungünstige + Winde oder Strömungen beeinträchtigt. + While being aboard a ship, the druid + uses this ritual to force the Elemental Spirits + Of Water to serve him and commands them to carry + the ship across the water at a higher speed. In + addition, the ship will not be affected by + unfavourable winds or currents. + + + Nicht nur der Feldscher kann den + Verwundeten einer Schlacht helfen. Druiden + vermögen mittels einer Beschwörung der + Elementargeister des Lebens Wunden zu schließen, + gebrochene Knochen zu richten und selbst + abgetrennte Glieder wieder zu regenerieren. + Combat medics are not the only ones + who can help those who got injured during a + battle. Druids are, with the help of a summons + of + the Elemental Spirits Of Life, able to heal + wounds, mend broken bones or even regenerate + separated limbs as well. + + + Diese Beschwörung öffnet ein Tor in + die Ebene der Elementargeister des Windes. + Sofort erheben sich in der Umgebung des Tors + starke Winde oder gar Stürme und behindern alle + Schützen einer Schlacht. + This summons opens a gate to the plane + of Elemental Spirits Of Wind. Immediately, + strong winds or even storms will rise near the + gate and hinder all archers during a battle. + + + Dieses Ritual beschwört einige + Elementargeister der Magie und schickt sie in + die Reihen der feindlichen Magier. Diesen wird + das Zaubern für die Dauer des Kampfes deutlich + schwerer fallen. + This ritual summons some Elemental + Spirits Of Magic and sends them into the ranks + of the enemy mages. Casting spells will be much + harder for them during the battle. + + + Mit Hilfe dieses Zaubers kann der + Magier eigene Aura im Verhältnis 2:1 auf einen + anderen Magier des gleichen Magiegebietes + übertragen. + The caster can transfer aura at a + ratio of 2:1 to another member of the same + school of magic with the help of this spell. + + + Der Druide beschwört mit diesem Ritual + einen Elementargeist der Erde und bringt ihn + dazu, die Erde erbeben zu lassen. Dieses + Erdbeben wird alle Gebäude in der Region + beschädigen. + With this ritual the druid summons an + Elemental Spirit Of Earth that brings the ground + to shake. This earthquake damages all buildings + in the target region. + + + Die Beschwörung von Elementargeistern + der Stürme ist ein uraltes Ritual. Der Druide + bannt die Elementare in die Segel der Schiffe, + wo sie helfen, das Schiff mit hoher + Geschwindigkeit über die Wellen zu tragen. Je + mehr Kraft der Druide in den Zauber investiert, + desto größer ist die Zahl der Elementargeister, + die sich bannen lassen. Für jedes Schiff wird + ein Elementargeist benötigt. + Calling the Elemental Spirits Of Storm + is an ancient ritual. The druid binds the + elementals to a ship's sails where they can help + to carry the vessel across the waves at an + amazing speed. The more power the druid invests, + the greater is the number of spirits bound. Each + ship needs an own spirit. + + + Mit diesem Spruch erzeugt man ein Runenschwert. Die + Klinge des schwarzen Schwertes ist mit alten, magischen Runen verziert, + und ein seltsames Eigenleben erfüllt die warme Klinge. Um es zu + benutzen, + muss man ein Schwertkämpfer von beachtlichem Talent (7) sein. Der + Träger + des Runenschwertes erhält einen Talentbonus von +4 im Kampf und wird so + gut wie immun gegen alle Formen von Magie. + This spell creates a magical sword. It requires a + skill + of at least 7, but adds +4 to the combat skill of its' owner as well as + making them almost immune against magical attacks. + + + Dieser Beutel umschließt eine kleine Dimensionsfalte, + in + der bis zu 200 Gewichtseinheiten transportiert werden können, ohne dass + sie auf das Traggewicht angerechnet werden. Pferde und andere Lebewesen + sowie besonders sperrige Dinge (Wagen und Katapulte) können nicht in dem + Beutel transportiert werden. Auch ist es nicht möglich, einen + Zauberbeutel in einem anderen zu transportieren. Der Beutel selber wiegt + 1 + GE. + This bag encloses a dimensional rift in which up to + 200 + units of weight can be carries. Horses and other large objects cannot be + put into the bag. The bag itself has a weight of 1. + + + Dieses mächtige Ritual erschafft einen Ring der Macht. + Ein Ring der Macht erhöht die Stärke jedes Zaubers, den sein Träger + zaubert, als wäre der Magier eine Stufe besser. + A ring of power adds +1 to the power of each spell + cast + by its' wearer. + + + Der Spruch ermöglicht es einem Magier, + ein Amulett des Wahren Sehens zu erschaffen. Das + Amulett erlaubt es dem Träger, alle Einheiten, + die durch einen Ring der Unsichtbarkeit + geschützt sind, zu sehen. Einheiten allerdings, + die sich mit ihrem Tarnungs-Talent verstecken, + bleiben weiterhin unentdeckt. + This spell enables the caster to + create an Amulet Of True Sight. Wearing such an + amulet, a person can discover anyone wearing a + Ring of Invisibility. Anyway, units concealed by + the use of their stealth skill will remain + undiscovered. + + + Mit Hilfe dieses Zauber entzieht der Magier einem + Quarzkristall all seine magischen Energien. Der Kristall wird dann, wenn + er zu feinem Staub zermahlen und verteilt wird, die beim Zaubern + freigesetzten magischen Energien aufsaugen und die Kraft aller Zauber + reduzieren, welche in der betreffenden Woche in der Region gezaubert + werden. + This spell creates a portable crystal of antimagic + which can be used by anybody to reduce or even eliminate the power of + all spells cast in the region during the same week. + + + Die berühmte Bardin Miriam bhean'Meddaf war bekannt + für ihr außergewöhnliches Geschick mit der Harfe. Ihre Finger sollen + sich so schnell über die Saiten bewegt haben, das sie nicht mehr + erkennbar waren. Dieser Zauber, der recht einfach in einen Silberring + zu bannen ist, bewirkt eine um das zehnfache verbesserte + Geschicklichkeit und Gewandheit der Finger. (Das soll sie auch an + anderer Stelle ausgenutzt haben, ihr Ruf als Falschspielerin war + berüchtigt). Handwerker können somit das zehnfache produzieren, + und bei einigen anderen Tätigkeiten könnte dies ebenfalls von Nutzen + sein. + The famous bard Mirim was known for exceptionally + limber + play of the harp. Her spell, which is easy to ban into a little silver + ring, increases the wearer's dexterity by a factor of ten, which is siad + to be useful to both craftsmen and shady natures. + + + Mit diesem Spruch kann der Zauberer + einen Ring der Unsichtbarkeit erschaffen. Der + Träger des Ringes wird für alle Einheiten + anderer Parteien unsichtbar, egal wie gut ihre + Wahrnehmung auch sein mag. In einer unsichtbaren + Einheit muss jede Person einen Ring tragen. + With this spell the caster can create + a Ring Of Invisibility. The wearer of this ring + will be invisible to all units of other + factions, no matter how good their perception + skill may be. In an invisible unit, each person + must wear a Ring Of Invisibility. + + + Mit dieser Formel bindet der Magier + auf ewig die Kräfte der Erde in die Mauern der + Burg, in der er sich gerade befindet. Weder + magisch noch mit schwerem Geschütz können + derartig gestärkte Mauern zerstört werden, und + auch das Alter setzt ihnen weniger zu. Das + Gebäude bietet sodann auch einen besseren Schutz + gegen Angriffe mit dem Schwert wie mit Magie. + With this spell the druid eternally + binds the powers of earth to the walls of the + castle in which he currently is. No magic and no + ballistic attacks will ever be able to destroy a + wall that has been fortified in this way and the + castle will also be less affected by aging. In + addition, the building will provide a better + protection against attacks by sword or by magic. + + + Nicht wenige Druiden freunden sich im + Laufe ihres Lebens in der Natur mit den ältesten + Freunden der großen Völker an. Sie erlernen, mit + einem einzigen heulenden Ruf viele ihrer Freunde + herbeizurufen, um ihnen im Kampf beizustehen. + During their life in the wilderness, + many druids make friends with the wolves who are + the oldest friends of the great races. They + learn to call many of them with a single howl to + aid them in combat. + + + Dieser schwierige, aber effektive + Kampfzauber benutzt die Elementargeister des + Steins, um eine Reihe von Gegnern für die Dauer + des Kampfes in Stein zu verwandeln. Die + betroffenen Personen werden nicht mehr kämpfen, + können jedoch auch nicht verwundet werden. + This complicated but effective spell + uses the Elemental Spirits Of Stone to turn a + number of enemies to stone for the duration of + combat. The affected persons won't be able to + fight any more, but they can't be wounded + either. + + + Mit dieser Formel bindet der Magier zu + Beginn eines Kampfes einige Elementargeister des + Fels in die Mauern des Gebäudes, in dem er sich + gerade befindet. Das Gebäude bietet sodann einen + besseren Schutz gegen Angriffe mit dem Schwert + wie mit Magie. + At the beginning of a battle, the + magician binds some Elemental Spirits Of Rock to + the walls of the builing in which he currently + is. The structure will then provide a better + protection against attacks by sword or by magic. + + + Wie die alten Lehren der Druiden + berichten, besteht das, was die normalen Wesen + Magie nennen, aus Elementargeistern. Der Magier + beschwört und bannt diese in eine Form, um den + gewünschten Effekt zu erzielen. Dieses Ritual + nun vermag es, in diese Welt gerufene + Elementargeister zu vertreiben, um so ein Objekt + von Magie zu befreien. + Old legends of the druids say that + what normal people call 'magic' consists of + elemental spirits. A magician summons these + spirits and binds them to various forms to + achieve the desired effects. This ritual is able + to expel any elemental spirits that have been + summoned to this world and thereby dispels any + magic on the target. + + + Große Macht liegt in Orten, an denen + das Leben pulsiert. Der Druide kann diese Kraft + sammeln und so ein Tor in die Welt der + Geistwesen erschaffen. Der Druide kann dann + Stufe*5 Gewichtseinheiten durch das Tor + entsenden. + A great power lies within those places + that are pulsing with life. A druid can focus + this power and thereby create a gate into the + World Of Spirits. He can then send level*5 + weight units of living or dead matter through + the gate. + + + Ein Druide, den es in die Welt der + Geister verschlagen hat, kann mit Hilfe dieses + Zaubers Stufe*5 Gewichtseinheiten in einen Wald + auf der materiellen Welt zurückschicken. + A druid who has traveled to the World + Of Spirits can use this spell to send level*5 + weight units of living or dead matter back to a + forest in the material world. + + + Dieses Ritual beschwört verschiedene + Naturgeister in den Boden der Region, welche + diese fortan bewachen. In einer so gesegneten + Region werden niemals wieder die Toten ihre + Gräber verlassen, und anderswo entstandene + Untote werden sie wann immer möglich meiden. + This ritual binds various rural + spirits to a specific territory to guard the + land. In a region blessed in this way the dead + won't ever rise from their graves again. + Existing undead also shun the sacred grounds and + will avoid entering the protected area whenever + possible. + + + Der Druide nehme etwas präpariertes + Leder, welches er in einem großen Ritual der + Reinigung von allen unreinen Geistern befreie, + und binde dann einige kleine Geister der Luft + und des Wassers in das Material. Aus dem so + vorbereiteten Leder fertige er nun ein kleines + Beutelchen, welches in ihm aufbewahrte Kräuter + besser zu konservieren vermag. + The druid takes some specially + prepared leather and performes a great ritual + during which the leather is cleansed of all + impure spirits. Then he binds some minor spirits + of air and water to the material. After + completing this process, the druid works the + enchanted leather into a small pouch which is + suitable to contain herbs, for it is able to + preserve them for a long time and prevents rot. + + + Mit Hilfe dieses Zaubers weckt der + Druide die in den Wälder der Region + schlummernden Ents aus ihrem äonenlangen Schlaf. + Die wilden Baumwesen werden sich ihm anschließen + und ihm beistehen, jedoch nach einiger Zeit + wieder in Schlummer verfallen. + With the help of this spell the druid + awakens the ents who are slumbering in the + forests of a region from aeons of sleep. These + strange tree-creatures will join him and aid his + cause, but after a while they will sink back + into their slumber. + + + Dieses Ritual segnet einen Steinkreis, + der zuvor aus Steinen und etwas Holz gebaut + werden muss. Die Segnung des Druiden macht aus + dem Kreis eine mächtige Stätte magischen + Wirkens, die Schutz vor Magie und erhöhte Aura- + Regeneration bewirkt. Man sagt, Jungfrauen seien + in der Umgebung von Steinkreisen seltsame Wesen + begegnet. + This ritual blesses a circle of stones + that has to be constructed from stones and some + wood before. The druid's blessing turns the + circle into a place of great magic that is + suitable for rituals of all kinds. It protects + from hostile magic and improves aura + regeneration. Virgins are said to have been + visited by strange creatures in the vicinity of + these places. + + + Das Ziel des Zauberers wird von einer + harmlosen Verwünschung heimgesucht. + The target of this spell becomes + subject to a harmless curse. + + + Dieses vor dem Kampf zu zaubernde Ritual gibt den + eigenen Truppen einen zusätzlichen Bonus auf ihre Rüstung. Jeder + Treffer reduziert die Kraft des Zaubers, so dass der Schild sich + irgendwann im Kampf auflösen wird. + Performing this ritual before going into battle gives + your troups an additional bonus to their armor. Every hit reduces the + energy of the spell, dissolving it at some point during battle. + + + Der Zauberer schleudert fokussiertes + Chaos in die Reihen der Gegner. Das ballförmige + Chaos wird jeden verwunden, den es trifft. + The sorcerer hurls a ball of + concentrated chaos into the ranks of his + enemies. It will seriously hurt anyone who gets + hit. + + + Der Magier öffnet seinen Geist den + Sphären des Chaos und wird so für einige Zeit + über mehr magische Kraft verfügen. Doch die + Hilfe der Herren der Sphären hat seinen Preis, + und so wird die Phase der Macht abgelöst von + einer Phase der Schwäche. + The sorcerer opens his mind to the + Spheres Of Chaos so that he can access a greater + ammount of magical power for a while. But the + help of the Chaos Lords has its price - and so + the period of power will be followed by a period + of weakness. + + + Mit diesem Ritual kann der Magier + einen Teil seiner Lebensenergie opfern, um dafür + an magischer Kraft zu gewinnen. Erfahrene + Ritualmagier berichten, das sich das Ritual, + einmal initiiert, nur schlecht steuern ließe und + die Menge der so gewonnenen Kraft stark + schwankt. So steht im 'Buch des Blutes' + geschrieben: 'So richte Er aus das Zeichen der + vier Elemente im Kreis des Werdens und Vergehens + und Weihe ein jedes mit einem Tropfen Blut. + Sodann begebe Er in der Mitten der Ewigen Vierer + sich und lasse Leben verrinnen, auf das Kraft + geboren werde.' + With this ritual the sorcerer can + sacrifice part of his life force in order to + gain raw astral power. Experienced mages report + that this ritual, once started, is hard to + control and that the ammount of power gained in + this way varies. + + + In diesem blutigen Ritual opfert der + Magier vor der Schlacht ein Neugeborenes vor den + Augen seiner Armee. Die so gerufenen Blutgeister + werden von den Soldaten Besitz ergreifen und sie + in einen Blutrausch versetzen. + During this bloody ritual the sorcerer + sacrifices a newborn child before a battle right + in front of his army. In this way he attracts + spirits of blood that will take control of the + soldiers who are present and force them into a + blood frenzy. + + + Dieser heimtückische Fluch + beeinträchtigt die magischen Fähigkeiten des + Opfers erheblich. Eine chaosmagische Zone um das + Opfer vermindert seine Konzentrationsfähigkeit + und macht es ihm sehr schwer Zauber zu wirken. + This wicked curse affects the magical + abilities of the target. A field of raw chaos + magic around the target lessens its + concentration and makes it very hard to cast any + spells. + + + Nächtelang muss der Schwarzmagier + durch die Friedhöfe und Gräberfelder der Region + ziehen um dann die ausgegrabenen Leichen beleben + zu können. Die Untoten werden ihm zu Diensten + sein, doch sei der Unkundige gewarnt, dass die + Beschwörung der Mächte des Todes ein + zweischneidiges Schwert sein kann. + For many nights the sorcerer has to + roam the graveyards and former battlefields of a + region in order to find corpses to animate. The + Undead will serve his will, but beware! Dealing + with the mysteries of unlife can be a dangerous + thing. + + + Mit diesem Ritual wird eine dunkle + Gewitterfront beschworen, die sich + unheilverkündend über der Region auftürmt. Der + magische Regen wird alles Erz rosten lassen und + so viele Waffen des Gegners zerstören. + This ritual conjurs up a dark + thunderstorm that affects a whole region. The + magic rain will let rust any ore and thus + destroy many weapons of the enemy. + + + Mit Hilfe dieses Zaubers kann der + Magier eigene Aura im Verhältnis 2:1 auf einen + anderen Magier des gleichen Magiegebietes + übertragen. + With the help of this spell, the + caster can transfer aura at a ratio of 2:1 to + another member of the same school of magic. + + + Der Zauberer erschafft eine Wand aus + Feuer in der angegebenen Richtung. Sie verletzt + jeden, der sie durchschreitet. + The spell creates an opaque wall of + fire in the gives direction that will harm + anyone passing through it. + + + In einem aufwendigen Ritual opfert der + Schwarzmagier einige Bauern und verteilt dann + die Leichen auf magische Weise in den Brunnen + der Region. + In a complicated ritual the sorcerer + sacrifices the lives of ten peasants and + magically spreads their corpses within the wells + of a region. + + + Vor den Augen der feindlichen Soldaten + opfert der Schwarzmagier die zehn Bauern in + einem blutigen, grausamen Ritual und beschwört + auf diese Weise Geister des Wahnsinns über die + feindlichen Truppen. Diese werden im Kampf + verwirrt reagieren und nicht in der Lage sein, + den Anweisungen ihrer Offiziere zu folgen. + Before the eyes of the enemy soldiers + the sorcerer sacrifices ten peasants in a bloody + ritual and thereby summons spirits of madness + upon the enemy troops. The enemy soldiers will + be in confusion during battle and no more be + able to follow the commands of their leaders. + + + Mit Hilfe dunkler Rituale beschwört + der Zauberer Dämonen aus der Sphäre der + Schatten. Diese gefürchteten Wesen können sich + fast unsichtbar unter den Lebenden bewegen, ihre + finstere Aura ist jedoch für jeden spürbar. Im + Kampf sind Schattendämonen gefürchtete Gegner. + Sie sind schwer zu treffen und entziehen ihrem + Gegner Kraft. + With the help of dark rituals the + sorcerer summons demons from the Sphere Of + Shadows. These fearsome creatures can walk + almost unseen among the living, but their dark + aura can be sensed by everyone. Shadow demons + are feared in combat for they are hard to hit + and have the ability to drain strength from + their victims. + + + Dieses Ritual beschwört wütende Elementargeister der + Hitze. Eine Dürre sucht das Land heim. Bäume verdorren, Tiere + verenden, und die Ernte fällt aus. Für Tagelöhner gibt es kaum noch + Arbeit in der Landwirtschaft zu finden. + This Ritual summons an angry elemental spirit that + puts a drought on the entire region. Trees wither, animals die of + thirst and the harvest is destroyed. Workers find little to no work + in farming. + + + Dieses Ritual bindet die bereits + entfliehenden Seelen einiger Kampfopfer an ihren + toten Körper, wodurch sie zu untoten Leben + wiedererweckt werden. Ob sie ehemals auf der + Seite des Feindes oder der eigenen kämpften, ist + für das Ritual ohne belang. + This ritual binds the escaping souls + of some casualties back to their dead bodies and + thus condemns them to an undead existance under + the control of the sorcerer. The ritual affects + the corpses of allies and foes alike - no matter + on which side of the battle the soldiers fought + before their death. + + + Dieses magische Artefakt verleiht dem + Träger die Stärke eines ausgewachsenen + Höhlentrolls. Seine Tragkraft erhöht sich auf + das 50fache und auch im Kampf werden sich die + erhöhte Kraft und die trollisch zähe Haut + positiv auswirken. + This artifact gives the one wearing it + the strength of a cavetroll. He will be able to + carry fifty times as much as normal and also in + combat his enhanced strength and tough troll + skin will serve him well. + + + Der Schwarzmagier kann mit diesem + dunklen Ritual einen Riss in das Gefüge der + Magie bewirken, der alle magische Kraft aus der + Region reißen wird. Alle magisch begabten in der + Region werden einen Großteil ihrer Aura + verlieren. + With this dark ritual the + chaossorcerer causes a deep rift to appear in + the astral balance that will tear all magical + power from a region. All spellcasters in that + region will lose most of their aura. + + + Dieses Ritual, ausgeführt vor einem + Kampf, verwirbelt die astralen Energien auf dem + Schlachtfeld und macht es so feindlichen Magier + schwieriger, ihre Zauber zu wirken. + This ritual, performed before a + battle, causes the astral energies on the + battlefield to whirl and churn and thereby makes + spellcasting more difficult for the enemy mages. + + + Diese Elementarbeschwörung ruft einen + Feuerteufel herbei, ein Wesen aus den tiefsten + Niederungen der Flammenhöllen. Der Feuerteufel + wird sich begierig auf die Wälder der Region + stürzen und sie in Flammen setzen. + This elemental summoning calls a fire + fiend, a creature from the deepest hell. The + demon will eagerly rush into the forests of a + region and set them ablaze. + + + Genau um Mitternacht, wenn die Kräfte + der Finsternis am größten sind, kann auch ein + Schwarzmagier seine Kräfte nutzen um + Verzauberungen aufzuheben. Dazu zeichnet er ein + Pentagramm in das verzauberte Objekt und beginnt + mit einer Anrufung der Herren der Finsternis. + Die Herren werden ihm beistehen, doch ob es ihm + gelingt, den Zauber zu lösen, hängt allein von + seiner eigenen Kraft ab. + At midnight, when the Powers of + Darkness are at their peak, the sorcerer can use + his powers to destroy enchantments. In order to + do so, he draws a pentagram on a surface of the + enchanted object and begins calling the Lords Of + Darkness. The Lords will aid him, but whether he + is able to undo the target spell or not depends + upon his own power. + + + Nur geflüstert wird dieses Ritual an + den dunklen Akademien an die Adepten + weitergegeben, gehört es doch zu den + finstersten, die je niedergeschrieben wurden. + Durch die Anrufung unheiliger Dämonen wird die + Kraft der lebenden Toten verstärkt und sie + verwandeln sich in untote Monster großer Kraft. + Only whispered the knowledge of + performing this ritual is passed to the adepts + of the dark academies, for it is one of the + darkest that has ever been written down. By + calling unholy demons the strength of the living + dead is greatly increased and they are turned + into undead monsters of immense power. + + + Mit einem düsteren Ritual und unter + Opferung seines eigenen Blutes beschwört der + Schwarzmagier einen großen Geist von der + Elementarebene der Gifte. Der Geist manifestiert + sich als giftgrüner Schwaden über der Region und + wird allen, die mit ihm in Kontakt kommen, + Schaden zufügen. + By performing a gruesome ritual and + sacrificing his own blood the Sorcerer conjurs + up a spirit from the Elemental Plane Of Poison. + It will take the form of a green cloud of toxic + gases that envelops a whole region and that will + harm anyone within. + + + Mit diesem dunklen Ritual erzeugt der + Magier einen Köder, der für Drachen einfach + unwiderstehlich riecht. Ob die Drachen aus der + Umgebung oder aus der Sphäre des Chaos stammen, + konnte noch nicht erforscht werden. Es soll + beides bereits vorgekommen sein. Der Köder hält + etwa 6 Wochen, muss aber in einem + drachengenehmen Terrain platziert werden. + Performing this dark ritual, the + sorcerer creates a bait that exhales an + irresistable scent to dragons. It is not known + whether the dragons come from surrounding + regions or if they have their origin in the + Sphere Of Chaos. The bait will exist for about + six weeks, but it must be placed in a tarrain + that is suitable for dragons. + + + Mit Hilfe dunkler Rituale beschwört + der Zauberer Dämonen aus der Sphäre der + Schatten. Diese gefürchteten Wesen können sich + fast unsichtbar unter den Lebenden bewegen, ihre + finstere Aura ist jedoch für jeden spürbar. Im + Kampf sind Schattenmeister gefürchtete Gegner. + Sie sind schwer zu treffen und entziehen ihrem + Gegner Kraft und Leben. + With the help of dark rituals the + sorcerer summons demons from the Sphere Of + Shadows. These fearsome creatures can walk + almost unseen among the living, but their dark + aura can be sensed by everyone. Shadowmasters + are feared in combat for they are hard to hit + and have the ability to drain strength and life + force from their victims. + + + 'Und so reibe das Blut eines wilden + Kämpfers in den Stahl der Klinge und beginne die + Anrufung der Sphären des Chaos. Und hast du + alles zu ihrem Wohlgefallen getan, so werden sie + einen niederen der ihren senden, das Schwert mit + seiner Macht zu beseelen...' + 'So take the blood of a fierce warrior + and apply it to the steel of the blade. Then + start calling the Spheres Of Chaos. If you did + everything to their pleasure, they will send a + minor one of their kind to fulfill the sword + with his power.' + + + Einem erfahrenen Magier wird + irgendwann auf seinen Wanderungen ein + ungewöhnliches Exemplar einer Gattung begegnen, + welches sich dem Magier anschließen wird. + During their travel, seasoned + magicians will occasionally befriend an extraordinary + creature of an unusual species that will join them. + + + Durch das Opfern von 200 Bauern kann + der Chaosmagier ein Tor zur astralen Welt + öffnen. Das Tor kann in der Folgewoche verwendet + werden, es löst sich am Ende der Folgewoche auf. + By sacrificing the lives of 200 + peasants, the chaossorcerer is able to open a + planar gate. This gate can be used during the + following week to transfer units to the astral + plane. It dissipates at the end of the following + week. + + + Der Zauberer sendet dem Ziel des + Spruches einen Traum. + The mentalist sends a dream to the + target of the spell. + Le mentaliste envoie un rêve à la + cible du sort. + + + Dieser Zauber vermag dem Gegner ein + geringfügig versetztes Bild der eigenen Truppen + vorzuspiegeln. Die Schattenritter haben keinen + effektiven Angriff und Verwundungen im Kampf + zerstören sie sofort. + This spell creates illusionary + duplicates of allied troops. The shadow knights + can't do real damage and are instantly destroyed + if wounded. + Ce sort crée des copies illusoires de + troupes alliées. Les guerriers illusoires ne + peuvent faire de dégats réels et sont + instantanément détruits lorsqu'ils sont blessés. + + + Der Traumweber beschwört vor dem + Kampf grauenerregende Trugbilder herauf, die + viele Gegner in Panik versetzen. Die Betroffenen + werden versuchen, vor den Trugbildern zu + fliehen. + Before a battle the mentalist creates + terrifying illusions of hideous creatures that + will cause panic among the enemies. Those who + believe in the illusions will try to flee from + battle. + + + Dieses magische Ritual beruhigt die + gequälten Seelen der gewaltsam zu Tode + gekommenen und ermöglicht es ihnen so, ihre + letzte Reise in die Anderlande zu beginnen. Je + Stufe des Zaubers werden ungefähr 50 Seelen ihre + Ruhe finden. Der Zauber vermag nicht, bereits + wieder auferstandene lebende Tote zu erlösen, da + deren Bindung an diese Welt zu stark ist. + This ritual calms the tortured souls + of those who died a violent death and finally + releases them to the Otherlands. About 50 souls + per level of the spell will be released. The + spell will not affect existing undead, because + they are too strongly tied to the Material + World. + + + Mit Hilfe dieses Zaubers kann der + Traumweber die Illusion eines beliebigen + Gebäudes erzeugen. Die Illusion kann betreten + werden, ist aber ansonsten funktionslos und + benötigt auch keinen Unterhalt. Sie wird einige + Wochen bestehen bleiben. + With this spell the mentalist can + create the illusion of any building. The + illusion can be entered, but it has no function + and requires no maintenance. It will remain + existing for several weeks. + + + Mit Hilfe dieses Zaubers kann der + Traumweber eigene Aura im Verhältnis 2:1 auf + einen anderen Traumweber übertragen. + With the help of this spell the + mentalist can transfer aura at a ratio of 2:1 to + another mentalist. + + + Mit Hilfe dieses arkanen Rituals + vermag der Traumweber die wahre Gestalt einer + Gruppe + zu verschleiern. Unbedarften Beobachtern + erscheint + sie dann als einer anderen Rasse zugehörig. + With the help of this ritual the + mentalist is able to conceal the true form of a + target unit. To unknowing observers all persons + in the target unit appear to be of a different + race. + + + Dieser Zauber ermöglicht es dem + Traumweber, in die Träume einer Einheit + einzudringen und so einen Bericht über die + Umgebung zu erhalten. + This spell enables the mentalist to + penetrate the dreams of a target unit and gather + information about that unit's surroundings. He + will receive a report from the corresponding + region. + + + Dieser Kampfzauber führt dazu, dass + einige Gegner im Kampf unter schwerer Müdigkeit + leiden. Die Soldaten verschlafen manchmal ihren + Angriff und verteidigen sich schlechter. + This combat spell causes several + enemies to suffer from an unnatural tiredness + during combat. The soldiers will defend + themselves worse than normal and sometimes sink + into a slumber instead of attacking. + + + Stirbt ein Krieger im Kampf so macht + sich seine Seele auf die lange Wanderung zu den + Sternen. Mit Hilfe eines Rituals kann ein + Traumweber versuchen, die Seele wieder + einzufangen und in den Körper des Verstorbenen + zurückzubringen. Zwar heilt der Zauber keine + körperlichen Verwundungen, doch ein Behandelter + wird den Kampf überleben. + When a warrior dies in a battle, his + soul begins its long journey to the stars. With + the help of this ritual, the mentalist can try + to catch those escaping souls and bring them + back to their bodies. The spell does not heal + physical injuries, but an affected person will + survive the battle. + + + Dieser Zauber führt in der betroffenen + Region für einige Wochen zu Schlaflosigkeit und + Unruhe. Den Betroffenen fällt das Lernen + deutlich schwerer. + This spell causes insomnia and + restlessness in a whole region for several + weeks. All affected persons will learn much + slower than normal. + + + Dieser Zauber läßt einige feindliche + Kämpfer einschlafen. Schlafende Kämpfer greifen + nicht an und verteidigen sich schlechter, sie + wachen jedoch auf, sobald sie im Kampf getroffen + werden. + This spell causes several enemies to + fall asleep. Sleeping warriors don't attack and + defend themselves worse than normal, but they'll + wake up if they get hit during combat. + + + Mit diesem Zauber dringt der + Traumweber in die Gedanken und Traumwelt seines + Opfers ein und kann so seine intimsten + Geheimnisse ausspähen. Seine Fähigkeiten, seinen + Besitz und seine Parteizugehörigkeit wird nicht + länger ungewiss sein. + With this spell the mentalist + penetrates the thoughts and dreams of his victim + to reveal his most intimate secrets. The + target's faction, skills and possessions will no + longer be unknown. + + + Der Magier beschwört ein kleines Monster, einen Alp. Dieses bewegt sich + langsam auf sein Opfer zu (das sich an einem beliebigen Ort an der Welt + befinden kann, der Magier oder seine Partei braucht es nicht zu sehen). + Sobald das Opfer erreicht ist, wird es gnadenlos gequält, und nur durch + einen starken Gegenzauber oder den Tod des beschwörenden Magiers kann + das Opfer wieder Frieden finden. Bei der Beschwörung des Alps verliert + der Magier einen kleinen Teil seiner Aura für immer. + The magician spawns a little monster, a nightmare. The nightmare slowly + approaches its victim (which may be at an arbitrary place in eressea, it + is not needed for the magician or his party to see the victim). As soon + as + the victim is reached the nightmare starts to torment it without mercy, + only a powerfull counter spell or the death of the casting magician can + redeem + the victim. When spawning the nightmare the magician loses a small amount + of + his aura forever. + + + Ein mit diesem Zauber belegtes Drachenauge, welches zum Abendmahle + verzehrt wird, erlaubt es dem Benutzer, in die Träume einer anderen + Person einzudringen und diese zu lesen. Lange Zeit wurde eine solche + Fähigkeit für nutzlos erachtet, bis die ehemalige waldelfische + Magistra für Kampfmagie, Liarana Sonnentau von der Akademie Thall, + eine besondere Anwendung vorstellte: Feldherren träumen vor großen + Kämpfen oft unruhig und verraten im Traum ihre Pläne. Dies kann dem + Anwender einen großen Vorteil im kommenden Kampf geben. Aber Vorsicht: + Die Interpretation von Träumen ist eine schwierige Angelegenheit. + An enchanted eye of a dragon gives the person who eats it for supper the + power to see + other people's dreams. For a long time this abillity was counted as + beeing + useless until + the former elfish mistress for theurgy of war, Liarana Sonnentau from + the + academy Thall, + presented a special appliance for this artefact: Before a battle + captains + often have an + uncomfortable sleep and betray their plans in their dreams. This might + give the user of + the artefact a small advantage in the upcoming battle, but be warned: + Interpreting dreams + is a difficult exercise. + + + Mit diesem Spruch kann der Zauberer eine Sphäre der + Unsichtbarkeit + erschaffen. Die Späre macht ihren Träger sowie neunundneunzig weitere + Personen in derselben Einheit unsichtbar. + Using this spell the magician can create a Sphere of + Invisibility. This artefact hides the person bearing it and one hundred + persons in the same unit. + + + Dieser Zauber ermöglicht es dem + Traumweber, den Schlaf aller aliierten Einheiten + in + der Region so zu beeinflussen, dass sie für + einige + Zeit einen Bonus in allen Talenten bekommen. + This spell allows the mentalist to + influence the sleep of all allied units in a + region + in such a way that they will gain a bonus to all + talents for some time. + + + Dieser Zauber ermöglicht es dem + Traumweber die natürlichen und aufgezwungenen + Traumbilder einer Person, eines Gebäudes, + Schiffes oder einer Region zu unterscheiden und + diese zu entwirren. + This spell allows the mentalist to + distinguish between the natural and unnatural + dreams of a person, a ship, a building or a + region and remove those that are of magical + origin. + + + Aus 'Die Gesänge der Alten' von + Firudin dem Weisen: 'Diese verführerische kleine Melodie + und einige einschmeichelnde Worte überwinden das + Misstrauen der Bauern im Nu. Begeistert werden sie sich + Euch anschliessen und selbst Haus und Hof in Stich + lassen.' + From the 'Songs of the Elder' by + Firudin the Sage: 'This enticing little melody and its + ingratiating words will lure the peasants in no time. + They will leave home and hearth to follow your lead.' + + + Dieser fröhliche Gesang wird sich wie + ein Gerücht in der Region ausbreiten und alle Welt in + Feierlaune versetzten. Überall werden Tavernen und + Theater gut gefüllt sein und selbst die Bettler satt + werden. + This joyous song will spread like + wildfire throughout the region and cause festive spirits + in all the population. All the taverns and theaters will + be packed to the brim and even the beggars will not go + hungry. + + + Mit diesem Zauber greift der Magier + direkt den Geist seiner Gegner an. Ein Schlag aus + astraler und elektrischer Energie trifft die Gegner, + wird die Magieresistenz durchbrochen, verliert ein Opfer + permanent einen Teil seiner Erinnerungen. Wird es zu oft + ein Opfer dieses Zaubers kann es daran sterben. + With this spell the mentalist directly + attacks his enemies' souls. A blast of astral and + electrical energy strikes the foes. If a victim fails to + resist the magic, he will permanently lose part of his + memories. Being the target of this spell for too many + times may result in death. + + + Dieser Zauber - dessen Anwendung in + den meisten Kulturen streng verboten ist - löst im Opfer + ein unkontrollierbares Verlangen nach körperlicher Liebe + aus. Die betroffenen Personen werden sich Hals über Kopf + in ein Liebesabenteuer stürzen, zu blind vor Verlangen, + um an etwas anderes zu denken. Meistens bereuen sie es + einige Wochen später... + This spell - whose use is forbidden in + most cultures - creates an uncontrollable desire for + physical love in the victim. The affected persons will + rush head over heels into a love affair, unable to think + of anything else. Most of them will regret this a few + months later... + + + + + + Winter + winter + + + Sommer + summer + + + Frühling + spring + + + Herbst + fall + + + die erste Woche + the first week + + + die zweite Woche + the second week + + + die letzte Woche + the last week + + + der ersten Woche + of the first week + + + der zweiten Woche + of the second week + + + der letzten Woche + of the third week + + + Feldsegen + harvest moon + + + Nebeltage + impenetrable fog + + + Sturmmond + storm moon + + + Herdfeuer + hearth fire + + + Eiswind + icewind + + + Schneebann + snowbane + + + Blütenregen + flowerrain + + + Mond der milden Winde + mild winds + + + Sonnenfeuer + sunfire + + + des zweiten Zeitalters + the second age + + + neuer Zeitrechnung + of the new age + + + + + + Gemein + common + + + Kein Magiegebiet + no magic school yet + + + Illaun + Illaun + + + Tybied + Tybied + + + Gwyrrd + Gwyrrd + + + Cerddor + Cerddor + + + Draig + Draig + + + + + Baum + tree + + + Bäume + trees + + + + Mallornbaum + mallorn tree + + + Mallornbäume + mallorn trees + + + ALLIANZ + ALLIANCE + + + AUSSTOSSEN + KICK + + + NEU + NEW + + + KOMMANDO + COMMAND + + + VERLASSEN + LEAVE + + + BEITRETEN + JOIN + + + EINLADEN + INVITE + + + Steine + stones + + + + Pferde + horses + + + Bauern + peasants + + + Silber + silver + + + Laen + laen + + + + Schößlinge + saplings + + + + Mallornschößlinge + mallorn saplings + + + + Bäume + trees + + + + Mallorn + mallorn + + + + Eisen + iron + + + + Winter + winter + + + + Frühling + spring + + + + Sommer + summer + + + + Herbst + autumn + + + + Vorlage für den nächsten Zug: + Template for the next turn: + + + + Wir schreiben %s des Monats %s im Jahre %d %s. + It is %s of the month of %s in the %d. year of %s. + + + + Wir schreiben %s des Monats %s im Jahre %d %s. Es ist + %s. + It is %s of the month of %s in the %d. year of %s. It is + %s. + + + + aggressiv + aggressive + + + + vorne + front + + + + hinten + rear + + + + defensiv + defensive + + + + flieht + fleeing + + + + kämpft nicht + not fighting + + + + bekommt keine Hilfe + gets no aid + + + + Attacke gegen: + Attacked against + + + + Kämpft gegen: + Fighting against + + + + Hilft: + Helping + + + + Heer + army + + + + Unbekannte Partei + unknown faction + + + einer unbekannten Partei + an unknown faction + + + und + and + + + + Das Schiff des Elfen hat ein rotes Segel + + + + Der Zwerg hat eine Nuss dabei + + + + Die Katze führt eine Hellebarde + + + + Das Schiff mit dem grünen Segel liegt links neben dem + mit + einem weissen Segel + + + + Auf dem Schiff mit grünen Segeln kam der Speerkämpfer + + + + Der Krieger mit dem Kreis im Wappen hat einen Keks + + + + Der Krieger des mittleren Schiffs hat ein Schwert + + + + Auf dem gelben Segel prankt ein Kreuz als Wappen + + + + Der Mensch kam mit dem ersten Schiff + + + + Das Schiff mit dem Stern im Wappen liegt neben dem der + einen Mandelkern hat + + + + Das Schiff des Kriegers, der ein Apfel hat, liegt neben + dem, der ein Kreuz als Wappen hat + + + + Der Krieger mit dem Turm im Wappen trägt eine Axt + + + + Das Schiff des Menschen liegt neben dem blauen Schiff + + + + Das Insekt trägt einen Baum als Wappen + + + + Das Schiff mit dem Stern im Wappen liegt neben dem des + Kriegers, der einen Zweihänder führt + + + + Held + hero + + + + Helden + heroes + + + + + + Dunkel + dark + + + + Schwarz + black + + + + Licht + light + + + + Flammen + flame + + + + Eis + ice + + + + Klein + gully + + + + Hoch + high + + + + Hügel + hill + + + + Berg + mountain + + + + Wald + wood + + + + Sumpf + swamp + + + + Schnee + snow + + + + Sonnen + sun + + + + Mond + moon + + + + See + sea + + + + Tal + valley + + + + Schatten + shadow + + + + Höhlen + cave + + + + Blut + blood + + + + Wild + wild + + + + Chaos + chaos + + + + Nacht + night + + + + Nebel + mist + + + + Grau + grey + + + + Frost + cold + + + + Finster + gloom + + + + Düster + black + + + + Sternen + star + + + + + 'Ho ho ho!' Ein dicker Gnom fliegt auf einem von + 8 Jungdrachen gezogenen Schlitten durch die Nacht und vermacht Deiner + Partei ein Sonnensegel. (Um das Segel einer Einheit zu geben, gib + ihr den Befehl 'BEANSPRUCHE 1 Sonnensegel'). + 'Ho ho ho!' A fat little gnome Gnom on a sled + pulled by 8 young dragons flies through the stary night and presents + your faction with a solar sail. (To claim this item, one of your units + must issue the order 'CLAIM 1 solar sail'. + + + + 'Ho ho ho!' Ein dicker Gnom fliegt auf einem von + 8 Jungdrachen gezogenen Schlitten durch die Nacht und vermacht Deiner + Partei eine Phiole mit Sternenstaub. (Informationen dazu gibt es mit + BEANSPRUCHE und ZEIGE). + 'Ho ho ho!' A fat little gnome Gnom on a sled + pulled by 8 young dragons flies through the stary night and presents + your faction with a vial of stardust. (To get more information about + this item, use the CLAIM and SHOW commands). + + + + 'Ho ho ho!' Ein dicker Gnom fliegt auf einem von + 8 Jungdrachen gezogenen Schlitten durch die Nacht und vermacht Deiner + Partei einen wundervoll geschmueckten Weihnachtsbaum. (Informationen dazu gibt es mit + BEANSPRUCHE und ZEIGE). + 'Ho ho ho!' A fat little gnome Gnom on a sled + pulled by 8 young dragons flies through the stary night and presents + your faction with a beautifully decorated tree. (To get more information about + this item, use the CLAIM and SHOW commands). + + + + Gral + grail + + + + Grale + grails + + + + Lerntrank + brain boost + + + + Lerntränke + brain boosts + + + + GE je + stone per + + + + GE je + stones per + + + + GE + stone + + + + GE + stones + + + + bewacht die Region + guards the region + + + + hungert + hungry + + + + Fernzauber + far + + + + Seezauber + sea + + + + Schiffszauber + ship + + + + Magier exklusiv + magicians only + + + + Keine + none + + + + Wir helfen der Partei + We are helping the faction + + + + Wir helfen den Parteien + We are helping the factions + + + + hilft der Partei + is helping the faction + + + + hilft den Parteien + is helping the factions + + + + hat die Region durchquert + has traveled through the region + + + + haben die Region durchquert + have traveled through the region + + + + durchgereist + travel + + + + benachbart + neighbour + + + + vom Turm erblickt + from lighthouse + + + + und + and + + + + Dorfbewohner + Villagers + + + + Bauernmob + Angry mob + + + + Aufgebrachte Bauern + Furious peasants + + + + Söldner + Mercenaries + + + + Sumpfbewohner + Swamp people + + + + Waldbewohner + Woodsmen + + + + Nomaden + Nomads + + + + Eisleute + Ice people + + + + Bergbewohner + Mountain people + + + + Magie der Elemente + Magic of the Elements + + + + Schwerter, Armbrüste, Langbögen + Swords, Crossbows and Longbows + + + + Gorms Almanach der Rationellen Kriegsführung + Gorm's Almanach of Rational War + + + + Katamarane, Koggen, Karavellen + The dragonship, the caravell and the longboat + + + + Wege der Sterne + Ways of the Start + + + + Nadishahs Kleine Gift- und Kräuterkunde + Nadishah's collected lore on poisonous and beneficial herbs + + + + Mandricks Kompendium der Alchemie + Mandrick's alchemistic compendium + + + + Die Konstruktion der Burgen und Schlösser von Zentralandune + + + + Die Esse + + + + Über die Gewinnung von Erzen + + + + Barinions Lieder, eine Einführung für Unbedarfte + + + + die Ruine eines alten Tempels + the ruins of an ancient temple + + + + eine alte Burgruine + the ruins of a castle + + + + ein zerfallenes Bauernhaus + a dilapitated farm + + + + eine Leiche am Wegesrand + a corpse by the wayside + + + + eine Leiche am Wegesrand + a corpse by the wayside + + + + Feuerdrache + fire dragon + + + + Ein Alp starb, ohne sein Ziel zu erreichen. + An alp died before it reached its target. + + + + unbewaffnet + unarmed + + + + Trefferpunkte + hitpoints + + + + Rüstung + armor + + + + Angriff + attack + + + + Angriffe + attacks + + + + Verteidigung + defense + + + + Kann Waffen benutzen. + May use weapons. + + + + Ist durch Stichwaffen, Bögen und Armbrüste schwer zu verwunden. + Is hard to hit by piercing weapons. + + + + Ist durch Hiebwaffen schwer zu verwunden. + Is hard to hit by slashing weapons. + + + + Ist durch Schlagwaffen und Katapulte schwer zu verwunden. + Is hard to hit by blunt weapons and catapults. + + + + ein Angriff mit der Waffe oder unbewaffnet + an attack with a weapon or an unarmed attack + + + + ein unbewaffneter Angriff + an unarmed attack + + + + ein magischer Angriff + a magical attack + + + + Klon von %s + Clone of %s + + + + ein Angriff, der Gebäudeschaden verursacht + an attack causing structural damage to buildings + + + + Präkampfzauber + pre-combat spell + + + + Postkampfzauber + post-combat spell + + + + Kampfzauber + combat spell + + + + Normaler Zauber + regular spell + + + + Es ist Winter, und Insekten können nur in Wüsten oder mit Hilfe des Nestwärme-Tranks Personen rekrutieren. + It is winter, and insects can only recruit in deserts or with the aid of nestwarmth potions. + + + + Es ist Spätherbst, und diese Woche ist die letzte vor dem Winter, in der Insekten rekrutieren können. + It is the last week before winter in which insects can still recruit. + + + + Eigentümer + Owner + + + + + eine Straße + a road + + + + Straßen + roads + + + + Straße + road + + + + eine zu %d%% vollendete Straße + a road that is %d%% complete + + + + ein Straßenanschluß + a connection to another road + + + + eine unvollständige Straße + an incomplete road + + + + Wand + wall + + + + eine Wand + a wall + + + + Feuerwand + firewall + + + + eine Feuerwand + a firewall + + + + Nebelwand + wall of fog + + + + eine Nebelwand + a wall of fog + + + + Irrlichter + wisps + + + + eine Gruppe von Irrlichtern + a cloud of wisps + + + + gewaltiges offenes Tor + massive open door + + + + ein gewaltiges offenes Tor + a massive open door + + + + gewaltiges verschlossenes Tor + massive locked door + + + + ein gewaltiges verschlossenes Tor + a massive locked door + + + + Illusionswand + illusionary wall + + + + eine Illusionswand + an illusionary wall + + + + + + Die Region ist verwüstet, der Boden karg. + The region is ravaged, the ground infertile. + + + + Einheit-Nr + unitid + + + + Schiff-Nr + shipid + + + + völker + tribes + + + + Gebäude-Nr + buildingid + + + + + Aura + aura + + + + Rasse + race + + + + Zauber-ID + spellid + + + + Richtung + direction + + + + Gebäudetyp + buildingtype + + + + diff --git a/core/res/en/strings.xml b/core/res/en/strings.xml new file mode 100644 index 000000000..893007b43 --- /dev/null +++ b/core/res/en/strings.xml @@ -0,0 +1,1680 @@ + + + + + + + ADDRESSES + + + REPORT + + + BZIP2 + + + COMPUTER + + + DEBUG + + + ITEMPOOL + + + SCORE + + + SILVERPOOL + + + STATISTICS + + + EXPRESS + + + ZIPPED + + + TEMPLATE + + + SKILLCHANGES + + + + INFO + + + + + caravel + + + boat + + + longboat + + + dragonship + + + trireme + + + balloon + + + + + a caravel + + + a boat + + + a longboat + + + a balloon + + + a dragonship + + + a trireme + + + + + active volcano + + + corridor + + + desert + + + firewall + + + fog + + + forest + + + glacier + + + glacier + + + hallway + + + hell + + + highland + + + iceberg + + + maelstrom + + + mountain + + + ocean + + + plain + + + swamp + + + thick fog + + + volcano + + + magical storm + + + + the volcano of %s + + + a %s + + + the deserts of %s + + + a %s + + + fog_trail %s + + + the forests of %s + + + the glacier of %s + + + Wall + + + the %s + + + %s + + + the highlands of %s + + + a %s + + + the mountains of %s + + + the %s + + + the plain of %s + + + the swamps of %s + + + %s + + + the volcano of %s + + + a %s + + + + caldera + + + portal + + + + + NW + + + NE + + + East + + + SE + + + SW + + + West + + + + west + + + northwest + + + northeast + + + east + + + southwest + + + southeast + + + + an unknown unit + + + + Dispatches + + + Events + + + Warnings and Errors + + + Economy and Trade + + + Resources and Production + + + Magic and Artefacts + + + Movement and Travel + + + Learning and Teaching + + + Battles + + + Miscellaneous + + + New Spells + + + + + academy + + + blessed stonecircle + + + caravanserei + + + dam + + + structure + + + harbour + + + fairy castle + + + inn + + + lighthouse + + + mage tower + + + mine + + + monument + + + quarry + + + sawmill + + + smithy + + + stable + + + stonecircle + + + tunnel + + + + + foundation + + + tradepost + + + fortification + + + tower + + + castle + + + fortress + + + citadel + + + + + herb + + + vial + + + vials + + + + + silver + + + silver + + + hp + + + hps + + + aura + + + auras + + + permaura + + + permauras + + + peasant + + + peasants + + + + + almond + + + almonds + + + amulet + + + amulets + + + antimagic crystal + + + antimagic crystals + + + amulet of chastity + + + amulets of chastity + + + amulet of the kitten + + + amulets of the kitten + + + amulet of darkness + + + amulets of darkness + + + amulet of gathering + + + amulets of gathering + + + amulet of healing + + + amulets of healing + + + amulet of true seeing + + + amulets of true seeing + + + apple + + + apples + + + aurafocus + + + aurafocuses + + + axe + + + axes + + + bow + + + bows + + + cart + + + carts + + + catapult + + + catapults + + + chainmail + + + chainmails + + + cookie + + + cookies + + + crossbow + + + crossbows + + + dolphin + + + dolphins + + + dragonblood + + + dragonblood + + + dragonhead + + + dragonheads + + + dragonhoard + + + dreameye + + + dreameyes + + + elven horse + + + elven horses + + + eye of dragon + + + eye of dragons + + + fairy boots + + + fairy boots + + + flaming sword + + + flaming swords + + + elven bow + + + elven bows + + + claymore + + + claymores + + + halberd + + + halberds + + + healingpotion + + + healingpotions + + + herbbag + + + herbbags + + + horse + + + horses + + + iron + + + iron + + + laen + + + laen + + + laen chainmail + + + laen chainmails + + + laen shield + + + laen shields + + + laen sword + + + laen swords + + + lance + + + lances + + + wood + + + wood + + + magic bag + + + magic bags + + + bag of conservation + + + bags of conservation + + + mallorn + + + mallorn + + + mallorn bow + + + mallorn bows + + + mallorn crossbow + + + mallorn crossbows + + + mallorn lance + + + mallorn lances + + + mallorn spear + + + mallorn spear + + + silverbag + + + silverchest + + + returnticket for the grand museum + + + returntickets for the grand museum + + + ticket to the grand museum + + + tickets to the grand museum + + + nut + + + nuts + + + pegasus + + + pegasi + + + man + + + men + + + platemail + + + platemails + + + pangolin + + + pangolins + + + presspass + + + presspasses + + + ring of invisibility + + + rings of invisibility + + + ring of power + + + rings of power + + + ring of quick fingers + + + rings of quick fingers + + + ring of regeneration + + + rings of regeneration + + + runesword + + + runeswords + + + rustychainmail + + + rustychainmails + + + rusty shield + + + rusty shields + + + rusty sword + + + rusty swords + + + seaserpenthead + + + seaserpentheads + + + shield + + + shields + + + sack of holding + + + sacks of holding + + + spear + + + spears + + + stone + + + stones + + + sword + + + swords + + + pot of toadslime + + + pots of toadslime + + + trollbelt + + + trollbelts + + + unit + + + units + + + potion of skills + + + potions of skills + + + astralcrystal + + + astralcrystals + + + seed + + + seeds + + + mallorn seed + + + mallorn seeds + + + firework + + + fireworks + + + gingerbread heart + + + gingerbread hearts + + + + + balm + + + spice + + + gem + + + gems + + + myrrh + + + oil + + + silk + + + incense + + + balm + + + spice + + + myrrh + + + oil + + + silk + + + incense + + + + + Belt of Heroic Legends + + + Belts of Heroic Legends + + + + + flatroot + + + flatroots + + + tangy temerity + + + tangy temerities + + + owlsgaze + + + owlsgazes + + + spider ivy + + + spider ivies + + + cobalt fungus + + + cobalt fungi + + + elvendear + + + elvendears + + + bugleweed + + + bugleweeds + + + knotroot + + + knotroots + + + bubblemorel + + + bubblemorels + + + waterfinder + + + waterfinders + + + peyote + + + peyote + + + sand reeker + + + sand reekers + + + windbag + + + windbags + + + fjord fungus + + + fjord fungi + + + mandrake + + + mandrakes + + + rock weed + + + rock weed + + + gapgrowth + + + gapgrowths + + + cave lichen + + + cave lichen + + + ice begonia + + + ice begonias + + + white hemlock + + + white hemlocks + + + snowcrystal petal + + + snowcrystal petals + + + seven mile tea + + + seven mile teas + + + goliath water + + + goliath waters + + + water of life + + + waters of life + + + busybeer + + + busybeers + + + ointment + + + ointments + + + peasant blood + + + peasant bloods + + + brain wax + + + brain waxes + + + duncebun + + + duncebuns + + + potion of nest warmth + + + potions of nest warmth + + + horsepower potion + + + horsepower potions + + + berserkers blood potion + + + berserkers blood potions + + + peasant love potion + + + peasant love potion + + + potion of truth + + + potions of truth + + + elixir of power + + + elixirs of power + + + healing potion + + + healing potions + + + + + AGGRESSIVE + + + ALL + + + EACH + + + NUMBER + + + AURA + + + TREES + + + PEASANTS + + + AID + + + GUARD + + + CASTLE + + + DEFENSIVE + + + UNIT + + + ERESSEA + + + FLEE + + + FOREIGN + + + BUILDING + + + ITEMS + + + GIVE + + + MERCY + + + HELP + + + REAR + + + AFTER + + + CONTROL + + + HERBS + + + COMBAT + + + NOT + + + NEXT + + + FACTION + + + FACTIONSTEALTH + + + PAUSE + + + MEN + + + PRIVATE + + + REGION + + + SHIP + + + SILVER + + + ROADS + + + LEVEL + + + TEMPORARY + + + POTIONS + + + FOR + + + BEFORE + + + FRONT + + + SPELLS + + + + + alchemy + + + armoursmithing + + + bow + + + masonry + + + cartmaking + + + catapult + + + crossbow + + + entertainment + + + espionage + + + forestry + + + herbalism + + + magic + + + melee + + + mining + + + perception + + + polearm + + + quarrying + + + riding + + + roadwork + + + sailing + + + shipcraft + + + endurance + + + stealth + + + tactics + + + taxation + + + trade + + + taming + + + unarmed combat + + + weaponsmithing + + + + + + // + + + WORK + + + ATTACK + + + BANNER + + + STEAL + + + BESIEGE + + + NAME + + + USE + + + DESCRIBE + + + PRAY + + + ENTER + + + GUARD + + + BID + + + MESSAGE + + + DEFAULT + + + EMAIL + + + END + + + RIDE + + + FOLLOW + + + RESEARCH + + + GM + + + GROUP + + + HELP + + + JIHAD + + + COMBATSPELL + + + BUY + + + CONTACT + + + COMBAT + + + TEACH + + + LEARN + + + SUPPLY + + + LOCALE + + + MAKE + + + MOVE + + + RESTART + + + NUMBER + + + SACRIFICE + + + OPTION + + + PASSWORD + + + PLANT + + + PIRACY + + + PREFIX + + + RECRUIT + + + REPORT + + + RESERVE + + + ROUTE + + + SABOTAGE + + + SORT + + + SPY + + + QUIT + + + SYNONYM + + + HIDE + + + CARRY + + + TAX + + + ENTERTAIN + + + ORIGIN + + + FORGET + + + SELL + + + LEAVE + + + CAST + + + SHOW + + + DESTROY + + + GROW + + + LYCANTROPE + + + + + Options + + + Level + + + Political Status + + + Herbs required + + + under construction + + + damage + + + Your faction has been eliminated. We hope that you had a good time, and if you liked the game, you should sign up and play again. + + + + skills + + + has + + + size + + + spells + + + combat spells + + + none + + + Addresses + + + anonymous + + + attack + + + defense + + + armour + + + damage + + + + + wand + + + wands + + + + + + northwest coast + + + northeast coast + + + east coast + + + southeast coast + + + southwest coast + + + west coast + + + + + No orders were received for your faction! + + + + + Mistelzweig + mistletoe + + + Mistelzweige + mistletoes + + diff --git a/core/res/equipment.xml b/core/res/equipment.xml new file mode 100644 index 000000000..a998ef712 --- /dev/null +++ b/core/res/equipment.xml @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/fr/strings.xml b/core/res/fr/strings.xml new file mode 100644 index 000000000..850085eaf --- /dev/null +++ b/core/res/fr/strings.xml @@ -0,0 +1,2082 @@ + + + + + + PASSAGE + + + XEPOTION + + + XEBALLON + + + XELAEN + + + GUERRE + + + PAIX + + + XONTORMIA + + + ALLIANCE + + + ADRESSES + + + RAPPORT + + + BZIP2 + + + ORDINATEUR + + + DEBOGUER + + + RESSOURCES COMMUNES + + + SCORE + + + ARGENT COMMUN + + + STATISTIQUES + + + EXPRESS + + + ZIPPE + + + MODELE + + + MODIFICATIONS + + + + INFO + + + + + nef + + + chaloupe + + + barge + + + drakkar + + + galère + + + ballon + + + + + une nef + + + une chaloupe + + + une barge + + + un ballon + + + un drakkar + + + une galère + + + + + volcan actif + + + couloir + + + désert + + + mur de feu + + + brume + + + forêt + + + glacier + + + vestibule + + + l'enfer + + + colline + + + iceberg + + + maelström + + + montagne + + + océan + + + plaine + + + marais + + + brouillard + + + volcan + + + tempête magique + + + + le volcan %s + + + un %s + + + le désert de %s + + + un %s + + + fog_trail %s + + + la forêt de %s + + + le glacier de %s + + + le %s + + + %s + + + les collines de %s + + + un %s + + + un %s + + + les montagnes de %s + + + l'%s + + + la plaine de %s + + + les marais de %s + + + %s + + + le volcan de %s + + + une %s + + + + caldera + + + portail + + + + + NO + + + NE + + + Est + + + SE + + + SO + + + Ouest + + + + ouest + + + nord-ouest + + + nord-est + + + est + + + sud-ouest + + + sud-est + + + + une unité inconnue + + + + Messages et Evénements + + + Avertissements et Erreurs + + + Economie et Commerce + + + Ressources et Production + + + Magie et Reliques + + + Déplacements et Voyages + + + Apprentissage et Enseignement + + + Batailles + + + Divers + + + Nouveaux Sorts + + + + + université + + + cromlech sacré + + + caravansérail + + + barrage + + + bâtiment + + + port + + + château illusoire + + + auberge + + + phare + + + donjon + + + mine + + + monument + + + carrière + + + scierie + + + forge + + + écurie + + + cromlech + + + tunnel + + + + + palissade + + + comptoir + + + rempart + + + tour + + + château + + + place-forte + + + citadelle + + + + + plante + + + fiole + + + fioles + + + + + écu + + + écus + + + point de vie + + + points de vie + + + aura + + + aura + + + aura permanente + + + aura permanente + + + paysan + + + paysans + + + + + amande + + + amandes + + + amulette + + + amulettes + + + cristal antimagie + + + cristaux antimagie + + + amulette de chasteté + + + amulettes de chasteté + + + amulette du chaton + + + amulettes du chaton + + + amulette de ténèbres + + + amulettes de ténèbres + + + amulette de rassemblement + + + amulettes de rassemblement + + + amulette de soin + + + amulettes de soin + + + amulette de vérité + + + amulettes de vérité + + + pomme + + + pommes + + + focus + + + foci + + + hache + + + haches + + + arc + + + arcs + + + chariot + + + chariots + + + catapulte + + + catapultes + + + cotte de mailles + + + cottes de mailles + + + gâteau + + + gâteaux + + + arbalète + + + arbalètes + + + dauphin + + + dauphins + + + sang de dragon + + + sang de dragon + + + tête de dragon + + + têtes de dragons + + + trésor de dragon + + + oniroeil + + + oniryeux + + + cheval elfique + + + chevaux elfiques + + + oeil de dragon + + + yeux de dragon + + + bottes elfiques + + + bottes elfiques + + + épée ardente + + + épées ardentes + + + grand arc + + + grands arcs + + + claymore + + + claymores + + + halebarde + + + halebardes + + + potion de soin + + + potions de soin + + + sac de plantes + + + sacs de plantes + + + cheval + + + chevaux + + + lingot + + + lingots + + + laen + + + laen + + + cotte en laen + + + cottes en laen + + + bouclier en laen + + + boucliers en laen + + + épée en laen + + + épées en laen + + + lance + + + lances + + + stère + + + stères + + + sac magique + + + sacs magiques + + + sac de conservation + + + sacs de conservation + + + mallorn + + + mallorn + + + arc en mallorn + + + arcs en mallorn + + + arbalète en mallorn + + + arbalètes en mallorn + + + lance en mallorn + + + lances en mallorn + + + épieu en mallorn + + + épieux en mallorn + + + bourse + + + cassette + + + returnticket for the grand museum + + + returntickets for the grand museum + + + ticket to the grand museum + + + tickets to the grand museum + + + noix + + + noix + + + pégase + + + pégases + + + homme + + + hommes + + + armure de plates + + + armures de plates + + + carte de presse + + + cartes de presse + + + anneau d'invisibilité + + + anneaux d'invisibilité + + + anneau de pouvoir + + + anneaux de pouvoir + + + anneau de dextérité + + + anneaux de dextérité + + + anneau de régénération + + + anneaux de régénération + + + épée runique + + + épées runiques + + + cotte de mailles rouillée + + + cottes de mailles rouillées + + + bouclier rouillé + + + boucliers rouillés + + + épée rouillée + + + épées rouillées + + + tête de serpent de mer + + + têtes de serpents de mer + + + bouclier + + + boucliers + + + sac de contenance + + + sacs de contenance + + + épieu + + + épieux + + + pierre + + + pierres + + + épée + + + épées + + + pot de bave de crapaud + + + pots de bave de crapaud + + + ceinture de troll + + + ceintures de trolls + + + unité + + + unités + + + potion de compétences + + + potions de compétences + + + cristal astral + + + cristaux astraux + + + graine + + + graines + + + graine de mallorn + + + graines de mallorn + + + feu d'artifice + + + feux d'artifice + + + coeur de pain d'épices + + + coeurs de pain d'épices + + + + + baume + + + épices + + + joyau + + + joyaux + + + myrrhe + + + huile + + + soie + + + encens + + + baume + + + épices + + + myrrhe + + + huile + + + soie + + + encens + + + + + Ceinture des Légendes + + + Ceintures des Légendes + + + + + astragale + + + astragales + + + méritoine + + + méritoines + + + oeil de hibou + + + yeux de hibou + + + soie d'araignée + + + soies d'araignée + + + obbadion + + + obbadions + + + cheveux d'elfe + + + cheveux d'elfe + + + ortigal + + + ortigals + + + tubercule de respiplante + + + tubercules de respiplante + + + oreille de morille + + + oreilles de morille + + + hydropousse + + + hydropousses + + + ossiphage + + + ossiphages + + + fleur de souffre + + + fleurs de souffre + + + feuille de Tshaï + + + feuilles de Tshaï + + + bélidane + + + bélidanes + + + racine de mandragore + + + racines de mandragore + + + percepierre + + + percepierres + + + tanemiel + + + tanemiels + + + boralme + + + boralmes + + + ficoïde à cristaux + + + ficoïdes à cristaux + + + blémissure + + + blémissures + + + rose des neiges + + + roses des neiges + + + thé de sept lieues + + + thé de sept lieues + + + breuvage de Goliath + + + breuvage de Goliath + + + élixir de vie + + + élixir de vie + + + vin du travail acharné + + + vin du travail acharné + + + onguent de soin + + + onguents de soin + + + fiole d'essence vitale + + + fioles d'essence vitale + + + huile de cogitation + + + huile de cogitation + + + petit pain rance + + + petits pains rances + + + extrait de canicule + + + extraits de canicule + + + fourrage de l'étalon + + + fourrage de l'étalon + + + vin de folie + + + vin de folie + + + philtre d'amour + + + philtres d'amour + + + sirop de claivoyance + + + sirops de claivoyance + + + elixir d'endurance + + + elixir d'endurance + + + potion de survie + + + potions de survie + + + + + AGRESSIF + + + TOUT + + + NOMBRE + + + AURA + + + ARBRES + + + PAYSANS + + + SOUTIEN + + + GUARDE + + + CHATEAU + + + DEFENSIF + + + UNITE + + + ERESSEA + + + FUITE + + + ETRANGER + + + BATIMENT + + + OBJETS + + + DONNER + + + PITIE + + + AIDE + + + DERRIERE + + + APRES + + + CONTROLE + + + PLANTES + + + COMBAT + + + NON + + + SUIVANT + + + FACTION + + + CAMOUFLAGE + + + PAUSE + + + HOMMES + + + PRIVE + + + REGION + + + BATEAU + + + ECUS + + + ROUTES + + + NIVEAU + + + TEMPORAIRE + + + POTIONS + + + POUR + + + AVANT + + + DEVANT + + + SORTS + + + + + alchimie + + + armurier + + + arc + + + maçon + + + charron + + + catapulte + + + arbalète + + + divertissement + + + espionnage + + + bucheron + + + herboriste + + + magie + + + mêlée + + + mineur + + + observation + + + hast + + + perrayeur + + + équitation + + + cantonnier + + + navigation + + + charpentier + + + endurance + + + discrétion + + + tactique + + + percepteur + + + commerce + + + dresseur + + + mains-nues + + + fourbisseur + + + + + + // + + + TRAVAILLER + + + ATTAQUER + + + ANNONCE + + + VOLER + + + ASSIEGER + + + NOMMER + + + UTILISER + + + DECRIRE + + + PRIER + + + ENTRER + + + GUARDER + + + OFFRIR + + + MESSAGE + + + DEFAUT + + + EMAIL + + + FIN + + + CHEVAUCHER + + + SUIVRE + + + CHERCHER + + + GM + + + GROUPER + + + AIDER + + + JIHAD + + + PREPARER + + + ACHETER + + + CONTACTER + + + COMBATTRE + + + ENSEIGNER + + + APPRENDRE + + + FOURNIR + + + LOCAL + + + FAIRE + + + ALLER + + + RECOMMENCER + + + NOMBRE + + + SACRIFIER + + + OPTION + + + PASSWORD + + + PLANTER + + + PIRATERIE + + + PREFIXE + + + RECRUTER + + + RAPPORT + + + RESERVER + + + TRAJET + + + SABOTER + + + TRIER + + + ESPIONNER + + + ABANDONNER + + + SYNONYME + + + CACHER + + + TRANSPORTER + + + TAXER + + + DIVERTIR + + + ORIGINE + + + OUBLIER + + + VENDRE + + + SORTIR + + + INCANTER + + + MONTRER + + + DETRUIRE + + + ACCROITRE + + + METAMORPHOSE + + + + + sangsunicornes + + + sangsunicorne + + + cauchemars + + + cauchemar + + + ombreillards + + + ombreillard + + + draco tenebrae + + + draco tenebrae + + + nains + + + nain + + + elfes + + + elfe + + + orques + + + orque + + + snotlings + + + snotling + + + gobelins + + + gobelin + + + humains + + + humain + + + trolls + + + troll + + + démons + + + démons + + + insectes + + + insecte + + + hobbits + + + hobbit + + + chats + + + chat + + + atlantes + + + atlante + + + morts-vivants + + + mort-vivant + + + illusions + + + illusion + + + dragonnets + + + dragonnet + + + dragons + + + dragon + + + wyrms + + + wyrm + + + ents + + + ent + + + dragons-chats + + + dragon-chat + + + draconiens + + + draconien + + + spéciaux + + + spécial + + + enchantements + + + enchantement + + + golems de fer + + + golem de fer + + + golems de pierre + + + golem de pierre + + + ombres + + + ombre + + + lémures + + + lémure + + + yétis + + + yéti + + + quauquemaires + + + quauquemaire + + + crapauds + + + crapaud + + + céphalophages + + + céphalophage + + + paysans + + + paysan + + + wargs + + + warg + + + lynx + + + lynx + + + vers des profondeurs + + + ver des profondeurs + + + rats + + + rat + + + dragons chinois + + + dragon chinois + + + loups + + + loup + + + fantômes + + + fantôme + + + chats des rêves + + + chat des rêves + + + chats de l'Enfer + + + chat de l'Enfer + + + tigres + + + tigre + + + dauphins + + + dauphin + + + tortues géantes + + + tortue géante + + + krakens + + + kraken + + + serpents de mer + + + serpent de mer + + + guerriers illusoires + + + guerrier illusoire + + + diablotins + + + diablotin + + + nymphes + + + nymphe + + + licornes + + + licorne + + + hiboux + + + hibou + + + fées + + + fée + + + aigles + + + aigle + + + centaures + + + centaure + + + squelettes + + + squelette + + + liches + + + liche + + + zombies + + + zombie + + + zombies juju + + + zombie juju + + + goules + + + goule + + + spectres + + + spectre + + + fantômes du musée + + + fantôme du musée + + + gnomes + + + gnome + + + modèles + + + modèle + + + métamorphes + + + métamorphe + + + + + + Options + + + Niveau + + + Statut Politique + + + Plantes nécessaires + + + en construction + + + de dégâts + + + Votre faction a été éliminée. Nous espérons que vous vous êtes bien amusé malgré tout, et vous encourageons à vous réincrire pour une nouvelle partie. + + + + compétences + + + possessions + + + taille + + + sorts + + + sorts de combat + + + aucun + + + Adresses + + + anonyme + + + attaque + + + défense + + + armure + + + dégâts + + + + + baguette + + + baguettes + + + + + + côte nord-ouest + + + côte nord-est + + + côte est + + + côte sud-est + + + côte sud-ouest + + + côte ouest + + + + Aucun ordre reçu pour votre faction ! + + diff --git a/core/res/messages.xml b/core/res/messages.xml new file mode 100644 index 000000000..354209ceb --- /dev/null +++ b/core/res/messages.xml @@ -0,0 +1,8520 @@ + + + + + + + Ein Alp starb in $region($region), ohne sein Ziel zu erreichen. + An alp died in $region($region) before reaching its target. + + + + + + "Einheiten können die folgenden Gegenstände beanspruchen: $resources($items)" + "Units can claim the following items: $resources($items)" + + + + + + + + "$if($isnull($region),"Es","In $region($region)") wurde$if($eq($number,1),"","n") $int($number) $race($race,$number) gesichtet." + "$if($isnull($region),"","In $region($region), ")$int($number) $race($race,$number) were discovered." + + + + + + + "$unit($mage) läßt einen Teil seiner selbst in die Erde fliessen. Die Bäume, die Transformation überlebt haben, erscheinen nun viel kräftiger." + "The power of $unit($mage) flows into the region and the trees which survived the spell appear stronger now." + "The power of $unit($mage) flows into the region and the trees which survived the spell appear stronger now." + + + + + + + "$unit($mage) beschwört einen Luftgeist, der die $ship($ship) in die Wolken hebt." + "$unit($mage) summons a wind spirit that lifts the $ship($ship) into the clouds." + + + + + + "$unit($mage) beschwört einen Schleier der Verwirrung." + "$unit($mage) summons a fog of confusion." + + + + + Eine Feuerwand blockiert die Ein- und Ausreise. ($int36($id)) + + + + + Der Magier besitzt die Gabe des Chaos. ($int36($id)) + The magician possesses the gift of Chaos. ($int36($id)) + + + + Dieser mächtige Bann scheint die Einheit ihres freien Willens zu berauben. Solange der Zauber wirkt, wird sie nur den Befehlen ihres neuen Herrn gehorchen. ($int36($id)) + + + + Dieser Beeinflussungszauber scheint die Einheit einem ganz bestimmten Volk wohlgesonnen zu machen. ($int36($id)) + + + + Dieser Zauber verursacht einen gigantischen magischen Strudel. Der Mahlstrom wird alle Schiffe, die in seinen Sog geraten, schwer beschädigen. ($int36($id)) + + + + Heilung ist in dieser Region magisch beeinflusst. ($int36($id)) + + + + Dieses Schiff hat sich verfahren. ($int36($id)) + + + + + + + + "$unit($unit) ist im Traum eine Fee erschienen. ($int36($id))" + "In a dream, a fairy appears to $unit($unit). ($int36($id))" + + + + + + + "$unit($unit) wird von bösen Alpträumen geplagt. ($int36($id))" + "$unit($unit) is haunted by terrbile nightmares. ($int36($id))" + + + + + + + "$unit($unit) wird von einem glitzernden Funkenregen umgeben. ($int36($id))" + "$unit($unit) is surrounded by a shower of glittering sparkles. ($int36($id))" + + + + + + + "Ein schimmernder Lichterkranz umgibt $unit($unit). ($int36($id))" + "A circle of shimmering lights surrounds $unit($unit). ($int36($id))" + + + + + + + "Eine Melodie erklingt, und $unit($unit) tanzt bis spät in die Nacht hinein. ($int36($id))" + "A haunting melody fills the air, and $unit($unit) dances until late into the night. ($int36($id))" + + + + + + + "$unit($unit) findet eine kleine Flöte, die eine wundersame Melodie spielt. ($int36($id))" + "$unit($unit) finds a small flute that plays a beautiful melody. ($int36($id))" + + + + + + + "Die Frauen des nahegelegenen Dorfes bewundern $unit($unit) verstohlen. ($int36($id))" + "The women of the nearby village cast furtive looks at $unit($unit). ($int36($id))" + + + + + + + "Eine Gruppe vorbeiziehender Bergarbeiter rufen $unit($unit) eindeutig Zweideutiges nach. ($int36($id))" + "A group of passing miners makes passes at $unit($unit). ($int36($id))" + + + + + + + "$unit($unit) bekommt von einer Schlange einen Apfel angeboten. ($int36($id))" + "A large green snake offers $unit($unit) a fine-looking apple. ($int36($id))" + + + + + + "A spell is deflecting magical energies and weakening all other spells cast in the region. ($int36($id))" + "Dieser Zauber scheint magische Energien irgendwie abzuleiten und so alle in der Region gezauberten Sprüche in ihrer Wirkung zu schwächen oder ganz zu verhindern. ($int36($id))" + + + + + + + "Ein Einhorn berührt $unit($unit) mit seinem Horn und verschwindet kurz darauf im Unterholz. ($int36($id))" + "A unicorn touches $unit($unit) with its horn and vanishes into the forest quickly after. ($int36($id))" + + + + + + + "Vogelzwitschern begleitet $unit($unit) auf all seinen Wegen. ($int36($id))" + "Bird songs follow $unit($unit) on all his travels. ($int36($id))" + + + + + + + "Leuchtende Blumen erblühen rund um das Lager von $unit($unit). ($int36($id))" + "Brightly coloured flowers pop up all around $unit($unit)'s camp. ($int36($id))" + + + + + + + "Über $unit($unit) zieht eine Gruppe Geier ihre Kreise. ($int36($id))" + "A group of vultures circles above $unit($unit). ($int36($id))" + + + + + + + "Der Kopf von $unit($unit) hat sich in einen grinsenden Totenschädel verwandelt. ($int36($id))" + "The head of $unit($unit) has turned into a madly grinning skull. ($int36($id))" + + + + + + + "Ratten folgen $unit($unit) auf Schritt und Tritt. ($int36($id))" + "Rats follow $unit($unit)'s every step. ($int36($id))" + + + + + + + "Pestbeulen befallen den Körper von $unit($unit). ($int36($id))" + "The body of $unit($unit) is disfigured by hideous boils. ($int36($id))" + + + + + + + "Eine dunkle Fee erscheint $unit($unit) im Schlaf. Sie ist von schauriger Schönheit. ($int36($id))" + "A dark and mysterious fairy appears before $unit($unit). She is of bewitching beauty. ($int36($id))" + + + + + + + "Fäulnisgeruch dringt $unit($unit) aus allen Körperöffnungen. ($int36($id))" + "The stench of decay is poring from all the orifices of $unit($unit). ($int36($id))" + + + + + + + + "$unit($unit) mag $faction($faction) zu mögen. ($int36($id))" + "$unit($unit) likes $faction($faction). ($int36($id))" + + + + + + + + "$unit($unit) scheint $race($race, 0) zu mögen. ($int36($id))" + "$unit($unit) seems to like $race($race, 0). ($int36($id))" + + + + + + + + "$unit($unit) ist ungewöhnlich ungeschickt in $skill($skill). ($int36($id))" + "$unit($unit) has some troubles with $skill($skill). ($int36($id))" + + + + + + + + "$unit($unit) ist ungewöhnlich geschickt in $skill($skill). ($int36($id))" + "$unit($unit) is incredibly skilled at $skill($skill). ($int36($id))" + + + + + + + + "$unit($unit) wird noch $int($duration) $if($eq($duration,1), "Woche", "Wochen") unter unserem Bann stehen. ($int36($id))" + "$unit($unit) will be under our influence for $int($duration) more $if($eq($duration,1), "week", "weeks"). ($int36($id))" + + + + + + + + + "$int($number) $if($eq($number,1), "Person", "Personen") von $unit($unit) $if($eq($number,1), "ist", "sind") noch $int($duration) $if($eq($duration,1), "Woche", "Wochen") beschleunigt. ($int36($id))" + "$int($number) $if($eq($number,1), "member", "members") of $unit($unit) $if($eq($number,1), "is", "are") accelerated for $int($duration) more $if($eq($duration,1), "week", "weeks"). ($int36($id))" + + + + + + + + "$int($number) $if($eq($number,1), "Person", "Personen") von $unit($unit) $if($eq($number,1), "fühlt", "fühlen") sich vor Kälte geschützt. ($int36($id))" + "$int($number) $if($eq($number,1), "member", "members") of $unit($unit) $if($eq($number,1), "is", "are") protected from the cold. ($int36($id))" + + + + + + "Ein unbekannter Zauber liegt auf dem Schiff. ($int36($id))" + "An unknown spell lies on this ship. ($int36($id))" + + + + + + "Ein unbekannter Zauber liegt auf der Einheit. ($int36($id))" + "An unknown spell lies on this unit. ($int36($id))" + + + + + + "Ein unbekannter Zauber liegt auf dem Gebäude. ($int36($id))" + "An unknown spell lies on this building. ($int36($id))" + + + + + + "Ein unbekannter Zauber liegt auf der Region. ($int36($id))" + "An unknown spell lies on this region. ($int36($id))" + + + + + + "Eine Wolke negativer Energie liegt über der Region. ($int36($id))" + "A fog of negative energy enshrouds the region. ($int36($id))" + "A fog of negative energy enshrouds the region. ($int36($id))" + + + + + + "Die Leute strotzen nur so vor Kraft. ($int36($id))" + "Testosterone levels are at an all-time high. ($int36($id))" + + + + + + + "$unit($unit) wird von einem Alp geritten. ($int36($id))" + "$unit($unit) is chased by a nightmare. ($int36($id))" + + + + + + + "$unit($unit) stürzt sich von einem amourösen Abenteuer ins nächste. ($int36($id))" + "$unit($unit) goes from one amourous adventure to another. ($int36($id))" + "$unit($unit) goes from one amourous adventure to another. ($int36($id))" + + + + + + + "$unit($unit) kann sich kaum konzentrieren. ($int36($id))" + "$unit($unit) can hardly focus on anything. ($int36($id))" + "$unit($unit) can hardly focus on anything. ($int36($id))" + + + + + + + "Die Ausrüstung von $unit($unit) scheint unsichtbar. ($int36($id))" + "$unit($unit)'s equipment is invisible. ($int36($id))" + "$unit($unit)'s equipment is invisible. ($int36($id))" + + + + + + "Die natürliche Widerstandskraft gegen Verzauberung ist gestärkt. ($int36($id))" + "($int36($id))" + "The magical resistance has been strengthened. ($int36($id))" + + + + + + "Die natürliche Widerstandskraft gegen Verzauberung bestimmter Einheiten in dieser Region wurde gestärkt. ($int36($id))" + "($int36($id))" + "The magical resistance of some units in this region was boosted. ($int36($id))" + + + + + + "Die natürliche Widerstandskraft gegen Verzauberung bestimmter Einheiten in dieser Region wurde geschwächt. ($int36($id))" + "The magical resistance of some units in this region was weakened. ($int36($id))" + "($int36($id))" + + + + + + "Diese Mauern wirken, als wären sie direkt aus der Erde gewachsen und nicht erbaut. ($int36($id))" + "These walls appear to have grown straight out of the earth. ($int36($id))" + "($int36($id))" + + + + + + "Ein magischer Schimmer liegt auf diesen Mauern. ($int36($id))" + "A magical shimmer lies on these walls. ($int36($id))" + "($int36($id))" + + + + + + "Die Straßen sind erstaunlich trocken und gut begehbar, doch an manchen Stellen bilden sich wieder die erste Schlammlöcher. ($int36($id))" + "The roads are extremely dry and well-kept, but some areas show the first signs of potholes reappearing. ($int36($id))" + "($int36($id))" + + + + + + "Die Straßen sind erstaunlich trocken und gut begehbar. ($int36($id))" + "The roads are extremely dry and well-kept. ($int36($id))" + "($int36($id))" + + + + + + "Albträume plagen die Leute. ($int36($id))" + "Nightmares plague the population. ($int36($id))" + "($int36($id))" + + + + + + "Die Leute haben schöne Träume. ($int36($id))" + "($int36($id))" + "The people in this region have sweet dreams. ($int36($id))" + + + + + + "Diese Region wurde von den Göttern verflucht. Das Meer ist eine ekelige Brühe, braunschwarze, stinkende Gase steigen aus den unergründlichen Tiefen hervor, und untote Seeungeheuer, Schiffe zerfressend und giftige grüne Galle geifernd, sind der Schrecken aller Seeleute, die diese Gewässer durchqueren. Niemand kann hier lange überleben. ($int36($id))" + "($int36($id))" + "This region was cursed by the gods. The sea is a foul cesspool, noxious gases rise from the deep, undead seamonsters attack all ships. Noone can live here for long. ($int36($id))" + + + + + + "Diese Region wurde von den Göttern verflucht. Stinkende Nebel ziehen über die tote Erde und furchtbare Kreaturen ziehen über das Land. Die Brunnen sind vergiftet, und die wenigen essbaren Früchte sind von einem rosa Pilz überzogen. Niemand kann hier lange überleben. ($int36($id))" + "($int36($id))" + "This region was cursed by the gods. Noone can live here for long. ($int36($id))" + + + + + + "Ein Schleier der Verwirrung liegt über der Region. ($int36($id))" + "($int36($id))" + "A veil of confusion lies over the region. ($int36($id))" + + + + + + "In der Region treibt ein Giftelementar sein Unwesen. ($int36($id))" + "A poison elemental is spreading pestilence and death. ($int36($id))" + + + + + + "Die ganze Region ist von einer friedlichen Stimmung erfasst. ($int36($id))" + "($int36($id))" + "Everyone in this region seems to be in a peacful mood. ($int36($id))" + + + + + + "Es herrscht eine fröhliche und ausgelassene Stimmung. ($int36($id))" + "($int36($id))" + "Everyone in this region seems to be having a very good time. ($int36($id))" + + + + + + "Die Bauern sind unzufrieden. ($int36($id))" + "($int36($id))" + "The peasants are upset. ($int36($id))" + + + + + + "Alle Leute in der Region haben Schlafstörungen. ($int36($id))" + "($int36($id))" + "People in this region suffer from insomnia. ($int36($id))" + + + + + + "In dieser Gegend herrscht eine Dürre. ($int36($id))" + "($int36($id))" + "This region was hit by a drought. ($int36($id))" + + + + + + "In dieser Gegend steht das Korn besonders gut im Feld. ($int36($id))" + "($int36($id))" + "The grain in this region is especially healthy. ($int36($id))" + + + + + + "Die Winde scheinen dieses Schiff besonders zu beguenstigen. ($int36($id))" + "($int36($id))" + "The winds seem to favor this ship. ($int36($id))" + + + + + + "Untote schrecken vor dieser Region zurück. ($int36($id))" + "($int36($id))" + "The undead turn away from this region. ($int36($id))" + + + + + + "Der Zahn der Zeit kann diesen Mauern nichts anhaben. ($int36($id))" + "($int36($id))" + "Time cannot touch these walls. ($int36($id))" + + + + + + "Dichte Nebel bedecken diese Woche die Region. Keine Einheit schafft es, diese Nebel zu durchdringen und die Region zu verlassen. ($int36($id))" + "heavy fog makes it impossible to leave the region. ($int36($id))" + "heavy fog makes it impossible to leave the region. ($int36($id))" + + + + + + + "$unit($unit) setzt ein Sonnensegel. Die Geschwindigkeit des Schiffes erhöht um $int($speed)." + "$unit($unit) sets a solar sail. The ship's speed is increased by $int($speed)." + "$unit($unit) sets a solar sail. The ship's speed is increased by $int($speed)." + + + + + + "Interner Fehler: Meldung '$name' nicht definiert." + "Internal Error: Message '$name' is undefined." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Interner Fehler: Meldung '$name' nicht definiert." + "$unit($unit) in $region($region): '$order($command)' - Internal Error: Message '$name' is undefined." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier ist kein passendes Schloss." + "$unit($unit) in $region($region): '$order($command)' - No fitting lock can be found here." + "$unit($unit) in $region($region): '$order($command)' - No fitting lock can be found here." + + + + + + + + "$unit($unit) öffnet eines der Schlösser in $region($region) mit $if($eq($key,1),"dem Achatenen Schlüssel","dem Saphirnen Schlüssel")." + "$unit($unit) unlocks one of the locks in $region($region) with $if($eq($key,1),"the Agate Key","the Sapphire Key")." + "$unit($unit) unlocks one of the locks in $region($region) with $if($eq($key,1),"the Agate Key","the Sapphire Key")." + + + + + + + + "$unit($unit) verschließt eines der Schlösser in $region($region) mit $if($eq($key,1),"dem Achatenen Schlüssel","dem Saphirnen Schlüssel")." + "$unit($unit) locks one of the locks in $region($region) with $if($eq($key,1),"the Agate Key","the Sapphire Key")." + "$unit($unit) locks one of the locks in $region($region) with $if($eq($key,1),"the Agate Key","the Sapphire Key")." + + + + + + + "$unit($unit) in $region($region) verwandelt sich in ein Werwesen." + "$unit($unit) in $region($region) becomes a lycantrope." + "$unit($unit) in $region($region) becomes a lycantrope." + + + + + + + "SIEG! $if($eq($n,1), "Die Partei $winners hat", "Die Parteien $winners haben") die Siegbedingung für die erforderliche Zeit erfüllt. Das Spiel ist damit beendet." + "VICTORY! $if($eq($n,1), "The faction $winners has", "The factions $winners have") fulfilled the victory condition for the necessary time. The game is over." + "VICTORY! $if($eq($n,1), "The faction $winners has", "The factions $winners have") fulfilled the victory condition for the necessary time. The game is over." + + + + + + "Achtung: $faction($faction) hat die Siegbedingungen erfüllt und wird in $if($eq($remain,1),"einer Woche","$int($remain) Wochen") zum Sieger erklärt werden." + "Attention: $faction($faction) has fulfilled the victory condition and will be declared winner in $if($eq($remain,1),"one week","$int($remain) weeks")." + "Attention: $faction($faction) has fulfilled the victory condition and will be declared winner in $if($eq($remain,1),"one week","$int($remain) weeks")." + + + + + + + + "$unit($unit) wurde in $region($region) von einem GM gelöscht: \"$string\"." + "$unit($unit) in $region($region) was removed by a GM: \"$string\"." + "$unit($unit) in $region($region) was removed by a GM: \"$string\"." + + + + + + + + "$resource($item,1) (Gewicht: $weight($weight)): $description" + "$resource($item,1) (weight: $weight($weight)): $description" + + + + + "Ein Hauch des Lebens liegt über der Welt und alle Wesen fühlen sich frisch und erholt." + "Life itself touches the world and all beings are healed." + "Life itself touches the world and all beings are healed." + + + + + + + + "$unit($unit) hat Glück und findet einen Hort von $int($amount) $resource($item,$amount)." + "$unit($unit) luckily finds a cache of $int($amount) $resource($item,$amount)." + "$unit($unit) luckily finds a cache of $int($amount) $resource($item,$amount)." + + + + + + "$unit($unit) brennt ein großes Feuerwerk ab und Kaskaden bunter Sterne, leuchtende Wasserfälle aus Licht und strahlende Feuerdrachen erhellen den Himmel." + "A large firework is visible all over the sky." + "A large firework is visible all over the sky." + + + + + + + "In $region($region) wird ein großes Feuerwerk abgebrannt, welches noch hier zu bewundern ist. Kaskaden bunter Sterne, leuchtende Wasserfälle aus Licht und strahlende Feuerdrachen erhellen den Himmel." + "A large firework, visible all over the sky, has been started in $region($region)." + "A large firework, visible all over the sky, has been started in $region($region)." + + + + + + + "Zur Feier des Geburtstags von ${name} brennt $unit($unit) ein großes Feuerwerk ab. Kaskaden bunter Sterne, leuchtende Wasserfälle aus Licht und strahlende Feuerdrachen erhellen den Himmel." + "A large firework in honor of ${name} is visible all over the sky." + "A large firework in honor of ${name} is visible all over the sky." + + + + + + + + "Zur Feier des Geburtstags von ${name} wird in $region($region) ein großes Feuerwerk abgebrannt, welches noch hier zu bewundern ist. Kaskaden bunter Sterne, leuchtende Wasserfälle aus Licht und strahlende Feuerdrachen erhellen den Himmel." + "A large firework in honor of ${name}, visible all over the sky, has been started in $region($region)." + "A large firework in honor of ${name}, visible all over the sky, has been started in $region($region)." + + + + + + + "$int36($unit.id($unit))/$int($index) erzielt einen kritischen Treffer." + "$int36($unit.id($unit))/$int($index) does critical damage." + "$int36($unit.id($unit))/$int($index) does critical damage." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) muß mindestens 2 Stufen besser sein als $unit($student)." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) needs to be at least 2 levels better than $unit($student)." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) needs to be at least 2 levels better than $unit($student)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($student) lernt nicht." + "$unit($unit) in $region($region): '$order($command)' - $unit($student) is not learning." + "$unit($unit) in $region($region): '$order($command)' - $unit($student) is not learning." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dafür braucht die Einheit $resources($required)." + "$unit($unit) in $region($region): '$order($command)' - for this, the unit needs $resources($required)." + "$unit($unit) in $region($region): '$order($command)' - for this, the unit needs $resources($required)." + + + + + + $trailto($region) + $trailto($region) + + + + + + + "Deine Partei hat $int($units) Migranten und kann maximal $int($maxunits) Migranten aufnehmen." + "Your faction has $int($units) migrants out of a possible total of $int($maxunits)." + + + + + + + + + "Seit $int($age) Wochen Mitglied der Allianz '$name ($int36($id))', angeführt von $faction($leader)." + "Member of '$name ($int36($id))' for $int($age) weeks, led by $faction($leader)." + + + + + + + "Deine Partei hat $int($units) Helden und kann maximal $int($maxunits) Helden ernennen." + "Your faction has promoted $int($units) heroes out of a possible total of $int($maxunits)." + + + + + + + "Deine Partei hat $int($population) Personen in $int($units) Einheiten." + "Your faction has $int($population) people in $int($units) units." + + + + + + "Statistik für $region($region):" + "Statistics for $region($region):" + + + + + + "Unterhaltung: max. $int($max) Silber" + "Entertainment: max. $int($max) silver" + + + + + + "Moral der Bauern: $int($morale)" + "Peasant morale: $int($morale)" + + + + + + "Luxusgüter zum angegebenen Preis: $int($max)" + "Luxury goods at this price: $int($max)" + + + + + + "Lohn für Arbeit: $int($max) Silber" + "Worker salary: $int($max) silver" + + + + + + "Bauerneinnahmen: $int($max) Silber" + "Peasant wages: $int($max) silver" + + + + + + "Personen: $int($max)" + "People: $int($max)" + "People: $int($max)" + + + + + + "Rekruten: max. $int($max) Bauern" + "Recruits: $int($max) peasants" + "Recruits: $int($max) peasants" + + + + + + + "Deine Partei hat $int($score) Punkte. Der Durchschnitt für Parteien ähnlichen Alters ist $int($average) Punkte." + "Your faction has a score of $int($score). The average score for similar factions is $int($average)." + "Your faction has a score of $int($score). The average score for similar factions is $int($average)." + + + + + + + "Report für $game, $date" + "Report for $game, $date" + "Report for $game, $date" + + + + + + + "Im $direction($dir) der Region liegt $trailto($region)" + "To the $direction($dir) lies $trailto($region)" + + + + + + + "$resource($product,0) $int($price) Silber" + "$resource($product,0) for $int($price) silver" + "$resource($product,0) for $int($price) silver" + + + + + + + "Auf dem Markt wird für $resource($product,0) $int($price) Silber verlangt." + "Le marché local offre la $resource($product,0) au prix de $int($price) écus." + "The local market offers $resource($product,0) at a price of $int($price) silver." + + + + + + + "Auf dem Markt werden $resource($p1,0) und $resource($p2,0) feilgeboten." + "The local market offers $resource($p1,0) and $resource($p2,0)." + + + + + + "Auf dem Markt wird $resource($p1,0) feilgeboten." + "The local market offers $resource($p1,0)." + + + + + + "Dein Passwort lautet ${password}." + "Your password is ${password}." + "Your password is ${password}." + + + + + + "Die Mannschaft krank vom vergifteten Wasser, Planken, Ruder und Segel zerfressen von den Wassern des verfluchten Meeres, ergibt sich die $ship($ship) in ihr Schicksal und sinkt." + "Her sailors sick from the poisened ocean, planks, rudder und sails corroded by the waters of the cursed ocean, the $ship($ship) finally succumbs to her destiny and sinks." + + + + + + "$unit($unit) benutzt einen Talenttrunk und fühlt, wie sein Wissen zunimmt." + "$unit($unit) uses a potion of skills and feels his knowledge grow." + "$unit($unit) uses a potion of skills and feels his knowledge grow." + + + + + + + "$unit($unit) benutzt einen Astralkristall und gewinnt $int($aura) Aura hinzu." + "$unit($unit) uses an astralcrystal and gains $int($aura) aura." + "$unit($unit) uses an astralcrystal and gains $int($aura) aura." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Luxusgut wird hier nicht verkauft." + "$unit($unit) in $region($region): '$order($command)' - These goods are not on sale here." + "$unit($unit) in $region($region): '$order($command)' - These goods are not on sale here." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dazu benötigt man $resource($missing,0)." + "$unit($unit) in $region($region): '$order($command)' - This requires $resource($missing,0)." + "$unit($unit) in $region($region): '$order($command)' - This requires $resource($missing,0)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) nehmen nichts an." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) will not accept anything." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) will not accept anything." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) können nicht neu gruppiert werden." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot be regrouped." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot be regrouped." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) können nichts stehelen." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot steal anything." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot steal anything." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) geben nichts weg." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) do not give things away." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) do not give things away." + + + + + + + + "Eine Botschaft von $unit.dative($sender) aus $region($region): '$string'" + "A message by $unit($sender) from $region($region): '$string'" + + + + + + + + + "In $region($region) erhielt $unit($unit) eine Botschaft von $unit.dative($sender): '$string'" + "In $region($region), $unit($unit) received a message by $unit($sender): '$string'" + + + + + + + + + "In $region($region) erhielt $unit($unit) von $unit.dative($sender) $resources($items)" + "In $region($region), $unit($unit) received $resources($items) from $unit($sender)" + + + + + + "$building($building) hat diese Woche nicht funktioniert, da zu Beginn der Woche der Unterhalt nicht gezahlt werden konnte." + "$building($building) was nonfunctional because upkeep could not be paid at the beginning of the week." + + + + + + "Plötzlich löst sich $building($building) in kleine Traumwolken auf." + "$building($building) suddenly dissolves into small pink clouds." + + + + + + "Für das Gebäude $building($building) konnte die ganze Woche kein Unterhalt bezahlt werden." + "Upkeep for $building($building) could not be paid all week." + + + + + + + + + "In $region($region) stürzte $building($building) ein.$if($road," Beim Einsturz wurde die halbe Straße vernichtet.","")$if($opfer," $int($opfer) Opfer $if($eq($opfer,1),"ist","sind") zu beklagen.","")" + "$building($building) in $region($region) collapses.$if($opfer," There are $int($opfer) caualties.","")" + "$building($building) in $region($region) collapses.$if($opfer," There are $int($opfer) caualties.","")" + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In der Ebene der Herausforderung kann niemand rekrutiert werden." + "$unit($unit) in $region($region): '$order($command)' - You cannot recruit in this plane." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die einheit kann sich nicht so gut tarnen." + "$unit($unit) in $region($region): '$order($command)' -The unit cannot hide that well." + + + + + + + + "$ship($ship) was destroyed by $unit($unit)." + "$ship($ship) wurde von $unit($unit) zerstört." + + + + + + + + "$unit($unit) could not destroy $ship($ship)." + "$unit($unit) konnte $ship($ship) nicht zerstören." + + + + + + + + "$unit($unit) was detected while trying to destroy $ship($ship)." + "$unit($unit) wurde beim Versuch $ship($ship) zu zerstören entdeckt." + + + + + + + "Somebody attempted to destroy $ship($ship)." + "Es wurde versucht, $ship($ship) zu zerstören." + + + + + + + "$ship($ship) was destroyed." + "$ship($ship) wurde zerstört." + + + + + + + + + "$int($amount) Personen von $unit($unit) ertrinken.$if($isnull($region),""," Die Einheit rettet sich nach $region($region).")" + + + + + + + + "$unit($unit) ueberlebt unbeschadet und rettet sich nach $region($region)." + + + + + + + + "$ship($ship) versinkt in den Fluten von $region($region)." + + + + + + + + "$unit($unit) wurde beim versenken von $ship($ship) entdeckt." + + + + + + + + + "$unit($unit) entdeckte $unit($saboteur) beim versenken von $ship($ship)." + + + + + + + + "$unit($unit) ertrinkt in $region($region)." + "$unit($unit) drowns in in $region($region)." + + + + + + + + + "$int($amount) Personen in $unit($unit) in $region($region) ertrinken." + "$int($amount) people in $unit($unit) in $region($region) drown." + "$int($amount) people in $unit($unit) in $region($region) drown." + + + + + + + "$unit($unit) nimmt Schaden auf dem Wasser in $region($region)." + "$unit($unit) is taking damage on the water." + "$unit($unit) is taking damage on the water." + + + + + + "$unit($unit) schwenkt sein Szepter und sorgt für Verwirrung und Chaos in der Region." + + + + + + + + + "$unit($unit) stolpert bei der Erforschung der Region über $localize($location). Nähere Durchsuchung fördert ein zerfleddertes altes Buch mit dem Titel '$localize($book)' zu Tage. Der Wissensschub ist enorm." + + + + "Ein Alp hat sein Opfer gefunden und springt auf den Rücken von $unit($target)!" + + + + + + + + "$unit($unit) fühlt sich von starken magischen Energien durchströmt. ($int36($id))" + "Powerful magical energies are pulsing through $unit($unit). ($int36($id))" + + + + + + + + "$unit($unit) hat Schwierigkeiten seine magischen Energien zu sammeln. ($int36($id))" + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger sind für einen Moment benommen." + "$unit($mage) casts $spell($spell): $int($amount) fighters were momentarily stunned." + + + + + + + + "$unit($mage) besänftigt den Bauernaufstand in $region($region)." + "$unit($mage) quells the uprising in $region($region)." + + + + + + + + "$unit($mage) rief in $region($region) einen Riss in dem Gefüge der Magie hervor, der alle magische Kraft aus der Region riss." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger wurden in Schlaf versetzt." + "$unit($mage) casts $spell($spell): $int($amount) fighters have fallen asleep." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Kriegern wurde ihre Lebenskraft entzogen." + "$unit($mage) casts $spell($spell): $int($amount) fighters had their life energy drained." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Kriegern wurden versteinert." + "$unit($mage) casts $spell($spell): $int($amount) fighters were petrified." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger wurden in einen Blutrausch versetzt." + "$unit($mage) casts $spell($spell): $int($amount) fighters went into a mindless rage." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger wurden eingeschüchtert." + "$unit($mage) casts $spell($spell): $int($amount) fighters were intimidated." + + + + + + + + "$unit($mage) zaubert $spell($spell), aber es gab niemanden, der beeinflusst werden konnte." + "$unit($mage) casts $spell($spell), but nobody is impressed." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger wurden von Furcht gepackt." + "$unit($mage) casts $spell($spell): $int($amount) fighters were consumed by fear." + + + + + + + + "$unit($mage) zaubert $spell($spell): Das Kampfgetümmel erstirbt und er kann unbehelligt seines Weges ziehen." + "$unit($mage) casts $spell($spell): The noise of the battle dies down and he is able to slip away unharmed." + + + + + + + + "$unit($mage) zaubert $spell($spell): Ein Sturm kommt auf und die Schützen können kaum noch zielen." + "$unit($mage) casts $spell($spell): Strong stormwinds are blowing and the archers are having a hard time aiming." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger schleppten sich müde in den Kampf." + "$unit($mage) casts $spell($spell): $int($amount) fighters had trouble staying awake." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger wurden moralisch gestärkt." + "$unit($mage) casts $spell($spell): $int($amount) fighters had their moral boosted." + + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($amount) Krieger wurden magisch beschleunigt." + "$unit($mage) casts $spell($spell): $int($amount) fighters were magically accelerated." + + + + + + + "$unit($mage) ruft ein fürchterliches Unwetter über seine Feinde, doch es gab niemanden mehr, den dies treffen konnte." + + + + + + + "$unit($mage) ruft ein fürchterliches Unwetter über seine Feinde, doch der magische Regen zeigt keinen Effekt." + + + + + + + "$unit($mage) ruft ein fürchterliches Unwetter über seine Feinde. Der magischen Regen lässt alles Eisen rosten." + + + + + + + + + "$unit($mage) beschwört den Alp $unit($alp) für $unit($target)." + "$unit($mage) summons the alp $unit($alp) for $unit($target)." + + + + + + + + "$unit($mage) kümmert sich um die Verletzten und heilt $int($amount) Verwundete." + "$unit($mage) sees after the wounded and heals $int($amount)." + + + + + + + + + "$unit($mage) kümmert sich um die Verletzten und benutzt ein $resource($item,1), um den Zauber zu verstärken. $int($amount) Verwundete werden geheilt." + "$unit($mage) sees after the wounded and heals $int($amount). A $resource($item,1) improves the spell." + + + + + + + + + "Mit einem Ritual bindet $unit($mage) die magischen Kräfte der Erde von $region($region) in die Mauern von $building($building)." + "$unit($mage) performs a ritual that binds the magical forces of $region($region) into the walls of $building($building)." + + + + + + + + + "$unit($mage) opfert $unit($target) $int($amount) Aura." + "$unit($mage) sacrifices $int($amount) aura for $unit($target)." + + + + + + + + "$unit($mage) beginnt ein Ritual der Wiederbelebung. $int($amount) Krieger stehen von den Toten auf." + "$unit($mage) begins a ritual of resurrection. $int($amount) warriors rise from the dead." + + + + + + + + + "$unit($mage) beginnt ein Ritual der Wiederbelebung und benutzt ein $resource($item,1), um den Zauber zu verstärken. $int($amount) Krieger stehen von den Toten auf." + "$unit($mage) begins a ritual of resurrection using a $resource($item,1). $int($amount) warriors rise from the dead." + + + + + + + "$unit($mage) öffnet ein Chaostor." + "$unit($mage) opens a chaos gate." + + + + "Ein Wirbel aus blendendem Licht erscheint." + "A vortex of blinding light appears." + + + + + + + + "$unit($mage) kann in $region($region) keine Untoten rufen." + "$unit($mage) cannot summon any undead in $region($region)." + + + + + + + + + "$unit($mage) erweckt in $region($region) $int($amount) Untote aus ihren Gräbern." + "$unit($mage) calls $int($amount) undead from their graves in $region($region)." + + + + + + + + "$unit($mage) stört in $region($region) die Ruhe der Toten." + "$unit($mage) communicates with the dead in $region($region)." + + + + + + + "$unit($unit) gelingt es, durch die Nebel auf die Realität zu blicken." + "$unit($unit) manages to catch a glimpse of reality through the fog." + + + + + + + + "$unit($mage) konnte $int($amount) $if($eq($amount,1),"Bauer","Bauern") anwerben." + "$unit($mage) managed to recruit $int($amount) $if($eq($amount,1),"peasant","peasants")." + + + + + + "Ein bohrender Schmerz durchzuckt $unit($unit), Verwirrung macht sich breit." + "Pain pulses through $unit($unit), confusion spreads." + + + + + + ""AAAAAAAGHHHHHH!" - Ein Schrei durchzieht die Region, $unit($unit) windet sich vor Schmerz." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Eine höhere Macht hindert $unit($unit) daran, das Objekt zu übergeben. 'ES IST DEINS, MEIN KIND. DEINS GANZ ALLEIN'." + + + + + + + "$unit($unit) sendet ein Stoßgebet an den Herrn der Schreie." + + + + + + + "Der Eisberg $region($region) schmilzt." + "The iceberg $region($region) melts." + "The iceberg $region($region) melts." + + + + + + "Der Gletscher von $region($region) bricht und treibt davon." + "The glacier in $region($region) breaks up and drifts away." + "The glacier in $region($region) breaks up and drifts away." + + + + + + "Der Eisberg $region($region) treibt an eine Küste." + "The iceberg $region($region) drifts onto a coast." + "The iceberg $region($region) drifts onto a coast." + + + + + + "Die $ship($ship) wird bei einer Kollision mit einem Eisberg zerstört." + "The $ship($ship) has been destroyed by a collision with an iceberg." + "The $ship($ship) has been destroyed by a collision with an iceberg." + + + + + + "Die $ship($ship) wird bei einer Kollision mit einem Eisberg beschädigt." + "The $ship($ship) has been damaged by a collision with an iceberg." + "The $ship($ship) has been damaged by a collision with an iceberg." + + + + + + + "Die $ship($ship) treibt nach $direction($dir)." + "The ship $ship($ship) drifts to the $direction($dir)." + "The ship $ship($ship) drifts to the $direction($dir)." + + + + + + + "Der Eisberg $region($region) treibt nach $direction($dir)." + "The iceberg $region($region) drifts $direction($dir)." + "The iceberg $region($region) drifts $direction($dir)." + + + + + + "Wir erklären allen $race($race,2) den heiligen Krieg." + "We declare jihad on all $race($race,2)." + + + + + + "Die Götter erhören $unit($unit)." + "The Gods have listened to $unit($unit)." + "The Gods have listened to $unit($unit)." + + + + + + + "Die Götter gewähren uns die Kraft eines $special($int($level))." + "The Gods grant us the powers of $special ($int($level))." + "The Gods grant us the powers of $special ($int($level))." + + + + + + "Die Götter gewähren uns die Kraft eines ${special}." + "The Gods grant us the powers of ${special}." + "The Gods grant us the powers of ${special}." + + + + + + + + + + "$unit($unit) verlor $int($fallen) Personen$if($alive,", $int($alive) überlebten","")$if($run," und $int($run) flohen$if($isnull($runto),""," nach $region($runto)")","")." + "$unit($unit) lost $int($fallen) people$if($alive,", $int($alive) survived","")$if($run," and $int($run) fled$if($isnull($runto),""," to $region($runto)")","")." + + + + + + + + "$unit($unit) erzielte $int($hits) Treffer und tötete $int($kills) Gegner." + "$unit($unit) hit $int($hits) times and killed $int($kills) enemies." + "$unit($unit) hit $int($hits) times and killed $int($kills) enemies." + + + + + + "$string" + "$string" + + + + + + + "Heer $int($index): $name" + "Army $int($index): $name" + + + + + + + "Verwundert blicken die Bauern von $region($region) auf ein neues Gebäude." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) gewinnt durch das Ritual $int($amount) Aura." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) receives $int($amount) aura." + + + + + + + + "$unit($mage) beschwört Naturgeister in den Boden von $region($region)." + "$unit($mage) summons natural spirits into the ground of $region($region)." + + + + + + + + + "$unit($mage) verwandelt $int($amount) aus $unit($target) in $race($race,0)." + "$unit($mage) transforms $int($amount) from $unit($target) into $race($race,0)." + + + + + + + + "$unit($mage) verwandelt $unit($target) in $race($race,0)." + "$unit($mage) tranforms $unit($target) to $race($race,0)." + + + + + + "$unit($mage) erlöst die gequälten Seelen der Toten." + "$unit($mage) redeems the tormented souls of the dead." + + + + + + "$unit($mage) verwandelt sich in einen Wyrm." + "$unit($mage) turns into a wyrm." + + + + + + + "$unit($mage) ruft Irrlichter in $region($region)." + "$unit($mage) summons wisps in $region($region)." + "$unit($mage) summons wisps in $region($region)." + + + + + + + "$unit($mage) erschafft in $region($region) eine Wand aus Feuer." + "$unit($mage) creates a wall of fire in $region($region)." + "$unit($mage) creates a wall of fire in $region($region)." + + + + + + + "$unit($mage) wiegelt in $region($region) die Bauern zum Aufstand auf." + "$unit($mage) incites a revolt among the peasants of $region($region)." + + + + + + + + + "$unit($mage) wiegelt in $region($region) $int($amount) Bauern zum Aufstand auf." + "$unit($mage) incites a revolt among $int($amount) peasants of $region($region)." + + + + + + + + "$unit($mage) sorgt in $region($region) für Trübsal unter den Bauern." + "$unit($mage) causes great sadness among the peasants of $region($region)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier erschafft ein Traumgebäude." + "$unit($unit) in $region($region): '$order($command)' - The magician creates an illusionary building." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) kann keine $race($race,1)-Gestalt annehmen." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Elementar ist zu klein, um das Gebäude zu tragen." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Elementar weigert sich, nach $direction($direction) zu gehen." + "$unit($unit) in $region($region): '$order($command)' - The elemental refuses to go $direction($direction)." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) ist von unserer Art, das Ritual wäre verschwendete Aura." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) is one of our kind, we should not waste aura on this." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) wird von uns aufgenommen." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) has become one of our kind." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) ruft Drachen nach $region($target)." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) calls dragons to $region($target)." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) erschafft $int($amount) ${object}." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) creates $int($amount) ${object}." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) creates $int($amount) ${object}." + + + + + + + + "Ein Beben erschüttert $building($building). Viele kleine Pseudopodien erheben das Gebäude und tragen es in Richtung $direction($direction)." + + + + + + + + "$unit($unit) benutzt in $region($region) ein Traumauge." + "$unit($unit) uses a dreameye in $region($region)." + + + + + + + "$unit($unit) benutzt in $region($region) einen Antimagiekristall." + "$unit($unit) uses an antimagic crystal in $region($region)." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Sphären des Chaos geben dem Magier einen Teil ihrer Kraft." + "$unit($unit) in $region($region): '$order($command)' - The sphere of chaos returns a part of his power to the magician." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier konnte keinen Fluch zerstören." + "$unit($unit) in $region($region): '$order($command)' - The magician could not destroy any magic." + + + + + + + + "In $region($region) dehnt $unit($unit) die Zeit für $int($amount) Personen." + "In $region($region), $unit($unit) bends time for $int($amount) men." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier zerstört $int($succ) Flüche auf ${target}." + "$unit($unit) in $region($region): '$order($command)' - The magician destroys $int($succ) spells on ${target}." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier zerstört den Fluch($id) auf ${target}." + "$unit($unit) in $region($region): '$order($command)' - The magician destroys the spell on ${target}." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Zauber ist nicht stark genug, um den Fluch auf ${target} zu zerstören." + "$unit($unit) in $region($region): '$order($command)' - The spell is not strong enough to destroy the curse on ${target}." + + + + + + + "$unit($mage) beschwört einen Giftelementar in $region($region)." + "$unit($mage) summons a poison elemental in $region($region)." + + + + + + + + "$unit($unit) in $region($region) wird von einem Unbekannten verflucht." + "$unit($unit) in $region($region) was cursed by an unknown magician." + + + + + + + + "$unit($mage) belegt $unit($target) mit einem Zauber." + "$unit($mage) puts a spell on $unit($target)." + + + + + + + "$unit($mage) belegt $unit($target) mit einem Kälteschutz." + "$unit($mage) puts protection from cold on $unit($target)." + + + + + + + "$unit($mage) legt einen Rosthauch auf $unit($target), doch der Rosthauch fand keine Nahrung." + "$unit($mage) puts a spell of rust on $unit($target), but it shows no effect." + + + + + + + + "$unit($mage) legt einen Rosthauch auf $unit($target). $int($amount) Waffen wurden vom Rost zerfressen." + "$unit($mage) puts a spell of rust on $unit($target). $int($amount) weapons are eaten by rust." + + + + + + + $if($isnull($mage),"Ein unentdeckter Magier",$unit($mage)) erschuf einen heiligen Hain von $int($amount) Schößlingen. + $if($isnull($mage),"An unknown magician ",$unit($mage)) created a holy forest of $int($amount) young trees. + + + + + + "$if($isnull($mage),"Ein unentdeckter Magier",$unit($mage)) segnet in einem kurzen Ritual die Felder." + "$if($isnull($mage),"an unseen magician",$unit($mage)) blesses the fields in a short ritual." + + + + + + "$unit($mage) beschwört die Mächte des Wassers und ein gigantischer Strudel bildet sich." + "$unit($mage) summons the power of the seas and a giant maelstrom forms." + + + + + + + "$unit($mage) belebt $int($amount) Bäume." + "$unit($mage) revives $int($amount) trees." + + + + + + + "$unit($mage) sorgt für trockene Straßen in $region($region)." + "$unit($mage) creates dry and well-repaired roads in $region($region)." + + + + + + + "$unit($mage) erfleht den Segen der Götter des Windes und des Wassers für $ship($ship)." + "$unit($mage) asks the gods of wind and water on behalf of the $ship($ship)." + + + + + + + + "$unit($unit) transferiert $int($aura) Aura auf $unit($target)." + "$unit($unit) transfers $int($aura) Aura to $unit($target)." + "$unit($unit) transfers $int($aura) Aura to $unit($target)." + + + + + + + + "$unit($mage) entzieht $unit($target) $int($aura) Aura." + "$unit($mage) draws $int($aura) aura from $unit($target)." + + + + + + + "$unit($unit) fühlt seine magischen Kräfte schwinden und verliert $int($aura) Aura." + "$unit($unit) feels the powers of magic fade and loses $int($aura) aura." + + + + + + "$unit($unit) fühlt sich einen Moment seltsam geschwächt." + "$unit($unit) fühlt strangely weakened." + + + + + + + "$unit($unit) konnte $unit($target) keine Aura entziehen." + "$unit($unit) could not draw aura from $unit($target)." + + + + + + + + "$unit($unit) wurde von $region($source) nach $unit($target) teleportiert." + "$unit($unit) was teleported from $region($source) to $unit($target)." + + + + + + + + + "$unit($mage) fand heraus, dass auf $ship($ship) der Zauber $curse($curse) liegt, der noch etwa $int($months) Wochen bestehen bleibt." + "$unit($mage) discovers that $ship($ship) is charmed with $curse($curse), which will last for, about $int($months) more weeks." + + + + + + + + + "$unit($mage) fand heraus, dass auf $building($building) der Zauber $curse($curse) liegt, der noch etwa $int($months) Wochen bestehen bleibt." + "$unit($mage) discovers that $building($building) is charmed with $curse($curse), which will last for, about $int($months) more weeks." + + + + + + + + + "$unit($mage) fand heraus, dass auf $unit($unit) der Zauber $curse($curse) liegt, der noch etwa $int($months) Wochen bestehen bleibt." + "$unit($mage) discovers that $unit($unit) is charmed with $curse($curse) that will last for about $int($months) more weeks." + + + + + + + + + "$unit($mage) fand heraus, dass auf $region($region) der Zauber $curse($curse) liegt, der noch etwa $int($months) Wochen bestehen bleibt." + "$unit($mage) discovers that $region($region) is charmed with $curse($curse), which will last for, about $int($months) more weeks." + + + + + + + + "$unit($mage) fand heraus, dass auf $ship($ship) der Zauber $curse($curse) liegt, dessen Kraft ausreicht, um noch Jahrhunderte bestehen zu bleiben." + "$unit($mage) discovers that $ship($ship) is charmed with $curse($curse), which will last for centuries." + + + + + + + + "$unit($mage) fand heraus, dass auf $building($building) der Zauber $curse($curse) liegt, dessen Kraft ausreicht, um noch Jahrhunderte bestehen zu bleiben." + "$unit($mage) discovers that $building($building) is charmed with $curse($curse), which will last for centuries." + + + + + + + + "$unit($mage) fand heraus, dass auf $unit($unit) der Zauber $curse($curse) liegt, dessen Kraft ausreicht, um noch Jahrhunderte bestehen zu bleiben." + "$unit($mage) discovers that $unit($unit) is charmed with $curse($curse), which will last for centuries." + + + + + + + + "$unit($mage) fand heraus, dass auf $region($region) der Zauber $curse($curse) liegt, dessen Kraft ausreicht, um noch Jahrhunderte bestehen zu bleiben." + "$unit($mage) discovers that $region($region) is charmed with $curse($curse), which will last for centuries." + + + + + + + "$unit($mage) meint, dass auf $ship($ship) ein Zauber liegt, konnte aber über den Zauber nichts herausfinden." + "It appears to $unit($mage) that $ship($ship) is charmed, but no details have been revealed." + "It appears to $unit($mage) that $ship($ship) is charmed, but no details have been revealed." + + + + + + + "$unit($mage) meint, dass auf $building($building) ein Zauber liegt, konnte aber über den Zauber nichts herausfinden." + "It appears to $unit($mage) that $building($building) is charmed, but no details have been revealed." + "It appears to $unit($mage) that $building($building) is charmed, but no details have been revealed." + + + + + + + "$unit($mage) meint, dass $unit($unit) verzaubert ist, konnte aber über den Zauber nichts herausfinden." + "It appears to $unit($mage) that $unit($unit) is charmed, but no details have been revealed." + "It appears to $unit($mage) that $unit($unit) is charmed, but no details have been revealed." + + + + + + + "$unit($mage) meint, dass auf $region($region) ein Zauber liegt, konnte aber über den Zauber nichts herausfinden." + "It appears to $unit($mage) that $region($region) is charmed, but no details have been revealed." + "It appears to $unit($mage) that $region($region) is charmed, but no details have been revealed." + + + + + + + "$unit($mage) meint, dass auf $ship($ship) kein Zauber liegt." + "It appears to $unit($mage) that $ship($ship) is not charmed." + "It appears to $unit($mage) that $ship($ship) is not charmed." + + + + + + + "$unit($mage) meint, dass auf $building($building) kein Zauber liegt." + "It appears to $unit($mage) that $building($building) is not charmed." + "It appears to $unit($mage) that $building($building) is not charmed." + + + + + + + "$unit($mage) meint, dass auf $unit($target) kein Zauber liegt." + "It appears to $unit($mage) that $unit($target) is not charmed." + "It appears to $unit($mage) that $unit($target) is not charmed." + + + + + + + "$unit($mage) meint, dass auf $region($region) kein Zauber liegt." + "It appears to $unit($mage) that $region($region) is not charmed." + "It appears to $unit($mage) that $region($region) is not charmed." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Region konnte nicht verzaubert werden." + "$unit($unit) in $region($region): '$order($command)' - The region could not be charmed." + "$unit($unit) in $region($region): '$order($command)' - The region could not be charmed." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $ship($ship) widersteht dem Zauber." + "$unit($unit) in $region($region): '$order($command)' - $ship($ship) resists the spell." + "$unit($unit) in $region($region): '$order($command)' - $ship($ship) resists the spell." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Gebäude $int36($id) konnte nicht verzaubert werden." + "$unit($unit) in $region($region): '$order($command)' - Building $int36($id) could not be charmed." + "$unit($unit) in $region($region): '$order($command)' - Building $int36($id) could not be charmed." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) widersteht dem Zauber." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) resists the spell." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) resists the spell." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Schiff $int36($id) wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - Ship $int36($id) could not be located." + "$unit($unit) in $region($region): '$order($command)' - Ship $int36($id) could not be located." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Gebäude $int36($id) wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - Building $int36($id) could not be located." + "$unit($unit) in $region($region): '$order($command)' - Building $int36($id) could not be located." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Einheit $id wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - Unit $id could not be located." + "$unit($unit) in $region($region): '$order($command)' - Unit $id could not be located." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es wurde kein Ziel gefunden." + "$unit($unit) in $region($region): '$order($command)' - The spell could not find a target." + + + + + + + "$unit($mage) erleidet durch den Tod seines Vertrauten einen Schock." + "$unit($mage) receives a shock when his familiar dies." + + + + + + + + "$unit($unit) schafft es nicht, genug Kraft aufzubringen, um $spell($spell) auf Stufe $int($level) zu zaubern." + "$unit($unit) cannot muster enough energy to cast $spell($spell) on level $int($level)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Für diesen Zauber fehlen noch $resources($list)." + "$unit($unit) in $region($region): '$order($command)' - Casting this spell requires an additional $resources($list)." + + + + + + + + "$unit($unit) hat nicht genügend Komponenten um $spell($spell) auf Stufe $int($level) zu zaubern." + "$unit($unit) has insufficient components to cast $spell($spell) on level $int($level)." + + + + + + + + "$unit($unit) unterläuft in $region($region) beim Zaubern von $spell($spell) ein Patzer." + "$unit($unit) fumbles while casting $spell($spell) in $region($region)." + + + + + + + + "Als $unit($unit) in $region($region) versucht, $spell($spell) zu zaubern, scheint plötzlich ein Beben durch die magische Essenz zu laufen und ein furchtbarer Sog versucht $unit($unit) in eine andere Dimension zu ziehen. Mit letzter Kraft gelingt es $unit($unit) sich zu retten." + "When $unit($unit) in $region($region) tries to cast $spell($spell), a sudden disturbance ripples through the magical realm and a terrible force attempts to drag the magician to another dimension. However, with a final effort of strength, $unit($unit) manages to save himself." + + + + + + + + "Als $unit($unit) in $region($region) versucht, $spell($spell) zu zaubern erhebt sich plötzlich ein dunkler Wind. Bizarre geisterhafte Gestalten kreisen um den Magier und scheinen sich von den magischen Energien des Zaubers zu ernähren. Mit letzter Kraft gelingt es $unit($unit) dennoch den Spruch zu zaubern." + "When $unit($unit) in $region($region) tries to cast $spell($spell), strong winds suddenly rise. Bizare ghostlike creatures circle around the magician and seem to be leeching his magical energy. However, with a final effort of strength, $unit($unit) manages to complete the spell." + + + + + + + + "Eine Botschaft von $unit.dative($unit) in $region($region): 'Ups! Quack, Quack!'" + "A message from $unit($unit) in $region($region): 'Oops! Croak, Croak!'" + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($mage) kann Zauber, die durch $unit($unit) gewirkt werden, nicht zusätzlich in die Ferne richten." + "$unit($unit) in $region($region): '$order($command)' - $unit($mage) cannot direct spells that are channeled through $unit($unit) into distant regions." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($mage) kann nicht genug Energie aufbringen, um diesen Spruch durch $unit($unit) zu wirken." + "$unit($unit) in $region($region): '$order($command)' - $unit($mage) cannot raise enough energy to channel the spell through $unit($unit)." + + + + + + + + "$unit($mage) ruft einen Vertrauten. $race($race, 0) können $skills lernen." + "$unit($mage) summons a familiar. $race($race, 0) can learn ${skills}." + + + + + + + + "$unit($unit) hat einen feuchtfröhlichen Abend in der Taverne verbracht. Ausser einem fürchterlichen Brummschädel ist da auch noch das dumme Gefühl $unit($mage) seine ganze Lebensgeschichte erzählt zu haben." + + + + + + + "$unit($unit) hat einen feuchtfröhlichen Abend in der + Taverne verbracht. Ausser einem fürchterlichen Brummschädel ist da auch + noch das dumme Gefühl die ganze Taverne mit seiner Lebensgeschichte + unterhalten zu haben." + + + + + + + + + "$unit($mage) gelingt es $unit($unit) zu verzaubern. $unit($unit) wird für etwa $int($duration) Wochen unseren Befehlen gehorchen." + + + + + + + + + "$unit($unit) gelingt es $spell($spell) zu zaubern, doch der Spruch zeigt keine Wirkung." + "$unit($unit) manages to cast $spell($spell), but the spell seems to have no effect." + + + + + + + + "$unit($unit) fühlt sich nach dem Zaubern von $spell($spell) viel erschöpfter als sonst und hat das Gefühl, dass alle weiteren Zauber deutlich mehr Kraft als normalerweise kosten werden." + "$unit($unit) feels far more exhausted than he should after casting $spell($spell) and assumes that any following spells will cost far more energy than usual." + + + + + + + "$unit($unit) in $region($region) hat rasende Kopfschmerzen und kann sich nicht mehr richtig konzentrieren. Irgendwas bei diesem Zauber ist fürchterlich schiefgelaufen." + "$unit($unit) in $region($region) is hit by a massive headacheand cannot concentrate on the spell. Some part of this ritual has gone very wrong indeed." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier verfängt sich in seinem eigenen Zauber." + + + "In der Region erstrahlen des Nachts bunte Lichter, Gloeckchen klingeln und frohes Kindergelaechter klingt durch den Wald." + "At night, colourful lights can be seen in this region, bells are a-ringing and the laughter of happy children seems to be everywhere in the forests." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Zauber von $unit.dative($unit) war viel zu schwach und löst sich gleich wieder auf." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) verzaubert ${target}." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) puts a spell on ${target}." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) gelingt es zwar die Region zu verzaubern, aber irgendwas ging schief." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) manages to put a spell on the region, but something went wrong nonetheless." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) gelingt es die Region zu verzaubern." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) puts a spell on the region." + + + + + + + "$unit($mage) erhöht die Körperkraft von $unit.dative($target) beträchtlich." + "$unit($mage) increases the strength of $unit($target) dramatically." + + + + + + + + "$unit($unit) in $region($region) regeneriert $int($amount) Aura." + "$unit($unit) régénère $int($amount) aura en $region($region)." + "$unit($unit) regenerates $int($amount) aura in $region($region)." + + + + + + "$string" + "$string" + + + + + + + + + "$unit($unit) in $region($region) verbraucht $int($cost) Silber für das Studium von $skill($skill)." + "$unit($unit) dépense $int($cost) écus en $region($region) pour apprendre $skill($skill)." + "$unit($unit) spends $int($cost) silver in $region($region) to study $skill($skill)." + + + + + + + "$unit($teacher) kann durch Dumpfbackenbrot nur $int($amount) Schüler lehren." + "Due to the effect of duncebuns, $unit($teacher) can only teach $int($amount) students." + + + + + + + + + "$unit($teacher) lehrt $unit($student) $skill($skill) auf Stufe $int($level)." + "$unit($teacher) teaches $unit($student) $skill($skill) to level $int($level)." + "$unit($teacher) teaches $unit($student) $skill($skill) to level $int($level)." + + + + + + + + "$unit($teacher) lehrt $unit($student) $skill($skill)." + "$unit($teacher) teaches $unit($student) $skill($skill)." + "$unit($teacher) teaches $unit($student) $skill($skill)." + + + + + + "$string" + "$string" + + + + + + + + "$unit($unit) verkauft $int($amount) $resource($resource,$amount)." + "$unit($unit) sells $int($amount) $resource($resource,$amount)." + "$unit($unit) sells $int($amount) $resource($resource,$amount)." + + + + + + + + "$unit($unit) kauft $int($amount) $resource($resource,$amount)." + "$unit($unit) buys $int($amount) $resource($resource,$amount)." + "$unit($unit) buys $int($amount) $resource($resource,$amount)." + + + + + + + "$unit($unit) bezahlt $int($money) Silber für den Kauf von Luxusgütern." + "$unit($unit) pays $int($money) silver for luxury items." + "$unit($unit) pays $int($money) silver for luxury items." + + + + + + + + "$unit($unit) verdient in $region($region) $int($amount) Silber durch den Verkauf von Luxusgütern." + "$unit($unit) earned $int($amount) silver in $region($region) by selling luxury items." + "$unit($unit) earned $int($amount) silver in $region($region) by selling luxury items." + + + + + + + + + "$unit($unit) arbeitet in $region($region) für einen Lohn von $int($amount)$if($eq($wanted,$amount),""," statt $int($wanted)") Silber." + "$unit($unit) works in $region($region) for a wage of $int($amount) $if($eq($wanted,$amount),""," out of $int($wanted)") silver." + "$unit($unit) works in $region($region) for a wage of $int($amount) $if($eq($wanted,$amount),""," out of $int($wanted)") silver." + + + + + + + + "$unit($unit) arbeitet in $region($region) für einen Lohn von $int($amount) Silber." + "In $region($region), $unit($unit) works for a wage of $int($amount) silver." + + + + + + + + + "$unit($unit) verdient in $region($region) $int($amount)$if($eq($wanted,$amount),""," statt $int($wanted)") Silber durch Unterhaltung." + "In $region($region), $unit($unit) earns only $int($amount) instead of$if($eq($wanted,$amount),""," of$if($eq($wanted,$amount),""," of $int($wanted)") ") with entertainment." + + + + + + + + "$unit($unit) fängt in $region($region) Fische im Wert von $int($amount) Silber." + "In $region($region), $unit($unit) catches fish worth $int($amount) silver." + + + + + + + + "$unit($unit) verdient in $region($region) $int($amount) Silber durch Unterhaltung." + "$unit($unit) earns $int($amount) in $region($region) with entertainment." + "$unit($unit) earns $int($amount) in $region($region) with entertainment." + + + + + + + + + "$unit($unit) verdient in $region($region) $int($amount)$if($eq($wanted,$amount),""," statt $int($wanted)") Silber durch Zauberei." + + + + + + + + "$unit($unit) verdient in $region($region) $int($amount) Silber durch Zauberei." + "$unit($unit) earns $int($amount) silver through simple magical services in $region($region)." + + + + + + + + + "$unit($unit) klaut in $region($region) $int($amount)$if($eq($wanted,$amount),""," statt $int($wanted)") Silber." + "$unit($unit) steals only $int($amount) silver instead of$if($eq($wanted,$amount),""," of$if($eq($wanted,$amount),""," of $int($wanted)") ") in $region($region)." + "$unit($unit) steals only $int($amount) silver instead of$if($eq($wanted,$amount),""," of$if($eq($wanted,$amount),""," of $int($wanted)") ") in $region($region)." + + + + + + + + "$unit($unit) klaut in $region($region) $int($amount) Silber." + "$unit($unit) steals $int($amount) silver in $region($region)." + + + + + + + + + "$unit($unit) treibt in $region($region) Steuern in Höhe von $int($amount)$if($eq($wanted,$amount),""," statt $int($wanted)") Silber ein." + "$unit($unit) collects taxes of only $int($amount) instead of$if($eq($wanted,$amount),""," of$if($eq($wanted,$amount),""," of $int($wanted)") ") silver in $region($region)." + + + + + + + + "$unit($unit) treibt in $region($region) Steuern in Höhe von $int($amount) Silber ein." + "$unit($unit) collects taxes of $int($amount) silver in $region($region)." + + + + + + + + + + "$unit($unit) verdient$if($eq($mode,4)," am Handel","") in $region($region) $int($amount)$if($eq($wanted,$amount),""," statt $int($wanted)") Silber$if($eq($mode,1)," durch Unterhaltung",$if($eq($mode,2)," durch Steuern",$if($eq($mode,3)," durch Handel",$if($eq($mode,5)," durch Diebstahl",$if($eq($mode,6)," durch Zauberei","")))))." + "$unit($unit) earns $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") in $region($region)." + "$unit($unit) earns $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") in $region($region)." + + + + + + + + + "$unit($unit) in $region($region) findet $int($amount) $resource($herb,$amount)." + "$unit($unit) in $region($region) finds $int($amount) $resource($herb,$amount)." + "$unit($unit) in $region($region) finds $int($amount) $resource($herb,$amount)." + + + + + + + "$unit($unit) züchtet $int($amount) Pferde." + "$unit($unit) breeds $int($amount) horses." + "$unit($unit) breeds $int($amount) horses." + + + + + + + + + "$unit($unit) pflanzt in $region($region) $int($amount) $resource($herb,$amount)." + "$unit($unit) plants $int($amount) $resource($herb,$amount) in $region($region)." + "$unit($unit) plants $int($amount) $resource($herb,$amount) in $region($region)." + + + + + + + "$unit($unit) in $region($region) entdeckt eine Laenader." + "$unit($unit) discovers laen in $region($region)." + + + + + + "Die Laenader in $region($region) ist erschöpft." + "There is no more laen in $region($region)." + + + + + + + + "$unit($unit) in $region($region) hat ein zu niedriges Talent, um $resource($resource,0) abzubauen." + "$unit($unit) in $region($region) is not proficient enough to produce $resource($resource,0)." + "$unit($unit) in $region($region) is not proficient enough to produce $resource($resource,0)." + + + + + + + + + + "$unit($unit) in $region($region) produziert $int($amount)$if($eq($wanted,$amount),""," von $int($wanted)") $resource($resource,$wanted)." + "$unit($unit) in $region($region) produces $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") $resource($resource,$amount)." + "$unit($unit) in $region($region) produces $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") $resource($resource,$amount)." + + + + + + + + + + "$unit($unit) in $region($region) produziert $int($amount)$if($eq($wanted,$amount),""," von $int($wanted)") $resource($resource,$wanted)." + "$unit($unit) in $region($region) produces $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") $resource($resource,$amount)." + "$unit($unit) in $region($region) produces $int($amount)$if($eq($wanted,$amount),""," of $int($wanted)") $resource($resource,$amount)." + + + + + + + + "$unit($unit) baut für $int($size) an $building($building) weiter." + "$unit($unit) builds $int($size) more on $building($building)." + "$unit($unit) builds $int($size) more on $building($building)." + + + + + + + + "$unit($unit) baut für $int($size) an $ship($ship) weiter." + "$unit($unit) builds $int($size) more on $ship($ship)." + "$unit($unit) builds $int($size) more on $ship($ship)." + + + + + + "$string" + "$string" + + + + + + + "$unit($unit) stirbt beim Versuch, die Feuerwand nach $region($region) zu durchqueren." + "$unit($unit) dies trying to cross the wall of fire into $region($region)." + "$unit($unit) dies trying to cross the wall of fire into $region($region)." + + + + + + + "$unit($unit) erleidet beim Durchqueren der Feuerwand nach $region($region) schwere Verbrennungen." + "$unit($unit) steps through the wall of fire into $region($region) and receives severe burn damage." + "$unit($unit) steps through the wall of fire into $region($region) and receives severe burn damage." + + + + + + + + + "$unit($unit) transportiert $unit($target) von $region($start) nach $region($end)." + "$unit($unit) transported $unit($target) from $region($start) to $region($end)." + "$unit($unit) transported $unit($target) from $region($start) to $region($end)." + + + + + + + + + + "$unit($unit) $if($eq($mode,1),"reitet", "wandert") von $region($start) nach $region($end).$if($isnull($regions),""," Dabei wurde $trail($regions) durchquert.")" + "$unit($unit) $if($eq($mode,1),"chevauche", "marche") de $region($start) vers $region($end) trans $trail($regions)" + "$unit($unit) $if($eq($mode,1),"rides", "walks") from $region($start) to $region($end)$if($isnull($regions),""," by way of $trail($regions)")." + + + + + + + "$unit($unit) entdeckt dass im $direction($direction) $terrain($region) ist." + "$unit($unit) discovered that an ocean lies in the $direction($direction)." + "$unit($unit) discovered that an ocean lies in the $direction($direction)." + + + + + + + "$unit($unit) entdeckt, dass $region($region) $terrain($region) ist." + "$unit($unit) discovered that $region($region) is $terrain($region)." + "$unit($unit) discovered that $region($region) is $terrain($region)." + + + + + + + "$unit($unit) ist in dieser Runde gelandet und kann nicht weiter ins Landesinnere nach $region($region) vorstossen." + "$unit($unit) has just landed and cannot continue moving to $region($region)." + "$unit($unit) has just landed and cannot continue moving to $region($region)." + + + + + + + "Die Mannschaft der $ship($ship) kann in letzter Sekunde verhindern, dass das Schiff in $region($region) auf Land aufläuft." + "In the very last moment, the crew of the $ship($ship) saved the ship from grounding in $region($region)." + "In the very last moment, the crew of the $ship($ship) saved the ship from grounding in $region($region)." + + + + + + + "Die $ship($ship) konnte in $region($region) nicht einreisen, die Küste ist zu gefährlich für das Schiff." + "The $ship($ship) could not berth in $region($region). The coast is too dangerous for the vessel." + "The $ship($ship) could not berth in $region($region). The coast is too dangerous for the vessel." + + + + + + + "Die Mannschaft der $ship($ship) weigert sich, nach $direction($direction) zu reisen." + " The crew of the $ship($ship) refuses to travel to the$direction($direction)." + " The crew of the $ship($ship) refuses to travel to the$direction($direction)." + + + + + + + "Die Mannschaft der $ship($ship) weigert sich, nach $region($region) zu reisen." + "The crew of the $ship($ship) refuses to travel to $region($region)." + "The crew of the $ship($ship) refuses to travel to $region($region)." + + + + + + + "$unit($unit) weigert sich, nach $direction($direction) zu reisen." + "$unit($unit) refuses to travel to the$direction($direction)." + "$unit($unit) refuses to travel to the$direction($direction)." + + + + + + + "$unit($unit) weigert sich, nach $region($region) zu reisen." + "$unit($unit) refuses to travel to $region($region)." + "$unit($unit) refuses to travel to $region($region)." + + + + + + + "Die $ship($ship) konnte $region($region) nicht verlassen." + "The $ship($ship) could not leave $region($region)." + "The $ship($ship) could not leave $region($region)." + + + + + + + + "$unit($unit) wurde in $region($region) von $unit.dative($guard) aufgehalten." + "$unit($unit) was kept in $region($region) by $unit($guard)." + "$unit($unit) was kept in $region($region) by $unit($guard)." + + + + + + "Wir haben den Krieg mit $faction($faction) beendet." + "We declared peace with $faction($faction)." + "We declared peace with $faction($faction)." + + + + + + "$faction($faction) hat den Krieg mit uns beendet." + "$faction($faction) has declared peace with us." + "$faction($faction) has declared peace with us." + + + + + + "Wir haben $faction($faction) den Krieg erklärt." + "We declared war on $faction($faction)." + "We declared war on $faction($faction)." + + + + + + "$faction($faction) hat uns den Krieg erklärt." + "$faction($faction) has declared war on us." + "$faction($faction) has declared war on us." + + + + + + + + "$unit($unit) konnte nicht von $region($region) nach $region($target) reisen, da der Besitzer der Region es verhinderte." + "$unit($unit) could not travel from $region($region) to $region($target) because the owner denied entrance." + + + + + + + "$unit($unit) konnte aus $region($region) nicht ausreisen." + "$unit($unit) could not leave $region($region)." + "$unit($unit) could not leave $region($region)." + + + + + + + "$unit($follower) ist $unit($unit) gefolgt." + "$unit($follower) followed $unit($unit)." + "$unit($follower) followed $unit($unit)." + + + + + + + "$unit($follower) konnte $unit($unit) nicht folgen." + "$unit($follower) could not follow $unit($unit)." + "$unit($follower) could not follow $unit($unit)." + + + + + + + "$unit($unit) entdeckt, dass es keinen Weg nach $direction($direction) gibt." + "$unit($unit) discovers that there is no route going $direction($direction)." + "$unit($unit) discovers that there is no route going $direction($direction)." + + + + + + + + "$unit($unit) konnte von $region($region) nicht nach $direction($direction) ausreisen, der Nebel war zu dicht." + "$unit($unit) could not travel $direction($direction) from $region($region), the fog was too dense." + + + + + + "$string" + "$string" + + + + + + "In $region($region) erschienen die Herren der Bäume." + "In $region($region), the lords of the trees have risen." + "In $region($region), the lords of the trees have risen." + + + + + + "In $region($region) erhoben sich die Toten aus den Gräbern." + "The dead rise from their graves in $region($region)." + + + + + + + + "$unit($unit) vermehrt sich um $int($amount) $race($race,$amount)." + "$unit($unit) breeds $int($amount) new $race($race,$amount)." + "$unit($unit) breeds $int($amount) new $race($race,$amount)." + + + + + "Die Partei bekommt einen Spitznamen." + "Your faction received a nickname." + + + + + + + "Die Partei bekommt von $unit.dative($unit) in $region($region) einen Spitznamen." + "Your faction received a nickname from $unit($unit)." + + + + + + + "$building($building) in $region($region) bekommt einen Spitznamen." + "$building($building) in $region($region) received a nickname." + + + + + + + + "$building($building) in $region($region) bekommt von $unit.dative($renamer) einen Spitznamen." + "$building($building) in $region($region) received a nickname from $unit($renamer)." + + + + + + + "Die $ship($ship) in $region($region) bekommt einen Spitznamen." + "$ship($ship) in $region($region) received a nickname." + + + + + + + + "Die $ship($ship) in $region($region) bekommt von $unit.dative($renamer) einen Spitznamen." + "$ship($ship) in $region($region) received a nickname from $unit($renamer)." + + + + + + + "$unit($renamed) in $region($region) bekommt einen Spitznamen." + "$unit($renamed) in $region($region) received a nickname." + + + + + + + + "$unit($renamed) in $region($region) bekommt von $unit.dative($renamer) einen Spitznamen." + "$unit($renamed) in $region($region) received a nickname from $unit($renamer)." + + + + + + "$if($eq($dead,1),"Ein Bauer","$int($dead) Bauern") verhungert." + "$if($eq($dead,1),"One peasant starves","$int($dead) peasants starve")." + "$if($eq($dead,1),"One peasant starves","$int($dead) peasants starve")." + + + + + + "Der Vulkan in $region($region) bricht aus." + "The volcano in $region($region) breaks out." + "The volcano in $region($region) breaks out." + + + + + + + "Der Vulkan in $region($regionv) bricht aus. Die Lavamassen verwüsten $region($regionn)." + "The volcano in $region($regionv) breaks out. The lava devastates $region($regionn)." + "The volcano in $region($regionv) breaks out. The lava devastates $region($regionn)." + + + + + + + + "Beim Vulkanausbruch in $region($region) sterben $int($dead) Personen in $unit($unit)." + "$int($dead) people in $unit($unit) perisch when the volcano in $region($region) breaks out." + "$int($dead) people in $unit($unit) perisch when the volcano in $region($region) breaks out." + + + + + + "Aus dem Vulkankrater von $region($region) steigt kein Rauch mehr." + "The volcano of $region($region) stops releasing smoke." + "The volcano of $region($region) stops releasing smoke." + + + + + + "Aus dem Vulkankrater von $region($region) steigt plötzlich Rauch." + "Columns of smoke are released by the volcano of $region($region)." + "Columns of smoke are released by the volcano of $region($region)." + + + + + + + "$unit($unit) in $region($region) desertiert." + "$unit($unit) in $region($region) abandons your cause." + "$unit($unit) in $region($region) abandons your cause." + + + + + + + + "$unit($unit) reißt die Straße zwischen $region($from) und $region($to) ein." + "$unit($unit) demolishes the road between $region($from) and $region($to)." + "$unit($unit) demolishes the road between $region($from) and $region($to)." + + + + + + + "$unit($unit) in $region($region) kann keine Kräuter finden." + "$unit($unit) could not find any herbs in $region($region)." + "$unit($unit) could not find any herbs in $region($region)." + + + + + + + + + "$unit($unit) in $region($region) stellt fest, dass es hier $localize($amount) $resource($herb,0) gibt." + "$unit($unit) discovers that $localize($amount) $resource($herb,0) grow in $region($region)." + + + + + + + "$unit($unit) reißt einen Teil von $building($building) ein." + "$unit($unit) tears down parts of $building($building)." + + + + + + + "$unit($unit) zerstört $building($building)." + "$unit($unit) destroys $building($building)." + "$unit($unit) destroys $building($building)." + + + + + + + + "$unit($unit) erweitert in $region($region) das Straßennetz um $int($size)." + "$unit($unit) extends the road network in $region($region) by $int($size)." + "$unit($unit) extends the road network in $region($region) by $int($size)." + + + + + + + + "$unit($unit) $if($eq($amount,1),"schließt","schließen") sich $int($amount) $resource($rtype,$amount) an." + "$int($amount) $resource($rtype,$amount) $if($eq($amount,1),"joins","join") $unit($unit)." + + + + + + + "$unit($mage) legt einen Schleier um die Ausrüstung von $unit.dative($target)." + "$unit($mage) shrouds the equipment of $unit($target) in shadows." + + + + + + + + "Die $ship($ship) in $region($region) entdeckt ein Opfer im $direction($dir)." + "The $ship($ship) in $region($region) made $direction($dir) a target." + "The $ship($ship) in $region($region) made $direction($dir) a target." + + + + + + + "Die $ship($ship) in $region($region) kann keine Schiffe aufbringen." + "The $ship($ship) could not capture other ships in $region($region)." + "The $ship($ship) could not capture other ships in $region($region)." + + + + + + "Langsam kehren andere Völker nach $region($region) zurück." + "Little by little, people return to $region($region)." + "Little by little, people return to $region($region)." + + + + + + "Vor den vielen Orks in $region($region) fliehen die anderen Einwohner." + "People $region($region) flee from an Orc superiority." + "People $region($region) flee from an Orc superiority." + + + + + + + + "$unit($unit) in $region($region) beschädigt die $ship($ship)." + "$unit($unit) in $region($region) damages the $ship($ship)." + + + + + + + + "$unit($unit) in $region($region) versenkt die $ship($ship)." + "$unit($unit) sunk $ship($ship) in $region($region)." + "$unit($unit) sunk $ship($ship) in $region($region)." + + + + + + "$unit($unit) marschiert in eine Antimagiezone und löst sich auf." + "$unit($unit) walks into an antimagical zone and dissolves." + + + + + + "$unit($unit) hat sich unbemerkt verflüchtigt." + "$unit($unit) has dissolved without a trace." + "$unit($unit) has dissolved without a trace." + + + + + + "$unit($unit) wird sich bald verflüchtigen." + "$unit($unit) will dissolve soon." + "$unit($unit) will dissolve soon." + + + + + + + "$int($amount) Bauern flohen aus Furcht vor $unit($unit)." + "$int($amount) peasants fled in fear of $unit($unit)." + "$int($amount) peasants fled in fear of $unit($unit)." + + + + + + + + "$int($amount) Bauern werden zu $race($race,0) und schliessen sich $unit($unit) an." + "$int($amount) peasants become $race($race,0) and join the ranks of $unit($unit)." + + + + + + + "$unit($unit) verspeiste $int($amount) Pferde." + "$unit($unit) ate $int($amount) horses." + + + + + + + "$unit($unit) verspeiste $int($amount) Bauern." + "$unit($unit) ate $int($amount) peasants." + "$unit($unit) ate $int($amount) peasants." + + + + + + + "ERESSEA $int36($faction) \"${password}\" - Deine Befehle hatten ein falsches Passwort." + "ERESSEA $int36($faction) \"${password}\" - Your orders had the wrong password." + + + + + + "Das Passwort für diese Partei lautet ${value}." + "Le mot de passe de cette faction est '${value}'" + "The password of this faction is '$value'." + + + + + + "Die Reportadresse wurde nicht geändert, '${value}' ist keine gültige email." + " Address not changed, '$value' is an invalid email." + " Address not changed, '$value' is an invalid email." + + + + + + "Die Reportadresse wurde auf ${value} geändert." + " Address has been changed to '$value'." + " Address has been changed to '$value'." + + + + + + "Das Banner wurde auf '$value' geändert." + "Banner has been changed to '$value'." + "Banner has been changed to '$value'." + + + + + + + + + "Eine Partei muß mindestens $int($turns) Wochen alt sein, bevor sie angegriffen oder bestohlen werden kann." + "A faction must be at least $int($turns) weeks old before it can be attacked or stolen from." + + + + + + + + "$unit($unit) wurden in $region($region) $int($amount) Silberstücke geklaut." + "In $region($region), thieves stole $int($amount) silver from $unit($unit)." + "In $region($region), thieves stole $int($amount) silver from $unit($unit)." + + + + + + + "$unit($target) ertappte $unit($unit) beim versuchten Diebstahl." + "$unit($target) caught $unit($unit) in attempted theft." + "$unit($target) caught $unit($unit) in attempted theft." + + + + + + + "$unit($unit) wurde von $unit.dative($target) beim versuchten Diebstahl ertappt." + "$unit($unit) was caught by $unit($target) in attempted theft." + "$unit($unit) was caught by $unit($target) in attempted theft." + + + + + + "$unit($unit) fühlt sich beobachtet." + "$unit($unit) feels watched." + "$unit($unit) feels watched." + + + + + + + "$unit($unit) gelang es nicht, sich nahe genug an $unit($target) heranzuschleichen." + "$unit($unit) could not sneak close enough to $unit($target)." + "$unit($unit) could not sneak close enough to $unit($target)." + + + + + + + "$unit($spy) gelang es nicht, etwas über $unit($target) herauszufinden." + "$unit($spy) could not find out anything about $unit($target)." + "$unit($spy) could not find out anything about $unit($target)." + + + + + + + + "$unit($spy) gelang es, Informationen über $unit($target) ($status) herauszubekommen." + "$unit($spy) managed to gather information about $unit($target)." + + + + + + + "$unit($target) ist ein $type-Magier." + "$unit($target) is a $type-magician" + + + + + + + "$unit($target) beherrscht ${skills}." + "$unit($target) has the skills ${skills}." + + + + + + + "Im Gepäck von $unit($target) sind $resources($items)." + "$unit($target) carries $resources($items)" + + + + + + + + "$unit($target) gehört der Partei $faction($faction) an." + "$unit($target) belongs to $faction($faction)." + + + + + + + "$unit($target) fühlt sich $if($isnull($spy),"","durch $unit($spy) ")beobachtet." + "$unit($target) feels watched by $unit($spy)." + "$unit($target) feels watched by $unit($spy)." + + + + + + + + "$faction($from) gibt ein Almosen von $int($amount) Silber an $faction($to)." + "$faction($from) donates $int($amount) silver to $faction($to)." + "$faction($from) donates $int($amount) silver to $faction($to)." + + + + + + + + "$unit($unit) vergißt durch Dumpfbackenbrot $int($weeks) Wochen des Talentes $skill($skill)." + "$unit($unit) eats a Dumpfbackenbrot and forgets $int($weeks) weeks worth of $skill($skill)." + "$unit($unit) eats a Dumpfbackenbrot and forgets $int($weeks) weeks worth of $skill($skill)." + + + + + + + "$unit($unit) in $region($region) wird durch unzureichende Nahrung geschwächt." + "$unit($unit) is weakened due to malnourishment." + "$unit($unit) is weakened due to malnourishment." + + + + + + + + + "$unit($unit) verliert in $region($region) $int($dead) von $int($add($live,$dead)) Personen durch Unterernährung." + "$unit($unit) loses $int($dead) of $int($add($live,$dead)) people due to starvation in $region($region)." + "$unit($unit) loses $int($dead) of $int($add($live,$dead)) people due to starvation in $region($region)." + + + + + + + + "$unit($unit): '$order($command)' - Die Einheit benutzt bereits $resource($using,0)." + "$unit($unit): '$order($command)' - The unit already uses $resource($using,0)." + "$unit($unit): '$order($command)' - The unit already uses $resource($using,0)." + + + + + + "Die $ship($ship) ist zu stark beschädigt und sinkt." + "The $ship($ship) suffers too heavy damage and sinks." + "The $ship($ship) suffers too heavy damage and sinks." + + + + + + + "Die $ship($ship) entdeckt, dass $region($region) Festland ist." + "The $ship($ship) discovers that $region($region) has no shore." + + + + + + + + "Die $ship($ship) fliegt von $region($from) nach $region($to)." + "The $ship($ship) flies from $region($from) to $region($to)." + + + + + + + + "Die $ship($ship) segelt von $region($from) nach $region($to)." + "The $ship($ship) sails from $region($from) to $region($to)." + + + + + + + + "Die $ship($ship) wird in $region($region) von Stürmen abgetrieben$if($sink," und sinkt","")." + "The $ship($ship) in $region($region) drifts in heavy storm$if($sink," and sinks","")." + "The $ship($ship) in $region($region) drifts in heavy storm$if($sink," and sinks","")." + + + + + + + + + "Die $ship($ship) fährt in den Mahlstrom von $region($region) und nimmt $int($damage) Schaden$if($sink," und sinkt","")." + "The $ship($ship) sails into the maelstrom of $region($region) and takes $int($damage) damage$if($sink,". The ship sinks","")." + + + + + + + "$unit($unit) vergißt $skill($skill)." + "$unit($unit) forgets $skill($skill)." + "$unit($unit) forgets $skill($skill)." + + + + + + + "$unit($unit) gibt das Kommando an $unit($recipient)." + "$unit($unit) gave control to $unit($recipient)." + "$unit($unit) gave control to $unit($recipient)." + + + + + + + + "$unit($unit) gibt $int($amount) Dumpfbackenbrot an $unit($recipient)." + "$unit($unit) administers $int($amount) duncebuns to $unit($recipient)." + + + + + + + + + "$unit($unit) in $region($region) rekrutiert $int($amount) von $int($want) Personen." + "$unit($unit) in $region($region) recruits $int($amount) $int($want) people." + "$unit($unit) in $region($region) recruits $int($amount) $int($want) people." + + + + + + + + "$unit($unit) belagert $building($building). Dabei richten die Katapulte Zerstörungen von $int($destruction) Größenpunkten an." + "$building($building) is under siege by $unit($unit). During siege, catapults caused $int($destruction) points destruction." + "$building($building) is under siege by $unit($unit). During siege, catapults caused $int($destruction) points destruction." + + + + + + + "$unit($unit) belagert $building($building)." + "$building($building) is under siege by $unit($unit)." + + + + + + + + "$unit($unit) ertrinkt beim Untergang der $ship($ship) in $region($region)." + "$unit($unit) drowns when $ship($ship) in $region($region) sinks." + "$unit($unit) drowns when $ship($ship) in $region($region) sinks." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann nicht bewachen, da sie versucht zu fliehen." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot guard the region because it's trying to flee." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot guard the region because it's trying to flee." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann den Befehl in dieser Runde nicht ausführen, da sie an einem Kampf teilgenommen hat." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot execute this command because it has been in combat." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot execute this command because it has been in combat." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Gebäude kann nur einmal pro Runde erweitert werden." + "$unit($unit) in $region($region): '$order($command)' - The building can be expanded only once per turn." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Objekt ist unzerstörbar." + "$unit($unit) in $region($region): '$order($command)' - This object is indestructible." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ohne Zutaten kann ein Alchemist nichts herstellen." + "$unit($unit) in $region($region): '$order($command)' - Ohne Zutaten kann ein Alchemist nichts herstellen." + "$unit($unit) in $region($region): '$order($command)' - Without ingredients an alchemist can not produce anything." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nicht alle Zutaten vorhanden." + "$unit($unit) in $region($region): '$order($command)' - Nicht alle Zutaten vorhanden." + "$unit($unit) in $region($region): '$order($command)' - Not all ingredients present." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Eine Partei kann nur einmal neu starten." + "$unit($unit) in $region($region): '$order($command)' - Restart can only be used once." + "$unit($unit) in $region($region): '$order($command)' - Restart can only be used once." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Werwesen können nicht arbeiten." + "$unit($unit) in $region($region): '$order($command)' - Lycantropes don't work." + "$unit($unit) in $region($region): '$order($command)' - Lycantropes don't work." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Werwesen können nicht mit anderen Personen gemischt werden." + "$unit($unit) in $region($region): '$order($command)' - Lycantropes may not be mixed with normal people." + "$unit($unit) in $region($region): '$order($command)' - Lycantropes may not be mixed with normal people." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann sich nicht verwandeln." + "$unit($unit) in $region($region): '$order($command)' - This unit can not change shape." + "$unit($unit) in $region($region): '$order($command)' - This unit can not change shape." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diese Einheit ist kein Werwesen." + "$unit($unit) in $region($region): '$order($command)' - This unit is not in lycantropic form." + "$unit($unit) in $region($region): '$order($command)' - This unit is not in lycantropic form." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diese Einheit ist schon ein Werwesen." + "$unit($unit) in $region($region): '$order($command)' - This unit already assumed lycantropic form." + "$unit($unit) in $region($region): '$order($command)' - This unit already assumed lycantropic form." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Talent kann nicht höher gelernt werden." + "$unit($unit) in $region($region): '$order($command)' - This skill cannot be raised any higher." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Snotlinge sind zu dumm, um auf den Feldern zu arbeiten." + "$unit($unit) in $region($region): '$order($command)' - We snotlings is too stupid fer dat!" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei muß mindestens 9 Wochen alt sein, um einen Neustart zu versuchen." + "$unit($unit) in $region($region): '$order($command)' - Your faction is not old enough to start over." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Optionen ZIP und BZIP2 können nur um, nicht ausgeschaltet werden." + "$unit($unit) in $region($region): '$order($command)' - options ZIP and BZIP2 can only be switched, not turned off." + "$unit($unit) in $region($region): '$order($command)' - options ZIP and BZIP2 can only be switched, not turned off." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Einheiten einer Partei, die noch immun gegen Angriffe ist, dürfen nicht bewachen." + "$unit($unit) in $region($region): '$order($command)' - units of a faction that can't be attacked may not guard." + "$unit($unit) in $region($region): '$order($command)' - units of a faction that can't be attacked may not guard." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In dieser Region kann man nichts verkaufen." + "$unit($unit) in $region($region): '$order($command)' - there is no trade in this region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Bereits ein Synonym gesetzt." + "$unit($unit) in $region($region): '$order($command)' - synonym already set." + "$unit($unit) in $region($region): '$order($command)' - synonym already set." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Kein Synonym angegeben." + "$unit($unit) in $region($region): '$order($command)' - synonym missing." + "$unit($unit) in $region($region): '$order($command)' - synonym missing." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ungültiges Synonym." + "$unit($unit) in $region($region): '$order($command)' - invalid synonym." + "$unit($unit) in $region($region): '$order($command)' - invalid synonym." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ungültiges Prefix." + "$unit($unit) in $region($region): '$order($command)' - invalid prefix." + "$unit($unit) in $region($region): '$order($command)' - invalid prefix." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier hat bereits einen Klon." + "$unit($unit) in $region($region): '$order($command)' - The magician already has a clone." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Gebäude auf dem Ozean können nicht betreten werden." + "$unit($unit) in $region($region): '$order($command)' - Buildings on the ocean may not be entered." + "$unit($unit) in $region($region): '$order($command)' - Buildings on the ocean may not be entered." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier werden niemals Bäume wachsen." + "$unit($unit) in $region($region): '$order($command)' - Trees won't grow here." + "$unit($unit) in $region($region): '$order($command)' - Trees won't grow here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nur ein Magier kann einen Astralkristall benutzen." + "$unit($unit) in $region($region): '$order($command)' - Only mages may use an astralcrystal." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Verbände können nur zwischen Einheiten derselben Partei gebildet werden." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist in keinem Verband." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Eine Einheit kann nur in einem Verband Mitglied sein." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Wie sollen wir uns tarnen?" + "$unit($unit) in $region($region): '$order($command)' - What should we disguise us as?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Wieviel sollen wir einreißen?" + "$unit($unit) in $region($region): '$order($command)' - How much shall we tear down?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dorthin können wir die Einheit nicht transportieren." + "$unit($unit) in $region($region): '$order($command)' - We cannot transport this unit there." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit transportiert uns nicht." + "$unit($unit) in $region($region): '$order($command)' - the unit is not transporting us." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diese Einheit kennt keine Trankrezepte." + "$unit($unit) in $region($region): '$order($command)' - This unit knows no recipes for potions." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nur noch nicht gestärkte Untote können das Ziel dieses Zaubers sein." + "$unit($unit) in $region($region): '$order($command)' - Undead can only be affected once by this spell." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Passwort darf nur Buchstaben und Ziffern enthalten." + "$unit($unit) in $region($region): '$order($command)' - Your password may only contain alphanumeric symbols." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Gegen diese Rasse kann kein Jihad ausgerufen werden." + "$unit($unit) in $region($region): '$order($command)' - You cannot start a jihad against this race." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Gegen welche Rasse soll der Jihad ausgerufen werden?" + "$unit($unit) in $region($region): '$order($command)' - What race did you want the jihad to be against?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dazu muss erst die Spezialeigenschaft erworben werden." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Name und Beschreibung des Gebäudes können nicht geändert werden." + "$unit($unit) in $region($region): '$order($command)' - You cannot change the name and description of this building." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das kann die Einheit nicht." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man keine Schiffe bauen." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man keine Gebäude errichten." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann nicht unterrichten." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man nicht unterrichten." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Pferde müssen leider draußen bleiben." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man niemanden angreifen." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man niemanden bestehlen." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man nicht zaubern." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man nichts übergeben." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nur eine Einzelperson kann das Ticket benutzen." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Gegenstand funktioniert nur in der Eingangshalle." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Gegenstand funktioniert nur in der normalen Welt." + "$unit($unit) in $region($region): '$order($command)' - This item only works in the normal world." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Gut hat die Einheit nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have this good." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Gut wird hier produziert." + "$unit($unit) in $region($region): '$order($command)' - This good is not produced here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei kann keine weiteren Wyrme besitzen." + "$unit($unit) in $region($region): '$order($command)' - The faction cannot contain any more wyrms." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Vor den Besitzer eines Schiffes oder Gebäudes kann nicht sortiert werden." + "$unit($unit) in $region($region): '$order($command)' - You cannot sort before the owner of a ship or a building." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Besitzer eines Schiffes oder Gebäudes kann nicht neu sortiert werden." + "$unit($unit) in $region($region): '$order($command)' - The owner of a ship or a building cannot be sorted." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Befehl ist nur auf Einheiten innerhalb des selben Gebäudes oder Schiffes anwendbar." + "$unit($unit) in $region($region): '$order($command)' - That order only applies to units in the same building or ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Zieleinheit ist ungültig." + "$unit($unit) in $region($region): '$order($command)' - The target unit is invalid." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ungültiges Locale." + "$unit($unit) in $region($region): '$order($command)' - Invalid locale." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Um soetwas kann man nicht beten." + "$unit($unit) in $region($region): '$order($command)' - You cannot pray for this." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Soetwas kann man nicht opfern." + "$unit($unit) in $region($region): '$order($command)' - You cannot sacrifice this." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Auraangabe fehlerhaft oder zuwenig Aura." + "$unit($unit) in $region($region): '$order($command)' - Invalid aura specification or too little aura." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier ist nicht stark genug, sich den Göttern zu opfern." + "$unit($unit) in $region($region): '$order($command)' - This magician is not strong enough to be sacrificed to the gods." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Was und wieviel soll geopfert werden?" + "$unit($unit) in $region($region): '$order($command)' - What and how much should be sacrificed?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diese Kraft können selbst die Götter nicht mehr mächtiger machen." + "$unit($unit) in $region($region): '$order($command)' - Even the gods cannot improve this power." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nicht genug Karma." + "$unit($unit) in $region($region): '$order($command)' - Not enough karma." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff kann nicht aufs offene Meer hinaus segeln." + "$unit($unit) in $region($region): '$order($command)' - The ship cannot sail into the open seas." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei muß mindestens 10 Runden alt sein." + "$unit($unit) in $region($region): '$order($command)' - The faction has to be 10 turns old." + "$unit($unit) in $region($region): '$order($command)' - The faction has to be 10 turns old." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei hat schon einen Namen." + "$unit($unit) in $region($region): '$order($command)' - The faction is already named." + "$unit($unit) in $region($region): '$order($command)' - The faction is already named." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Gebäude hat schon einen Namen." + "$unit($unit) in $region($region): '$order($command)' - The building is already named." + "$unit($unit) in $region($region): '$order($command)' - The building is already named." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff hat schon einen Namen." + "$unit($unit) in $region($region): '$order($command)' - The ship is already named." + "$unit($unit) in $region($region): '$order($command)' - The ship is already named." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat schon einen Namen." + "$unit($unit) in $region($region): '$order($command)' - The unit is already named." + "$unit($unit) in $region($region): '$order($command)' - The unit is already named." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Keine gültige Rasse angegeben." + "$unit($unit) in $region($region): '$order($command)' - You did not specify a valid race." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit muß sich an Land befinden." + "$unit($unit) in $region($region): '$order($command)' - The unit must be on land." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $building($building) ist kein Steinkreis." + "$unit($unit) in $region($region): '$order($command)' - $building($building) is not a stonecircle." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $building($building) muss vor der Weihe fertiggestellt sein." + "$unit($unit) in $region($region): '$order($command)' - $building($building) has to be complete before it can be blessed." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In $region($target) sind keine Gräber." + "$unit($unit) in $region($region): '$order($command)' - There are no graves in $region($target)." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei muß mindestens 81 Wochen alt sein, um einen Neustart mit einer anderen Rasse zu versuchen." + "$unit($unit) in $region($region): '$order($command)' - The faction must be at least 81 weeks old to restart with a new race." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Soll eine Einheit oder ein Schiff verfolgt werden?" + "$unit($unit) in $region($region): '$order($command)' - Is this unit or ship supposed to be followed?" + "$unit($unit) in $region($region): '$order($command)' - Is this unit or ship supposed to be followed?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Soll eine Einheit oder ein Schiff eine neue Nummer bekommen?" + "$unit($unit) in $region($region): '$order($command)' - Is this unit or ship supposed to get a new number?" + "$unit($unit) in $region($region): '$order($command)' - Is this unit or ship supposed to get a new number?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier können nur Orks rekrutiert werden." + "$unit($unit) in $region($region): '$order($command)' - You can recruit only Orcs here." + "$unit($unit) in $region($region): '$order($command)' - You can recruit only Orcs here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Region befindet sich in Aufruhr." + "$unit($unit) in $region($region): '$order($command)' - There are riots in this region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Gebäude ist noch nicht fertig gebaut." + "$unit($unit) in $region($region): '$order($command)' - The building is not finished yet." + "$unit($unit) in $region($region): '$order($command)' - The building is not finished yet." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Für das Gebäude wurde noch kein Unterhalt bezahlt." + "$unit($unit) in $region($region): '$order($command)' - Maintenance has not been paid yet." + "$unit($unit) in $region($region): '$order($command)' - Maintenance has not been paid yet." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist mit Ausschiffen beschäftigt.." + "$unit($unit) in $region($region): '$order($command)' - The unit is busy disembarking." + "$unit($unit) in $region($region): '$order($command)' - The unit is busy disembarking." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Typ Einheit kann keine Schiffe betreten." + "$unit($unit) in $region($region): '$order($command)' - Swimmers cannot enter ships." + "$unit($unit) in $region($region): '$order($command)' - Swimmers cannot enter ships." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Typ Einheit kann keine Gebäude betreten." + "$unit($unit) in $region($region): '$order($command)' - this type of unit cannot enter a building." + "$unit($unit) in $region($region): '$order($command)' - this type of unit cannot enter a building." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit oder ihre Tiere würden dort nicht überleben." + "$unit($unit) in $region($region): '$order($command)' - The unit or its animals would not survive there." + "$unit($unit) in $region($region): '$order($command)' - The unit or its animals would not survive there." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dorthin kann die Einheit uns nicht transportieren." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot transport us to this place." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot transport us to this place." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $building($building) wird belagert." + "$unit($unit) in $region($region): '$order($command)' - $building($building) is under siege." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Eintritt in $building($building) wurde verwehrt." + "$unit($unit) in $region($region): '$order($command)' - Entrance to $building($building) was denied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ein Vertrauter wird beschworen, verschwindet jedoch wieder, als er keine Verbindung zu seinem Element herstellen kann." + "$unit($unit) in $region($region): '$order($command)' - A familiar is summoned, but disappears again when it cannot get in contact with its natural element." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nur normale Personen können Steuern eintreiben." + "$unit($unit) in $region($region): '$order($command)' - Only normal characters can collect taxes." + "$unit($unit) in $region($region): '$order($command)' - Only normal characters can collect taxes." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dafür braucht ein Einheit mindestens Kräuterkunde 7." + "$unit($unit) in $region($region): '$order($command)' - A skill of herbalism 7 or higher is required." + "$unit($unit) in $region($region): '$order($command)' - A skill of herbalism 7 or higher is required." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Einheiten in den hinteren Reihen können nicht angreifen." + "$unit($unit) in $region($region): '$order($command)' - Units cannot attack from the second row." + "$unit($unit) in $region($region): '$order($command)' - Units cannot attack from the second row." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - unbekannter Kampfstatus." + "$unit($unit) in $region($region): '$order($command)' - unknown combat status." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist nicht bewaffnet und kampffähig." + "$unit($unit) in $region($region): '$order($command)' - The unit is not armed and ready to fight." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei hat bereits ein Magiegebiet." + "$unit($unit) in $region($region): '$order($command)' - The faction has already chosen a magical school." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) können nicht arbeiten." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot work." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot work." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hungernde Soldaten kämpfen nicht." + "$unit($unit) in $region($region): '$order($command)' - Starving units do not fight." + "$unit($unit) in $region($region): '$order($command)' - Starving units do not fight." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hungernde Einheiten können nicht zaubern." + "$unit($unit) in $region($region): '$order($command)' - Starving units cannot cast spells." + "$unit($unit) in $region($region): '$order($command)' - Starving units cannot cast spells." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hungernde Einheiten können nicht bewachen." + "$unit($unit) in $region($region): '$order($command)' - Starving units cannot guard." + "$unit($unit) in $region($region): '$order($command)' - Starving units cannot guard." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Zeige alle was?" + "$unit($unit) in $region($region): '$order($command)' - Show all what?" + "$unit($unit) in $region($region): '$order($command)' - Show all what?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So etwas kann man hier nicht bauen." + "$unit($unit) in $region($region): '$order($command)' - You cannot build this here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Im astralen Nebel konnte niemand entdeckt werden." + "$unit($unit) in $region($region): '$order($command)' - Noone could be seen in the astral fog." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ein Zauber in dieser Region verhindert das." + "$unit($unit) in $region($region): '$order($command)' - There is an active spell in this region that prevents this." + "$unit($unit) in $region($region): '$order($command)' - There is an active spell in this region that prevents this." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber kann nur im Astralraum gezaubert werden." + "$unit($unit) in $region($region): '$order($command)' - This spell can only be cast in the astral plane." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier gibt es keine Verbindung zur astralen Welt." + "$unit($unit) in $region($region): '$order($command)' - There is no connection to the astral plane here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Von hier aus kann man die astrale Ebene nicht erreichen." + "$unit($unit) in $region($region): '$order($command)' - You cannot reach the astral plane from here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Einheit ist kein Magier." + "$unit($unit) in $region($region): '$order($command)' - Unit is not a magician." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Parameter nicht korrekt angegeben." + "$unit($unit) in $region($region): '$order($command)' - Incorrect parameter." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier befindet sich nicht auf einem Schiff." + "$unit($unit) in $region($region): '$order($command)' - The magician is not on board a ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Auf dem Schiff liegt bereits so ein Zauber." + "$unit($unit) in $region($region): '$order($command)' - The ship is already under this spell." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es ist zu gefährlich, ein sturmgepeitschtes Schiff fliegen zu lassen." + "$unit($unit) in $region($region): '$order($command)' - It is too dangerous to fly the ship in the storm." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Syntax Error." + "$unit($unit) in $region($region): '$order($command)' - Syntax Error." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Auraangabe fehlerhaft." + "$unit($unit) in $region($region): '$order($command)' - wrong Aura values." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Zu dieser Einheit kann keine Aura übertragen werden." + "$unit($unit) in $region($region): '$order($command)' - You cannot pass aura on to this unit." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Auf dem Gebäude liegt bereits so ein Zauber." + "$unit($unit) in $region($region): '$order($command)' - There is alrady a spell on that building." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber kann nicht auf hoher See gezaubert werden." + "$unit($unit) in $region($region): '$order($command)' - This spell cannot be cast while you are on the ocean." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber kann nicht auf Monster gezaubert werden." + "$unit($unit) in $region($region): '$order($command)' - This spell cannot be cast on monsters." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber kann nicht auf Untote gezaubert werden." + "$unit($unit) in $region($region): '$order($command)' - This spell cannot be cast on undead." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Stimmung in der Region ist so schlecht, dass niemand auf den Zauber reagiert." + "$unit($unit) in $region($region): '$order($command)' - The mood in this region is so bad that nobody reacts t the spell." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) wusste trotz intensivem Verhör nichts über $region($tregion) zu berichten." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So viele Persoenen übersteigen die Kräfte des Magiers." + "$unit($unit) in $region($region): '$order($command)' -This many people exceed the powers of the magician." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) hat unaufkündbare Bindungen an seine alte Partei." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) have unbreakable prior commitments to their faction." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber gelingt nur in einer Ozeanregion." + "$unit($unit) in $region($region): '$order($command)' - This spell works only in an ocean region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In einer Region ohne Bäume kann man diesen Zauber nicht wirken." + "$unit($unit) in $region($region): '$order($command)' - You cannot cast this spell in a region without trees." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Ziel wurde vergessen." + "$unit($unit) in $region($region): '$order($command)' - No target has been supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das ist keine gültige Rasse." + "$unit($unit) in $region($region): '$order($command)' - This is not a valid race." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Rasse und Zieleinheit wurden vergessen." + "$unit($unit) in $region($region): '$order($command)' - Race and target unit have not been supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die maximale Aura reicht nicht für diesen Zauber." + "$unit($unit) in $region($region): '$order($command)' - Magician's maximum Aura is not high enough for this spell." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier hat bereits einen Vertrauten." + "$unit($unit) in $region($region): '$order($command)' - The magician already has a familiar." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Flammen finden keine Nahrung. Das Feuer erlischt, ohne Schaden anzurichten." + "$unit($unit) in $region($region): '$order($command)' - The flames find no kindling. The fire dies quickly, causing no damage whatsoever." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Um einen Heimstein zu erschaffen, muß der Zauberer in einer Burg sein." + "$unit($unit) in $region($region): '$order($command)' - The magician has to be in a castle to create a homestone." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das ist keine Waldregion." + "$unit($unit) in $region($region): '$order($command)' - This is not a forest region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dorthin führt kein Weg." + "$unit($unit) in $region($region): '$order($command)' - No way is leading in this direction." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Zielregion wurde nicht korrekt angegeben." + "$unit($unit) in $region($region): '$order($command)' - Target region was supplied incorrectly." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Zauber funktioniert nur in der Geisterwelt." + "$unit($unit) in $region($region): '$order($command)' - This spell will only work in the realm of spirits." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Wege zwischen Geisterwelt und Realität scheinen blockiert zu sein." + "$unit($unit) in $region($region): '$order($command)' - The paths to the spirit world seem to be blocked." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Zauber funktioniert nur in Wäldern." + "$unit($unit) in $region($region): '$order($command)' - This spell works only in forests." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Zauber funktioniert nur in der materiellen Welt." + "$unit($unit) in $region($region): '$order($command)' - This spell works only in the material world." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Selbst der mächtigste Magier der Welt könnte keinen Ozean austrocknen lassen." + "$unit($unit) in $region($region): '$order($command)' - Even the gods cannot dry out an entire ocean." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber kann nicht im Sumpf gezaubert werden." + "$unit($unit) in $region($region): '$order($command)' - You cannot cast this spell in a swamp." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber kann nur auf Land gelegt werden." + "$unit($unit) in $region($region): '$order($command)' - This spell works only ashore." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Zauber scheint ungewöhnlich schwach zu sein. Irgendetwas hat die magischen Energien abgeleitet." + "$unit($unit) in $region($region): '$order($command)' - the spell seems exceptionally weak. Something has interfred with the magical energies." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann den Befehl in dieser Runde nicht ausführen, da sie sich bewegt hat." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot execute this command because it has moved." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit bewegt sich nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not move." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier befindet sich nicht auf einem Schiff." + "$unit($unit) in $region($region): '$order($command)' - The magician is not on board a ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff kann in diese Richtung nicht ablegen." + "$unit($unit) in $region($region): '$order($command)' - The ship cannot leave in this direction." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dazu muß sich der Magier in der Burg oder an Bord des Schiffes befinden." + "$unit($unit) in $region($region): '$order($command)' - To do this, the magician has to be in a castle or on board a ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Zauber schlägt fehl." + "$unit($unit) in $region($region): '$order($command)' - The spell fails." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Magiegebiet kann die Einheit nicht lernen." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot learn this magic sphere." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es wurde kein Magiegebiet angegeben." + "$unit($unit) in $region($region): '$order($command)' - No magic sphere was supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diesen Spruch kann der Vertraute nicht zaubern." + "$unit($unit) in $region($region): '$order($command)' - the familiar cannot cast this spell." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diesen Spruch kann man nicht in die Ferne richten." + "$unit($unit) in $region($region): '$order($command)' - You cannot cast this spell on a distant target." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diesen Spruch kann man nicht auf einem sich bewegenden Schiff stehend zaubern." + "$unit($unit) in $region($region): '$order($command)' - You cannot cast this spell while standing on a moving ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber ist nur im Kampf sinnvoll." + "$unit($unit) in $region($region): '$order($command)' - this spell makes only sense in combat." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Selbst in der Bibliothek von Xontormia konnte dieser Spruch nicht gefunden werden." + "$unit($unit) in $region($region): '$order($command)' - Even in the Xontormia Library, this spell could not be found." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es wurde kein Zauber angegeben." + "$unit($unit) in $region($region): '$order($command)' - There was no spell supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diesen Kampfzauber gibt es nicht." + "$unit($unit) in $region($region): '$order($command)' - This combat spell does not exist." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Bauern nehmen dieses großzügige Geschenk nicht an." + "$unit($unit) in $region($region): '$order($command)' - The peasants did not accept this gracious gift." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diesen Zauber kennt die Einheit nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not know this spell." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es konnten keine Luxusgüter verkauft werden." + "$unit($unit) in $region($region): '$order($command)' - No luxury items could be sold." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit geht nicht zu den Bauern." + "$unit($unit) in $region($region): '$order($command)' - The unit does not go to the peasants." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diese Rasse kann eine Burg nicht belagern." + "$unit($unit) in $region($region): '$order($command)' - This race cannot siege a castle." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Trank bekommt der Einheit nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit disagreed with the potion." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Nestwärme kann nur von Insektenvölkern benutzt werden." + "$unit($unit) in $region($region): '$order($command)' - this potion can only be used by insects." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Heiltrank wird automatisch bei Bedarf benutzt." + "$unit($unit) in $region($region): '$order($command)' - This healing potion will be automatically used when needed." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit besitzt den Trank nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have this potion." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es konnten keine Luxusgüter gekauft werden." + "$unit($unit) in $region($region): '$order($command)' - No luxury items could be bought." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es konnten keine Personen übergeben werden." + "$unit($unit) in $region($region): '$order($command)' - No person could be handed over." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Magier arbeiten grundsätzlich nur alleine!" + "$unit($unit) in $region($region): '$order($command)' - Magicians always work alone!" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei hat ein anderes Magiegebiet." + "$unit($unit) in $region($region): '$order($command)' - The faction has a different magic sphere." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Zuviele Alchemisten in der Partei." + "$unit($unit) in $region($region): '$order($command)' - Too many alchemists in the faction." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Zuviele Magier in der Partei." + "$unit($unit) in $region($region): '$order($command)' - Too many magicians in the faction." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hochqualifizierte Personen weigern sich, für andere Parteien zu arbeiten." + "$unit($unit) in $region($region): '$order($command)' - Highly qualified people refuse to work for other parties." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit schließt sich den Bauern an." + "$unit($unit) in $region($region): '$order($command)' - The unit joins the local peasants." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit springt über Bord und ertrinkt." + "$unit($unit) in $region($region): '$order($command)' - The unit jumps over board and drowns." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Region wird bewacht." + "$unit($unit) in $region($region): '$order($command)' - The region is guarded." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Unbekannte Option." + "$unit($unit) in $region($region): '$order($command)' - Unknown Option." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit wird belagert." + "$unit($unit) in $region($region): '$order($command)' - The unit is under siege." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Zum Straßenbau braucht man Steine." + "$unit($unit) in $region($region): '$order($command)' - You need stones to build a road." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Wohin soll die Botschaft gehen?" + "$unit($unit) in $region($region): '$order($command)' - Who is supposed to get this message?" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist nicht der Burgherr." + "$unit($unit) in $region($region): '$order($command)' - The unit is not in command of a castle." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist nicht Burgherr der größten Burg in der Region." + "$unit($unit) in $region($region): '$order($command)' - The unit is not in command of the largest castle in the region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist nicht der Kapitän des Schiffes." + "$unit($unit) in $region($region): '$order($command)' - The unit is not captain of a ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist in keiner Burg." + "$unit($unit) in $region($region): '$order($command)' - The unit is not in a castle." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist auf keinem Schiff." + "$unit($unit) in $region($region): '$order($command)' - The unit is not on board a ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist auf einem Schiff." + "$unit($unit) in $region($region): '$order($command)' - The unit is on board a ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat zuwenig Silber, um zu rekrutieren." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have enough silver for recruiting." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat nicht mehr genug Kristalle für so viele Personen." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have enough crystals left for this many people." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit befindet sich weder in einer Burg noch auf einem Schiff." + "$unit($unit) in $region($region): '$order($command)' - The unit is neither in a castle nor on board a ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Unterschiedliche Typen können nicht gemischt werden." + "$unit($unit) in $region($region): '$order($command)' - Different types do not mix." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Uns gehört nichts, was man abreißen oder versenken könnte." + "$unit($unit) in $region($region): '$order($command)' - We do not have anything that could be demolished." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Unbekannter Hilfe-Modus." + "$unit($unit) in $region($region): '$order($command)' - Unknown Help- Mode." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Unbekannte Meldungs-Option." + "$unit($unit) in $region($region): '$order($command)' - Unknown Report-Option." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Um in Wüsten Straßen bauen zu können, muß zuerst eine Karawanserei errichtet werden." + "$unit($unit) in $region($region): '$order($command)' - You've got to build a caravansary before building roads through deserts." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Um in Sümpfen Straßen bauen zu können, muß zuerst ein Damm errichtet werden." + "$unit($unit) in $region($region): '$order($command)' - You've got to build a dam before building roads through swamps." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So viele Leute kann die Partei nicht aufnehmen." + "$unit($unit) in $region($region): '$order($command)' - The faction cannot hire so many strangers." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So viele Fremde kann die Partei nicht aufnehmen." + "$unit($unit) in $region($region): '$order($command)' - The faction cannot hire so many strangers." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So viele Fremde kann Deine Partei nicht aufnehmen." + "$unit($unit) in $region($region): '$order($command)' - Your faction cannot hire so many strangers." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So etwas kann man nicht verkaufen." + "$unit($unit) in $region($region): '$order($command)' - You cannot sell this." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So etwas kann man nicht machen." + "$unit($unit) in $region($region): '$order($command)' - You cannot produce this." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So etwas kann man nicht auf dem Markt kaufen." + "$unit($unit) in $region($region): '$order($command)' - You cannot buy that on a market place." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So etwas hat die Einheit nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have such a thing." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Pferde kann man nur in einer Pferdezucht züchten." + "$unit($unit) in $region($region): '$order($command)' - You can breed horses only in a stable." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - So etwas gibt es hier nicht." + "$unit($unit) in $region($region): '$order($command)' - That resource does not exist in this region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Personen können nur an Menschen übergeben werden." + "$unit($unit) in $region($region): '$order($command)' - Characters can be given only to Human parties." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ohne einen Handelsposten gibt es keinen Markt." + "$unit($unit) in $region($region): '$order($command)' - There is no marketplace without at least a tradepost." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nur Elfen können diese Bögen herstellen." + "$unit($unit) in $region($region): '$order($command)' - Only Elves can make these bows." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nur die EMail-Adresse angeben!" + "$unit($unit) in $region($region): '$order($command)' - Submit only email-address, please!" + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nummer kann nicht vergeben werden." + "$unit($unit) in $region($region): '$order($command)' - Number can not be assigned." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nummer ist schon belegt." + "$unit($unit) in $region($region): '$order($command)' - Number is already in use." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nummer ist nicht im gültigen Bereich." + "$unit($unit) in $region($region): '$order($command)' - Number is not valid." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nichts angegeben, was wir übergeben sollen." + "$unit($unit) in $region($region): '$order($command)' - Item to be handed over was not supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Namen dürfen keine Klammern enthalten." + "$unit($unit) in $region($region): '$order($command)' - Names may not contain parenthesis." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Nachricht zu lang - gekürzt." + "$unit($unit) in $region($region): '$order($command)' - Message has been cut (too long).." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Man muß angeben, ob eine Burg, ein Schiff, eine Region oder eine Einheit beschrieben werden soll." + "$unit($unit) in $region($region): '$order($command)' - Specify if description is for a castle, a ship, a region or a unit." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Man muß angeben, ob eine Burg, ein Schiff, eine Einheit, eine Region oder eine Partei benannt werden soll." + "$unit($unit) in $region($region): '$order($command)' - Specify if a castle, a ship, a region or a unit is supposed to be named." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es sind keine Kräuter zu finden." + "$unit($unit) in $region($region): '$order($command)' - No herbs could be found." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Man braucht mindestens zwei Pferde, um sie zu züchten." + "$unit($unit) in $region($region): '$order($command)' - You need at least two horses to breed more." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Magier müssen zum studieren allein sein." + "$unit($unit) in $region($region): '$order($command)' - When studying, magicians need to be alone." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Leere Einheiten können nicht übergeben werden." + "$unit($unit) in $region($region): '$order($command)' - Empty units can not be handed over." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Laen kann nur in einem Bergwerk abgebaut werden." + "$unit($unit) in $region($region): '$order($command)' - Laen can be excavated only in a mine." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Keiner hier kann Straßen bauen." + "$unit($unit) in $region($region): '$order($command)' - Nobody here can build roads." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann keine weiteren Güter handeln." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot trade any more goods." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Keiner hier kann ein Gebäude errichten." + "$unit($unit) in $region($region): '$order($command)' - Nobody here can construct a building." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Keiner hier ist gelernter Schiffbauer." + "$unit($unit) in $region($region): '$order($command)' - Nobody here is a skilled ship builder." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit will nicht transportiert werden." + "$unit($unit) in $region($region): '$order($command)' - The unit doen not want to be transported." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Insekten können im Winter nur in Wüsten rekrutiert werden." + "$unit($unit) in $region($region): '$order($command)' - In winter, insects can be recruited only in deserts." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In Gletschern können keine Insekten rekrutiert werden." + "$unit($unit) in $region($region): '$order($command)' - Insects cannot be recruited in glaciers." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In dieser Einheit gibt es niemanden, den man transferieren könnte." + "$unit($unit) in $region($region): '$order($command)' - Nobody in this unit can be transferred." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Illusionen können eine Region nicht bewachen." + "$unit($unit) in $region($region): '$order($command)' - Illusions cannot guard a region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier kann man keine Straße bauen." + "$unit($unit) in $region($region): '$order($command)' - You cannot build a road here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier gibt es schon einen Hafen." + "$unit($unit) in $region($region): '$order($command)' - There is already a port in this region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier gibt es keine Bauern." + "$unit($unit) in $region($region): '$order($command)' - There are no peasants in this region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier gibt es keinen normalen Wald." + "$unit($unit) in $region($region): '$order($command)' - There is no normal forest in this region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Hier gibt es keine Mallornbäume." + "$unit($unit) in $region($region): '$order($command)' - There are no Mallorn trees here." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit fährt nicht mit uns." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have a RIDE-order." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Geldgebot fehlt." + "$unit($unit) in $region($region): '$order($command)' - Money offer is missing." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat nicht genügend Materialien für den Schiffbau." + "$unit($unit) in $region($region): '$order($command)' - the unit is lacking materials to build the ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Für das Elixier benötigt man Drachenblut." + "$unit($unit) in $region($region): '$order($command)' - Dragon blood is required for this elixir." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Falsches Passwort." + "$unit($unit) in $region($region): '$order($command)' - Wrong password." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es wurde keine EMail-Adresse angegeben." + "$unit($unit) in $region($region): '$order($command)' - No email-address was supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es wurde kein Name angegeben." + "$unit($unit) in $region($region): '$order($command)' - No name was supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es konnte kein Bauer gefangen werden." + "$unit($unit) in $region($region): '$order($command)' - No peasant could be caught." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es gibt keine Abstimmung mit dieser Nummer." + "$unit($unit) in $region($region): '$order($command)' - There is no agreement with this number." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Einheit muß zuerst die Region bewachen." + "$unit($unit) in $region($region): '$order($command)' - first, the unit must guard the region." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Einheit ist nicht bewaffnet und kampffähig." + "$unit($unit) in $region($region): '$order($command)' - The unit is not armed and fighting fit." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ein Schiff oder eine Burg muß angegeben werden." + "$unit($unit) in $region($region): '$order($command)' - A ship or a castle must be supplied." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ein Fluch verhindert die Übergabe." + "$unit($unit) in $region($region): '$order($command)' - A curse prevented the transfer from happening." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Talent wurde nicht erkannt." + "$unit($unit) in $region($region): '$order($command)' - The skill could not be recognized." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieses Talent kann die Einheit nicht lernen." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot learn this skill." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diesen Gegenstand kann man nicht benutzen." + "$unit($unit) in $region($region): '$order($command)' - This item cannot be used." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit nimmt niemanden an." + "$unit($unit) in $region($region): '$order($command)' - This unit does not take anybody." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Eine hungernde Einheit kann niemanden weggeben." + "$unit($unit) in $region($region): '$order($command)' - Hungry units cannot give anybody away." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diese Einheit kann niemanden weggeben." + "$unit($unit) in $region($region): '$order($command)' - This unit cannot give anybody away." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) hat keinen Kontakt mit uns aufgenommen." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) did not contact us." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es kann hier kein Kontakt zur Astralwelt aufgenommen werden." + "$unit($unit) in $region($region): '$order($command)' - There is no connection to the astral plane." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) hat keinen Kontakt mit uns aufgenommen und widersteht dem Zauber." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) did not contact us, and resists the spell." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) hat keinen Kontakt mit uns aufgenommen, aber widersteht dem Zauber nicht." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) did not contact us, but cannot resist the spell." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Richtung wurde nicht erkannt." + "$unit($unit) in $region($region): '$order($command)' - Direction was not recognized." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Richtung '$dirname' wurde nicht erkannt." + "$unit($unit) in $region($region): '$order($command)' - Direction '$dirname' was not recognized." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Region wird von Nichtalliierten bewacht." + "$unit($unit) in $region($region): '$order($command)' - This region is guarded by a non allied faction." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Region wird von $unit($guard), einer nichtalliierten Einheit, bewacht." + "$unit($unit) in $region($region): '$order($command)' - This region is guarded by $unit($guard), a non-allied unit." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Pferde würden ertrinken." + "$unit($unit) in $region($region): '$order($command)' - The horses would drown." + "$unit($unit) in $region($region): '$order($command)' - The horses would drown." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - The faction could not be found." + "$unit($unit) in $region($region): '$order($command)' - The faction could not be found." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Lernkosten können nicht bezahlt werden." + "$unit($unit) in $region($region): '$order($command)' - Tuition was too high to be paid." + "$unit($unit) in $region($region): '$order($command)' - Tuition was too high to be paid." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Gegenstand kann nur in der realen Welt benutzt werden." + "$unit($unit) in $region($region): '$order($command)' - This object can only be used in the real world." + "$unit($unit) in $region($region): '$order($command)' - This object can only be used in the real world." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) ist nicht ausreichend getarnt." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) is not sufficiently stealthy." + "$unit($unit) in $region($region): '$order($command)' - $unit($unit) is not sufficiently stealthy." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - The unit could not be found." + "$unit($unit) in $region($region): '$order($command)' - The unit could not be found." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Einheit kann nichts gegeben werden." + "$unit($unit) in $region($region): '$order($command)' - You cannot give anything to this unit." + + + + + + + + + "$unit($mage) horcht $unit($unit) über $region($tregion) aus." + "$unit($mage) questions $unit($unit) about $region($tregion)." + + + + + + + + "$unit($mage) verschafft $unit($unit) einige feuchtfröhliche Stunden mit heftigen Nachwirkungen." + "$unit($mage) invites $unit($unit) for a few too many drinks and a massive hangover." + + + + + + + "$unit($unit) hat höllische Kopfschmerzen und kann sich an die vergangene Woche nicht mehr erinnern. Nur noch daran, wie alles mit einer fröhlichen Feier in irgendeiner Taverne anfing...." + "$unit($unit) has a splitting headache and can hardly remember last week. Except that it all started in the tavern..." + + + + + + + + "$unit($mage) besänftigt $unit($unit)." + "$unit($mage) calms $unit($unit)." + + + + + + + "$unit($unit) verfiel dem Glücksspiel und hat fast sein ganzes Hab und gut verspielt." + "$unit($unit) gambles for high stakes and loses almost everything." + + + + + + + + + "$unit($unit) schenkt $unit($mage) $resources($items)." + "$unit($unit) gives $unit($mage) $resources($items)." + + + + + + + + + "$unit($mage) läßt $unit($target) als $race($race,$unit.size($target)) erscheinen." + "$unit($mage) makes $unit($target) appear as $race($race,$unit.size($target))." + + + + + + + "$unit($unit) wird kurz von einem magischen Licht umhüllt." + "$unit($unit) is briefly surrounded by a magical light." + + + + + + + + "$unit($unit) konnte nur $int($ships) von $int($maxships) Schiffen verzaubern." + "$unit($unit) could only enchant $int($ships) of $int($maxships) ships." + + + + + + "$unit($unit) beschwört einen magischen Wind, der die Schiffe über das Wasser treibt." + "$unit($unit) calls up a magical storm that whips the ship over the waters." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit weiß nichts über Botanik." + "$unit($unit) in $region($region): '$order($command)' - The unit does not know anything about herbalism." + "$unit($unit) in $region($region): '$order($command)' - The unit does not know anything about herbalism." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit weiß nicht, wie man gaukelt." + "$unit($unit) in $region($region): '$order($command)' - The unit does not know how to entertain." + "$unit($unit) in $region($region): '$order($command)' - The unit does not know how to entertain." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit trägt zuviel Gewicht, um sich bewegen zu können." + "$unit($unit) in $region($region): '$order($command)' - The unit is too heavily loaded to move." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann soviele Pferde nicht bändigen." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot tame that many horses." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann sich nicht fortbewegen." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot move." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann nicht handeln." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot trade." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann keine Tränke herstellen." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot make potions." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann keine weiteren langen Befehle ausführen." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot execute more long orders." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat nicht genug Silber." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have enough silver." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have enough silver." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist nicht erfahren genug dafür." + "$unit($unit) in $region($region): '$order($command)' - The unit is not experienced enough to do this." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist nicht der Eigentümer." + "$unit($unit) in $region($region): '$order($command)' - The unit is not he owner." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist nicht bewaffnet und kampffähig." + "$unit($unit) in $region($region): '$order($command)' - The unit is not armed and fighting fit." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist mit uns alliiert." + "$unit($unit) in $region($region): '$order($command)' - This unit is one of our allies." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist in keiner Taverne." + "$unit($unit) in $region($region): '$order($command)' - The unit is not in a tavern." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist eine der unsrigen." + "$unit($unit) in $region($region): '$order($command)' - This unit is one of our own." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist auf hoher See." + "$unit($unit) in $region($region): '$order($command)' - The unit is off shore." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat soetwas nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have this." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat nicht genug Wagenlenker oder zuviel andere Fracht, um die Wagen aufzuladen." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have enough coachmen or too much freights to lad the wagons." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat nicht genug Silber." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have enough silver." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat keinen Kontakt mit uns aufgenommen." + "$unit($unit) in $region($region): '$order($command)' - The unit did not contact us." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat keine Spionage gelernt." + "$unit($unit) in $region($region): '$order($command)' - The unit has not yet learned espionage." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat keine Kräuter." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have any herbs." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat diesen Trank nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have this potion." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat diesen Gegenstand nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have this item." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat diesen Gegenstand zwar, aber sämtliche $int($reservation) $resource($resource,$reservation) sind reserviert." + "$unit($unit) in $region($region): '$order($command)' - The unit has this item, but all $int($reservation) $resource($resource,$reservation) are reserved." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat diese Kräuter nicht." + "$unit($unit) in $region($region): '$order($command)' - The unit does not have these herbs." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Einheiten dürfen nicht mehr als $int($maxsize) Personen enthalten." + "$unit($unit) in $region($region): '$order($command)' - Units may not have more than $int($maxsize) members." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit darf nicht an Bord kommen, da sie das Schiff überladen würde." + "$unit($unit) in $region($region): '$order($command)' - The unit cannot go aboard, the ship would be overloaded." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit darf nicht an Bord kommen." + "$unit($unit) in $region($region): '$order($command)' - This unit has no permission to come on board." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit befindet sich nicht in unserer Burg." + "$unit($unit) in $region($region): '$order($command)' - The unit is not in our castle." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit befindet sich nicht an Bord unseres Schiffes." + "$unit($unit) in $region($region): '$order($command)' - The unit is not on board our ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Burg wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - The castle could not be found." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Botschaft enthält keinen Text." + "$unit($unit) in $region($region): '$order($command)' - The message does not contain text." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Bauern sind schlecht gelaunt." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Anzahl zu verkaufender Produkte fehlt." + "$unit($unit) in $region($region): '$order($command)' - The amount of items for sale is missing." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Anzahl zu kaufender Produkte fehlt." + "$unit($unit) in $region($region): '$order($command)' - The amount of items to buy is missing." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Fluch verhindert das." + "$unit($unit) in $region($region): '$order($command)' - The escape prevented that from happening." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Belagerungszustand macht Spionage unmöglich." + "$unit($unit) in $region($region): '$order($command)' - Espionage was not possible due to siege." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Belagerungszustand macht die Kontaktaufnahme unmöglich." + "$unit($unit) in $region($region): '$order($command)' - Contact was not possible due to siege." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Befehl wurde nicht erkannt." + "$unit($unit) in $region($region): '$order($command)' - Unknown command." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dazu gibt es keine Informationen." + "$unit($unit) in $region($region): '$order($command)' - There is no information available for the request." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - The ship could not be found." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff muß erst verlassen werden." + "$unit($unit) in $region($region): '$order($command)' - First you have to leave the ship." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff ist zu schwer beladen, um in See zu stechen." + "$unit($unit) in $region($region): '$order($command)' - The ship is too heavily loaded to sail." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $ship($ship) ist zu groß, um fliegen zu können." + "$unit($unit) in $region($region): '$order($command)' - $ship($ship) is too bulky to fly." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff ist schon fertig." + "$unit($unit) in $region($region): '$order($command)' - The ship is already completed." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff ist noch nicht fertig gebaut." + "$unit($unit) in $region($region): '$order($command)' - The ship has not yet been completed." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff ist auf hoher See." + "$unit($unit) in $region($region): '$order($command)' - The ship is off shore." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff hat sich bereits bewegt." + "$unit($unit) in $region($region): '$order($command)' - The ship has moved already." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Besitzer muss das Gebäude zuerst verlassen." + "$unit($unit) in $region($region): '$order($command)' - The owner must first LEAVE the building." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff gehört uns nicht." + "$unit($unit) in $region($region): '$order($command)' - The ship is not ours." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Gebäude gehört uns nicht." + "$unit($unit) in $region($region): '$order($command)' - The building is not ours." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Schiff befindet sich auf hoher See." + "$unit($unit) in $region($region): '$order($command)' - The ship is still off shore." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das macht wenig Sinn." + "$unit($unit) in $region($region): '$order($command)' - That does not make much sense." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das kann man nicht sabotieren." + "$unit($unit) in $region($region): '$order($command)' - That cannot be sabotaged." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das ist sinnlos." + "$unit($unit) in $region($region): '$order($command)' - That is useless." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das geht nicht mehr." + "$unit($unit) in $region($region): '$order($command)' - This is no longer possible." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Gebäude wurde nicht gefunden." + "$unit($unit) in $region($region): '$order($command)' - Building could not be found." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Gebäude gehört uns nicht." + "$unit($unit) in $region($region): '$order($command)' - The building is not ours." + "$unit($unit) in $region($region): '$order($command)' - The building is not ours." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Das Gebäude ist bereits fertig." + "$unit($unit) in $region($region): '$order($command)' - The building is already completed." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit kann nicht unterrichtet werden." + "$unit($unit) in $region($region): '$order($command)' - this unit cannot be taught." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Beschreibung zu lang - gekürzt." + "$unit($unit) in $region($region): '$order($command)' - Description has been cut (too long)." + "$unit($unit) in $region($region): '$order($command)' - Description has been cut (too long)." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Auf hoher See kann man nicht bewachen." + "$unit($unit) in $region($region): '$order($command)' - You cannot guard off shore." + "$unit($unit) in $region($region): '$order($command)' - You cannot guard off shore." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Auf dem Schiff befinden sich zuwenig erfahrene Seeleute." + "$unit($unit) in $region($region): '$order($command)' - There are not enough experienced sailors on board the ship." + "$unit($unit) in $region($region): '$order($command)' - There are not enough experienced sailors on board the ship." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - ${error}." + "$unit($unit) in $region($region): '$order($command)' - ${error}." + + + + + + + "$unit($unit) benutzt ein $resource($item,1)." + "$unit($unit) uses a $resource($item,1)." + "$unit($unit) uses a $resource($item,1)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $resource($item,0) können nur von Ein-Personen Einheiten benutzt werden." + "$unit($unit) in $region($region): '$order($command)' - $resource($item,0) can only be used by single-person units." + "$unit($unit) in $region($region): '$order($command)' - $resource($item,0) can only be used by single-person units." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit ist noch zu erschöpft vom Einmarsch um zu attackieren." + "'$order($command)' - $unit($unit) marched into $region($region) during the last turn and is too exhausted to attack." + "'$order($command)' - $unit($unit) marched into $region($region) during the last turn and is too exhausted to attack." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) sind friedliebend und attackieren niemand." + "'$order($command)' - $race($race,0) are peace-loving and will not attack anyone." + "'$order($command)' - $race($race,0) are peace-loving and will not attack anyone." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit steht nicht im benötigten Gebäude, $localize($building)." + "$unit($unit) in $region($region): '$order($command)' - The unit must be in a $localize($building) to produce this." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dazu braucht man das Talent $skill($skill)." + "$unit($unit) in $region($region): '$order($command)' - this requires the skill $skill($skill)." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Man benötigt mindestens $int($minskill) $skill($skill), um $resource($product,0) zu pflanzen." + "$unit($unit) in $region($region): '$order($command)' - At least $skill($skill) $int($minskill) is needed for planting $resource($product,0)." + "$unit($unit) in $region($region): '$order($command)' - At least $skill($skill) $int($minskill) is needed for planting $resource($product,0)." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Man benötigt mindestens $int($minskill) $skill($skill), um $resource($product,0) zu produzieren." + "$unit($unit) in $region($region): '$order($command)' - You need at least $int($minskill) $skill($skill), to produce $resource($product,0)." + + + + + + "$string" + "$string" + + + + + + "$string" + "$string" + + + + + + "$string" + "$string" + + + + + + + + "$unit($unit) übergibt $int($amount) Person$if($eq($amount,1),"","en") an $unit($target)." + "$unit($unit) transfers $int($amount) person$if($eq($amount,1),"","s") to $unit($target)." + + + + + + + + + + "$unit($unit) übergibt $int($amount) $resource($resource,$amount) an $unit($target)." + "$unit($unit) gives $int($amount) $resource($resource,$amount) to $unit($target)." + + + + + + + + + + "$unit($target) erhält $int($amount) $resource($resource,$amount) von $unit($unit)." + "$unit($target) receives $int($amount) $resource($resource,$amount) from $unit($unit)." + + + + + + + + "$unit($unit) übergibt $int($amount) Person$if($eq($amount,1),"","en") an die Bauern." + "$unit($unit) transfers $int($amount) person$if($eq($amount,1),"","s") to the local peasants." + + + + + + + + + "$unit($unit) übergibt $int($amount) $resource($resource,$amount) an die Bauern." + "$unit($unit) gives $int($amount) $resource($resource,$amount) to the local peasants." + + + + + + + + "$unit($unit) bezahlt den Unterhalt von $building($building)." + "$unit($unit) pays the maintenance for $building($building)." + "$unit($unit) pays the maintenance for $building($building)." + + + + + + + + "$unit($unit) fehlen $resource($item,0) für den Betrieb von $building($building)." + "$unit($unit) lacks $resource($item,0) to operate $building($building)." + + + + + + + "$unit($unit) kann den Unterhalt von $building($building) nicht bezahlen." + "$unit($unit) cannot pay the maintenance for $building($building)." + "$unit($unit) cannot pay the maintenance for $building($building)." + + + + + + "Der Unterhalt von $building($building) konnte nur verspätet gezahlt werden, das Gebäude war diese Woche nicht funktionstüchtig." + "The upkeep for $building($building) was paid late, the building was not operational this week." + "The upkeep for $building($building) was paid late, the building was not operational this week." + + + + + + + + "$unit($unit) verdient am Handel in $region($region) Steuern in Höhe von $int($amount) Silber." + "$unit($unit) collected $int($amount) silver trade tax in $region($region)." + "$unit($unit) collected $int($amount) silver trade tax in $region($region)." + + + + + + + "$unit($unit) benutzt $resource($potion,1)." + "$unit($unit) uses $resource($potion,1)." + "$unit($unit) uses $resource($potion,1)." + + + + + + "Hier wütete die Pest, und $int($dead) Bauern starben." + "The region is visited by the plague and $int($dead) peasants died." + "The region is visited by the plague and $int($dead) peasants died." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Um in Gletschern Straßen bauen zu können, muß zuerst ein Tunnel errichtet werden." + "$unit($unit) in $region($region): '$order($command)' - You've got to build a tunnel before building roads through glaciers." + "$unit($unit) in $region($region): '$order($command)' - You've got to build a tunnel before building roads through glaciers." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Syntax: MAGIEGEBIET [1-5]." + "$unit($unit) in $region($region): '$order($command)' - Syntax: MAGIC SPHERE [1-5]." + "$unit($unit) in $region($region): '$order($command)' - Syntax: MAGIC SPHERE [1-5]." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Deine Partei muss mindestens $int($turns) alt sein, um etwas an andere Parteien übergeben zu können." + "$unit($unit) in $region($region): '$order($command)' - Your faction must be at least $int($turns) weeks old to give something to another faction." + "$unit($unit) in $region($region): '$order($command)' - Your faction must be at least $int($turns) weeks old to give something to another faction." + + + + + + "Bitte sende die Befehle nächste Runde ein, wenn du weiterspielen möchtest." + "Merci d'envoyer vos ordres pour le tour suivant si vous désirez continuer à jouer." + "Please send in orders for the next turn if you want to continue playing." + + + + + + + "Deine Partei ist noch $int($turns) Wochen immun gegen Angriffe." + "Votre faction est immunisée contre les agressions durant $int($turns) semaines encore." + "Your faction is immune against assaults for $int($turns) more weeks." + + + + + + + + "$faction($member) ist mit $int($votes) Stimmen aus $alliance($alliance) ausgeschlossen worden." + "$faction($member) was kicked from $alliance($alliance) by $int($votes) of the alliance's members." + "$faction($member) was kicked from $alliance($alliance) by $int($votes) of the alliance's members." + + + + + + "$alliance($alliance) scheidet aus dem Spiel aus, nachdem alle Tempel verloren gingen." + "$alliance($alliance) has to leave the game after all their temples were lost." + "$alliance($alliance) has to leave the game after all their temples were lost." + + + + + + + "$int($votes) Mitglieder von $alliance($alliance) haben versucht, Deine Partei aus der Allianz auszuschliessen." + "$int($votes) members of $alliance($alliance) tried to kick you out of the alliance." + "$int($votes) members of $alliance($alliance) tried to kick you out of the alliance." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In dieser Regione können Pyramiden gebaut werden." + "$unit($unit) in $region($region): '$order($command)' - Pyramids may be build in this region." + "$unit($unit) in $region($region): '$order($command)' - Pyramids may be build in this region." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Auf $ship($ship) liegt beeits ein Zauber." + "$unit($unit) in $region($region): '$order($command)' - There is already a spell on $ship($ship)." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es ist zu gefährlich, diesen Zauber auf das fliegende Schiff $ship($ship) zu legen." + "$unit($unit) in $region($region): '$order($command)' - It is far too dangerous to put this spell on the flying ship $ship($ship)." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In dieser Region können keine Pyramiden gebaut werden. Die nächste Pyramidenregion ist zwischen $int($mindist) und $int($maxdist) Regionen entfernt." + "$unit($unit) in $region($region): '$order($command)' - No pyramids may be build in this region. The closest region to build a pyramid in is between $int($mindist) and $int($maxdist) regions away." + "$unit($unit) in $region($region): '$order($command)' - No pyramids may be build in this region. The closest region to build a pyramid in is between $int($mindist) and $int($maxdist) regions away." + + + + + + + + "$unit($unit) kann in $region($region) nicht durch das Wurmloch reisen, da die Einheit entweder zu gross ist oder teure Talente besitzt." + "$unit($unit) cannot travel through the wormhole in $region($region) because the unit is either too big or has restricted skills." + "$unit($unit) cannot travel through the wormhole in $region($region) because the unit is either too big or has restricted skills." + + + + + + + "$unit($unit) reist durch ein Wurmloch nach $region($region)." + "$unit($unit) travels through a wormhole to $region($region)." + "$unit($unit) travels through a wormhole to $region($region)." + + + + + + "In $region($region) erscheint ein Wurmloch." + "A wormhole appears in $region($region)." + "A wormhole appears in $region($region)." + + + + + + "Das Wurmloch in $region($region) schließt sich." + "The wormhole in $region($region) disappears." + "The wormhole in $region($region) disappears." + + + + + + + "$int($amount) Krieger von $unit($unit) benutzen ihre Flammenschwerter." + "$int($amount) fighters of $unit($unit) are using their flaming sword." + + + + + + + "$int($amount) Krieger von $unit($unit) feuern ihre Katapulte ab." + "$int($amount) fighters of $unit($unit) launch their catapults." + + + + + + "Der Kampf wurde ausgelöst von ${factions}." + "The battle was initiated by ${factions}." + + + + + + "$unit($unit) konnte durch einen Heiltrank überleben." + "$unit($unit) was saved by a haling potion." + + + + + + "$unit($unit) konnte dem Gegner eine Falle stellen." + "$unit($unit) lured the enemy into an ambush." + + + + + + "$unit($unit) überrascht den Gegner." + "$unit($unit) surprises the enemies." + + + + + + + "$unit($unit) versucht $spell($spell) zu zaubern, doch der Zauber schlägt fehl!" + "$unit($unit) tries to cast $spell($spell), but the spell fails!" + + + + + "Der Kampf wurde abgebrochen, da alle Verteidiger flohen." + "The battle was aborted because all enemies escaped." + + + + + + "... in der $int($row). Kampflinie:" + "... in combat rank $int($row):" + + + + + + + "$unit($mage) zaubert $spell($spell), aber niemand war in Reichweite." + "$unit($mage) casts $spell($spell), but nobody was in range." + + + + + + "Einheiten nach dem Kampf:" + "Units after the battle:" + + + + + + "" + "" + + + + + + + + + "$unit($mage) ruft $int($amount) $race($race, $amount) zu Hilfe." + "$unit($mage) calls for the help of $int($amount) $race($race, $amount)." + + + + + + + "$unit($mage) beschwört Trugbilder herauf." + "$unit($mage) summons a mirage." + + + + + + + "$unit($mage) murmelt eine düster klingende Formel. Ein plötzlicher Tumult entsteht, der sich jedoch schnell wieder legt." + + + + + + + "$unit($mage) murmelt eine düster klingende Formel. Ein plötzlicher Tumult entsteht und bringt die Kampfaufstellung durcheinander." + + + + + + + "$unit($mage) stimmt einen seltsamen Gesang an. Ein plötzlicher Tumult entsteht, der sich jedoch schnell wieder legt." + + + + + + + "$unit($mage) stimmt einen seltsamen Gesang an. Ein plötzlicher Tumult entsteht und bringt die Kampfaufstellung durcheinander." + "$unit($mage) begins a mysterious chant. great confusion sweeps through the ranks of the enemy." + + + + + + + + "$unit($mage) läßt die Mauern von $building($building) in einem unheimlichen magischen Licht erglühen." + + + + + + + "Einheiten vor der $int($turn). Runde:" + "Units before turn $int($turn):" + + + + + + "In $region($region) findet ein Kampf statt." + "There is a battle in $region($region)." + + + + + + + + "$unit($mage) zaubert $spell($spell): $int($dead) $if($eq($dead,1),"Krieger wurde", "Krieger wurden") getötet." + "$unit($mage) casts $spell($spell): $int($dead) $if($eq($dead,1),"enemy was", "enemies were") killed." + + + + + + + + "$unit($mage) läßt die Erde in $region($region) erzittern." + "$unit($mage) shakes the earth in $region($region)." + + + + + + + + "$unit($mage) verflucht das Land in $region($region), und eine Dürreperiode beginnt." + "$unit($mage) puts a curse on the lands of $region($region) and a drought sets in." + + + + + + + "$unit($mage) erschafft einen Klon." + "$unit($mage) creates a clone." + + + + + + + + + "$unit($mage) verliert sich in die Träume von $unit($unit) und erhält einen Eindruck von $region($region)." + + + + + + + + + "$unit($mage) verschafft $unit($unit) ein schönes Nachtleben in $region($region)." + + + + + + + + "$unit($mage) sorgt für schlechten Schlaf in $region($region)." + + + + + + + + + "$unit($mage) beschwört $int($amount) $race($race,$amount)." + "$unit($mage) summons $int($amount) $race($race,$amount)." + + + + + + + + + "$unit($mage) erschafft in $region($region) eine verheerende Feuersbrunst. $int($amount) Bäume fallen den Flammen zum Opfer." + "$unit($mage) creates a flaming inferno in $region($region). $int($amount) trees fall vistim to the flames." + + + + + + + + "Mit einem Ritual bindet $unit($mage) die magischen Kräfte der Erde in die Mauern von $building($building)." + + + + + + + + "$unit($mage) weight $building($building)." + "$unit($mage) blesses $building($building)." + + + + + + + + "$unit($mage) ruft das Feuer der Sonne auf $region($region) hinab. Eis schmilzt und verwandelt sich in Morast. Reißende Ströme spülen die mageren Felder weg und ersäufen Mensch und Tier. Was an Bauten nicht den Fluten zum Opfer fiel, verschlingt der Morast. Die sengende Hitze verändert die Region für immer." + + + + + + + + "$unit($mage) ruft das Feuer der Sonne auf $region($region) hinab. Die Felder verdorren und Pferde verdursten. Die Hungersnot kostet vielen Bauern das Leben. Vertrocknete Bäume recken ihre kahlen Zweige in den blauen Himmel, von dem erbarmungslos die sengende Sonne brennt." + + + + + + + + "$unit($mage) ruft das Feuer der Sonne auf $region($region) hinab. Die Felder verdorren und Pferde verdursten. Die Hungersnot kostet vielen Bauern das Leben. Vertrocknete Bäume recken ihre kahlen Zweige in den blauen Himmel, von dem erbarmungslos die sengende Sonne brennt. Die Dürre verändert die Region für immer." + + + + + + + + "$unit($mage) ruft das Feuer der Sonne auf $region($region) hinab. Das Eis zerbricht und eine gewaltige Flutwelle verschlingt die Region." + + + + "Die Darbietungen eines fahrenden Gauklers begeistern die Leute. Die fröhliche und ausgelassene Stimmung seiner Lieder überträgt sich auf alle Zuhörer." + "A touring minstrel entertains the locals. The joyous and generous disposition of his songs prove infectious." + + + + + + + "Die Darbietungen von $unit($mage) begeistern die Leute. Die fröhliche und ausgelassene Stimmung seiner Lieder überträgt sich auf alle Zuhörer." + "$unit($mage) entertains the locals. The joyous and generous disposition of his songs prove infectious." + + + "In der Luft liegt ein wunderschönes Lied, dessen friedfertiger Stimmung sich niemand entziehen kann. Einige Leute werfen sogar ihre Waffen weg." + "A wondrous song fills the air and enchants the public. The song's peaceful melody makes several listeners drop their weapon." + + + + + + "Die Gesangskunst von $unit($mage) begeistert die Leute. Die friedfertige Stimmung des Lieds überträgt sich auf alle Zuhörer. Einige werfen ihre Waffen weg." + "The marvelous singing of $unit($mage) enchants the public. The song's peaceful melody makes several listeners drop their weapon." + + + + + + + "$unit($mage) beschwört $int($number) Dämonen aus dem Reich der Schatten." + "$unit($mage) summons $int($number) demons from the realm of shadows." + + + + + + + + "$unit($mage) zaubert $spell($spell)." + "$unit($mage) casts $spell($spell)." + + + + + + + + + + "$unit($mage) zaubert $spell($spell). $int($amount) Krieger verloren Erinnerungen, $int($dead) wurden getötet." + + + + + + + + + "$unit($mage) zaubert $spell($spell). $int($amount) Krieger verloren kurzzeitig ihr Gedächtnis." + "$unit($mage) casts $spell($spell). $int($amount) fighters are temporarily losing some of their memories." + + + + + + + + + "$unit($mage) ruft $int($amount) $race($race, 0) zu Hilfe." + + + + + + + + "$unit($unit) tötete $int($dead) Krieger." + "$unit($unit) killed $int($dead) opponents." + + + + + + + + + + "Heer $int($index)($abbrev): $int($dead) Tote, $int($fled) Geflohene, $int($survived) Überlebende." + "Army $int($index)($abbrev): $int($dead) dead, $int($fled) fled, $int($survived) survivors." + + + + + + + + "$unit($mage) in $region($region): '$order($command)' - Dieser Zauber kann nicht mit Stufenangabe gezaubert werden." + "$unit($mage) in $region($region): '$order($command)' - This spell cannot be cast with variable level." + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dorthin führt kein Weg." + "$unit($unit) in $region($region): '$order($command)' - There is no way leading there." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Zu $region($target) kann kein Kontakt hergestellt werden." + "$unit($unit) in $region($region): '$order($command)' - $region($target) could not be contacted." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit $unit($target) hat keinen Kontakt mit uns aufgenommen." + "$unit($unit) in $region($region): '$order($command)' - The unit $unit($target) did not contact us." + + + + + + + + + "Antimagie von $unit.dative($self) blockiert in $region($region) einen Zauber von $unit.dative($mage)." + "In $region($region), anti-magic from $unit($self) blocks the spell of $unit($mage)." + + + + + + + + + "$unit($self) schwächt in $region($region) einen Zauber von $unit.dative($mage) durch Antimagie ab." + "In $region($region), anti-magic from $unit($self) reduces the effect of $unit($mage)'s spell." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $int($pacified) Regionen wurden befriedet." + "$unit($unit) in $region($region): '$order($command)' - $int($pacified) regions have been pacified." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Keine Region konnte befriedet werden." + "$unit($unit) in $region($region): '$order($command)' - No region could be pacified." + + + + + + + + "$unit($unit) in $region($region) bläst das Horn des Tanzes. In der ganzen Region breitet sich eine friedliche Feststimmmung aus." + "$unit($unit) in $region($region) blows the Horn of Dancing. Peaceful harmony spreads over the region." + + + + + + + + "$unit($unit) in $region($region) bläst das Horn des Tanzes, doch niemand hier lässt sich von Stimmung anstecken." + "$unit($unit) in $region($region) blows the Horn of Dancing, but nobody here gets into the mood." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die $ship($ship) wird jetzt schneller ihr Ziel erreichen." + "$unit($unit) in $region($region): '$order($command)' - The $ship($ship) will now be faster." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Magier fühlt sich durch den Trank magische gestärkt." + "$unit($unit) in $region($region): '$order($command)' - The mage is magically invigorated." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Ausser sich vor Furcht geben die Bauern dem Barden $int($money) Silber." + "$unit($unit) in $region($region): '$order($command)' - Stricken with fear the peasants give the bard $int($money) silver." + + + + + + + + "$unit($unit) spielt einen Dudelsack. Ausser sich vor Furcht geben die Bauern $int($money) Silber." + "$unit($unit) plays the bagpipe. Stricken with fear the peasants give $int($money) silver." + + + + + + + + "$unit($unit) in $region($region) erschafft eine Akademie der Künste." + "$unit($unit) in $region($region) creates an academy of arts." + + + + + + + + "$unit($unit) in $region($region) erschafft eine Skulptur." + "$unit($unit) in $region($region) creates a sculpture." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Region ist zu weit entfernt." + "$unit($unit) in $region($region): '$order($command)' - That region is too far away." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Wege aus dieser Region sind blockiert." + "$unit($unit) in $region($region): '$order($command)' - The connections from to this regions are blocked." + + + + + + + "$unit($unit) erscheint plötzlich." + "$unit($unit) appears." + + + + + + + "$unit($unit) wird durchscheinend und verschwindet." + "$unit($unit) disappears." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) ist zu schwer." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) is too heavy." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Partei hat bereits $int($count) von $int($max) Helden." + "$unit($unit) in $region($region): '$order($command)' - The faction already has $int($count) of $int($max) heroes." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) können keine Helden erwählen." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot be heroes." + + + + + + + + "$unit($unit) wird mit $int($cost) Silber zum Helden ernannt." + "$unit($unit) uses $int($cost) silber for a promotion." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Einheit hat nur $int($have) von $int($cost) benötigtem Silber." + "$unit($unit) in $region($region): '$order($command)' - The unit has $int($have) of $int($cost) silver required." + + + + + + + "Eine gewaltige Flutwelle verschlingt $region($region) und alle Bewohner." + "A tidal wave wipes out $region($region) and all who lived there." + + + + + + + + "Eine gewaltige Flutwelle verschlingt $unit($unit) in $region($region)." + "A tidal wave wipes out $region($region) and kills $unit($unit)." + + + + + + + + "$unit($unit) reaktiviert den astralen Schutzschild in $region($region)." + "$unit($unit) reactivates the astral protection shield in $region($region)." + + + + + + + + + + "$unit($unit) in $region($region): $int($number) $race($race,$number) $if($eq($number,1),"kehrte auf seine", "kehrten auf ihre") Felder zurück." + "$unit($unit) in $region($region): $int($number) $race($race,$number) returned to the fields." + + + + + + + + + + "$unit($unit) in $region($region): $int($number) $race($race,$number) $if($eq($number,1),"wurde zum Baum", "wurden zu Bäumen")." + "$unit($unit) in $region($region): $int($number) $race($race,$number) turned into $if($eq($number,1),"a tree", "trees")." + + + + + + + + + + "$unit($unit) in $region($region): $int($number) $race($race,$number) $if($eq($number,1),"verfaulte", "verfaulten")." + "$unit($unit) in $region($region): $int($number) $race($race,$number) whithered and died." + + + + + + + + + + "$unit($unit) in $region($region): $int($number) $race($race,$number) $if($eq($number,1),"zerfiel", "zerfielen") zu Staub." + "$unit($unit) in $region($region): $int($number) $race($race,$number) turned to dust." + + + + + + + + + + "$unit($unit) in $region($region): $int($number) $race($race,$number) $if($eq($number,1),"verschwand", "verschwanden") über Nacht." + "$unit($unit) in $region($region): $int($number) $race($race,$number) disappearedin the night." + + + + + + + + + "Der Waldbrand in $region($region) griff auch auf $region($next) über, und $int($trees) verbrannten." + "The fire in $region($region) spread to $region($next) and $int($trees) were burnt." + + + + + + + + "$unit($mage) ruft in $region($region) eine Pest hervor." + "$unit($mage) sends the plague on $region($region)." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Eine Partei darf nicht aus mehr als $int($allowed) Einheiten bestehen." + "$unit($unit) in $region($region): '$order($command)' - A faction may not consist of more than $int($allowed) units." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Eine Allianz darf aus nicht mehr als $int($allowed) Einheiten bestehen." + "$unit($unit) in $region($region): '$order($command)' - An alliance may not consist of more than $int($allowed) units." + + + + + + + + + + "$unit($unit) in $region($region): $int($amount) $resource($item,$amount) zerfallen zu Staub." + "$unit($unit) in $region($region): $int($amount) $resource($item,$amount) turn to dust." + + + + + + + + + "Die $ship($ship) ist mit gutem Wind gesegnet$if($lt($duration,3),", doch der Zauber beginnt sich bereits aufzulösen",""). ($int36($id))" + "The $ship($ship) is blessed with favourable winds$if($lt($duration,3),", but the spell is starting to wear thin",""). ($int36($id))" + + + + + + + "Kräftige Stürme haben dieses Schiff in die Luft gehoben. ($int36($id))" + "Powerful storms have lifted this ship high into the air. ($int36($id))" + + + + + + + "Mächtige Magie verhindert den Kontakt zur Realität. ($int36($id))" + "Powerful magic disrupts our contact with reality. ($int36($id))" + + + + + + + + "Ein silberner Schimmer umgibt die $ship($ship). ($int36($id))" + "A silvery shimmer surrounds the $ship($ship). ($int36($id))" + + + + + + + + "Auf den Mauern von $building($building) erkennt man seltsame Runen. ($int36($id))" + "The walls of $building($building) are inscribed with strange runes. ($int36($id))" + + + + + + + + + "$unit($mage) erschafft $int($number) $resource($item,$number)." + "$unit($mage) creates $int($number) $resource($item,$number)." + + + + + + + + "Auf den Planken von $ship($ship) erkennt man seltsame Runen. ($int36($id))" + "The plank of $ship($ship) are inscribed with strange runes. ($int36($id))" + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Kompassnadel springt wild hin und her und es lässt sich keine Richtung erkennen." + "$unit($unit) in $region($region): '$order($command)' - The needle jumps wildly and there is no specific direction recognizable." + "$unit($unit) in $region($region): '$order($command)' - The needle jumps wildly and there is no specific direction recognizable." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Die Kompassnadel zeigt nach $direction($dir)." + "$unit($unit) in $region($region): '$order($command)' - The needle points $direction($dir)." + "$unit($unit) in $region($region): '$order($command)' - The needle points $direction($dir)." + + + + + + + + "$unit($unit) wird aus der astralen Ebene nach $region($region) geschleudert." + "$unit($unit) is sent from the astral plain to $region($region)." + "$unit($unit) is sent from the astral plain to $region($region)." + + + + + + + + "$unit($target) wird von $unit($unit) in eine andere Welt geschleudert." + "$unit($unit) sends $unit($target) to another world." + "$unit($unit) sends $unit($target) to another world." + + + + + + + + "$unit($unit) versuchte erfolglos, $unit($target) in eine andere Welt zu schleudern." + "$unit($unit) tried but failed to send $unit($target) to another world." + "$unit($unit) tried but failed to send $unit($target) to another world." + + + + + + + "NUMMER PARTEI $int36($id): Die Partei kann nicht mehr als einmal ihre Nummer wecheln." + "NUMBER FACTION $int36($id): Your faction can only change its number once." + + + + + + + "NUMMER PARTEI $int36($id): Diese Nummer wird von einer anderen Partei benutzt." + "NUMBER FACTION $int36($id): This number is being used by another faction." + + + + + + + + "Eine Botschaft von $unit($unit): '$message'" + "A message from $unit($unit): '$message'" + + + + + + + + "Plötzlich stolpert $unit($unit) über einige $localize($name). Nach kurzem Zögern entschließen die $localize($name), sich Deiner Partei anzuschließen." + + + + + + + "$unit($unit) entdeckt ein kleines Dorf. Die meisten Häuser wurden durch einen über die Ufer getretenen Fluß zerstört. Eine Gruppe der verzweifelten Menschen schließt sich deiner Partei an." + "$unit($unit) discovers a small village. Most of the houses have been destroyed by flooding, and a group of the distressed villagers join your faction." + + + + "Ein Bauernmob erhebt sich und macht Jagd auf Schwarzmagier." + "An angry mob forms and hunts practitioners of the dark arts." + + + + + + + "Vertrauter von $unit($unit)" + "Familiar of $unit($unit)" + + + + + + + + + "$unit($unit) rekrutiert $int($amount) $localize($archetype)." + "$unit($unit) recruits $int($amount) $localize($archetype)." + + + + + + + "Dein Passwort enthält Zeichen, die bei der Nachsendung von Reports Probleme bereiten können. Bitte beachte, dass Passwortenur aus Buchstaben von A bis Z und Zahlen bestehen dürfen. Dein neues Passwort ist '${newpass}'." + "Your password was changed because it contained illegal characters. Legal passwords may only contain numbers and letters from A to Z. Your new Password is '${newpass}'." + + + + "Miiauuuuuu..." + "Meeoooooow..." + + + + + + + "Die Götter segnen $unit($unit) mit der richtigen Rasse." + "The gods are blessing $unit($unit) with the correct race." + + + + + + + "Der Versuch, die Greifenschwingen zu benutzen, schlug fehl. $unit($unit) konnte die Ebene der Herausforderung nicht verlassen." + + + + + + + "$unit($unit) springt in die ewigen Feuer des Kraters." + + + + + + + + "$unit($unit) springt in die ewigen Feuer des Kraters." + + + + + + + + "In $region($region) erklingt die Stimme des Torwächters: 'Nur wer ohne materielle Güter und noch lernbegierig ist, der darf die Ebene der Herausforderung betreten. Und vergiß nicht mein Trinkgeld.'. $unit($unit) erhielt keinen Einlaß." + + + + + + + + "In $region($region) öffnet sich ein Portal. Eine Stimme ertönt, und spricht: 'Willkommen in der Ebene der Herausforderung'. $unit($unit) durchschreitet das Tor zu einer anderen Welt." + + + + + + + "$unit($unit) scheint von einer seltsamen Krankheit befallen." + "$unit($unit) is stricken by a strange disease." + + + + + + + + + "$unit($unit) erbeutet $int($amount) $resource($item,$amount)." + "$unit($unit) collects $int($amount) $resource($item,$amount)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es ist so schön friedlich, man möchte hier niemanden angreifen." + "$unit($unit) in $region($region): '$order($command)' - It's so quiet and peaceful, nobody wants to attack anybody right now." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) können nichts lernen." + "$unit($unit) in $region($region): '$order($command)' - $race($race,0) cannot study." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Migranten können keine kostenpflichtigen Talente lernen." + "$unit($unit) in $region($region): '$order($command)' - Migrants cannot study this." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es kann maximal $int($amount) Magier pro Partei geben." + "$unit($unit) in $region($region): '$order($command)' - There may not be more tha $int($amount) magicians in your faction." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Es kann maximal $int($amount) Alchemisten pro Partei geben." + "$unit($unit) in $region($region): '$order($command)' - There may not be more tha $int($amount) alchemists in your faction." + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - $unit($target) versteht unsere Art von Magie nicht." + "$unit($unit) in $region($region): '$order($command)' - $unit($target) does not understand our kind of magic." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Der Kapitän muß ein Segeltalent von mindestens $int($value) haben, um $ship($ship) zu befehligen." + "$unit($unit) in $region($region): '$order($command)' - The captain needs a sailing skill of at least $int($value), to command $ship($ship)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - In dieser Region gibt es keine Brücken und Straßen mehr zu bauen." + "$unit($unit) in $region($region): '$order($command)' - the roads and bridges in this region are complete." + + + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Um $localize($name) zu bauen, braucht man ein Talent von mindestens $int($value)." + "$unit($unit) in $region($region): '$order($command)' - To build $localize($name) requires a skill of at least $int($value)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Diese Einheit kämpft nicht." + "$unit($unit) in $region($region): '$order($command)' - This unit will not fight." + + + + + + + + + "$unit($unit) erhielt $resources($items) von der $ship($ship)." + "$unit($unit) received $resources($items) from the $ship($ship)." + + + + + + + + + "$unit($unit) in $region($region): '$order($command)' - Dieser Zauber kann nciht auf Untote gezaubert werden." + + + + + + + + "Achtung: $faction($faction) hat seit $int($turns) Wochen keine + Züge eingeschickt und könnte dadurch in Kürze aus dem Spiel + ausscheiden." + "Warning: $faction($faction) has not been sending in + orders for $int($turns) turns and may be leaving the game soon." + + + diff --git a/core/res/prefixes.xml b/core/res/prefixes.xml new file mode 100644 index 000000000..0e8e9587a --- /dev/null +++ b/core/res/prefixes.xml @@ -0,0 +1,31 @@ + + + Dunkel + Licht + Klein + Hoch + Huegel + Berg + Wald + Sumpf + Schnee + Sonnen + Mond + See + Tal + Schatten + Hoehlen + Blut + Wild + Chaos + Nacht + Nebel + Grau + Frost + Finster + Duester + flame + ice + star + black + diff --git a/core/res/resources/cart.xml b/core/res/resources/cart.xml new file mode 100644 index 000000000..a72b222d3 --- /dev/null +++ b/core/res/resources/cart.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/core/res/resources/horse.xml b/core/res/resources/horse.xml new file mode 100644 index 000000000..abf6d856d --- /dev/null +++ b/core/res/resources/horse.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/res/resources/hp.xml b/core/res/resources/hp.xml new file mode 100644 index 000000000..fb1b6e32f --- /dev/null +++ b/core/res/resources/hp.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/core/res/resources/iron.xml b/core/res/resources/iron.xml new file mode 100644 index 000000000..94dbc6390 --- /dev/null +++ b/core/res/resources/iron.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/res/resources/laen.xml b/core/res/resources/laen.xml new file mode 100644 index 000000000..efb13c5ed --- /dev/null +++ b/core/res/resources/laen.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/res/resources/log.xml b/core/res/resources/log.xml new file mode 100644 index 000000000..c017682fc --- /dev/null +++ b/core/res/resources/log.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/resources/mallorn.xml b/core/res/resources/mallorn.xml new file mode 100644 index 000000000..e5c01506b --- /dev/null +++ b/core/res/resources/mallorn.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/resources/mallornseed.xml b/core/res/resources/mallornseed.xml new file mode 100644 index 000000000..1c7c1a800 --- /dev/null +++ b/core/res/resources/mallornseed.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/core/res/resources/peasant.xml b/core/res/resources/peasant.xml new file mode 100644 index 000000000..32e1e1ca1 --- /dev/null +++ b/core/res/resources/peasant.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/core/res/resources/seed.xml b/core/res/resources/seed.xml new file mode 100644 index 000000000..2193fb8c8 --- /dev/null +++ b/core/res/resources/seed.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/core/res/resources/stone.xml b/core/res/resources/stone.xml new file mode 100644 index 000000000..eed2fd2df --- /dev/null +++ b/core/res/resources/stone.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/res/ships.xml b/core/res/ships.xml new file mode 100644 index 000000000..6c6821be4 --- /dev/null +++ b/core/res/ships.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/spellbooks/cerddor.xml b/core/res/spellbooks/cerddor.xml new file mode 100644 index 000000000..50e1dbf0c --- /dev/null +++ b/core/res/spellbooks/cerddor.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/core/res/spellbooks/draig.xml b/core/res/spellbooks/draig.xml new file mode 100644 index 000000000..592516d75 --- /dev/null +++ b/core/res/spellbooks/draig.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/core/res/spellbooks/gray.xml b/core/res/spellbooks/gray.xml new file mode 100644 index 000000000..6ce74e537 --- /dev/null +++ b/core/res/spellbooks/gray.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/core/res/spellbooks/gwyrrd.xml b/core/res/spellbooks/gwyrrd.xml new file mode 100644 index 000000000..264bc53e4 --- /dev/null +++ b/core/res/spellbooks/gwyrrd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/core/res/spellbooks/illaun.xml b/core/res/spellbooks/illaun.xml new file mode 100644 index 000000000..259fe68d8 --- /dev/null +++ b/core/res/spellbooks/illaun.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/core/res/spellbooks/tybied.xml b/core/res/spellbooks/tybied.xml new file mode 100644 index 000000000..f6db0bfd5 --- /dev/null +++ b/core/res/spellbooks/tybied.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/res/spells.xml b/core/res/spells.xml new file mode 100644 index 000000000..cb7fcf911 --- /dev/null +++ b/core/res/spells.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/spoils.xml b/core/res/spoils.xml new file mode 100644 index 000000000..952971cab --- /dev/null +++ b/core/res/spoils.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/terrains.xml b/core/res/terrains.xml new file mode 100644 index 000000000..edd8895ab --- /dev/null +++ b/core/res/terrains.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/weapons/axe.xml b/core/res/weapons/axe.xml new file mode 100644 index 000000000..7ee77b21e --- /dev/null +++ b/core/res/weapons/axe.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/core/res/weapons/bow.xml b/core/res/weapons/bow.xml new file mode 100644 index 000000000..c64392966 --- /dev/null +++ b/core/res/weapons/bow.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/weapons/catapult.xml b/core/res/weapons/catapult.xml new file mode 100644 index 000000000..2ea16d9a2 --- /dev/null +++ b/core/res/weapons/catapult.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/core/res/weapons/crossbow.xml b/core/res/weapons/crossbow.xml new file mode 100644 index 000000000..318628b81 --- /dev/null +++ b/core/res/weapons/crossbow.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/weapons/firesword.xml b/core/res/weapons/firesword.xml new file mode 100644 index 000000000..9e8284fe6 --- /dev/null +++ b/core/res/weapons/firesword.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/res/weapons/greatbow.xml b/core/res/weapons/greatbow.xml new file mode 100644 index 000000000..542bd41a8 --- /dev/null +++ b/core/res/weapons/greatbow.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/res/weapons/greatsword.xml b/core/res/weapons/greatsword.xml new file mode 100644 index 000000000..481ff680c --- /dev/null +++ b/core/res/weapons/greatsword.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/weapons/halberd.xml b/core/res/weapons/halberd.xml new file mode 100644 index 000000000..bf3fb4af7 --- /dev/null +++ b/core/res/weapons/halberd.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/core/res/weapons/laensword.xml b/core/res/weapons/laensword.xml new file mode 100644 index 000000000..31ec8b9e6 --- /dev/null +++ b/core/res/weapons/laensword.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/res/weapons/lance.xml b/core/res/weapons/lance.xml new file mode 100644 index 000000000..1271c48f9 --- /dev/null +++ b/core/res/weapons/lance.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/weapons/mallornbow.xml b/core/res/weapons/mallornbow.xml new file mode 100644 index 000000000..f61ea8e97 --- /dev/null +++ b/core/res/weapons/mallornbow.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/core/res/weapons/mallorncrossbow.xml b/core/res/weapons/mallorncrossbow.xml new file mode 100644 index 000000000..8d4a5027b --- /dev/null +++ b/core/res/weapons/mallorncrossbow.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/weapons/mallornlance.xml b/core/res/weapons/mallornlance.xml new file mode 100644 index 000000000..88973f3e0 --- /dev/null +++ b/core/res/weapons/mallornlance.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/core/res/weapons/mallornspear.xml b/core/res/weapons/mallornspear.xml new file mode 100644 index 000000000..8c3782e4c --- /dev/null +++ b/core/res/weapons/mallornspear.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/core/res/weapons/rep_crossbow.xml b/core/res/weapons/rep_crossbow.xml new file mode 100644 index 000000000..1e550a190 --- /dev/null +++ b/core/res/weapons/rep_crossbow.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/core/res/weapons/runesword.xml b/core/res/weapons/runesword.xml new file mode 100644 index 000000000..566fe1589 --- /dev/null +++ b/core/res/weapons/runesword.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/core/res/weapons/rustyaxe.xml b/core/res/weapons/rustyaxe.xml new file mode 100644 index 000000000..5399de62d --- /dev/null +++ b/core/res/weapons/rustyaxe.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/core/res/weapons/rustygreatsword.xml b/core/res/weapons/rustygreatsword.xml new file mode 100644 index 000000000..ac1385b11 --- /dev/null +++ b/core/res/weapons/rustygreatsword.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/res/weapons/rustyhalberd.xml b/core/res/weapons/rustyhalberd.xml new file mode 100644 index 000000000..4f237b989 --- /dev/null +++ b/core/res/weapons/rustyhalberd.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/core/res/weapons/rustysword.xml b/core/res/weapons/rustysword.xml new file mode 100644 index 000000000..f73dcd6e2 --- /dev/null +++ b/core/res/weapons/rustysword.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/res/weapons/spear.xml b/core/res/weapons/spear.xml new file mode 100644 index 000000000..c6479707a --- /dev/null +++ b/core/res/weapons/spear.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/core/res/weapons/sword.xml b/core/res/weapons/sword.xml new file mode 100644 index 000000000..0d3830e2e --- /dev/null +++ b/core/res/weapons/sword.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/scripts/callbacks.lua b/core/scripts/callbacks.lua new file mode 100644 index 000000000..33d34e4b5 --- /dev/null +++ b/core/scripts/callbacks.lua @@ -0,0 +1,11 @@ +callbacks = {} + +callbacks["attrib_init"] = function(attr) + if attr.name ~= nil then + local init = callbacks["init_" .. attr.name] + if init ~=nil then + init(attr) + end + end +end + diff --git a/core/scripts/default.lua b/core/scripts/default.lua new file mode 100755 index 000000000..6d22b4f32 --- /dev/null +++ b/core/scripts/default.lua @@ -0,0 +1,118 @@ +function change_locales(localechange) + for loc, flist in pairs(localechange) do + for index, name in pairs(flist) do + f = get_faction(atoi36(name)) + if f ~= nil then + f.locale = loc + print("LOCALECHANGE ", f, loc) + end + end + end +end + +function dbupdate() + update_scores() + edb = db.open(config.basepath.."/eressea.db") + if edb~=nil then + edb:update_factions() + edb:update_scores() + else + print("could not open "..config.basepath.."/eressea.db") + end +end + +function nmr_check(maxnmrs) + local nmrs = get_nmrs(1) + if nmrs >= maxnmrs then + print("Shit. More than " .. maxnmrs .. " factions with 1 NMR (" .. nmrs .. ")") + write_summary() + eressea.write_game("aborted.dat") + return -1 + end + print (nmrs .. " Factions with 1 NMR") + return 0 +end + +function open_game(turn) + file = "" .. get_turn() + return eressea.read_game(file .. ".dat") +end + +function write_emails(locales) + local files = {} + local key + local locale + local file + for key, locale in pairs(locales) do + files[locale] = io.open(config.basepath .. "/emails." .. locale, "w") + end + + local faction + for faction in factions() do + if faction.email~="" then + files[faction.locale]:write(faction.email .. "\n") + end + end + + for key, file in pairs(files) do + file:close() + end +end + +function write_addresses() + local file + local faction + + file = io.open(config.basepath .. "/adressen", "w") + for faction in factions() do + -- print(faction.id .. " - " .. faction.locale) + file:write(tostring(faction) .. ":" .. faction.email .. ":" .. faction.info .. "\n") + end + + file:close() +end + +function write_aliases() + local file + local faction + + file = io.open(config.basepath .. "/aliases", "w") + for faction in factions() do + local unit + if faction.email ~= "" then + file:write("partei-" .. itoa36(faction.id) .. ": " .. faction.email .. "\n") + for unit in faction.units do + file:write("einheit-" .. itoa36(unit.id) .. ": " .. faction.email .. "\n") + end + end + end + + file:close() +end + +function write_files(locales) + write_passwords() + write_reports() + write_summary() + -- write_emails(locales) + -- write_aliases() + -- write_addresses() +end + +function write_scores() + scores = {} + for r in regions() do + f = r.owner + if f~=nil then + value = scores[f.id] + if value==nil then value=0 end + value = value + r:get_resource("money")/100 + scores[f.id] = value + end + end + for f in factions() do + score=scores[f.id] + if score==nil then score=0 end + print(math.floor(score)..":"..f.name..":"..itoa36(f.id)) + end +end diff --git a/core/scripts/dumptable.lua b/core/scripts/dumptable.lua new file mode 100644 index 000000000..58f10e8df --- /dev/null +++ b/core/scripts/dumptable.lua @@ -0,0 +1,95 @@ +--------------------------------------------- +-- Return indentation string for passed level +--------------------------------------------- +local function tabs(i) + return string.rep(".",i).." " -- Dots followed by a space +end + +----------------------------------------------------------- +-- Return string representation of parameter's value & type +----------------------------------------------------------- +local function toStrType(t) + local function fttu2hex(t) -- Grab hex value from tostring() output + local str = tostring(t); + if str == nil then + return "tostring() failure! \n" + else + local str2 = string.match(str,"[ :][ (](%x+)") + if str2 == nil then + return "string.match() failure: "..str.."\n" + else + return "0x"..str2 + end + end + end + -- Stringify a value of a given type using a table of functions keyed + -- by the name of the type (Lua's version of C's switch() statement). + local stringify = { + -- Keys are all possible strings that type() may return, + -- per http://www.lua.org/manual/5.1/manual.html#pdf-type. + ["nil"] = function(v) return "nil (nil)" end, + ["string"] = function(v) return '"'..v..'" (string)' end, + ["number"] = function(v) return v.." (number)" end, + ["boolean"] = function(v) return tostring(v).." (boolean)" end, + ["function"] = function(v) return fttu2hex(v).." (function)" end, + ["table"] = function(v) return fttu2hex(v).." (table)" end, + ["thread"] = function(v) return fttu2hex(v).." (thread)" end, + ["userdata"] = function(v) return fttu2hex(v).." (userdata)" end + } + return stringify[type(t)](t) +end + +------------------------------------- +-- Count elements in the passed table +------------------------------------- +local function lenTable(t) -- What Lua builtin does this simple thing? + local n=0 -- '#' doesn't work with mixed key types + if ("table" == type(t)) then + for key in pairs(t) do -- Just count 'em + n = n + 1 + end + return n + else + return nil + end +end + +-------------------------------- +-- Pretty-print the passed table +-------------------------------- +local function do_dumptable(t, indent, seen) + -- "seen" is an initially empty table used to track all tables + -- that have been dumped so far. No table is dumped twice. + -- This also keeps the code from following self-referential loops, + -- the need for which was found when first dumping "_G". + if ("table" == type(t)) then -- Dump passed table + seen[t] = 1 + if (indent == 0) then + print ("The passed table has "..lenTable(t).." entries:") + indent = 1 + end + for f,v in pairsByKeys(t) do + if ("table" == type(v)) and (seen[v] == nil) then -- Recurse + print( tabs(indent)..toStrType(f).." has "..lenTable(v).." entries: {") + do_dumptable(v, indent+1, seen) + print( tabs(indent).."}" ) + else + print( tabs(indent)..toStrType(f).." = "..toStrType(v)) + end + end + else + print (tabs(indent).."Not a table!") + end +end + +-------------------------------- +-- Wrapper to handle persistence +-------------------------------- +function dumptable(t) -- Only global declaration in the package + -- This wrapper exists only to set the environment for the first run: + -- The second param is the indentation level. + -- The third param is the list of tables dumped during this call. + -- Getting this list allocated and freed was a pain, and this + -- wrapper was the best solution I came up with... + return do_dumptable(t, 0, {}) +end diff --git a/core/scripts/eventbus.lua b/core/scripts/eventbus.lua new file mode 100644 index 000000000..e69de29bb diff --git a/core/scripts/gates.lua b/core/scripts/gates.lua new file mode 100644 index 000000000..aeb1e2a3c --- /dev/null +++ b/core/scripts/gates.lua @@ -0,0 +1,25 @@ +-- implements gates and travel between them +-- used in HSE and Eressea + +function gate_travel(b, units) + -- we've found which units we want to exchange, now swap them: + local u + for key, u in pairs(units) do + u.region = b.region + u.building = b + end +end + +function gate_units(b, maxsize) + local size = maxsize + local units = {} + local u + + for u in b.units do + if u.number<=size and u.weight<=u.capacity then + units[u] = u + size = size - u.number + end + end + return units +end diff --git a/core/scripts/init.lua b/core/scripts/init.lua new file mode 100755 index 000000000..bafa3da63 --- /dev/null +++ b/core/scripts/init.lua @@ -0,0 +1,43 @@ +require(config.game .. ".modules") +require "default" +require "resources" + +function run_editor() + local turn = get_turn() + if turn==0 then + turn = read_turn() + set_turn(turn) + end + eressea.read_game(turn .. ".dat") + gmtool.editor() +end + +function run_tests() + print("running tests") + require "lunit" + lunit.clearstats() + local argv = tests or {} + local stats = lunit.main(argv) + if stats.errors > 0 or stats.failed > 0 then + return 1 + end + return 0 +end + +function run_turn() + require(config.game .. ".main") + + local turn = get_turn() + if turn==0 then + turn = read_turn() + set_turn(turn) + end + + orderfile = orderfile or config.basepath .. '/orders.' .. turn + print("executing turn " .. get_turn() .. " with " .. orderfile) + local result = process(orderfile) + if result==0 then + dbupdate() + end + return result +end diff --git a/core/scripts/multis.lua b/core/scripts/multis.lua new file mode 100644 index 000000000..ad0b77783 --- /dev/null +++ b/core/scripts/multis.lua @@ -0,0 +1,101 @@ +function kill_nonstarters() + for f in factions() do + if f.lastturn==1 then + kill_faction(f, true) + end + end +end + +function kill_multis(multis, destructive) + for idx, fno in ipairs(multis) do + local f = get_faction(fno) + if f~=nil and f.email=="doppelspieler@eressea.de" then + kill_faction(f, destructive) + end + end +end + +function mark_multis(multis, block) + if multis~=nil then + for idx, fno in ipairs(multis) do + local f = get_faction(fno) + if f~=nil and f.email~="doppelspieler@eressea.de" then + print("* multi-player " .. tostring(f)) + mark_multi(f, block) + end + end + end +end + +-- destroy a faction +-- destructive: kill all of its buildings and the home region, too. + +function kill_faction(f, destructive) + for u in f.units do + local r = u.region + local b = u.building + unit.destroy(u) + if destructive and b~=nil then + building.destroy(b) + local nuke = true + for v in r.units do + if v.faction.id~=f.id then + -- print("cannot nuke: " .. tostring(v.faction)) + nuke = false + break + end + end + r.terrain_name = nil + if nuke and num_oceans(r)<=1 then + -- print("nuke!") + r.terrain = "ocean" + else + -- print("cannot nuke: > 1 oceans") + r.terrain = "glacier" + r.peasants = 10 + r:set_resource("money", 100) + b = building.create(r, "monument") + b.size = 1 + b.name = "Memento Mori" + b.info = "Eine kleine " .. translate("race::" .. f.race .."_x") .. "-Statue erinnert hier an ein verschwundenes Volk" + end + end + end + faction.destroy(f) +end + +local function mark_multi(f, block) + f.password = "doppelspieler" + f.email = "doppelspieler@eressea.de" + f.banner = "Diese Partei steht wegen vermuteten Doppelspiels unter Beobachtung." + for u in f.units do + u.race_name = "toad" + if block and u.building~=nil then + local found = false + for u2 in u.region.units do + if u2.faction.id~=u.faction.id then + found = true + break + end + end + if not found then + u.region.terrain_name = "firewall" + u.region:set_flag(2) -- RF_BLOCKED + end + end + end +end + +local function num_oceans(r) + local oceans = 0 + local p = r:next(5) + for d = 0,5 do + local n = r:next(d) + if p.terrain~="ocean" and n.terrain=="ocean" then + oceans = oceans +1 + end + p = n + end + return oceans +end + diff --git a/core/scripts/resources.lua b/core/scripts/resources.lua new file mode 100644 index 000000000..688fe3d1d --- /dev/null +++ b/core/scripts/resources.lua @@ -0,0 +1,89 @@ +function peasant_getresource(u) + return u.region:get_resource("peasant") +end + +function peasant_changeresource(u, delta) + local p = u.region:get_resource("peasant") + p = p + delta + if p < 0 then + p = 0 + end + u.region:set_resource("peasant", p) + return p +end + +function hp_getresource(u) + return u.hp +end + +function hp_changeresource(u, delta) + local hp = u.hp + delta + + if hp < u.number then + if hp < 0 then + hp = 0 + end + u.number = hp + end + u.hp = hp + return hp +end + +function horse_limit(r) + return r:get_resource("horse") +end + +function horse_produce(r, n) + local horses = r:get_resource("horse") + if horses>=n then + r:set_resource("horse", horses-n) + else + r:set_resource("horse", 0) + end +end + +function log_limit(r) +-- if r:get_flag(1) then -- RF_MALLORN +-- return 0 +-- end + return r:get_resource("tree") + r:get_resource("sapling") +end + +function log_produce(r, n) + local trees = r:get_resource("tree") + if trees>=n then + r:set_resource("tree", trees-n) + else + r:set_resource("tree", 0) + n = n - trees + trees = r:get_resource("sapling") + if trees>=n then + r:set_resource("sapling", trees-n) + else + r:set_resource("sapling", 0) + end + end +end + +function mallorn_limit(r) + if not r:get_flag(1) then -- RF_MALLORN + return 0 + end + return r:get_resource("tree") + r:get_resource("sapling") +end + +function mallorn_produce(r, n) + local trees = r:get_resource("tree") + if trees>=n then + r:set_resource("tree", trees-n) + else + r:set_resource("tree", 0) + n = n - trees + trees = r:get_resource("sapling") + if trees>=n then + r:set_resource("sapling", trees-n) + else + r:set_resource("sapling", 0) + end + end +end diff --git a/core/scripts/schema.sql b/core/scripts/schema.sql new file mode 100644 index 000000000..f2b88d16e --- /dev/null +++ b/core/scripts/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE email(id INTEGER PRIMARY KEY, md5 VARCHAR(32) UNIQUE NOT NULL, email VARCHAR(32), bounces INT DEFAULT 0, confirmed TIMESTAMP DEFAULT NULL); +CREATE TABLE faction (id INTEGER PRIMARY KEY, user_id INTEGER REFERENCES user(id), no INTEGER, name VARCHAR(64), game_id INTEGER REFERENCES game(id), race VARCHAR(10), lang CHAR(2)); +CREATE TABLE faction_email (faction_id INTEGER REFERENCES faction(id), email_id INTEGER REFERENCES email(id)); +CREATE TABLE game (id INTEGER PRIMARY KEY, name VARCHAR(20), last_turn INTEGER); +CREATE TABLE score (turn INTEGER, faction_id INTEGER REFERENCES faction(id), value INTEGER, UNIQUE(turn, faction_id)); +CREATE TABLE user(id INTEGER PRIMARY KEY, email_id INTEGER REFERENCES email(id), creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP); diff --git a/core/scripts/setup.lua b/core/scripts/setup.lua new file mode 100644 index 000000000..e4bfb726b --- /dev/null +++ b/core/scripts/setup.lua @@ -0,0 +1,16 @@ +local srcpath = config.source_dir +local respath = srcpath .. '/' .. config.game .. '/res/' +local paths = { config.game..'/scripts/?.lua';'lunit/?.lua','external/lunit/?.lua','scripts/?.lua';'scripts/?' } + +tests = {'common'} +for idx, test in pairs(tests) do + tests[idx] = srcpath .. '/scripts/tests/' .. test .. '.lua' +end + +for idx, path in pairs(paths) do + package.path = srcpath .. '/' .. path .. ';' .. package.path +end + +read_xml(respath..'config-'..config.game..'.xml', respath..'catalog-'..config.game..'.xml') + +require "init" diff --git a/core/scripts/spells.lua b/core/scripts/spells.lua new file mode 100644 index 000000000..29991376c --- /dev/null +++ b/core/scripts/spells.lua @@ -0,0 +1,156 @@ +function creation_message(mage, type, number) + local msg = message.create("item_create_spell") + local err = 0 + err = err + msg:set_unit("mage", mage) + err = err + msg:set_int("number", number) + err = err + msg:set_resource("item", type) + if err ~= 0 then + return nil + else + return msg + end +end + +local function create_item(mage, level, name, number) + local count = number or 1 + mage:add_item(name, count); + local msg = creation_message(mage, name, count) + msg:send_faction(mage.faction) + return level +end + +-- Wasser des Lebens +function create_potion_p2(r, mage, level, force) + return create_item(mage, level, "p2", level) +end + +-- Siebenmeilentee +function create_potion_p0(r, mage, level, force) + return create_item(mage, level, "p0", level) +end + +-- Wundsalbe +function create_potion_ointment(r, mage, level, force) + return create_item(mage, level, "ointment", level) +end + +-- Bauernblut +function create_potion_peasantblood(r, mage, level, force) + return create_item(mage, level, "peasantblood", level) +end + +-- Pferdeglück +function create_potion_p9(r, mage, level, force) + return create_item(mage, level, "p9", level) +end + +-- Schaffenstrunk +function create_potion_p3(r, mage, level, force) + return create_item(mage, level, "p3", level) +end + +-- Heiltrank +function create_potion_p14(r, mage, level, force) + return create_item(mage, level, "p14", level) +end + +-- Elixier der Macht +function create_potion_p13(r, mage, level, force) + return create_item(mage, level, "p13", level) +end + +-- Erschaffe ein Flammenschwert +function create_firesword(r, mage, level, force) + return create_item(mage, level, "firesword") +end + +-- Erschaffe einen Gürtel der Trollstärke +function create_trollbelt(r, mage, level, force) + return create_item(mage, level, "trollbelt") +end + +-- Erschaffe einen Ring der Unsichtbarkeit +function create_roi(r, mage, level, force) + return create_item(mage, level, "roi") +end + +-- Erschaffe einen Ring der flinken Finger +function create_roqf(r, mage, level, force) + return create_item(mage, level, "roqf") +end + +-- Erschaffe ein Amulett des wahren Sehens +function create_aots(r, mage, level, force) + return create_item(mage, level, "aots") +end + +-- Erschaffe einen magischen Kräuterbeutel +function create_magicherbbag(r, mage, level, force) + return create_item(mage, level, "magicherbbag") +end + +-- Erschaffe einen Taktikkristal +function create_dreameye(r, mage, level, force) + return create_item(mage, level, "dreameye") +end + +-- Erschaffe einen Antimagiekristall +function create_antimagic(r, mage, level, force) + return create_item(mage, level, "antimagic") +end + +-- Erschaffe eine Sphäre der Unsichtbarkeit +function create_invisibility_sphere(r, mage, level, force) + return create_item(mage, level, "sphereofinv") +end + +-- Erschaffe einen Gürtel der Keuschheit +function create_chastitybelt(r, mage, level, force) + return create_item(mage, level, "ao_chastity") +end + +-- Erschaffe ein Runenschwert +function create_runesword(r, mage, level, force) + return create_item(mage, level, "runesword") +end + +-- Erschaffe ein Aurafokus +function create_focus(r, mage, level, force) + return create_item(mage, level, "aurafocus") +end + +-- Erschaffe einen Ring der Macht +function create_rop(r, mage, level, force) + return create_item(mage, level, "rop") +end + +-- Erschaffe einen Ring der Regeneration +function create_ror(r, mage, level, force) + return create_item(mage, level, "ror") +end + +-- Erschaffe einen Zauberbeutel +function create_bagofholding(r, mage, level, force) + return create_item(mage, level, "magicbag") +end + +-- TODO: +function earn_silver(r, mage, level, force) + local money = r:get_resource("money") + local wanted = 50 * force + local amount = wanted + if wanted > money then + amount = money + end + r:set_resource("money", money - amount) + mage:add_item("money", amount) + + local msg = message.create("income") + msg:set_unit("unit", mage) + msg:set_region("region", r) + msg:set_int("mode", 6) + msg:set_int("wanted", wanted) + msg:set_int("amount", amount) + msg:send_faction(mage.faction) + return level +end diff --git a/core/scripts/tests/attrib.lua b/core/scripts/tests/attrib.lua new file mode 100755 index 000000000..73d961d94 --- /dev/null +++ b/core/scripts/tests/attrib.lua @@ -0,0 +1,53 @@ +require "lunit" + +module("tests.eressea.attrib", package.seeall, lunit.testcase) + +function has_attrib(u, value) + for a in u.attribs do + if (a.data==value) then return true end + end + return false +end + +function test_attrib_global() + a = attrib.create('global', {}) + eressea.write_game('attrib.dat') + eressea.free_game() + eressea.read_game('attrib.dat') +end + +function test_attrib() + local r = region.create(0,0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + local u2 = unit.create(f, r, 1) + data = { arr = { 'a', 'b', 'c' }, name = 'familiar', events = { die = 'familiar_died' }, data = { mage = u2 } } + a = { 'a' } + b = { 'a' } + uno = u.id + u2no = u2.id + a = attrib.create(u, 12) + a = attrib.create(u, "enno") + a = attrib.create(u, u2) + a = attrib.create(u, data) + eressea.write_game("attrib.dat") + eressea.free_game() + eressea.read_game("attrib.dat") + u = get_unit(uno) + u2 = get_unit(u2no) + assert_false(has_attrib(u, 42)) + assert_true(has_attrib(u, "enno")) + assert_true(has_attrib(u, 12)) + + for a in u.attribs do + x = a.data + if (type(x)=="table") then + assert_equal('a', x.arr[1]) + assert_equal('familiar', x.name) + assert_equal('familiar_died', x.events.die) + assert_equal(u2, x.data.mage) + break + end + end +end + diff --git a/core/scripts/tests/bindings.lua b/core/scripts/tests/bindings.lua new file mode 100755 index 000000000..189d34769 --- /dev/null +++ b/core/scripts/tests/bindings.lua @@ -0,0 +1,61 @@ +require "lunit" + +local eressea = eressea +local _G = _G + +module("tests.bindings", lunit.testcase) + +function test_eressea() + assert_equal("function", _G.type(eressea.free_game)) + assert_equal("function", _G.type(eressea.read_game)) + assert_equal("function", _G.type(eressea.write_game)) + assert_equal("function", _G.type(eressea.read_orders)) +end + +function test_process() + assert_equal("function", _G.type(eressea.process.update_long_order)) + assert_equal("function", _G.type(eressea.process.markets)) + assert_equal("function", _G.type(eressea.process.produce)) + + assert_equal("function", _G.type(eressea.process.make_temp)) + assert_equal("function", _G.type(eressea.process.settings)) + assert_equal("function", _G.type(eressea.process.set_allies)) + assert_equal("function", _G.type(eressea.process.set_prefix)) + assert_equal("function", _G.type(eressea.process.set_stealth)) + assert_equal("function", _G.type(eressea.process.set_status)) + assert_equal("function", _G.type(eressea.process.set_name)) + assert_equal("function", _G.type(eressea.process.set_group)) + assert_equal("function", _G.type(eressea.process.set_origin)) + assert_equal("function", _G.type(eressea.process.quit)) + assert_equal("function", _G.type(eressea.process.study)) + assert_equal("function", _G.type(eressea.process.movement)) + assert_equal("function", _G.type(eressea.process.use)) + assert_equal("function", _G.type(eressea.process.battle)) + assert_equal("function", _G.type(eressea.process.siege)) + assert_equal("function", _G.type(eressea.process.leave)) + assert_equal("function", _G.type(eressea.process.promote)) + assert_equal("function", _G.type(eressea.process.renumber)) + assert_equal("function", _G.type(eressea.process.restack)) + assert_equal("function", _G.type(eressea.process.set_spells)) + assert_equal("function", _G.type(eressea.process.set_help)) + assert_equal("function", _G.type(eressea.process.contact)) + assert_equal("function", _G.type(eressea.process.enter)) + assert_equal("function", _G.type(eressea.process.magic)) + assert_equal("function", _G.type(eressea.process.give_control)) + assert_equal("function", _G.type(eressea.process.regeneration)) + assert_equal("function", _G.type(eressea.process.guard_on)) + assert_equal("function", _G.type(eressea.process.guard_off)) + assert_equal("function", _G.type(eressea.process.explain)) + assert_equal("function", _G.type(eressea.process.messages)) + assert_equal("function", _G.type(eressea.process.reserve)) + assert_equal("function", _G.type(eressea.process.claim)) + assert_equal("function", _G.type(eressea.process.follow)) + assert_equal("function", _G.type(eressea.process.alliance)) + assert_equal("function", _G.type(eressea.process.idle)) + assert_equal("function", _G.type(eressea.process.set_default)) +end + +function test_settings() + assert_equal("function", _G.type(eressea.settings.set)) + assert_equal("function", _G.type(eressea.settings.get)) +end diff --git a/core/scripts/tests/bson.lua b/core/scripts/tests/bson.lua new file mode 100755 index 000000000..ec0966bcd --- /dev/null +++ b/core/scripts/tests/bson.lua @@ -0,0 +1,65 @@ +require "lunit" + +module("tests.eressea.bson", package.seeall, lunit.testcase) + +function setup() + eressea.free_game() +end + +function test_bson_create() + local a = attrib.create("global", 12) + assert_not_equal(nil, a) + for a in attrib.get("global") do + assert_equal(a.data, 12) + end +end + +function test_illegal_arg() + local a = attrib.create(nil, 42) + assert_equal(nil, a) + a = attrib.create("fred", 42) + assert_equal(nil, a) +end + +function test_bson_readwrite() + local i, r = region.create(0, 0, "mountain") + attrib.create(r, 42) + i = eressea.write_game("test_read_write.dat") + assert_equal(0, i) + eressea.free_game() + r = get_region(0, 0) + assert_equal(nil, r) + i = eressea.read_game("test_read_write.dat") + assert_equal(0, i) + r = get_region(0, 0) + assert_not_equal(nil, r) + for a in attrib.get(r) do + assert_equal(a.data, 42) + end +end + +function test_bson() + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + assert_not_equal(nil, u) + assert_not_equal(nil, r) + assert_not_equal(nil, f) + attrib.create(r, 1) + assert_equal(attrib.get(r)().data, 1) + attrib.create(u, 3) + assert_equal(attrib.get(u)().data, 3) + attrib.create(f, 5) + assert_equal(attrib.get(f)().data, 5) +end + +function test_bson_with_multiple_attribs() + local r = region.create(0, 0, "mountain") + attrib.create(r, { a=1}) + attrib.create(r, { a=5}) + local total = 0 + for a in attrib.get(r) do + total = total + a.data.a; + end + assert_equal(6, total) +end diff --git a/core/scripts/tests/common.lua b/core/scripts/tests/common.lua new file mode 100755 index 000000000..88dae3080 --- /dev/null +++ b/core/scripts/tests/common.lua @@ -0,0 +1,1339 @@ +require "lunit" + +local function _test_create_ship(r) + local s = ship.create(r, config.ships[1]) + return s +end + +local function _test_unique_btype() + local utype = nil + for i = 1, #config.buildings do + bt = config.buildings[i] + if ((config.get_building(bt).flags / 4) % 2) ~= 0 then -- #define BTF_UNIQUE 0x04 + if ((config.get_building(bt).flags / 2) % 2) == 0 then -- #define BTF_NOBUILD 0x02 + utype = bt + end + end + end + return utype +end + +local function one_unit(r, f) + local u = unit.create(f, r, 1) + u:add_item("money", u.number * 100) + u:clear_orders() + return u +end + +local function two_units(r, f1, f2) + return one_unit(r, f1), one_unit(r, f2) +end + +local function two_factions() + local f1 = faction.create("noreply@eressea.de", "human", "de") + local f2 = faction.create("noreply@eressea.de", "elf", "de") + return f1, f2 +end + +module("tests.eressea.common", package.seeall, lunit.testcase) + +function setup() + eressea.free_game() + eressea.write_game("free.dat") + eressea.settings.set("nmr.removenewbie", "0") + eressea.settings.set("nmr.timeout", "0") + eressea.settings.set("NewbieImmunity", "0") + eressea.settings.set("rules.economy.food", "4") + eressea.settings.set("rules.encounters", "0") +end + +function test_fleeing_units_can_be_transported() + local r = region.create(0, 0, "plain") + local r1 = region.create(1, 0, "plain") + local f1, f2 = two_factions() + local u1, u2 = two_units(r, f1, f2) + local u3 = one_unit(r, f2) + u1.number = 100 + u1:add_order("ATTACKIEREN " .. itoa36(u2.id)) + u2.number = 100 + u2:add_order("FAHREN " .. itoa36(u3.id)) + u2:add_order("KAEMPFE FLIEHE") + u3.number = 100 + u3:add_order("KAEMPFE FLIEHE") + u3:add_order("TRANSPORT " .. itoa36(u2.id)) + u3:add_order("NACH O ") + u3:set_skill("riding", 2) + u3:add_item("horse", u2.number) + u3:add_order("KAEMPFE FLIEHE") + process_orders() + assert_equal(u3.region.id, r1.id, "transporter did not move") + assert_equal(u2.region.id, r1.id, "transported unit did not move") +end + +function test_plane() + local pl = plane.create(0, -3, -3, 7, 7) + local nx, ny = plane.normalize(pl, 4, 4) + assert_equal(nx, -3, "normalization failed") + assert_equal(ny, -3, "normalization failed") + local f = faction.create("noreply@eressea.de", "human", "de") + f.id = atoi36("tpla") + local r, x, y + for x = -3, 3 do for y = -3, 3 do + r = region.create(x, y, "plain") + if x==y then + local u = unit.create(f, r, 1) + end + end end +end + +function test_pure() + local r = region.create(0, 0, "plain") + assert_not_equal(nil, r) + assert_equal(r, get_region(0, 0)) +end + +function test_read_write() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r) + u.number = 2 + local fno = f.id + local uno = u.id + local result = 0 + assert_equal(r.terrain, "plain") + result = eressea.write_game("test_read_write.dat") + assert_equal(result, 0) + assert_not_equal(get_region(0, 0), nil) + assert_not_equal(get_faction(fno), nil) + assert_not_equal(get_unit(uno), nil) + r = nil + f = nil + u = nil + eressea.free_game() + assert_equal(get_region(0, 0), nil) + assert_equal(nil, get_faction(fno)) + assert_equal(nil, get_unit(uno)) + result = eressea.read_game("test_read_write.dat") + assert_equal(0, result) + assert_not_equal(nil, get_region(0, 0)) + assert_not_equal(nil, get_faction(fno)) + assert_not_equal(nil, get_unit(uno)) +end + +function test_gmtool() + local r1 = region.create(1, 0, "plain") + local r2 = region.create(1, 1, "plain") + local r3 = region.create(1, 2, "plain") + gmtool.open() + gmtool.select(r1, true) + gmtool.select_at(0, 1, true) + gmtool.select(r2, true) + gmtool.select_at(0, 2, true) + gmtool.select(r3, false) + gmtool.select(r3, true) + gmtool.select_at(0, 3, false) + gmtool.select(r3, false) + + local selections = 0 + for r in gmtool.get_selection() do + selections=selections+1 + end + assert_equal(2, selections) + assert_equal(nil, gmtool.get_cursor()) + + gmtool.close() +end + +function test_faction() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + assert(f) + f.info = "Spazz" + assert(f.info=="Spazz") + f:add_item("donotwant", 42) + f:add_item("stone", 42) + f:add_item("sword", 42) + local items = 0 + for u in f.items do + items = items + 1 + end + assert(items==2) + unit.create(f, r) + unit.create(f, r) + local units = 0 + for u in f.units do + units = units + 1 + end + assert(units==2) +end + +function test_unit() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r) + u.number = 20 + u.name = "Enno" + assert(u.name=="Enno") + u.info = "Spazz" + assert(u.info=="Spazz") + u:add_item("sword", 4) + assert(u:get_item("sword")==4) + assert(u:get_pooled("sword")==4) + u:use_pooled("sword", 2) + assert(u:get_item("sword")==2) +end + +function test_region() + local r = region.create(0, 0, "plain") + r:set_resource("horse", 42) + r:set_resource("money", 45) + r:set_resource("peasant", 200) + assert(r:get_resource("horse") == 42) + assert(r:get_resource("money") == 45) + assert(r:get_resource("peasant") == 200) + r.name = nil + r.info = nil + assert(r.name=="") + assert(r.info=="") + r.name = "Alabasterheim" + r.info = "Hier wohnen die siebzehn Zwerge" + assert(tostring(r) == "Alabasterheim (0,0)") +end + +function test_building() + local u + local f = faction.create("noreply@eressea.de", "human", "de") + local r = region.create(0, 0, "plain") + local b = building.create(r, "castle") + u = unit.create(f, r) + u.number = 1 + u.building = b + u = unit.create(f, r) + u.number = 2 + -- u.building = b + u = unit.create(f, r) + u.number = 3 + u.building = b + local units = 0 + for u in b.units do + units = units + 1 + end + assert(units==2) + local r2 = region.create(0, 1, "plain") + assert(b.region==r) + b.region = r2 + assert(b.region==r2) + assert(r2.buildings()==b) +end + +function test_message() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r) + local msg = message.create("item_create_spell") + msg:set_unit("mage", u) + msg:set_int("number", 1) + msg:set_resource("item", "sword") + msg:send_region(r) + msg:send_faction(f) + + return msg +end + +function test_hashtable() + local f = faction.create("noreply@eressea.de", "human", "de") + f.objects:set("enno", "smart guy") + f.objects:set("age", 10) + assert(f.objects:get("jesus") == nil) + assert(f.objects:get("enno") == "smart guy") + assert(f.objects:get("age") == 10) + f.objects:set("age", nil) + assert(f.objects:get("age") == nil) +end + +function test_events() + local fail = 1 + local function msg_handler(u, evt) + str = evt:get(0) + u2 = evt:get(1) + assert(u2~=nil) + assert(str=="Du Elf stinken") + message_unit(u, u2, "thanks unit, i got your message: " .. str) + message_faction(u, u2.faction, "thanks faction, i got your message: " .. str) + message_region(u, "thanks region, i got your message: " .. str) + fail = 0 + end + + plain = region.create(0, 0, "plain") + skill = 8 + + f = faction.create("noreply@eressea.de", "elf", "de") + f.age = 20 + + u = unit.create(f, plain) + u.number = 1 + u:add_item("money", u.number*100) + u:clear_orders() + u:add_order("NUMMER PARTEI test") + u:add_handler("message", msg_handler) + msg = "BOTSCHAFT EINHEIT " .. itoa36(u.id) .. " Du~Elf~stinken" + f = faction.create("noreply@eressea.de", "elf", "de") + f.age = 20 + + u = unit.create(f, plain) + u.number = 1 + u:add_item("money", u.number*100) + u:clear_orders() + u:add_order("NUMMER PARTEI eviL") + u:add_order(msg) + process_orders() + assert(fail==0) +end + +function test_recruit2() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r) + u.number = 1 + u:add_item("money", 2000) + u:clear_orders() + u:add_order("MACHE TEMP 1") + u:add_order("REKRUTIERE 1 Elf") + u:add_order("REKRUTIERE 1 Mensch") + u:add_order("REKRUTIERE 1") + process_orders() +end + +function test_guard() + region.create(1, 0, "plain") + local r = region.create(0, 0, "plain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + f1.age = 20 + local u1 = unit.create(f1, r, 10) + u1:add_item("sword", 10) + u1:add_item("money", 10) + u1:set_skill("melee", 10) + u1:clear_orders() + u1:add_order("NACH O") + u1.name="Kalle Pimp" + + local f2 = faction.create("noreply@eressea.de", "human", "de") + f2.age = 20 + local u2 = unit.create(f2, r, 1) + local u3 = unit.create(f2, r, 1) + local b = building.create(r, "castle") + b.size = 10 + u2.building = b + u3.building = b + u2:clear_orders() + u2:add_order("ATTACKIEREN " .. itoa36(u1.id)) -- you will die... + u2:add_item("money", 100) + u3:add_item("money", 100) + process_orders() + assert_equal(r, u1.region, "unit may not move after combat") +end + +function test_recruit() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r) + u.number = 1 + local n = 3 + r:set_resource("peasant", 200) + u:clear_orders() + u:add_item("money", 110*n+20) + u:add_order("REKRUTIERE " .. n) + process_orders() + assert(u.number == n+1) + local p = r:get_resource("peasant") + assert(p<200 and p>=200-n) + -- assert(u:get_item("money")==10) +end + +function test_produce() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:clear_orders() + local sword = config.get_resource('sword') + u:set_skill(sword.build_skill_name, 3) + u:add_item("iron", 10) + u:add_item("money", u.number * 10) + u:add_order("MACHE Schwert") + process_orders() + assert_equal(10-3/sword.build_skill_min*sword.materials['iron'], u:get_item("iron")) + assert_equal(3/sword.build_skill_min, u:get_item("sword")) +end + +function test_work() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:add_item("money", u.number * 10) -- humans cost 10 + u:set_skill("herbalism", 5) + u:clear_orders() + u:add_order("ARBEITEN") + process_orders() + assert(u:get_item("money")>=10) +end + +function test_upkeep() + eressea.settings.set("rules.economy.food", "0") + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 5) + u:add_item("money", u.number * 11) + u:clear_orders() + u:add_order("LERNE Waffenbau") + process_orders() + assert(u:get_item("money")==u.number) +end + +function test_id() + local r = region.create(0, 0, "plain") + + local f = faction.create("noreply@eressea.de", "human", "de") + f.id = atoi36("42") + assert(get_faction(42)~=f) + assert(get_faction("42")==f) + assert(get_faction(atoi36("42"))==f) + + local u = unit.create(f, r, 1) + u.id = atoi36("42") + assert(get_unit(42)~=u) + assert(get_unit("42")==u) + assert(get_unit(atoi36("42"))==u) + + local b = building.create(r, "castle") + -- b.id = atoi36("42") + local fortytwo = itoa36(b.id) + assert(get_building(fortytwo)==b) + assert(get_building(atoi36(fortytwo))==b) + + local s = _test_create_ship(r) + assert_not_nil(s) + -- s.id = atoi36("42") + local fortytwo = itoa36(s.id) + assert(get_ship(fortytwo)==s) + assert(get_ship(atoi36(fortytwo))==s) +end + +function test_herbalism() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:add_item("money", u.number * 100) + u:set_skill("herbalism", 5) + u:clear_orders() + u:add_order("MACHE Samen") + process_orders() +end + +function test_mallorn() + local r = region.create(0, 0, "plain") + r:set_flag(1, false) -- not mallorn + r:set_resource("tree", 100) + assert(r:get_resource("tree")==100) + local m = region.create(0, 0, "plain") + m:set_flag(1, true) -- mallorn + m:set_resource("tree", 100) + assert(m:get_resource("tree")==100) + + local f = faction.create("noreply@eressea.de", "human", "de") + + local u1 = unit.create(f, r, 1) + u1:add_item("money", u1.number * 100) + u1:set_skill("forestry", 2) + u1:clear_orders() + u1:add_order("MACHE HOLZ") + + local u2 = unit.create(f, m, 1) + u2:add_item("money", u2.number * 100) + u2:set_skill("forestry", 2) + u2:clear_orders() + u2:add_order("MACHE HOLZ") + + local u3 = unit.create(f, m, 1) + u3:add_item("money", u3.number * 100) + u3:set_skill("forestry", 2) + u3:clear_orders() + u3:add_order("MACHE Mallorn") + + process_orders() + + assert_equal(2, u1:get_item("log")) + assert_equal(2, u2:get_item("log")) + local mallorn_cfg = config.get_resource("mallorn") + if mallorn_cfg then + assert_equal(1, u3:get_item("mallorn")) + else + assert_equal(-1, u3:get_item("mallorn")) + assert_equal(0, u3:get_item("log")) + end +end + +function test_coordinate_translation() + local pl = plane.create(1, 500, 500, 1001, 1001) -- astralraum + local pe = plane.create(1, -8761, 3620, 23, 23) -- eternath + local r = region.create(1000, 1000, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + assert_not_equal(nil, r) + assert_equal(r.x, 1000) + assert_equal(r.y, 1000) + local nx, ny = plane.normalize(pl, r.x, r.y) + assert_equal(nx, 1000) + assert_equal(ny, 1000) + local r1 = region.create(500, 500, "plain") + f:set_origin(r1) + nx, ny = f:normalize(r1) + assert_equal(0, nx) + assert_equal(0, ny) + local r0 = region.create(0, 0, "plain") + nx, ny = f:normalize(r0) + assert_equal(0, nx) + assert_equal(0, ny) + nx, ny = f:normalize(r) + assert_equal(500, nx) + assert_equal(500, ny) + local rn = region.create(1010, 1010, "plain") + nx, ny = f:normalize(rn) + assert_equal(-491, nx) + assert_equal(-491, ny) + + local re = region.create(-8760, 3541, "plain") -- eternath + nx, ny = f:normalize(rn) + assert_equal(-491, nx) + assert_equal(-491, ny) +end + +function test_control() + local u1, u2 = two_units(region.create(0, 0, "plain"), two_factions()) + local r = u1.region + local b = building.create(r, "castle") + u1.building = b + u2.building = b + assert_equal(u1, b.owner) + u1:clear_orders() + u1:add_order("GIB " .. itoa36(u2.id) .. " KOMMANDO") + u1:add_order("VERLASSE") + process_orders() + assert_equal(u2, b.owner) +end + +function test_store_unit() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + local fid = f.id + u:add_item("money", u.number * 100) + local filename = config.basepath .. "/data/test.unit.dat" + store = storage.create(filename, "wb") + assert_not_equal(store, nil) + store:write_unit(u) + store:close() + eressea.free_game() + -- recreate world: + r = region.create(0, 0, "plain") + f = faction.create("noreply@eressea.de", "human", "de") + f.id = fid + store = storage.create(filename, "rb") + assert_not_nil(store) + u = store:read_unit() + store:close() + assert(u) + assert(u:get_item("money") == u.number * 100) +end + +function test_building_other() + local r = region.create(0,0, "plain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + local f2 = faction.create("noreply@eressea.de", "human", "de") + local b = building.create(r, "castle") + b.size = 10 + local u1 = unit.create(f1, r, 3) + u1.building = b + u1:add_item("money", 100) + + local u2 = unit.create(f2, r, 3) + u2:set_skill("building", 10) + u2:add_item("money", 100) + u2:add_item("stone", 100) + u2:clear_orders() + u2:add_order("MACHEN BURG " .. itoa36(b.id)) + process_orders() + assert_not_equal(10, b.size) +end + +function test_config() + assert_not_equal(nil, config.basepath) + assert_not_equal(nil, config.locales) +end + +local function _test_create_laen() + eressea.settings.set("rules.terraform.all", "1") + local r = region.create(0,0, "mountain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + r:set_resource("laen", 50) + return r, u1 +end + +function test_laen1() + local r, u1 = _test_create_laen() + + u1:add_item("money", 1000) + u1:set_skill("mining", 14) + u1:clear_orders() + u1:add_order("MACHEN Laen") + + process_orders() + assert_equal(0, u1:get_item("laen")) +end + +function test_laen2() + local r, u1 = _test_create_laen() + + u1:add_item("money", 1000) + u1:set_skill("mining", 15) + u1:clear_orders() + u1:add_order("MACHEN Laen") + u1.name = "Laenmeister" + + local b = building.create(r, "mine") + b.size = 10 + u1.building = b + local laen = r:get_resource("laen") + + process_orders() + init_reports() + write_report(u1.faction) + assert_equal(laen - 2, r:get_resource("laen")) + assert_equal(2, u1:get_item("laen")) +end + +function test_mine() + local r = region.create(0,0, "mountain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + + u1:add_item("money", 1000) + u1:set_skill("mining", 1) + u1:clear_orders() + u1:add_order("MACHEN Eisen") + + local b = building.create(r, "mine") + b.size = 10 + u1.building = b + local iron = r:get_resource("iron") + + process_orders() + assert_equal(2, u1:get_item("iron")) -- skill +1 + assert_equal(iron - 1, r:get_resource("iron")) -- only 1/2 is taken away +end + +function test_guard_resources() + -- this is not quite http://bugs.eressea.de/view.php?id=1756 + local r = region.create(0,0, "mountain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + f1.age=20 + local f2 = faction.create("noreply@eressea.de", "human", "de") + f2.age=20 + local u1 = unit.create(f1, r, 1) + u1:add_item("money", 100) + u1:set_skill("melee", 3) + u1:add_item("sword", 1) + u1:clear_orders() + u1:add_order("BEWACHEN") + + local u2 = unit.create(f2, r, 1) + u2:add_item("money", 100) + u2:set_skill("mining", 3) + u2:clear_orders() + u2:add_order("MACHEN EISEN") + + process_orders() + local iron = u2:get_item("iron") + assert_true(iron > 0) + process_orders() + assert_equal(iron, u2:get_item("iron")) +end + +local function is_flag_set(flags, flag) + return math.fmod(flags, flag*2) - math.fmod(flags, flag) == flag; +end + +function test_hero_hero_transfer() + local r = region.create(0,0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + f.age=20 + local UFL_HERO = 128 + + local u1 = unit.create(f, r, 1) + local u2 = unit.create(f, r, 1) + u1:add_item("money", 10000) + u1.flags = u1.flags + UFL_HERO + u2.flags = u2.flags + UFL_HERO + u1:clear_orders() + u1:add_order("GIB " .. itoa36(u2.id) .. " 1 PERSONEN") + u1:add_order("REKRUTIEREN 1") + process_orders() + assert_equal(1, u1.number) + assert_equal(2, u2.number) + assert_false(is_flag_set(u1.flags, 128), 128, "recruiting into an empty hero unit should not create a hero") + assert_true(is_flag_set(u2.flags, 128), 128, "unit is not a hero?") +end + +function test_hero_normal_transfer() + local r = region.create(0,0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + f.age=20 + local UFL_HERO = 128 + + local u1 = unit.create(f, r, 1) + local u2 = unit.create(f, r, 1) + u1:add_item("money", 10000) + u1.flags = u1.flags + UFL_HERO + u1:clear_orders() + u1:add_order("GIB " .. itoa36(u2.id) .. " 1 PERSONEN") + process_orders() + assert_equal(1, u1.number) + assert_equal(1, u2.number) + assert_true(is_flag_set(u1.flags, 128), "unit is not a hero?") + assert_false(is_flag_set(u2.flags, 128), "unit turned into a hero") +end + +function test_expensive_skills_cost_money() + local r = region.create(0,0, "mountain") + local f = faction.create("noreply@eressea.de", "elf", "de") + local u = unit.create(f, r, 1) + u:add_item("money", 10000) + u:clear_orders() + u:add_order("LERNEN MAGIE Gwyrrd") + process_orders() + assert_equal(9900, u:get_item("money")) + assert_equal(1, u:get_skill("magic")) +end + +function test_food_is_consumed() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:add_item("money", 100) + u:clear_orders() + u:add_order("LERNEN Reiten") -- don't work + eressea.settings.set("rules.economy.food", "4") + process_orders() + assert_equal(100, u:get_item("money")) +end + +function test_food_can_override() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:add_item("money", 100) + u:clear_orders() + u:add_order("LERNEN Reiten") -- don't work + eressea.settings.set("rules.economy.food", "0") + process_orders() + assert_equal(90, u:get_item("money")) +end + +function test_swim_and_survive() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + f.nam = "chaos" + local u = unit.create(f, r, 1) + process_orders() + r.terrain = "ocean" + local s = _test_create_ship(r) + u:clear_orders() + u:add_order("BETRETE SCHIFF " .. itoa36(s.id)) + process_orders() + assert_equal(u.number, 1) +end + +function test_swim_and_die() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + local uid = u.id + process_orders() + r.terrain = "ocean" + u = get_unit(uid) + assert_not_equal(get_unit(uid), nil) + process_orders() + assert_equal(get_unit(uid), nil) +end + +function test_ride_with_horse() + region.create(1, 0, "plain") + region.create(2, 0, "plain") + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:add_item("horse", 1) + local horse_cfg = config.get_resource("horse") + u:add_item("sword", (horse_cfg.capacity - u.weight)/100) + u:set_skill("riding", 2) + + u:clear_orders() + u:add_order("NACH O O") + process_orders() + assert_equal(u.region.x, 2) + + u:add_item("sword", 1) + u:clear_orders() + u:add_order("NACH W W") + process_orders() + assert_equal(u.region.x, 1) +end + +function test_ride_with_horses_and_cart() + region.create(1, 0, "plain") + region.create(2, 0, "plain") + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + local horse_cfg = config.get_resource("horse") + local cart_cfg = config.get_resource("cart") + local sword_cfg = config.get_resource("sword") + + u:set_skill("riding", 3) + + local capacity = (horse_cfg.capacity-horse_cfg.weight)*2 - u.weight + if cart_cfg~=nil then + capacity = capacity + cart_cfg.capacity-cart_cfg.weight + end + u:add_item("sword", capacity / sword_cfg.weight) + + u:add_item("horse", 1) + if cart_cfg~=nil then + -- we need 2 horses for a cart, so this should fail: + u:add_item("cart", 1) + u:clear_orders() + u:add_order("NACH O O") + process_orders() + assert_equal(0, u.region.x) + end + + -- here is your second horse, milord: + u:add_item("horse", 1) + assert_equal(2, u:get_item("horse")) + + -- ride + u:clear_orders() + u:add_order("NACH O O") + process_orders() + assert_equal(2, u.region.x) + + -- walk + u:add_item("sword", 1000/sword_cfg.weight) + u:clear_orders() + u:add_order("NACH W W") + process_orders() + assert_equal(1, u.region.x) + + -- make this fellow too heavy + u:add_item("sword", 1000/sword_cfg.weight) + u:clear_orders() + u:add_order("NACH W W") + process_orders() + assert_equal(1, u.region.x) +end + +function test_walk_and_carry_the_cart() + region.create(1, 0, "plain") + local r = region.create(2, 0, "plain") + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 10) + u:add_item("cart", 1) + + -- walk + u:clear_orders() + u:add_order("NACH O O") + process_orders() + assert_equal(1, u.region.x) +end + +module("tests.report", package.seeall, lunit.testcase) + +function setup() + eressea.free_game() + eressea.settings.set("nmr.removenewbie", "0") + eressea.settings.set("nmr.timeout", "0") + eressea.settings.set("rules.economy.food", "4") +end + +local function find_in_report(f, pattern, extension) + extension = extension or "nr" + local filename = config.reportpath .. "/" .. get_turn() .. "-" .. itoa36(f.id) .. "." .. extension + local report = io.open(filename, 'r'); + assert_not_nil(report) + t = report:read("*all") + report:close() + + local start, _ = string.find(t, pattern) +-- posix.unlink(filename) + return start~=nil +end + +function test_coordinates_no_plane() + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + init_reports() + write_report(f) + assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) +end + +function test_coordinates_named_plane() + local p = plane.create(0, -3, -3, 7, 7, "Hell") + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + init_reports() + write_report(f) + assert_true(find_in_report(f, r.name .. " %(0,0,Hell%), Berg")) +end + +function test_coordinates_unnamed_plane() + local p = plane.create(0, -3, -3, 7, 7) + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + init_reports() + write_report(f) + assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) +end + +function test_coordinates_noname_plane() + local p = plane.create(0, -3, -3, 7, 7, "") + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + init_reports() + write_report(f) + assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) +end + +function test_lighthouse() + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + region.create(1, 0, "mountain") + region.create(2, 0, "ocean") + region.create(0, 1, "firewall") + region.create(3, 0, "mountain") + region.create(4, 0, "plain") + local u = unit.create(f, r, 1) + local b = building.create(r, "lighthouse") + b.size = 100 + u.building = b + u:set_skill("perception", 9) + u:add_item("money", 1000) + assert_not_nil(b) + + init_reports() + write_report(f) + assert_true(find_in_report(f, " %(1,0%) %(vom Turm erblickt%)")) + assert_true(find_in_report(f, " %(2,0%) %(vom Turm erblickt%)")) + assert_true(find_in_report(f, " %(3,0%) %(vom Turm erblickt%)")) + + assert_false(find_in_report(f, " %(0,0%) %(vom Turm erblickt%)")) + assert_false(find_in_report(f, " %(0,1%) %(vom Turm erblickt%)")) + assert_false(find_in_report(f, " %(4,0%) %(vom Turm erblickt%)")) +end + +module("tests.parser", package.seeall, lunit.testcase) + +function setup() + eressea.free_game() + eressea.write_game("free.dat") + eressea.settings.set("rules.economy.food", "4") -- FOOD_IS_FREE + eressea.settings.set("rules.encounters", "0") + eressea.settings.set("rules.move.owner_leave", "0") +end + +function test_parser() + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + local filename = config.basepath .. "/data/orders.txt" + + local file = io.open(filename, "w") + assert_not_nil(file) + file:write('ERESSEA ' .. itoa36(f.id) .. ' "' .. f.password .. '"\n') + file:write('EINHEIT ' .. itoa36(u.id) .. "\n") + file:write("BENENNEN EINHEIT 'Goldene Herde'\n") + file:close() + + eressea.read_orders(filename) + process_orders() + assert_equal("Goldene Herde", u.name) +end + +function test_bug_1922() + -- see http://bugs.eressea.de/view.php?id=1922 + local r = region.create(0, 0, "ocean") + local r2 = region.create(1, 0, "plain") + r2:set_flag(2) -- region should not be accessible + + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + + local s = _test_create_ship(r) + + eressea.settings.set("rules.ship.drifting","0") -- ships are not drifting + eressea.settings.set("rules.economy.food","4") -- does not need silver + + u.ship = s + u:clear_orders() + u:add_order("NACH O") + u:set_skill("sailing",120) -- supadupa captain able to drive a trireme + process_orders() + assert_not_equal(r2.id, u.region.id) -- unit should not reach r2. + +end + + +function test_bug_1922_by_foot() + -- see http://bugs.eressea.de/view.php?id=1922 + local r = region.create(0, 0, "plain") + local r2 = region.create(1, 0, "plain") + r2:set_flag(2) -- region should not be accessible + + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + + eressea.settings.set("rules.economy.food","4") -- does not need silver + + u:clear_orders() + u:add_order("NACH O") + + process_orders() + assert_not_equal(r2.id, u.region.id) -- unit should not reach r2. + +end + + +function test_bug_1814() + -- see http://bugs.eressea.de/view.php?id=1814 + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + local filename = config.basepath .. "/data/1814.txt" + + local file = io.open(filename, "w") + file:write('ERESSEA ' .. itoa36(f.id) .. ' "' .. f.password .. '"\n') + file:write('EINHEIT ' .. itoa36(u.id) .. "\n") + file:write("; parse error follows: '\n") + file:write("; ARBEITE\n") + file:close() + + eressea.read_orders(filename) + process_orders() + init_reports() + write_report(f) + assert_false(find_in_report(f, "Der Befehl wurde nicht erkannt", "cr")) +end + +function test_bug_1679() + -- see http://bugs.eressea.de/view.php?id=1679 + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + local filename = config.basepath .. "/data/1679.txt" + + local file = io.open(filename, "w") + file:write('ERESSEA ' .. itoa36(f.id) .. ' "' .. f.password .. '"\n') + file:write('EINHEIT ' .. itoa36(u.id) .. "\n") + file:write("ARBEITEN\n") + file:write("NACH W\n") + file:close() + + eressea.read_orders(filename) + process_orders() + init_reports() + write_report(f) + assert_true(find_in_report(f, "Die Einheit kann keine weiteren langen Befehle", "cr")) + assert_false(find_in_report(f, "entdeckt, dass es keinen Weg nach Westen gibt")) +end + +function test_building_unique0() + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 20) + local f2 = faction.create("noreply@eressea.de", "human", "de") + local u2 = unit.create(f2, r, 20) + local utype = _test_unique_btype() + + if utype ~= nil then + assert_equal("harbour", utype) + u:set_skill("building", 20) + u:add_item("log", 10000) + u:add_item("iron", 10000) + u:add_item("stone", 10000) + u:add_item("money", 10000) + u2:set_skill("building", 20) + u2:add_item("log", 10000) + u2:add_item("iron", 10000) + u2:add_item("stone", 10000) + u2:add_item("money", 10000) + + -- build two harbours in one go + u:clear_orders() + u:add_order("MACHEN ".. translate(utype)) + u2:clear_orders() + u2:add_order("MACHEN ".. translate(utype)) + process_orders() + assert_not_nil(r.buildings) + bcount = 0 + for bs in r.buildings do + assert_equal(1, string.find(bs.name, translate(utype))) + if bs.size >= config.get_building(utype).maxsize then + bcount = bcount + 1 + end + end + assert_equal(1, bcount) -- only one should be completed + else + -- fail() -- no unique building in rules + end +end + +function test_building_unique() + local r = region.create(0, 0, "mountain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 20) + local f2 = faction.create("noreply@eressea.de", "human", "de") + local u2 = unit.create(f2, r, 20) + local utype = _test_unique_btype() + + if utype ~= nil then + u:set_skill("building", 20) + u:add_item("log", 10000) + u:add_item("iron", 10000) + u:add_item("stone", 10000) + u:add_item("money", 10000) + u2:set_skill("building", 20) + u2:add_item("log", 10000) + u2:add_item("iron", 10000) + u2:add_item("stone", 10000) + u2:add_item("money", 10000) + + -- start building two harbours + u:clear_orders() + u:add_order("MACHEN 1 Hafen") + u2:clear_orders() + u2:add_order("MACHEN 1 Hafen") + process_orders() + -- finish building partial harbours + u:clear_orders() + u:add_order("MACHEN ".. translate("harbour")) + u2:clear_orders() + u2:add_order("MACHEN Hafen") + process_orders() + if r.buildings == nil then + process_orders() + end + assert_not_nil(r.buildings) + bcount = 0 + local h1 = nil + for bs in r.buildings do + if h1 == nil then + h1 = bs.name + else + assert_equal(h1, bs.name) + end + assert_equal(1, string.find(bs.name, "Hafen")) + if bs.size >= config.get_building(utype).maxsize then + bcount = bcount + 1 + end + end + assert_equal(1, bcount) -- only one should be completed + end +end + +function test_bug_1875_use_normal() + -- see http://bugs.eressea.de/view.php?id=1875 + local r = region.create(0, 0, "plain") + r:set_resource("peasant", 0) + + eressea.settings.set("rules.economy.food", "0") -- food is not free + + local f = faction.create("noreply@eressea.de", "demon", "de") + local u = unit.create(f, r, 1) + + u:add_item("peasantblood", 1) + u:add_order("BENUTZE 1 Bauernblut") + + assert_equal(1, u:get_item("peasantblood")) + assert_equal(0, u:get_potion("peasantblood")) + + process_orders() + + assert_equal(0, u:get_item("peasantblood")) + assert_equal(0, r:get_resource("peasant")) + assert_equal(99, u:get_potion("peasantblood")) -- unit used one peasantblood effect +end + +function test_bug_1875_use_help() + -- see http://bugs.eressea.de/view.php?id=1875 + local r = region.create(0, 0, "plain") + r:set_resource("peasant", 0) + + eressea.settings.set("rules.economy.food", "0") -- food is not free + + local f = faction.create("noreply@eressea.de", "demon", "de") + local u = unit.create(f, r, 1) + local u2 = unit.create(f, r, 1) + + u:add_item("peasantblood", 1) + u:add_order("BENUTZE 1 Bauernblut") + + assert_equal(1, u:get_item("peasantblood")) + assert_equal(0, u:get_potion("peasantblood")) + assert_equal(0, u2:get_item("peasantblood")) + assert_equal(0, u2:get_potion("peasantblood")) + + process_orders() + + assert_equal(0, u:get_item("peasantblood")) + assert_equal(0, r:get_resource("peasant")) + assert_equal(0, r:get_resource("peasant")) + assert_equal(0, u2:get_potion("peasantblood")) -- first unit helps this unit + if 98~=u:get_potion("peasantblood") then + print(get_turn(), f, u) + write_reports() + end + assert_equal(98, u:get_potion("peasantblood")) -- unit uses one peasantblood effect +end + +function test_bug_1875_use_own_first() + -- see http://bugs.eressea.de/view.php?id=1875 + local r = region.create(0, 0, "plain") + r:set_resource("peasant", 0) + + eressea.settings.set("rules.economy.food", "0") -- food is not free + + local f = faction.create("noreply@eressea.de", "demon", "de") + local u = unit.create(f, r, 1) + local u2 = unit.create(f, r, 1) + + u:add_item("peasantblood", 1) + u:add_order("BENUTZE 1 Bauernblut") + u2:add_item("peasantblood", 1) + u2:add_order("BENUTZE 1 Bauernblut") + + assert_equal(1, u:get_item("peasantblood")) + assert_equal(0, u:get_potion("peasantblood")) + assert_equal(1, u2:get_item("peasantblood")) + assert_equal(0, u2:get_potion("peasantblood")) + + process_orders() + + assert_equal(0, u:get_item("peasantblood")) + assert_equal(0, r:get_resource("peasant")) + assert_equal(99, u:get_potion("peasantblood")) -- unit uses one peasantblood effect + assert_equal(99, u2:get_potion("peasantblood")) -- u2 uses its own effect before u's +end + + +function test_bug_1879_follow_unit() + local r = region.create(0, 0, "plain") + local r1 = region.create(1, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u1, u2 = two_units(r, f, f) + u1:clear_orders() + u1:set_skill("magic", 10) + u1:add_order("ZAUBERE STUFE 1 Kleine Flüche") + u1:add_order("FOLGEN EINHEIT " .. itoa36(u2.id)) + u2:clear_orders() + u2:add_order("NACH o") + process_orders() + assert_equal(u1.region.id, r1.id) + assert_equal(u2.region.id, r1.id) +end + +function test_bug_1870_leave_enter_e2() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u1, u2 = two_units(r, f, f) + local mine = building.create(r, "mine") + mine.size = 10 + u1.building = mine + + local b = building.create(r, "castle") + b.size = 10 + u2.building = b + + u1:clear_orders() + u1:add_order("LERNEN Burgenbau ") + u1:add_order("BETRETEN BURG " .. itoa36(b.id)) + + eressea.settings.set("rules.move.owner_leave", "0") + process_orders() + assert_equal(u1.building.id, b.id) +end + +function test_bug_1870_leave_enter_e3() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u1, u2 = two_units(r, f, f) + local mine = building.create(r, "mine") + mine.size = 10 + u1.building = mine + + local b = building.create(r, "castle") + b.size = 10 + u2.building = b + + u1:clear_orders() + u1:add_order("LERNEN Burgenbau ") + u1:add_order("BETRETEN BURG " .. itoa36(b.id)) + + eressea.settings.set("rules.move.owner_leave", "1") + process_orders() + assert_equal(u1.building.id, mine.id) +end + +function test_bug_1795_limit() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u1 = one_unit(r,f) + u1:add_item("money", 100000000) + u1:add_order("REKRUTIEREN 9999") + r:set_resource("peasant", 2000) -- no fractional growth! + local peasants = r:get_resource("peasant") + local limit,frac = math.modf(peasants/40) -- one day this should be a parameter + local growth = peasants * 0.001 + + process_orders() + + assert_equal(limit+1, u1.number, u1.number .. "!=" .. (limit+1)) + assert_equal(peasants+growth-limit, r:get_resource("peasant")) +end + +function test_bug_1795_demons() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "demon", "de") + local u1 = one_unit(r,f) + r:set_resource("peasant", 2000) + local peasants = r:get_resource("peasant") + local limit,frac = math.modf(peasants/40) + local growth = peasants * 0.001 + + u1:add_item("money", 100000000) + u1:add_order("REKRUTIEREN 9999") + + process_orders() + + assert_equal(limit+1, u1.number, u1.number .. "!=" .. (limit+1)) + assert_equal(peasants+growth, r:get_resource("peasant")) +end + +function test_faction_flags() + f = faction.create("noreply@eressea.de", "human", "de") + assert_equal(0, f.flags) + f.flags = 42 + assert_equal(42, f.flags) +end diff --git a/core/scripts/tests/orders.lua b/core/scripts/tests/orders.lua new file mode 100755 index 000000000..a2e568525 --- /dev/null +++ b/core/scripts/tests/orders.lua @@ -0,0 +1,266 @@ +require "lunit" + +local _G = _G +local eressea = eressea +local default_ship = config.ships[1] +local default_building = config.buildings[1] + +module("tests.orders", lunit.testcase) + +local r, f, u + +function setup() + eressea.free_game() + r = _G.region.create(0, 0, "mountain") + f = _G.faction.create("noreply@eressea.de", "human", "de") + u = _G.unit.create(f, r, 1) + u:clear_orders() + eressea.settings.set("rules.economy.food", "4") + eressea.settings.set("nmr.removenewbie", "0") + eressea.settings.set("nmr.timeout", "0") + eressea.settings.set("NewbieImmunity", "0") +end + +function test_learn() + u:add_order("LERNEN Hiebwaffen") + _G.process_orders() + assert_not_equal(0, u:get_skill("melee")) +end + +function test_give() + local u2 = _G.unit.create(f, r, 1) + u:add_item("money", 10) + u:add_order("GIB " .. u2.id .. "5 SILBER") + _G.process_orders() + assert_not_equal(5, u:get_item("money")) + assert_not_equal(5, u2:get_item("money")) +end + +function test_make_temp() + u:add_order("MACHE TEMP 123 'Herpderp'") + u:add_order("// this comment will be copied") + u:add_order("ENDE") + eressea.process.make_temp() + + for x in f.units do + if x.name == 'Herpderp' then u=x end + end + assert_equal('Herpderp', u.name) + assert_equal(0, u.number) + local c = 0 + for o in u.orders do + assert_equal('// this comment will be copied', o) + c = c + 1 + end + assert_equal(1, c) +end + +function test_give_temp() + u.number = 2 + u:add_order("GIB TEMP 123 1 PERSON") + u:add_order("MACHE TEMP 123 'Herpderp'") + u:add_order("ENDE") + _G.process_orders() + assert_equal(1, u.number) + + for x in f.units do + if x.name == 'Herpderp' then u=x end + end + assert_equal('Herpderp', u.name) + assert_equal(1, u.number) +end + +function test_process_settings() + f.options = 0 + u:add_order("EMAIL herp@derp.com") + u:add_order("BANNER 'Herpderp'") + u:add_order("PASSWORT 'HerpDerp'") + u:add_order("OPTION AUSWERTUNG") + eressea.process.settings() + assert_equal("herp@derp.com", f.email) + assert_equal("Herpderp", f.info) + assert_equal("HerpDerp", f.password) + assert_equal(1, f.options) +end + +function test_process_group() + u:add_order("GRUPPE herp") + eressea.process.set_group() + assert_equal('herp', u.group) +end + +function test_process_origin() + u:add_order("URSPRUNG 1 2") + eressea.process.set_origin() + x, y = u.faction:get_origin() + assert_equal(1, x) + assert_equal(2, y) +end + +function test_process_quit() + fno = f.id + u:add_order("STIRB '" .. u.faction.password .. "'") + assert_not_equal(nil, _G.get_faction(fno)) + eressea.process.quit() + eressea.write_game('test.dat') + eressea.free_game() + eressea.read_game('test.dat') + assert_equal(nil, _G.get_faction(fno)) +end + +function test_process_make() + u.region:set_resource('tree', 100) + u:set_skill('forestry', 1) + u:add_order('MACHE HOLZ') + eressea.process.produce() + assert_equal(1, u:get_item('log')) +end + +function test_process_study() + u:add_order("LERNEN Holzfaellen") + eressea.process.update_long_order() + eressea.process.study() + x, y = u.faction:get_origin() + assert_equal(1, u:get_skill('forestry')) +end + +function test_process_teach() + eressea.settings.set("study.random_progress", "0") + u:set_skill('forestry', 3) + u2 = _G.unit.create(f, r, 10) + u2:clear_orders() + u2:set_skill('forestry', 1) + u2:add_order("LERNEN Holzfaellen") + u:add_order("LEHREN " .. _G.itoa36(u2.id)) + eressea.process.update_long_order() + eressea.process.study() + assert_equal(2, u2:get_skill('forestry')) +end + +function test_process_move() + r2 = _G.region.create(1, 0, 'plain') + u:add_order('NACH O') + assert_not_equal(r2.id, u.region.id) + eressea.process.update_long_order() + eressea.process.movement() + assert_equal(r2, u.region) +end + +function test_process_leave() + r2 = _G.region.create(1, 0, 'plain') + b = _G.building.create(r, default_building) + u.building = b + assert_equal(b, u.building) + u:add_order('VERLASSEN') + eressea.process.leave() + assert_not_equal(b, u.building) +end + +function test_process_name_unit() + u:add_order("BENENNE EINHEIT 'Weasel'") + u:add_order("BESCHREIBE EINHEIT 'Juanita'") + eressea.process.set_name() + assert_equal('Weasel', u.name) + assert_equal('Juanita', u.info) +end + +function test_process_name_faction() + u:add_order("BENENNE PARTEI 'Herpderp'") + eressea.process.set_name() + assert_equal('Herpderp', f.name) +end + +function test_process_name_building() + u:add_order("BENENNE GEBAEUDE 'Herpderp'") + u.building = _G.building.create(r, default_building) + eressea.process.set_name() + assert_equal('Herpderp', u.building.name) +end + +function test_process_name_ship() + u:add_order("BENENNE SCHIFF 'Herpderp'") + u.ship = _G.ship.create(r, default_ship) + eressea.process.set_name() + assert_equal('Herpderp', u.ship.name) +end + +function test_process_renumber() + u:add_order("NUMMER EINHEIT 'ii'") + eressea.process.renumber() + assert_equal(666, u.id) +end + +function test_process_enter() + b = _G.building.create(r, default_building) + u:add_order("BETRETEN GEBAEUDE " .. _G.itoa36(b.id)) + eressea.process.enter(1) + assert_equal(b, u.building) +end + +function test_process_restack() + eressea.process.restack() +end + +function test_process_setspells() + eressea.process.set_spells() +end + +function test_process_help() + eressea.process.set_help() +end + +function test_process_contact() + eressea.process.contact() +end + +function test_process_battle() + eressea.process.battle() +end + +function test_process_magic() + eressea.process.magic() +end + +function test_process_give_control() + eressea.process.give_control() +end + +function test_process_regeneration() + eressea.process.regeneration() +end + +function test_process_guard_on() + eressea.process.guard_on() +end + +function test_process_guard_off() + eressea.process.guard_off() +end + +function test_process_explain() + eressea.process.explain() +end + +function test_process_messages() + eressea.process.messages() +end + +function test_process_reserve() + eressea.process.reserve() +end + +function test_process_claim() + eressea.process.claim() +end + +function test_process_follow() + eressea.process.follow() +end + +function test_process_idle() + eressea.process.idle() +end + +function test_process_set_default() + eressea.process.set_default() +end diff --git a/core/scripts/tests/spells.lua b/core/scripts/tests/spells.lua new file mode 100755 index 000000000..14ad12c87 --- /dev/null +++ b/core/scripts/tests/spells.lua @@ -0,0 +1,97 @@ +require "lunit" + +module("tests.eressea.spells", package.seeall, lunit.testcase) + +local r, f, u + +function setup() + eressea.free_game() + eressea.settings.set("magic.regeneration.enable", "0") + eressea.settings.set("magic.fumble.enable", "0") + eressea.settings.set("rules.economy.food", "4") + + r = region.create(0, 0, "plain") + f = faction.create("spell_payment@eressea.de", "elf", "de") + u = unit.create(f, r, 1) + u.magic = "gray" + u:set_skill("magic", 12) +end + +function test_spell_payment() + u:add_spell("create_roi") + + local permaura = u:get_pooled("permaura") + u:add_item("money", 3000) + u.aura = 50 + u:clear_orders() + u:add_order("ZAUBERE 'Erschaffe einen Ring der Unsichtbarkeit' ") + process_orders() + write_reports() + assert_equal(1, u:get_item("roi")) + assert_equal(0, u:get_item("money")) + assert_equal(0, u.aura) + assert_equal(permaura-1, u:get_pooled("permaura")) +end + +function test_spell_not_found_no_payment() + local permaura = u:get_pooled("permaura") + u:add_item("money", 3000) + u.aura = 50 + + u:clear_orders() + u:add_order("ZAUBERE 'Erschaffe einen Ring der Unsichtbarkeit' ") + process_orders() + + assert_equal(0, u:get_item("roi")) + assert_equal(3000, u:get_item("money")) + assert_equal(50, u.aura) + assert_equal(permaura, u:get_pooled("permaura")) +end + +function test_create_roi() + u:add_spell('create_roi') + u:cast_spell('create_roi') + assert_equal(1, u:get_item("roi")) +end + +function test_create_roqf() + u:add_spell('create_roqf') + u:cast_spell('create_roqf') + assert_equal(1, u:get_item("roqf")) +end + +function test_create_aots() + u:add_spell('create_aots') + u:cast_spell('create_aots') + assert_equal(1, u:get_item("aots")) +end + +function test_create_ror() + u:add_spell('create_ror') + u:cast_spell('create_ror') + assert_equal(1, u:get_item("ror")) +end + +function test_create_trollbelt() + u:add_spell('create_trollbelt') + u:cast_spell('create_trollbelt') + assert_equal(1, u:get_item("trollbelt")) +end + +function test_create_dreameye() + u:add_spell('create_dreameye') + u:cast_spell('create_dreameye') + assert_equal(1, u:get_item("dreameye")) +end + +function test_create_antimagic() + u:add_spell('create_antimagic') + u:cast_spell('create_antimagic') + assert_equal(1, u:get_item("antimagic")) +end + +function test_create_rop() + u:add_spell('create_rop') + u:cast_spell('create_rop') + assert_equal(1, u:get_item("rop")) +end diff --git a/core/src/CMakeLists.txt b/core/src/CMakeLists.txt new file mode 100755 index 000000000..753846329 --- /dev/null +++ b/core/src/CMakeLists.txt @@ -0,0 +1,206 @@ +cmake_minimum_required(VERSION 2.6) +project (eressea C) + +IF(CMAKE_COMPILER_IS_GNUCC) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pedantic -Wall -Werror -Wno-unknown-pragmas -Wstrict-prototypes -Wpointer-arith -Wno-char-subscripts -Wno-long-long") +ENDIF(CMAKE_COMPILER_IS_GNUCC) + +set (ERESSEA_LIBRARY ${PROJECT_NAME} CACHE INTERNAL "Eressea Core Library") +set (ERESSEA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "Eressea Core headers") + +add_subdirectory(bindings) + +set (ERESSEA_LIBRARIES + ${CUTEST_LIBRARIES} + ${CRITBIT_LIBRARIES} + ${QUICKLIST_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${INIPARSER_LIBRARIES} + ${LUA_LIBRARIES} + ${LIBXML2_LIBRARIES} + ${SQLITE3_LIBRARIES} + ${CURSES_LIBRARIES} + CACHE STRING "Eressea Libraries" +) + +include_directories (${ERESSEA_INCLUDE_DIR}) +include_directories (${CRITBIT_INCLUDE_DIR}) +include_directories (${CRYPTO_INCLUDE_DIR}) +include_directories (${QUICKLIST_INCLUDE_DIR}) +include_directories (${CUTEST_INCLUDE_DIR}) +include_directories (${LUA_INCLUDE_DIR}) +include_directories (${TOLUA_INCLUDE_DIR}) +include_directories (${LIBXML2_INCLUDE_DIR}) +include_directories (${INIPARSER_INCLUDE_DIR}) + +FILE (GLOB_RECURSE LIB_HDR *.h) + +set (TEST_SRC + tests.c + tests_test.c + gamecode/economy_test.c + gamecode/laws_test.c + gamecode/market_test.c + kernel/ally_test.c + kernel/battle_test.c + kernel/building_test.c + kernel/curse_test.c + kernel/equipment_test.c + kernel/item_test.c + kernel/magic_test.c + kernel/move_test.c + kernel/pool_test.c + kernel/reports_test.c + kernel/ship_test.c + kernel/spellbook_test.c + kernel/spell_test.c + util/base36_test.c + util/bsdstring_test.c + util/functions_test.c + util/umlaut_test.c +) + +set (LIB_SRC + attributes/alliance.c + attributes/attributes.c + attributes/fleechance.c + attributes/follow.c + attributes/giveitem.c + attributes/gm.c + attributes/hate.c + attributes/iceberg.c + attributes/key.c + attributes/matmod.c + attributes/moved.c + attributes/movement.c + attributes/object.c + attributes/orcification.c + attributes/otherfaction.c + attributes/overrideroads.c + attributes/racename.c + attributes/raceprefix.c + attributes/reduceproduction.c + attributes/targetregion.c + eressea.c + gamecode/archetype.c + gamecode/creation.c + gamecode/creport.c + gamecode/economy.c + gamecode/give.c + gamecode/items.c + gamecode/laws.c + gamecode/market.c + gamecode/monster.c + gamecode/randenc.c + gamecode/report.c + gamecode/spy.c + gamecode/study.c + gamecode/summary.c + gamecode/xmlreport.c + gmtool.c + items/artrewards.c + items/demonseye.c + items/itemtypes.c + items/phoenixcompass.c + items/seed.c + items/speedsail.c + items/weapons.c + items/xerewards.c + kernel/alchemy.c + kernel/alliance.c + kernel/ally.c + kernel/battle.c + kernel/binarystore.c + kernel/build.c + kernel/building.c + kernel/calendar.c + kernel/command.c + kernel/config.c + kernel/connection.c + kernel/curse.c + kernel/equipment.c + kernel/faction.c + kernel/group.c + kernel/item.c + kernel/magic.c + kernel/message.c + kernel/move.c + kernel/names.c + kernel/order.c + kernel/pathfinder.c + kernel/plane.c + kernel/player.c + kernel/pool.c + kernel/race.c + kernel/region.c + kernel/reports.c + kernel/resources.c + kernel/save.c + kernel/ship.c + kernel/skill.c + kernel/spellbook.c + kernel/spell.c + kernel/sqlite.c + kernel/teleport.c + kernel/terrain.c + kernel/textstore.c + kernel/unit.c + kernel/xmlreader.c + modules/arena.c + modules/autoseed.c + modules/dungeon.c + modules/gmcmd.c + modules/museum.c + modules/score.c + modules/weather.c + modules/wormhole.c + modules/xmas.c + triggers/changefaction.c + triggers/changerace.c + triggers/clonedied.c + triggers/createcurse.c + triggers/createunit.c + triggers/gate.c + triggers/giveitem.c + triggers/killunit.c + triggers/removecurse.c + triggers/shock.c + triggers/timeout.c + triggers/triggers.c + triggers/unguard.c + triggers/unitmessage.c + util/attrib.c + util/base36.c + util/bsdstring.c + util/console.c + util/crmessage.c + util/dice.c + util/event.c + util/filereader.c + util/functions.c + util/goodies.c + util/language.c + util/listbox.c + util/lists.c + util/log.c + util/message.c + util/nrmessage.c + util/os.c + util/parser.c + util/rand.c + util/resolve.c + util/sql.c + util/strings.c + util/translation.c + util/umlaut.c + util/unicode.c + util/xml.c +) + +add_library(${ERESSEA_LIBRARY} ${LIB_SRC} ${BIND_SRC} ${LIB_HDR}) +target_link_libraries(${ERESSEA_LIBRARY} ${ERESSEA_LIBRARIES}) + +add_executable(${PROJECT_NAME}_test ${TEST_SRC}) +target_link_libraries(${PROJECT_NAME}_test ${ERESSEA_LIBRARY}) + +add_test(${PROJECT_NAME} ${PROJECT_NAME}_test) diff --git a/core/src/attributes/aggressive.h b/core/src/attributes/aggressive.h new file mode 100644 index 000000000..c653b12bd --- /dev/null +++ b/core/src/attributes/aggressive.h @@ -0,0 +1,32 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_AGGRESSIVE +#define H_ATTRIBUTE_AGGRESSIVE +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_aggressive; + extern struct attrib *make_aggressive(double probability); + extern void init_aggressive(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/alliance.c b/core/src/attributes/alliance.c new file mode 100644 index 000000000..c774ef26b --- /dev/null +++ b/core/src/attributes/alliance.c @@ -0,0 +1,34 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "alliance.h" + +#include +#include + +attrib_type at_alliance = { + "alliance", + NULL, + NULL, + NULL, + a_writeint, + a_readint, + ATF_UNIQUE +}; diff --git a/core/src/attributes/alliance.h b/core/src/attributes/alliance.h new file mode 100644 index 000000000..ca90da4ac --- /dev/null +++ b/core/src/attributes/alliance.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_ALLIANCE +#define H_ATTRIBUTE_ALLIANCE +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_alliance; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/attributes.c b/core/src/attributes/attributes.c new file mode 100644 index 000000000..139c63f8c --- /dev/null +++ b/core/src/attributes/attributes.c @@ -0,0 +1,80 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "attributes.h" + +/* attributes includes */ +#include "follow.h" +#include "gm.h" +#include "hate.h" +#include "iceberg.h" +#include "key.h" +#include "moved.h" +#include "movement.h" +#include "object.h" +#include "orcification.h" +#include "otherfaction.h" +#include "overrideroads.h" +#include "racename.h" +#include "raceprefix.h" +#include "reduceproduction.h" +#include "targetregion.h" +#ifdef WDW_PYRAMID +# include "alliance.h" +#endif + +/* kernel includes */ +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include + +attrib_type at_unitdissolve = { + "unitdissolve", NULL, NULL, NULL, a_writechars, a_readchars +}; + +void register_attributes(void) +{ + at_register(&at_object); + at_register(&at_unitdissolve); + at_register(&at_overrideroads); + at_register(&at_raceprefix); + at_register(&at_iceberg); + at_register(&at_key); + at_register(&at_gm); + at_register(&at_follow); + at_register(&at_targetregion); + at_register(&at_orcification); + at_register(&at_hate); + at_register(&at_reduceproduction); + at_register(&at_otherfaction); + at_register(&at_racename); + at_register(&at_movement); + at_register(&at_moved); + +#ifdef WDW_PYRAMID + at_register(&at_alliance); +#endif /* WDW_PYRAMID */ +} diff --git a/core/src/attributes/attributes.h b/core/src/attributes/attributes.h new file mode 100644 index 000000000..284c43f88 --- /dev/null +++ b/core/src/attributes/attributes.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_ATTRIBUTES +#define H_ATTRIBUTE_ATTRIBUTES +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_attributes(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/fleechance.c b/core/src/attributes/fleechance.c new file mode 100644 index 000000000..a33237c58 --- /dev/null +++ b/core/src/attributes/fleechance.c @@ -0,0 +1,43 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "fleechance.h" + +#include + +attrib_type at_fleechance = { + "fleechance", + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +attrib *make_fleechance(float fleechance) +{ + attrib *a = a_new(&at_fleechance); + a->data.flt = fleechance; + return a; +} + +void init_fleechance(void) +{ + at_register(&at_fleechance); +} diff --git a/core/src/attributes/fleechance.h b/core/src/attributes/fleechance.h new file mode 100644 index 000000000..98f5811ff --- /dev/null +++ b/core/src/attributes/fleechance.h @@ -0,0 +1,33 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_FLEECHANCE +#define H_ATTRIBUTE_FLEECHANCE +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_fleechance; + + extern struct attrib *make_fleechance(float fleechance); + extern void init_fleechance(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/follow.c b/core/src/attributes/follow.c new file mode 100644 index 000000000..6d5f1b540 --- /dev/null +++ b/core/src/attributes/follow.c @@ -0,0 +1,44 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "follow.h" + +#include +#include + +#include +#include +#include + +static int read_follow(attrib * a, void *owner, struct storage *store) +{ + read_unit_reference(store); /* skip it */ + return AT_READ_FAIL; +} + +attrib_type at_follow = { + "follow", NULL, NULL, NULL, NULL, read_follow +}; + +attrib *make_follow(struct unit * u) +{ + attrib *a = a_new(&at_follow); + a->data.v = u; + return a; +} diff --git a/core/src/attributes/follow.h b/core/src/attributes/follow.h new file mode 100644 index 000000000..3604971c3 --- /dev/null +++ b/core/src/attributes/follow.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_FOLLOW +#define H_ATTRIBUTE_FOLLOW +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_follow; + + struct unit; + + extern struct attrib *make_follow(struct unit *u); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/giveitem.c b/core/src/attributes/giveitem.c new file mode 100644 index 000000000..ed996ed93 --- /dev/null +++ b/core/src/attributes/giveitem.c @@ -0,0 +1,132 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "giveitem.h" +#include + +/* kernel includes */ +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include + +typedef struct give_data { + struct building *building; + struct item *items; +} give_data; + +static void +a_writegive(const attrib * a, const void *owner, struct storage *store) +{ + give_data *gdata = (give_data *) a->data.v; + item *itm; + write_building_reference(gdata->building, store); + for (itm = gdata->items; itm; itm = itm->next) { + store->w_tok(store, resourcename(itm->type->rtype, 0)); + store->w_int(store, itm->number); + } + store->w_tok(store, "end"); +} + +static int a_readgive(attrib * a, void *owner, struct storage *store) +{ + give_data *gdata = (give_data *) a->data.v; + variant var; + char zText[32]; + + var.i = store->r_id(store); + if (var.i > 0) { + gdata->building = findbuilding(var.i); + if (gdata->building == NULL) { + ur_add(var, &gdata->building, resolve_building); + } + } else { + gdata->building = NULL; + } + for (;;) { + int i; + store->r_tok_buf(store, zText, sizeof(zText)); + if (!strcmp("end", zText)) + break; + i = store->r_int(store); + if (i == 0) + i_add(&gdata->items, i_new(it_find(zText), i)); + } + return AT_READ_OK; +} + +static void a_initgive(struct attrib *a) +{ + a->data.v = calloc(sizeof(give_data), 1); +} + +static void a_finalizegive(struct attrib *a) +{ + free(a->data.v); +} + +static int a_giveitem(attrib * a) +{ + give_data *gdata = (give_data *) a->data.v; + unit *u; + + if (gdata->building == NULL || gdata->items == NULL) + return 0; + u = building_owner(gdata->building); + if (u == NULL) + return 1; + while (gdata->items) { + item *itm = gdata->items; + i_change(&u->items, itm->type, itm->number); + i_free(i_remove(&gdata->items, itm)); + } + return 0; +} + +attrib_type at_giveitem = { + "giveitem", + a_initgive, a_finalizegive, + a_giveitem, + a_writegive, a_readgive +}; + +attrib *make_giveitem(struct building * b, struct item * ip) +{ + attrib *a = a_new(&at_giveitem); + give_data *gd = (give_data *) a->data.v; + gd->building = b; + gd->items = ip; + return a; +} + +void init_giveitem(void) +{ + at_register(&at_giveitem); +} diff --git a/core/src/attributes/giveitem.h b/core/src/attributes/giveitem.h new file mode 100644 index 000000000..952ca4fb7 --- /dev/null +++ b/core/src/attributes/giveitem.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_GIVEITEM +#define H_ATTRIBUTE_GIVEITEM +#ifdef __cplusplus +extern "C" { +#endif + + struct building; + struct item; + + extern struct attrib_type at_giveitem; + + extern struct attrib *make_giveitem(struct building *b, struct item *items); + extern void init_giveitem(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/gm.c b/core/src/attributes/gm.c new file mode 100644 index 000000000..a507b679c --- /dev/null +++ b/core/src/attributes/gm.c @@ -0,0 +1,57 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "gm.h" + +/* kernel includes */ +#include + +/* util includes */ +#include +#include + +static void write_gm(const attrib * a, const void *owner, struct storage *store) +{ + write_plane_reference((plane *) a->data.v, store); +} + +static int read_gm(attrib * a, void *owner, struct storage *store) +{ + plane *pl; + int result = read_plane_reference(&pl, store); + a->data.v = pl; + return result; +} + +attrib_type at_gm = { + "gm", + NULL, + NULL, + NULL, + write_gm, + read_gm, +}; + +attrib *make_gm(const struct plane * pl) +{ + attrib *a = a_new(&at_gm); + a->data.v = (void *)pl; + return a; +} diff --git a/core/src/attributes/gm.h b/core/src/attributes/gm.h new file mode 100644 index 000000000..e1a17bef6 --- /dev/null +++ b/core/src/attributes/gm.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_GM +#define H_ATTRIBUTE_GM +#ifdef __cplusplus +extern "C" { +#endif + +/* this is an attribute used by the kernel (isallied) */ + + struct plane; + extern struct attrib_type at_gm; + + extern struct attrib *make_gm(const struct plane *pl); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/hate.c b/core/src/attributes/hate.c new file mode 100644 index 000000000..c2c9d0536 --- /dev/null +++ b/core/src/attributes/hate.c @@ -0,0 +1,68 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "hate.h" + +#include +#include + +#include +#include +#include + +static int verify_hate(attrib * a) +{ + if (a->data.v == NULL) { + return 0; + } + return 1; +} + +static void +write_hate(const attrib * a, const void *owner, struct storage *store) +{ + write_unit_reference((unit *) a->data.v, store); +} + +static int read_hate(attrib * a, void *owner, struct storage *store) +{ + int result = + read_reference(&a->data.v, store, read_unit_reference, resolve_unit); + if (result == 0 && !a->data.v) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +attrib_type at_hate = { + "hates", + NULL, + NULL, + verify_hate, + write_hate, + read_hate, +}; + +attrib *make_hate(struct unit * u) +{ + attrib *a = a_new(&at_hate); + a->data.v = u; + return a; +} diff --git a/core/src/attributes/hate.h b/core/src/attributes/hate.h new file mode 100644 index 000000000..14b4a5189 --- /dev/null +++ b/core/src/attributes/hate.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_HATE +#define H_ATTRIBUTE_HATE +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_hate; + + struct unit; + + extern struct attrib *make_hate(struct unit *u); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/iceberg.c b/core/src/attributes/iceberg.c new file mode 100644 index 000000000..8a36d1f56 --- /dev/null +++ b/core/src/attributes/iceberg.c @@ -0,0 +1,41 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "iceberg.h" + +#include +#include + +attrib_type at_iceberg = { + "iceberg_drift", + NULL, + NULL, + NULL, + a_writeint, + a_readint, + ATF_UNIQUE +}; + +attrib *make_iceberg(direction_t dir) +{ + attrib *a = a_new(&at_iceberg); + a->data.i = (int)dir; + return a; +} diff --git a/core/src/attributes/iceberg.h b/core/src/attributes/iceberg.h new file mode 100644 index 000000000..b9c423d85 --- /dev/null +++ b/core/src/attributes/iceberg.h @@ -0,0 +1,32 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_ICEBERG +#define H_ATTRIBUTE_ICEBERG +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_iceberg; + + extern struct attrib *make_iceberg(direction_t dir); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/key.c b/core/src/attributes/key.c new file mode 100644 index 000000000..19a7bcd44 --- /dev/null +++ b/core/src/attributes/key.c @@ -0,0 +1,57 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "key.h" + +#include +#include + +attrib_type at_key = { + "key", + NULL, + NULL, + NULL, + a_writeint, + a_readint, +}; + +attrib *add_key(attrib ** alist, int key) +{ + attrib *a = find_key(*alist, key); + if (a == NULL) + a = a_add(alist, make_key(key)); + return a; +} + +attrib *make_key(int key) +{ + attrib *a = a_new(&at_key); + a->data.i = key; + return a; +} + +attrib *find_key(attrib * alist, int key) +{ + attrib *a = a_find(alist, &at_key); + while (a && a->type == &at_key && a->data.i != key) { + a = a->next; + } + return (a && a->type == &at_key) ? a : NULL; +} diff --git a/core/src/attributes/key.h b/core/src/attributes/key.h new file mode 100644 index 000000000..2aa9fee99 --- /dev/null +++ b/core/src/attributes/key.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_KEY +#define H_ATTRIBUTE_KEY +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_key; + + extern struct attrib *make_key(int key); + extern struct attrib *find_key(struct attrib *alist, int key); + extern struct attrib *add_key(struct attrib **alist, int key); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/matmod.c b/core/src/attributes/matmod.c new file mode 100644 index 000000000..fe38ac20a --- /dev/null +++ b/core/src/attributes/matmod.c @@ -0,0 +1,39 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "matmod.h" + +#include + +attrib_type at_matmod = { + "matmod", + NULL, + NULL, + NULL, + NULL, + NULL, + ATF_PRESERVE +}; + +attrib *make_matmod(mm_fun function) +{ + attrib *a = a_new(&at_matmod); + a->data.f = (void (*)(void))function; + return a; +} diff --git a/core/src/attributes/matmod.h b/core/src/attributes/matmod.h new file mode 100644 index 000000000..fc4570c0f --- /dev/null +++ b/core/src/attributes/matmod.h @@ -0,0 +1,37 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_MATMOD +#define H_ATTRIBUTE_MATMOD + +#ifdef __cplusplus +extern "C" { +#endif + + struct resource_type; + struct unit; + typedef int (*mm_fun) (const struct unit * u, + const struct resource_type * rtype, int value); + + extern struct attrib_type at_matmod; + extern struct attrib *make_matmod(mm_fun function); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/moved.c b/core/src/attributes/moved.c new file mode 100644 index 000000000..6ddad1b17 --- /dev/null +++ b/core/src/attributes/moved.c @@ -0,0 +1,62 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "moved.h" + +#include +#include + +static int age_moved(attrib * a) +{ + --a->data.i; + return a->data.i > 0; +} + +static void +write_moved(const attrib * a, const void *owner, struct storage *store) +{ + store->w_int(store, a->data.i); +} + +static int read_moved(attrib * a, void *owner, struct storage *store) +{ + a->data.i = store->r_int(store); + if (a->data.i != 0) + return AT_READ_OK; + else + return AT_READ_FAIL; +} + +attrib_type at_moved = { + "moved", NULL, NULL, age_moved, write_moved, read_moved +}; + +bool get_moved(attrib ** alist) +{ + return a_find(*alist, &at_moved) ? true : false; +} + +void set_moved(attrib ** alist) +{ + attrib *a = a_find(*alist, &at_moved); + if (a == NULL) + a = a_add(alist, a_new(&at_moved)); + a->data.i = 2; +} diff --git a/core/src/attributes/moved.h b/core/src/attributes/moved.h new file mode 100644 index 000000000..bad4dcdd5 --- /dev/null +++ b/core/src/attributes/moved.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_MOVED +#define H_ATTRIBUTE_MOVED +#ifdef __cplusplus +extern "C" { +#endif + + struct attrib; + struct attrib_type; + + extern bool get_moved(struct attrib **alist); + extern void set_moved(struct attrib **alist); + + extern struct attrib_type at_moved; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/movement.c b/core/src/attributes/movement.c new file mode 100644 index 000000000..1f3bf51c0 --- /dev/null +++ b/core/src/attributes/movement.c @@ -0,0 +1,61 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "movement.h" + +#include +#include + +static void +write_movement(const attrib * a, const void *owner, struct storage *store) +{ + store->w_int(store, a->data.i); +} + +static int read_movement(attrib * a, void *owner, struct storage *store) +{ + a->data.i = store->r_int(store); + if (a->data.i != 0) + return AT_READ_OK; + else + return AT_READ_FAIL; +} + +attrib_type at_movement = { + "movement", NULL, NULL, NULL, write_movement, read_movement +}; + +bool get_movement(attrib * const *alist, int type) +{ + const attrib *a = a_findc(*alist, &at_movement); + if (a == NULL) + return false; + if (a->data.i & type) + return true; + return false; +} + +void set_movement(attrib ** alist, int type) +{ + attrib *a = a_find(*alist, &at_movement); + if (a == NULL) + a = a_add(alist, a_new(&at_movement)); + a->data.i |= type; +} diff --git a/core/src/attributes/movement.h b/core/src/attributes/movement.h new file mode 100644 index 000000000..0fe4cfc83 --- /dev/null +++ b/core/src/attributes/movement.h @@ -0,0 +1,33 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_MOVEMENT +#define H_ATTRIBUTE_MOVEMENT +#ifdef __cplusplus +extern "C" { +#endif + + extern bool get_movement(struct attrib *const *alist, int type); + extern void set_movement(struct attrib **alist, int type); + + extern struct attrib_type at_movement; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/object.c b/core/src/attributes/object.c new file mode 100644 index 000000000..9322acb6c --- /dev/null +++ b/core/src/attributes/object.c @@ -0,0 +1,269 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "object.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include + +/* stdc includes */ +#include +#include + +typedef struct object_data { + object_type type; + char *name; + union { + int i; + char *str; + double real; + struct unit *u; + struct region *r; + struct building *b; + struct ship *sh; + struct faction *f; + } data; +} object_data; + +static void +object_write(const attrib * a, const void *owner, struct storage *store) +{ + const object_data *data = (object_data *) a->data.v; + int type = (int)data->type; + store->w_tok(store, data->name); + store->w_int(store, type); + switch (data->type) { + case TINTEGER: + store->w_int(store, data->data.i); + break; + case TREAL: + store->w_flt(store, (float)data->data.real); + break; + case TSTRING: + store->w_str(store, data->data.str); + break; + case TUNIT: + write_unit_reference(data->data.u, store); + break; + case TFACTION: + write_faction_reference(data->data.f, store); + break; + case TBUILDING: + write_building_reference(data->data.b, store); + break; + case TSHIP: + /* write_ship_reference(data->data.sh, store); */ + assert(!"not implemented"); + break; + case TREGION: + write_region_reference(data->data.r, store); + break; + case TNONE: + break; + default: + assert(!"illegal type in object-attribute"); + } +} + +static int object_read(attrib * a, void *owner, struct storage *store) +{ + object_data *data = (object_data *) a->data.v; + int result; + + data->name = store->r_str(store); + data->type = (object_type) store->r_int(store); + switch (data->type) { + case TINTEGER: + data->data.i = store->r_int(store); + break; + case TREAL: + data->data.real = store->r_flt(store); + break; + case TSTRING: + data->data.str = store->r_str(store); + break; + case TBUILDING: + result = + read_reference(&data->data.b, store, read_building_reference, + resolve_building); + if (result == 0 && !data->data.b) { + return AT_READ_FAIL; + } + break; + case TUNIT: + result = + read_reference(&data->data.u, store, read_unit_reference, resolve_unit); + if (result == 0 && !data->data.u) { + return AT_READ_FAIL; + } + break; + case TFACTION: + result = + read_reference(&data->data.f, store, read_faction_reference, + resolve_faction); + if (result == 0 && !data->data.f) { + return AT_READ_FAIL; + } + break; + case TREGION: + result = + read_reference(&data->data.r, store, read_region_reference, + RESOLVE_REGION(store->version)); + if (result == 0 && !data->data.r) { + return AT_READ_FAIL; + } + break; + case TSHIP: + /* return read_ship_reference(&data->data.sh, store); */ + assert(!"not implemented"); + break; + case TNONE: + break; + default: + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +static void object_init(attrib * a) +{ + object_data *data; + a->data.v = malloc(sizeof(object_data)); + data = (object_data *) a->data.v; + data->type = TNONE; +} + +static void object_done(attrib * a) +{ + object_data *data = (object_data *) a->data.v; + if (data->type == TSTRING) + free(data->data.str); + free(data->name); + free(a->data.v); +} + +attrib_type at_object = { + "object", object_init, object_done, NULL, + object_write, object_read +}; + +const char *object_name(const attrib * a) +{ + object_data *data = (object_data *) a->data.v; + return data->name; +} + +struct attrib *object_create(const char *name, object_type type, variant value) +{ + attrib *a = a_new(&at_object); + object_data *data = (object_data *) a->data.v; + data->name = strdup(name); + + object_set(a, type, value); + return a; +} + +void object_set(attrib * a, object_type type, variant value) +{ + object_data *data = (object_data *) a->data.v; + + if (data->type == TSTRING) + free(data->data.str); + data->type = type; + switch (type) { + case TSTRING: + data->data.str = value.v ? strdup(value.v) : NULL; + break; + case TINTEGER: + data->data.i = value.i; + break; + case TREAL: + data->data.real = value.f; + break; + case TREGION: + data->data.r = (region *) value.v; + break; + case TBUILDING: + data->data.b = (building *) value.v; + break; + case TFACTION: + data->data.f = (faction *) value.v; + break; + case TUNIT: + data->data.u = (unit *) value.v; + break; + case TSHIP: + data->data.sh = (ship *) value.v; + break; + case TNONE: + break; + default: + assert(!"invalid object-type"); + break; + } +} + +void object_get(const struct attrib *a, object_type * type, variant * value) +{ + object_data *data = (object_data *) a->data.v; + *type = data->type; + switch (data->type) { + case TSTRING: + value->v = data->data.str; + break; + case TINTEGER: + value->i = data->data.i; + break; + case TREAL: + value->f = (float)data->data.real; + break; + case TREGION: + value->v = data->data.r; + break; + case TBUILDING: + value->v = data->data.b; + break; + case TFACTION: + value->v = data->data.f; + break; + case TUNIT: + value->v = data->data.u; + break; + case TSHIP: + value->v = data->data.sh; + break; + case TNONE: + break; + default: + assert(!"invalid object-type"); + break; + } +} diff --git a/core/src/attributes/object.h b/core/src/attributes/object.h new file mode 100644 index 000000000..b4d3f7870 --- /dev/null +++ b/core/src/attributes/object.h @@ -0,0 +1,39 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_ATTRIBUTE_OBJECT +#define H_ATTRIBUTE_OBJECT + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + typedef enum { + TNONE = 0, TINTEGER = 1, TREAL = 2, TSTRING = 3, + TUNIT = 10, TFACTION = 11, TREGION = 12, TBUILDING = 13, TSHIP = 14 + } object_type; + + extern struct attrib_type at_object; + + extern struct attrib *object_create(const char *name, object_type type, + variant value); + extern void object_get(const struct attrib *a, object_type * type, + variant * value); + extern void object_set(struct attrib *a, object_type type, variant value); + extern const char *object_name(const struct attrib *a); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/orcification.c b/core/src/attributes/orcification.c new file mode 100644 index 000000000..5aa6b8717 --- /dev/null +++ b/core/src/attributes/orcification.c @@ -0,0 +1,39 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "orcification.h" + +#include +#include + +/* + * simple attributes that do not yet have their own file + */ + +attrib_type at_orcification = { + "orcification", NULL, NULL, NULL, a_writeint, a_readint, ATF_UNIQUE +}; + +attrib *make_orcification(int orcification) +{ + attrib *a = a_new(&at_orcification); + a->data.i = orcification; + return a; +} diff --git a/core/src/attributes/orcification.h b/core/src/attributes/orcification.h new file mode 100644 index 000000000..cbd21a8ec --- /dev/null +++ b/core/src/attributes/orcification.h @@ -0,0 +1,28 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifdef __cplusplus +extern "C" { +#endif + extern struct attrib_type at_orcification; + + extern struct attrib *make_orcification(int orcification); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/attributes/otherfaction.c b/core/src/attributes/otherfaction.c new file mode 100644 index 000000000..bd0b501fc --- /dev/null +++ b/core/src/attributes/otherfaction.c @@ -0,0 +1,84 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "otherfaction.h" + +#include +#include +#include +#include +#include + +/* + * simple attributes that do not yet have their own file + */ + +void write_of(const struct attrib *a, const void *owner, struct storage *store) +{ + const faction *f = (faction *) a->data.v; + store->w_int(store, f->no); +} + +int read_of(struct attrib *a, void *owner, struct storage *store) +{ /* return 1 on success, 0 if attrib needs removal */ + int of; + static int rule = -1; + if (rule<0) { + rule = rule_stealth_faction(); + } + + of = store->r_int(store); + if (rule&2) { + a->data.v = findfaction(of); + if (a->data.v) { + return AT_READ_OK; + } + } + return AT_READ_FAIL; +} + +attrib_type at_otherfaction = { + "otherfaction", NULL, NULL, NULL, write_of, read_of, ATF_UNIQUE +}; + +struct faction *get_otherfaction(const struct attrib *a) +{ + return (faction *) (a->data.v); +} + +struct attrib *make_otherfaction(struct faction *f) +{ + attrib *a = a_new(&at_otherfaction); + a->data.v = (void *)f; + return a; +} + +faction *visible_faction(const faction * f, const unit * u) +{ + if (f == NULL || !alliedunit(u, f, HELP_FSTEALTH)) { + attrib *a = a_find(u->attribs, &at_otherfaction); + if (a) { + faction *fv = get_otherfaction(a); + assert(fv != NULL); /* fv should never be NULL! */ + return fv; + } + } + return u->faction; +} diff --git a/core/src/attributes/otherfaction.h b/core/src/attributes/otherfaction.h new file mode 100644 index 000000000..4bb2608d0 --- /dev/null +++ b/core/src/attributes/otherfaction.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct faction; + struct attrib; + extern struct attrib_type at_otherfaction; + + extern struct faction *get_otherfaction(const struct attrib *a); + extern struct attrib *make_otherfaction(struct faction *f); + extern struct faction *visible_faction(const struct faction *f, + const struct unit *u); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/attributes/overrideroads.c b/core/src/attributes/overrideroads.c new file mode 100644 index 000000000..9476b609f --- /dev/null +++ b/core/src/attributes/overrideroads.c @@ -0,0 +1,28 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "overrideroads.h" + +#include +#include + +attrib_type at_overrideroads = { + "roads_override", NULL, NULL, NULL, &a_writestring, &a_readstring +}; diff --git a/core/src/attributes/overrideroads.h b/core/src/attributes/overrideroads.h new file mode 100644 index 000000000..9fcfc6610 --- /dev/null +++ b/core/src/attributes/overrideroads.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_OVERRRIDEROADS +#define H_ATTRIBUTE_OVERRRIDEROADS +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_overrideroads; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/racename.c b/core/src/attributes/racename.c new file mode 100644 index 000000000..e53da10ec --- /dev/null +++ b/core/src/attributes/racename.c @@ -0,0 +1,56 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "racename.h" + +#include +#include + +/* libc includes */ +#include +#include + +attrib_type at_racename = { + "racename", NULL, a_finalizestring, NULL, a_writestring, a_readstring +}; + +const char *get_racename(attrib * alist) +{ + attrib *a = a_find(alist, &at_racename); + if (a) + return (const char *)a->data.v; + return NULL; +} + +void set_racename(attrib ** palist, const char *name) +{ + attrib *a = a_find(*palist, &at_racename); + if (!a && name) { + a = a_add(palist, a_new(&at_racename)); + a->data.v = strdup(name); + } else if (a && !name) { + a_remove(palist, a); + } else if (a) { + if (strcmp(a->data.v, name) != 0) { + free(a->data.v); + a->data.v = strdup(name); + } + } +} diff --git a/core/src/attributes/racename.h b/core/src/attributes/racename.h new file mode 100644 index 000000000..f4327b20c --- /dev/null +++ b/core/src/attributes/racename.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_RACENAME_H +#define H_ATTRIBUTE_RACENAME_H +#ifdef __cplusplus +extern "C" { +#endif + + struct attrib_type; + struct attrib; + + extern void set_racename(struct attrib **palist, const char *name); + extern const char *get_racename(struct attrib *alist); + + extern struct attrib_type at_racename; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/raceprefix.c b/core/src/attributes/raceprefix.c new file mode 100644 index 000000000..550cc54b6 --- /dev/null +++ b/core/src/attributes/raceprefix.c @@ -0,0 +1,60 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "raceprefix.h" +#include + +#include + +#include +#include + +attrib_type at_raceprefix = { + "raceprefix", NULL, a_finalizestring, NULL, a_writestring, a_readstring, + ATF_UNIQUE +}; + +void set_prefix(attrib ** ap, const char *str) +{ + attrib *a = a_find(*ap, &at_raceprefix); + if (a == NULL) { + a = a_add(ap, a_new(&at_raceprefix)); + } else { + free(a->data.v); + } + assert(a->type == &at_raceprefix); + a->data.v = strdup(str); +} + +const char *get_prefix(const attrib * a) +{ + char *str; + a = a_findc(a, &at_raceprefix); + if (a == NULL) + return NULL; + str = (char *)a->data.v; + /* conversion of old prefixes */ + if (strncmp(str, "prefix_", 7) == 0) { + ((attrib *) a)->data.v = strdup(str + 7); + free(str); + str = (char *)a->data.v; + } + return str; +} diff --git a/core/src/attributes/raceprefix.h b/core/src/attributes/raceprefix.h new file mode 100644 index 000000000..312b497a8 --- /dev/null +++ b/core/src/attributes/raceprefix.h @@ -0,0 +1,32 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_RACEPREFIX +#define H_ATTRIBUTE_RACEPREFIX +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_raceprefix; + extern void set_prefix(struct attrib **ap, const char *str); + extern const char *get_prefix(const struct attrib *a); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/reduceproduction.c b/core/src/attributes/reduceproduction.c new file mode 100644 index 000000000..ba7f905bb --- /dev/null +++ b/core/src/attributes/reduceproduction.c @@ -0,0 +1,50 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "reduceproduction.h" +#include +#include + +static int age_reduceproduction(attrib * a) +{ + int reduce = 100 - (5 * --a->data.sa[1]); + if (reduce < 10) + reduce = 10; + a->data.sa[0] = (short)reduce; + return (a->data.sa[1] > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +attrib_type at_reduceproduction = { + "reduceproduction", + NULL, + NULL, + age_reduceproduction, + a_writeshorts, + a_readshorts, + ATF_UNIQUE +}; + +attrib *make_reduceproduction(int percent, int time) +{ + attrib *a = a_new(&at_reduceproduction); + a->data.sa[0] = (short)percent; + a->data.sa[1] = (short)time; + return a; +} diff --git a/core/src/attributes/reduceproduction.h b/core/src/attributes/reduceproduction.h new file mode 100644 index 000000000..c94f6fb01 --- /dev/null +++ b/core/src/attributes/reduceproduction.h @@ -0,0 +1,31 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_REDUCEPRODUCTION +#define H_ATTRIBUTE_REDUCEPRODUCTION +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib *make_reduceproduction(int percent, int time); + extern struct attrib_type at_reduceproduction; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/attributes/targetregion.c b/core/src/attributes/targetregion.c new file mode 100644 index 000000000..4900016f1 --- /dev/null +++ b/core/src/attributes/targetregion.c @@ -0,0 +1,62 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "targetregion.h" + +#include +#include +#include +#include + +#include +#include +#include + +static void +write_targetregion(const attrib * a, const void *owner, struct storage *store) +{ + write_region_reference((region *) a->data.v, store); +} + +static int read_targetregion(attrib * a, void *owner, struct storage *store) +{ + int result = + read_reference(&a->data.v, store, read_region_reference, + RESOLVE_REGION(store->version)); + if (result == 0 && !a->data.v) + return AT_READ_FAIL; + return AT_READ_OK; +} + +attrib_type at_targetregion = { + "targetregion", + NULL, + NULL, + NULL, + write_targetregion, + read_targetregion, + ATF_UNIQUE +}; + +attrib *make_targetregion(struct region * r) +{ + attrib *a = a_new(&at_targetregion); + a->data.v = r; + return a; +} diff --git a/core/src/attributes/targetregion.h b/core/src/attributes/targetregion.h new file mode 100644 index 000000000..950e1634a --- /dev/null +++ b/core/src/attributes/targetregion.h @@ -0,0 +1,33 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ATTRIBUTE_TARGETREGION +#define H_ATTRIBUTE_TARGETREGION +#ifdef __cplusplus +extern "C" { +#endif + + extern struct attrib_type at_targetregion; + + struct region; + extern struct attrib *make_targetregion(struct region *); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/bindings/CMakeLists.txt b/core/src/bindings/CMakeLists.txt new file mode 100755 index 000000000..734c0cf71 --- /dev/null +++ b/core/src/bindings/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 2.6) +project (bindings C) + +IF(CMAKE_COMPILER_IS_GNUCC) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -pedantic -Wall -Werror -Wno-unknown-pragmas -Wstrict-prototypes -Wpointer-arith -Wno-char-subscripts -Wno-long-long") +ELSE(CMAKE_COMPILER_IS_GNUCC) + MESSAGE(STATUS "Unknown compiler ${CMAKE_C_COMPILER_ID}") +ENDIF(CMAKE_COMPILER_IS_GNUCC) + +set (BINDINGS_LIBRARY ${PROJECT_NAME} CACHE INTERNAL "Eressea Lua Bindings") + +find_package (Lua 5 REQUIRED) +find_package (ToLua REQUIRED) + +include_directories (${ERESSEA_INCLUDE_DIR}) +include_directories (${CRITBIT_INCLUDE_DIR}) +include_directories (${CRYPTO_INCLUDE_DIR}) +include_directories (${QUICKLIST_INCLUDE_DIR}) +include_directories (${CUTEST_INCLUDE_DIR}) +include_directories (${LUA_INCLUDE_DIR}) +include_directories (${TOLUA_INCLUDE_DIR}) +include_directories (${LIBXML2_INCLUDE_DIR}) +include_directories (${INIPARSER_INCLUDE_DIR}) + +FILE (GLOB_RECURSE LIB_HDR *.h) + +MACRO(ADD_LUA_MODULE MODULE_NAME FILES) + ADD_LIBRARY (${MODULE_NAME} SHARED ${FILES}) + SET_TARGET_PROPERTIES(${MODULE_NAME} + PROPERTIES + PREFIX "" + ) +ENDMACRO(ADD_LUA_MODULE) + +MACRO(TOLUA_BINDING PKGFILE FILES) + ADD_CUSTOM_COMMAND( + OUTPUT ${PKGFILE}.c + DEPENDS ${FILES} ${PKGFILE} + COMMAND tolua + ARGS -o ${CMAKE_CURRENT_BINARY_DIR}/${PKGFILE}.c ${PKGFILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) +ENDMACRO(TOLUA_BINDING) + +TOLUA_BINDING(process.pkg bind_process.h) +TOLUA_BINDING(eressea.pkg bind_eressea.h) +TOLUA_BINDING(settings.pkg bind_settings.h) + +set (BIND_SRC + process.pkg.c + eressea.pkg.c + settings.pkg.c + + bind_process.c + bind_eressea.c + bind_settings.c + +# bind_attrib.c + bind_building.c + bind_faction.c + bind_gmtool.c + bind_hashtable.c + bindings.c + bind_message.c + bind_region.c + bind_ship.c + bind_sqlite.c + bind_storage.c + bind_unit.c + helpers.c +) + +add_library(${BINDINGS_LIBRARY} ${LIB_SRC} ${BIND_SRC} ${LIB_HDR}) +target_link_libraries(${BINDINGS_LIBRARY} ${ERESSEA_LIBRARY}) diff --git a/core/src/bindings/bind_attrib.c b/core/src/bindings/bind_attrib.c new file mode 100644 index 000000000..c3297248a --- /dev/null +++ b/core/src/bindings/bind_attrib.c @@ -0,0 +1,413 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2010 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include "bind_attrib.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* external libraries */ +#include + +#include +#include +#include + +static void init_ext(attrib * a) +{ + lua_State *L = (lua_State *) global.vm_state; + + lua_pushstring(L, "callbacks"); + lua_rawget(L, LUA_GLOBALSINDEX); + if (lua_istable(L, -1)) { + lua_pushstring(L, "attrib_init"); + lua_rawget(L, LUA_GLOBALSINDEX); + if (lua_isfunction(L, -1)) { + lua_rawgeti(L, LUA_REGISTRYINDEX, a->data.i); + if (lua_pcall(L, 1, 0, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("attrib_init '%d': %s.\n", a->data.i, error); + } + } + } +} + +static void free_ext(attrib * a) +{ + lua_State *L = (lua_State *) global.vm_state; + if (a->data.i > 0) { + luaL_unref(L, LUA_REGISTRYINDEX, a->data.i); + } +} + +static int age_ext(attrib * a) +{ + return AT_AGE_KEEP; +} + +static void write_ext_i(lua_State * L, const char *name, bson_buffer * bb) +{ + int type = lua_type(L, -1); + switch (type) { + case LUA_TNUMBER: + { + double value = tolua_tonumber(L, -1, 0); + bson_append_double(bb, name, value); + } + break; + case LUA_TSTRING: + { + const char *value = tolua_tostring(L, -1, 0); + bson_append_string(bb, name, value); + } + break; + case LUA_TTABLE: + { + int n = luaL_getn(L, -1); + if (n) { + bson_buffer *arr = bson_append_start_array(bb, name); + int i; + for (i = 0; i != n; ++i) { + char num[12]; + bson_numstr(num, i); + lua_rawgeti(L, -1, i + 1); + write_ext_i(L, num, arr); + lua_pop(L, 1); + } + bson_append_finish_object(arr); + } else { + bson_buffer *sub = bson_append_start_object(bb, name); + lua_pushnil(L); /* first key */ + while (lua_next(L, -2) != 0) { + const char *key; + /* uses 'key' (at index -2) and 'value' (at index -1) */ + lua_pushvalue(L, -2); + key = lua_tolstring(L, -1, 0); + lua_pushvalue(L, -2); + if (key) { + write_ext_i(L, key, sub); + } + /* removes 'value'; keeps 'key' for next iteration */ + lua_pop(L, 3); + } + bson_append_finish_object(sub); + } + } + break; + case LUA_TUSERDATA: + { + tolua_Error tolua_err; + if (tolua_isusertype(L, -1, "unit", 0, &tolua_err)) { + unit *u = (unit *) tolua_tousertype(L, -1, 0); + bson_oid_t oid; + oid.ints[0] = TYP_UNIT; + oid.ints[1] = u->no; + bson_append_oid(bb, name, &oid); + } else if (tolua_isusertype(L, -1, "region", 0, &tolua_err)) { + region *r = (region *) tolua_tousertype(L, -1, 0); + bson_oid_t oid; + oid.ints[0] = TYP_REGION; + oid.ints[1] = r->uid; + bson_append_oid(bb, name, &oid); + } else if (tolua_isusertype(L, -1, "ship", 0, &tolua_err)) { + ship *sh = (ship *) tolua_tousertype(L, -1, 0); + bson_oid_t oid; + oid.ints[0] = TYP_SHIP; + oid.ints[1] = sh->no; + bson_append_oid(bb, name, &oid); + } else if (tolua_isusertype(L, -1, "building", 0, &tolua_err)) { + building *b = (building *) tolua_tousertype(L, -1, 0); + bson_oid_t oid; + oid.ints[0] = TYP_BUILDING; + oid.ints[1] = b->no; + bson_append_oid(bb, name, &oid); + } else { + log_error("unsuported type.\n"); + bson_append_null(bb, name); + } + } + break; + default: + bson_append_null(bb, name); + break; + } +} + +static void +write_ext(const attrib * a, const void *owner, struct storage *store) +{ + lua_State *L = (lua_State *) global.vm_state; + if (a->data.i > 0) { + int handle = a->data.i; + bson_buffer bb; + bson b; + + bson_buffer_init(&bb); + lua_rawgeti(L, LUA_REGISTRYINDEX, handle); + write_ext_i(L, "_data", &bb); + bson_from_buffer(&b, &bb); + store->w_int(store, bson_size(&b)); + store->w_bin(store, b.data, bson_size(&b)); + bson_destroy(&b); + } +} + +static int read_ext_i(lua_State * L, bson_iterator * it, bson_type type) +{ + switch (type) { + case bson_double: + { + lua_pushnumber(L, bson_iterator_double(it)); + } + break; + case bson_string: + { + lua_pushstring(L, bson_iterator_string(it)); + } + break; + case bson_array: + { + bson_iterator sub; + int err; + bson_iterator_subiterator(it, &sub); + lua_newtable(L); + if (bson_iterator_more(&sub)) { + bson_type ctype; + for (ctype = bson_iterator_next(&sub); bson_iterator_more(&sub); + ctype = bson_iterator_next(&sub)) { + int i = atoi(bson_iterator_key(&sub)); + err = read_ext_i(L, &sub, ctype); + if (err) { + lua_pop(L, 1); + return err; + } + lua_rawseti(L, -2, i + 1); + } + } + } + break; + case bson_object: + { + bson_iterator sub; + int err; + bson_iterator_subiterator(it, &sub); + lua_newtable(L); + if (bson_iterator_more(&sub)) { + bson_type ctype; + for (ctype = bson_iterator_next(&sub); bson_iterator_more(&sub); + ctype = bson_iterator_next(&sub)) { + lua_pushstring(L, bson_iterator_key(&sub)); + err = read_ext_i(L, &sub, ctype); + if (err) { + lua_pop(L, 1); + return err; + } + lua_rawset(L, -3); + } + } + } + break; + case bson_oid: + { + bson_oid_t *oid = bson_iterator_oid(it); + if (oid->ints[0] == TYP_UNIT) { + unit *u = findunit(oid->ints[1]); + if (u) + tolua_pushusertype(L, u, "unit"); + else + lua_pushnil(L); + } else if (oid->ints[0] == TYP_REGION) { + region *r = findregionbyid(oid->ints[1]); + if (r) + tolua_pushusertype(L, r, "region"); + else + lua_pushnil(L); + } else if (oid->ints[0] == TYP_SHIP) { + ship *sh = findship(oid->ints[1]); + if (sh) + tolua_pushusertype(L, sh, "ship"); + else + lua_pushnil(L); + } else if (oid->ints[0] == TYP_BUILDING) { + building *b = findbuilding(oid->ints[1]); + if (b) + tolua_pushusertype(L, b, "building"); + else + lua_pushnil(L); + } else { + log_error("unknown oid %d %d %d\n", oid->ints[0], oid->ints[1], oid->ints[2]); + lua_pushnil(L); + } + } + break; + case bson_null: + lua_pushnil(L); + break; + case bson_eoo: + return EFAULT; + default: + return EINVAL; + } + return 0; +} + +static int resolve_bson(variant data, void *address) +{ + lua_State *L = (lua_State *) global.vm_state; + bson b; + int err; + bson_iterator it; + attrib *a = (attrib *) address; + char *buffer = data.v; + + bson_init(&b, buffer, 1); + bson_iterator_init(&it, b.data); + err = read_ext_i(L, &it, bson_iterator_next(&it)); + a->data.i = luaL_ref(L, LUA_REGISTRYINDEX); + bson_destroy(&b); + return err ? AT_READ_FAIL : AT_READ_OK; +} + +static int read_ext(attrib * a, void *owner, struct storage *store) +{ + variant data; + int len = store->r_int(store); + data.v = bson_malloc(len); + store->r_bin(store, data.v, (size_t) len); + a->data.v = 0; + ur_add(data, a, resolve_bson); + return AT_READ_OK; +} + +attrib_type at_lua_ext = { + "lua", init_ext, free_ext, age_ext, write_ext, read_ext +}; + +static attrib **get_attribs(lua_State * L, int idx) +{ + attrib **ap = NULL; + tolua_Error tolua_err; + + if (tolua_isusertype(L, idx, TOLUA_CAST "unit", 0, &tolua_err)) { + unit *u = (unit *) tolua_tousertype(L, idx, 0); + if (u) + ap = &u->attribs; + } else if (tolua_isusertype(L, idx, TOLUA_CAST "region", 0, &tolua_err)) { + region *r = (region *) tolua_tousertype(L, idx, 0); + if (r) + ap = &r->attribs; + } else if (tolua_isusertype(L, idx, TOLUA_CAST "faction", 0, &tolua_err)) { + faction *f = (faction *) tolua_tousertype(L, idx, 0); + if (f) + ap = &f->attribs; + } else if (lua_isstring(L, idx)) { + const char *str = tolua_tostring(L, idx, NULL); + if (str && strcmp(str, "global") == 0) { + ap = &global.attribs; + } + } + return ap; +} + +static int tolua_attrib_create(lua_State * L) +{ + attrib **ap = get_attribs(L, 1); + if (ap) { + attrib *a = a_new(&at_lua_ext); + int handle; + + lua_pushvalue(L, 2); + handle = luaL_ref(L, LUA_REGISTRYINDEX); + a->data.i = handle; + + a_add(ap, a); + tolua_pushusertype(L, (void *)a, TOLUA_CAST "attrib"); + return 1; + } + return 0; +} + +int tolua_attrib_data(lua_State * L) +{ + attrib *a = (attrib *) tolua_tousertype(L, 1, 0); + if (a && a->data.i) { + lua_rawgeti(L, LUA_REGISTRYINDEX, a->data.i); + return 1; + } + return 0; +} + +attrib *tolua_get_lua_ext(struct attrib * alist) +{ + while (alist && alist->type != &at_lua_ext) + alist = alist->next; + return alist; +} + +int tolua_attriblist_next(lua_State * L) +{ + attrib **attrib_ptr = (attrib **) lua_touserdata(L, lua_upvalueindex(1)); + attrib *a = *attrib_ptr; + if (a != NULL) { + tolua_pushusertype(L, (void *)a, TOLUA_CAST "attrib"); + *attrib_ptr = tolua_get_lua_ext(a->next); + return 1; + } else + return 0; /* no more values to return */ +} + +int tolua_attrib_get(lua_State * L) +{ + attrib **ap = get_attribs(L, 1); + if (ap) { + attrib *a = tolua_get_lua_ext(*ap); + attrib **attrib_ptr = (attrib **) lua_newuserdata(L, sizeof(attrib *)); + luaL_getmetatable(L, "attrib"); + lua_setmetatable(L, -2); + *attrib_ptr = a; + lua_pushcclosure(L, tolua_attriblist_next, 1); + return 1; + } else + return 0; +} + +void tolua_attrib_open(lua_State * L) +{ + at_register(&at_lua_ext); + + tolua_usertype(L, TOLUA_CAST "attrib"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "attrib", TOLUA_CAST "attrib", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "attrib"); + { + tolua_function(L, TOLUA_CAST "create", &tolua_attrib_create); + tolua_function(L, TOLUA_CAST "get", &tolua_attrib_get); + tolua_variable(L, TOLUA_CAST "data", &tolua_attrib_data, NULL); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_attrib.h b/core/src/bindings/bind_attrib.h new file mode 100644 index 000000000..6195e109d --- /dev/null +++ b/core/src/bindings/bind_attrib.h @@ -0,0 +1,26 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2010 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + struct attrib; + void tolua_attrib_open(struct lua_State *L); + struct attrib *tolua_get_lua_ext(struct attrib *alist); + int tolua_attriblist_next(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_building.c b/core/src/bindings/bind_building.c new file mode 100644 index 000000000..230ba5b88 --- /dev/null +++ b/core/src/bindings/bind_building.c @@ -0,0 +1,249 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include "bind_building.h" +#include "bind_unit.h" + +#include +#include +#include +#include + +#include + +#include + +int tolua_buildinglist_next(lua_State * L) +{ + building **building_ptr = + (building **) lua_touserdata(L, lua_upvalueindex(1)); + building *u = *building_ptr; + if (u != NULL) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "building"); + *building_ptr = u->next; + return 1; + } else + return 0; /* no more values to return */ +} + +static int tolua_building_addaction(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + const char *fname = tolua_tostring(L, 2, 0); + const char *param = tolua_tostring(L, 3, 0); + + building_addaction(self, fname, param); + + return 0; +} + +static int tolua_building_get_objects(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, (void *)&self->attribs, TOLUA_CAST "hashtable"); + return 1; +} + +static int tolua_building_get_region(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, building_getregion(self), TOLUA_CAST "region"); + return 1; +} + +static int tolua_building_set_region(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + building_setregion(self, (region *) tolua_tousertype(L, 2, 0)); + return 0; +} + +static int tolua_building_get_info(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, self->display); + return 1; +} + +static int tolua_building_set_info(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + const char *info = tolua_tostring(L, 2, 0); + free(self->display); + if (info) + self->display = strdup(info); + else + self->display = NULL; + return 0; +} + +static int tolua_building_get_name(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, building_getname(self)); + return 1; +} + +static int tolua_building_set_name(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + building_setname(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_building_get_size(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, self->size); + return 1; +} + +static int tolua_building_set_size(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + self->size = (int)tolua_tonumber(L, 2, 0); + return 0; +} + +static int tolua_building_get_units(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + unit **unit_ptr = (unit **) lua_newuserdata(L, sizeof(unit *)); + unit *u = self->region->units; + + while (u && u->building != self) + u = u->next; + luaL_getmetatable(L, "unit"); + lua_setmetatable(L, -2); + + *unit_ptr = u; + + lua_pushcclosure(L, tolua_unitlist_nextb, 1); + return 1; +} + +static int tolua_building_get_id(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->no); + return 1; +} + +static int tolua_building_get_type(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, self->type->_name); + return 1; +} + +static int tolua_building_get_typename(lua_State * L) +{ + building *b = (building *) tolua_tousertype(L, 1, 0); + if (b) { + int size = (int)tolua_tonumber(L, 2, b->size); + tolua_pushstring(L, buildingtype(b->type, b, size)); + return 1; + } + return 0; +} + +static int tolua_building_get_owner(lua_State * L) +{ + building *b = (building *) tolua_tousertype(L, 1, 0); + unit *u = b ? building_owner(b) : NULL; + tolua_pushusertype(L, u, TOLUA_CAST "unit"); + return 1; +} + +static int tolua_building_set_owner(lua_State * L) +{ + building *b = (building *) tolua_tousertype(L, 1, 0); + unit *u = (unit *) tolua_tousertype(L, 2, 0); + if (b!=u->building) { + u_set_building(u, b); + } + building_set_owner(u); + return 0; +} + +static int tolua_building_create(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + const char *bname = tolua_tostring(L, 2, 0); + if (bname) { + const building_type *btype = bt_find(bname); + if (btype) { + building *b = new_building(btype, r, default_locale); + tolua_pushusertype(L, (void *)b, TOLUA_CAST "building"); + return 1; + } + } + return 0; +} + +static int tolua_building_tostring(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, buildingname(self)); + return 1; +} + +static int tolua_building_destroy(lua_State * L) +{ + building *self = (building *) tolua_tousertype(L, 1, 0); + remove_building(&self->region->buildings, self); + return 0; +} + +void tolua_building_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "building"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "building", TOLUA_CAST "building", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "building"); + { + tolua_function(L, TOLUA_CAST "create", tolua_building_create); + tolua_function(L, TOLUA_CAST "destroy", tolua_building_destroy); + tolua_function(L, TOLUA_CAST "__tostring", tolua_building_tostring); + + tolua_variable(L, TOLUA_CAST "id", tolua_building_get_id, NULL); + tolua_variable(L, TOLUA_CAST "owner", tolua_building_get_owner, + tolua_building_set_owner); + tolua_variable(L, TOLUA_CAST "type", tolua_building_get_type, NULL); + tolua_variable(L, TOLUA_CAST "name", tolua_building_get_name, + tolua_building_set_name); + tolua_variable(L, TOLUA_CAST "info", tolua_building_get_info, + tolua_building_set_info); + tolua_variable(L, TOLUA_CAST "units", tolua_building_get_units, NULL); + tolua_variable(L, TOLUA_CAST "region", tolua_building_get_region, + tolua_building_set_region); + tolua_variable(L, TOLUA_CAST "size", tolua_building_get_size, + tolua_building_set_size); + tolua_function(L, TOLUA_CAST "add_action", tolua_building_addaction); + tolua_function(L, TOLUA_CAST "get_typename", tolua_building_get_typename); +#ifdef TODO + .property("type", &building_gettype) + .def_readwrite("size", &building::size) +#endif + tolua_variable(L, TOLUA_CAST "objects", tolua_building_get_objects, 0); + + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_building.h b/core/src/bindings/bind_building.h new file mode 100644 index 000000000..a6168b66c --- /dev/null +++ b/core/src/bindings/bind_building.h @@ -0,0 +1,23 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + int tolua_buildinglist_next(struct lua_State *L); + void tolua_building_open(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_eressea.c b/core/src/bindings/bind_eressea.c new file mode 100755 index 000000000..1972bd0b1 --- /dev/null +++ b/core/src/bindings/bind_eressea.c @@ -0,0 +1,24 @@ +#include "bind_eressea.h" + +#include +#include +#include +#include +#include + +void eressea_free_game(void) { + free_gamedata(); +} + +int eressea_read_game(const char * filename) { + return readgame(filename, IO_BINARY, 0); +} + +int eressea_write_game(const char * filename) { + remove_empty_factions(); + return writegame(filename, IO_BINARY); +} + +int eressea_read_orders(const char * filename) { + return readorders(filename); +} diff --git a/core/src/bindings/bind_eressea.h b/core/src/bindings/bind_eressea.h new file mode 100755 index 000000000..f8ba00d8c --- /dev/null +++ b/core/src/bindings/bind_eressea.h @@ -0,0 +1,15 @@ +#ifndef BIND_ERESSEA_H +#define BIND_ERESSEA_H +#ifdef __cplusplus +extern "C" { +#endif + +void eressea_free_game(void); +int eressea_read_game(const char * filename); +int eressea_write_game(const char * filename); +int eressea_read_orders(const char * filename); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/bindings/bind_faction.c b/core/src/bindings/bind_faction.c new file mode 100644 index 000000000..775cb5ab8 --- /dev/null +++ b/core/src/bindings/bind_faction.c @@ -0,0 +1,568 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include "bind_faction.h" +#include "bind_unit.h" +#include "bindings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +int tolua_factionlist_next(lua_State * L) +{ + faction **faction_ptr = (faction **) lua_touserdata(L, lua_upvalueindex(1)); + faction *f = *faction_ptr; + if (f != NULL) { + tolua_pushusertype(L, (void *)f, TOLUA_CAST "faction"); + *faction_ptr = f->next; + return 1; + } else + return 0; /* no more values to return */ +} + +static int tolua_faction_get_units(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + unit **unit_ptr = (unit **) lua_newuserdata(L, sizeof(unit *)); + + luaL_getmetatable(L, TOLUA_CAST "unit"); + lua_setmetatable(L, -2); + + *unit_ptr = self->units; + + lua_pushcclosure(L, tolua_unitlist_nextf, 1); + return 1; +} + +int tolua_faction_add_item(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + const char *iname = tolua_tostring(L, 2, 0); + int number = (int)tolua_tonumber(L, 3, 0); + int result = -1; + + if (iname != NULL) { + const item_type *itype = it_find(iname); + if (itype != NULL) { + item *i = i_change(&self->items, itype, number); + result = i ? i->number : 0; + } /* if (itype!=NULL) */ + } + lua_pushnumber(L, result); + return 1; +} + +static int tolua_faction_get_maxheroes(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) maxheroes(self)); + return 1; +} + +static int tolua_faction_get_heroes(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) countheroes(self)); + return 1; +} + +static int tolua_faction_get_score(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->score); + return 1; +} + +static int tolua_faction_get_id(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->no); + return 1; +} + +static int tolua_faction_set_id(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + int id = (int)tolua_tonumber(L, 2, 0); + if (findfaction(id) == NULL) { + renumber_faction(self, id); + lua_pushboolean(L, 1); + } else { + lua_pushboolean(L, 0); + } + return 1; +} + +static int tolua_faction_get_magic(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, magic_school[self->magiegebiet]); + return 1; +} + +static int tolua_faction_set_magic(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + const char *type = tolua_tostring(L, 2, 0); + magic_t mtype; + + for (mtype = 0; mtype != MAXMAGIETYP; ++mtype) { + if (strcmp(magic_school[mtype], type) == 0) { + self->magiegebiet = mtype; + break; + } + } + return 0; +} + +static int tolua_faction_get_age(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->age); + return 1; +} + +static int tolua_faction_set_age(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + int age = (int)tolua_tonumber(L, 2, 0); + self->age = age; + return 0; +} + +static int tolua_faction_get_flags(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->flags); + return 1; +} + +static int tolua_faction_set_flags(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + int flags = (int)tolua_tonumber(L, 2, self->flags); + self->flags = flags; + return 1; +} + +static int tolua_faction_get_options(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->options); + return 1; +} + +static int tolua_faction_set_options(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + int options = (int)tolua_tonumber(L, 2, self->options); + self->options = options; + return 1; +} + +static int tolua_faction_get_lastturn(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->lastorders); + return 1; +} + +static int tolua_faction_set_lastturn(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + if (self) { + self->lastorders = (int)tolua_tonumber(L, 2, self->lastorders); + } + return 0; +} + +static int tolua_faction_renumber(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + int no = (int)tolua_tonumber(L, 2, 0); + + renumber_faction(self, no); + return 0; +} + +static int tolua_faction_get_objects(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, (void *)&self->attribs, TOLUA_CAST "hashtable"); + return 1; +} + +static int tolua_faction_get_policy(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + faction *other = (faction *) tolua_tousertype(L, 2, 0); + const char *policy = tolua_tostring(L, 3, 0); + + int result = 0, mode; + for (mode = 0; helpmodes[mode].name != NULL; ++mode) { + if (strcmp(policy, helpmodes[mode].name) == 0) { + result = get_alliance(self, other) & mode; + break; + } + } + + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_faction_set_policy(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + faction *other = (faction *) tolua_tousertype(L, 2, 0); + const char *policy = tolua_tostring(L, 3, 0); + int value = tolua_toboolean(L, 4, 0); + + int mode; + for (mode = 0; helpmodes[mode].name != NULL; ++mode) { + if (strcmp(policy, helpmodes[mode].name) == 0) { + if (value) { + set_alliance(self, other, get_alliance(self, + other) | helpmodes[mode].status); + } else { + set_alliance(self, other, get_alliance(self, + other) & ~helpmodes[mode].status); + } + break; + } + } + + return 0; +} + +static int tolua_faction_normalize(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, 0); + region *r = (region *) tolua_tousertype(L, 2, 0); + if (r) { + plane *pl = rplane(r); + int nx = r->x, ny = r->y; + pnormalize(&nx, &ny, pl); + adjust_coordinates(f, &nx, &ny, pl, r); + tolua_pushnumber(L, (lua_Number) nx); + tolua_pushnumber(L, (lua_Number) ny); + return 2; + } + return 0; +} + +static int tolua_faction_set_origin(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, 0); + region *r = (region *) tolua_tousertype(L, 2, 0); + plane *pl = rplane(r); + int id = pl ? pl->id : 0; + + set_ursprung(f, id, r->x - plane_center_x(pl), r->y - plane_center_y(pl)); + return 0; +} + +static int tolua_faction_get_origin(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + + ursprung *origin = self->ursprung; + int x, y; + while (origin != NULL && origin->id != 0) { + origin = origin->next; + } + if (origin) { + x = origin->x; + y = origin->y; + } else { + x = 0; + y = 0; + } + + tolua_pushnumber(L, (lua_Number) x); + tolua_pushnumber(L, (lua_Number) y); + return 2; +} + +static int tolua_faction_destroy(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, 0); + destroyfaction(f); + return 0; +} + +static int tolua_faction_create(lua_State * L) +{ + const char *email = tolua_tostring(L, 1, 0); + const char *racename = tolua_tostring(L, 2, 0); + const char *lang = tolua_tostring(L, 3, 0); + struct locale *loc = find_locale(lang); + faction *f = NULL; + const struct race *frace = rc_find(racename); + if (frace == NULL) + frace = findrace(racename, find_locale("de")); + if (frace == NULL) + frace = findrace(racename, find_locale("en")); + if (frace != NULL) { + f = addfaction(email, NULL, frace, loc, 0); + } + if (!f) { + log_error("faction.create(%s, %s, %s)\n", email, racename, lang); + } + tolua_pushusertype(L, f, TOLUA_CAST "faction"); + return 1; +} + +static int tolua_faction_get_password(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, faction_getpassword(self)); + return 1; +} + +static int tolua_faction_set_password(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + faction_setpassword(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_faction_get_email(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, faction_getemail(self)); + return 1; +} + +static int tolua_faction_set_email(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + faction_setemail(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_faction_get_locale(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, locale_name(self->locale)); + return 1; +} + +static int tolua_faction_set_locale(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + self->locale = find_locale(name); + return 0; +} + +static int tolua_faction_get_race(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, self->race->_name[0]); + return 1; +} + +static int tolua_faction_set_race(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + race *rc = rc_find(name); + if (rc != NULL) { + self->race = rc; + } + + return 0; +} + +static int tolua_faction_get_name(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, faction_getname(self)); + return 1; +} + +static int tolua_faction_set_name(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + faction_setname(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_faction_get_uid(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, f->subscription); + return 1; +} + +static int tolua_faction_set_uid(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, 0); + f->subscription = (int)tolua_tonumber(L, 2, 0); + return 0; +} + +static int tolua_faction_get_info(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, faction_getbanner(self)); + return 1; +} + +static int tolua_faction_set_info(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + faction_setbanner(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_faction_get_alliance(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, f_get_alliance(self), TOLUA_CAST "alliance"); + return 1; +} + +static int tolua_faction_set_alliance(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + alliance *alli = (alliance *) tolua_tousertype(L, 2, 0); + + setalliance(self, alli); + + return 0; +} + +static int tolua_faction_get_items(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + item **item_ptr = (item **) lua_newuserdata(L, sizeof(item *)); + + luaL_getmetatable(L, TOLUA_CAST "item"); + lua_setmetatable(L, -2); + + *item_ptr = self->items; + + lua_pushcclosure(L, tolua_itemlist_next, 1); + + return 1; +} + +static int tolua_faction_tostring(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, factionname(self)); + return 1; +} + +#ifdef TODO /* these usertypes are undefined */ +static int tolua_faction_get_spells(lua_State * L) +{ + faction *self = (faction *) tolua_tousertype(L, 1, 0); + return tolua_quicklist_push(L, "spellbook", "spellbook_entry", self->spellbook->spells); +} +#endif + +void tolua_faction_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "faction"); + tolua_usertype(L, TOLUA_CAST "faction_list"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "faction", TOLUA_CAST "faction", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "faction"); + { + tolua_function(L, TOLUA_CAST "__tostring", tolua_faction_tostring); + + tolua_variable(L, TOLUA_CAST "id", tolua_faction_get_id, + tolua_faction_set_id); + tolua_variable(L, TOLUA_CAST "uid", &tolua_faction_get_uid, + &tolua_faction_set_uid); + tolua_variable(L, TOLUA_CAST "name", &tolua_faction_get_name, + &tolua_faction_set_name); + tolua_variable(L, TOLUA_CAST "info", &tolua_faction_get_info, + &tolua_faction_set_info); + tolua_variable(L, TOLUA_CAST "units", tolua_faction_get_units, NULL); + tolua_variable(L, TOLUA_CAST "heroes", tolua_faction_get_heroes, NULL); +#ifdef TODO + tolua_variable(L, TOLUA_CAST "spells", tolua_faction_get_spells, 0); +#endif + tolua_variable(L, TOLUA_CAST "maxheroes", tolua_faction_get_maxheroes, + NULL); + tolua_variable(L, TOLUA_CAST "password", tolua_faction_get_password, + tolua_faction_set_password); + tolua_variable(L, TOLUA_CAST "email", tolua_faction_get_email, + tolua_faction_set_email); + tolua_variable(L, TOLUA_CAST "locale", tolua_faction_get_locale, + tolua_faction_set_locale); + tolua_variable(L, TOLUA_CAST "race", tolua_faction_get_race, + tolua_faction_set_race); + tolua_variable(L, TOLUA_CAST "alliance", tolua_faction_get_alliance, + tolua_faction_set_alliance); + tolua_variable(L, TOLUA_CAST "score", tolua_faction_get_score, NULL); + tolua_variable(L, TOLUA_CAST "magic", &tolua_faction_get_magic, + tolua_faction_set_magic); + tolua_variable(L, TOLUA_CAST "age", tolua_faction_get_age, + tolua_faction_set_age); + tolua_variable(L, TOLUA_CAST "options", tolua_faction_get_options, + tolua_faction_set_options); + tolua_variable(L, TOLUA_CAST "flags", tolua_faction_get_flags, tolua_faction_set_flags); + tolua_variable(L, TOLUA_CAST "lastturn", tolua_faction_get_lastturn, + tolua_faction_set_lastturn); + + tolua_function(L, TOLUA_CAST "set_policy", &tolua_faction_set_policy); + tolua_function(L, TOLUA_CAST "get_policy", &tolua_faction_get_policy); + tolua_function(L, TOLUA_CAST "get_origin", &tolua_faction_get_origin); + tolua_function(L, TOLUA_CAST "set_origin", &tolua_faction_set_origin); + tolua_function(L, TOLUA_CAST "normalize", &tolua_faction_normalize); + + tolua_function(L, TOLUA_CAST "add_item", tolua_faction_add_item); + tolua_variable(L, TOLUA_CAST "items", tolua_faction_get_items, NULL); + + tolua_function(L, TOLUA_CAST "renumber", &tolua_faction_renumber); + tolua_function(L, TOLUA_CAST "create", &tolua_faction_create); + tolua_function(L, TOLUA_CAST "destroy", &tolua_faction_destroy); +#ifdef TODO + def("faction_origin", &faction_getorigin, + pure_out_value(_2) + pure_out_value(_3)),.def_readwrite("subscription", + &faction::subscription) + + .property("x", &faction_getorigin_x, &faction_setorigin_x) + .property("y", &faction_getorigin_y, &faction_setorigin_y) + + .def("add_notice", &faction_addnotice) +#endif + tolua_variable(L, TOLUA_CAST "objects", tolua_faction_get_objects, + NULL); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_faction.h b/core/src/bindings/bind_faction.h new file mode 100644 index 000000000..c390c5606 --- /dev/null +++ b/core/src/bindings/bind_faction.h @@ -0,0 +1,23 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + int tolua_factionlist_next(struct lua_State *L); + void tolua_faction_open(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_gmtool.c b/core/src/bindings/bind_gmtool.c new file mode 100644 index 000000000..654f16f30 --- /dev/null +++ b/core/src/bindings/bind_gmtool.c @@ -0,0 +1,251 @@ +#include +#include +#include + +#include "bind_gmtool.h" +#include "../gmtool.h" +#include "../gmtool_structs.h" + +#include +#include +#include +#include + +#include + +static int tolua_run_mapper(lua_State * L) +{ + run_mapper(); + return 0; +} + +static int tolua_highlight_region(lua_State * L) +{ + region *r = tolua_tousertype(L, 1, 0); + int select = tolua_toboolean(L, 2, 0); + highlight_region(r, select); + return 0; +} + +static int tolua_current_region(lua_State * L) +{ + map_region *mr = + cursor_region(¤t_state->display, ¤t_state->cursor); + tolua_pushusertype(L, mr ? mr->r : NULL, TOLUA_CAST "region"); + return 1; +} + +static int tolua_select_coordinate(lua_State * L) +{ + int nx = (int)tolua_tonumber(L, 1, 0); + int ny = (int)tolua_tonumber(L, 2, 0); + int select = tolua_toboolean(L, 3, 0); + if (current_state) { + select_coordinate(current_state->selected, nx, ny, select); + } + return 0; +} + +static int tolua_select_region(lua_State * L) +{ + region *r = tolua_tousertype(L, 1, 0); + int select = tolua_toboolean(L, 2, 0); + if (current_state && r) { + select_coordinate(current_state->selected, r->x, r->y, select); + } + return 0; +} + +typedef struct tag_iterator { + selection *list; + tag *node; + region *r; + int hash; +} tag_iterator; + +void tag_advance(tag_iterator * iter) +{ + while (iter->hash != MAXTHASH) { + if (iter->node) { + iter->node = iter->node->nexthash; + } + while (!iter->node && iter->hash != MAXTHASH) { + if (++iter->hash != MAXTHASH) { + iter->node = iter->list->tags[iter->hash]; + } + } + if (iter->node) { + iter->r = findregion(iter->node->coord.x, iter->node->coord.y); + if (iter->r) { + break; + } + } + } +} + +void tag_rewind(tag_iterator * iter) +{ + if (iter->list) { + iter->r = NULL; + iter->node = iter->list->tags[0]; + iter->hash = 0; + if (iter->node) { + iter->r = findregion(iter->node->coord.x, iter->node->coord.y); + } + if (!iter->r) { + tag_advance(iter); + } + } else { + iter->node = 0; + iter->hash = MAXTHASH; + } +} + +static int tolua_tags_next(lua_State * L) +{ + tag_iterator *iter = (tag_iterator *) lua_touserdata(L, lua_upvalueindex(1)); + if (iter->node) { + tolua_pushusertype(L, (void *)iter->r, TOLUA_CAST "region"); + tag_advance(iter); + return 1; + } else { + return 0; /* no more values to return */ + } +} + +static int tolua_selected_regions(lua_State * L) +{ + tag_iterator *iter = + (tag_iterator *) lua_newuserdata(L, sizeof(tag_iterator)); + + luaL_getmetatable(L, "tag_iterator"); + lua_setmetatable(L, -2); + + iter->list = current_state->selected; + tag_rewind(iter); + + lua_pushcclosure(L, tolua_tags_next, 1); + return 1; +} + +static int tolua_state_open(lua_State * L) +{ + unused(L); + state_open(); + return 0; +} + +static int tolua_state_close(lua_State * L) +{ + unused(L); + state_close(current_state); + return 0; +} + +static int tolua_make_island(lua_State * L) +{ + int x = (int)tolua_tonumber(L, 1, 0); + int y = (int)tolua_tonumber(L, 2, 0); + int s = (int)tolua_tonumber(L, 3, 0); + int n = (int)tolua_tonumber(L, 4, s / 3); + + n = build_island_e3(x, y, n, s); + tolua_pushnumber(L, n); + return 1; +} + +static int paint_handle; +static struct lua_State *paint_state; + +static void lua_paint_info(struct window *wnd, const struct state *st) +{ + struct lua_State *L = paint_state; + int nx = st->cursor.x, ny = st->cursor.y; + pnormalize(&nx, &ny, st->cursor.pl); + lua_rawgeti(L, LUA_REGISTRYINDEX, paint_handle); + tolua_pushnumber(L, nx); + tolua_pushnumber(L, ny); + if (lua_pcall(L, 2, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("paint function failed: %s\n", error); + lua_pop(L, 1); + tolua_error(L, TOLUA_CAST "event handler call failed", NULL); + } else { + const char *result = lua_tostring(L, -1); + WINDOW *win = wnd->handle; + int size = getmaxx(win) - 2; + int line = 0, maxline = getmaxy(win) - 2; + const char *str = result; + wxborder(win); + + while (*str && line < maxline) { + const char *end = strchr(str, '\n'); + if (!end) + break; + else { + size_t len = end - str; + int bytes = MIN((int)len, size); + mvwaddnstr(win, line++, 1, str, bytes); + wclrtoeol(win); + str = end + 1; + } + } + } +} + +static int tolua_set_display(lua_State * L) +{ + int type = lua_type(L, 1); + if (type == LUA_TFUNCTION) { + lua_pushvalue(L, 1); + paint_handle = luaL_ref(L, LUA_REGISTRYINDEX); + paint_state = L; + + set_info_function(&lua_paint_info); + } else { + set_info_function(NULL); + } + return 0; +} + +static int tolua_make_block(lua_State * L) +{ + int x = (int)tolua_tonumber(L, 1, 0); + int y = (int)tolua_tonumber(L, 2, 0); + int r = (int)tolua_tonumber(L, 3, 6); + const char *str = tolua_tostring(L, 4, TOLUA_CAST "ocean"); + const struct terrain_type *ter = get_terrain(str); + + make_block(x, y, r, ter); + return 0; +} + +void tolua_gmtool_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "tag_iterator"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_module(L, TOLUA_CAST "gmtool", 0); + tolua_beginmodule(L, TOLUA_CAST "gmtool"); + { + tolua_function(L, TOLUA_CAST "open", &tolua_state_open); + tolua_function(L, TOLUA_CAST "close", &tolua_state_close); + + tolua_function(L, TOLUA_CAST "editor", &tolua_run_mapper); + tolua_function(L, TOLUA_CAST "get_selection", &tolua_selected_regions); + tolua_function(L, TOLUA_CAST "get_cursor", &tolua_current_region); + tolua_function(L, TOLUA_CAST "highlight", &tolua_highlight_region); + tolua_function(L, TOLUA_CAST "select", &tolua_select_region); + tolua_function(L, TOLUA_CAST "select_at", &tolua_select_coordinate); + tolua_function(L, TOLUA_CAST "set_display", &tolua_set_display); + + tolua_function(L, TOLUA_CAST "make_block", &tolua_make_block); + tolua_function(L, TOLUA_CAST "make_island", &tolua_make_island); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_gmtool.h b/core/src/bindings/bind_gmtool.h new file mode 100644 index 000000000..415fe3d58 --- /dev/null +++ b/core/src/bindings/bind_gmtool.h @@ -0,0 +1,10 @@ +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + void tolua_gmtool_open(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_hashtable.c b/core/src/bindings/bind_hashtable.c new file mode 100644 index 000000000..17c6c4975 --- /dev/null +++ b/core/src/bindings/bind_hashtable.c @@ -0,0 +1,178 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include "bind_hashtable.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +static int tolua_hashtable_get(lua_State * L) +{ + hashtable self = (hashtable) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + attrib *a = a_find(*self, &at_object); + + for (; a && a->type == &at_object; a = a->next) { + const char *obj_name = object_name(a); + if (obj_name && name && strcmp(obj_name, name) == 0) { + variant val; + object_type type; + + object_get(a, &type, &val); + switch (type) { + case TNONE: + lua_pushnil(L); + break; + case TINTEGER: + lua_pushnumber(L, (lua_Number) val.i); + break; + case TREAL: + lua_pushnumber(L, (lua_Number) val.f); + break; + case TREGION: + tolua_pushusertype(L, val.v, TOLUA_CAST "region"); + break; + case TBUILDING: + tolua_pushusertype(L, val.v, TOLUA_CAST "building"); + break; + case TUNIT: + tolua_pushusertype(L, val.v, TOLUA_CAST "unit"); + break; + case TSHIP: + tolua_pushusertype(L, val.v, TOLUA_CAST "ship"); + break; + case TSTRING: + tolua_pushstring(L, (const char *)val.v); + break; + default: + assert(!"not implemented"); + } + return 1; + } + } + lua_pushnil(L); + return 1; +} + +static int tolua_hashtable_set_number(lua_State * L) +{ + hashtable self = (hashtable) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + lua_Number value = tolua_tonumber(L, 3, 0); + attrib *a = a_find(*self, &at_object); + variant val; + + val.f = (float)value; + + for (; a && a->type == &at_object; a = a->next) { + if (strcmp(object_name(a), name) == 0) { + object_set(a, TREAL, val); + return 0; + } + } + + a = a_add(self, object_create(name, TREAL, val)); + return 0; +} + +static int tolua_hashtable_set_string(lua_State * L) +{ + hashtable self = (hashtable) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + const char *value = tolua_tostring(L, 3, 0); + attrib *a = a_find(*self, &at_object); + variant val; + + val.v = strdup(value); + + for (; a && a->type == &at_object; a = a->next) { + if (strcmp(object_name(a), name) == 0) { + object_set(a, TSTRING, val); + return 0; + } + } + + a = a_add(self, object_create(name, TSTRING, val)); + return 0; +} + +static int tolua_hashtable_set_usertype(lua_State * L, int type) +{ + hashtable self = (hashtable) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + unit *value = tolua_tousertype(L, 3, 0); + attrib *a = a_find(*self, &at_object); + variant val; + + val.v = value; + + for (; a && a->type == &at_object; a = a->next) { + if (strcmp(object_name(a), name) == 0) { + object_set(a, type, val); + return 0; + } + } + + a = a_add(self, object_create(name, type, val)); + return 0; +} + +static int tolua_hashtable_set(lua_State * L) +{ + tolua_Error tolua_err; + if (tolua_isnumber(L, 3, 0, &tolua_err)) { + return tolua_hashtable_set_number(L); + } else if (tolua_isusertype(L, 3, TOLUA_CAST "unit", 0, &tolua_err)) { + return tolua_hashtable_set_usertype(L, TUNIT); + } else if (tolua_isusertype(L, 3, TOLUA_CAST "faction", 0, &tolua_err)) { + return tolua_hashtable_set_usertype(L, TFACTION); + } else if (tolua_isusertype(L, 3, TOLUA_CAST "ship", 0, &tolua_err)) { + return tolua_hashtable_set_usertype(L, TSHIP); + } else if (tolua_isusertype(L, 3, TOLUA_CAST "building", 0, &tolua_err)) { + return tolua_hashtable_set_usertype(L, TBUILDING); + } else if (tolua_isusertype(L, 3, TOLUA_CAST "region", 0, &tolua_err)) { + return tolua_hashtable_set_usertype(L, TREGION); + } + return tolua_hashtable_set_string(L); +} + +void tolua_hashtable_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "hashtable"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "hashtable", TOLUA_CAST "hashtable", + TOLUA_CAST "", NULL); + tolua_beginmodule(L, TOLUA_CAST "hashtable"); + { + tolua_function(L, TOLUA_CAST "get", tolua_hashtable_get); + tolua_function(L, TOLUA_CAST "set", tolua_hashtable_set); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_hashtable.h b/core/src/bindings/bind_hashtable.h new file mode 100644 index 000000000..5e23350f6 --- /dev/null +++ b/core/src/bindings/bind_hashtable.h @@ -0,0 +1,24 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + void tolua_hashtable_open(struct lua_State *L); + + typedef struct attrib **hashtable; + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_message.c b/core/src/bindings/bind_message.c new file mode 100644 index 000000000..2959c6c5c --- /dev/null +++ b/core/src/bindings/bind_message.c @@ -0,0 +1,333 @@ +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include + +/* lua includes */ +#include + +#include + +#define E_OK 0 +#define E_INVALID_MESSAGE 1 +#define E_INVALID_PARAMETER_NAME 2 +#define E_INVALID_PARAMETER_TYPE 3 +#define E_INVALID_PARAMETER_VALUE 4 + +typedef struct lua_message { + const message_type *mtype; + message *msg; + variant *args; +} lua_message; + +int mtype_get_param(const message_type * mtype, const char *param) +{ + int i; + for (i = 0; i != mtype->nparameters; ++i) { + if (strcmp(mtype->pnames[i], param) == 0) { + return i; + } + } + return mtype->nparameters; +} + +static lua_message *msg_create_message(const char *type) +{ + lua_message *lmsg = malloc(sizeof(lua_message)); + lmsg->msg = 0; + lmsg->args = 0; + lmsg->mtype = mt_find(type); + if (lmsg->mtype) { + lmsg->args = (variant *) calloc(lmsg->mtype->nparameters, sizeof(variant)); + } + return lmsg; +} + +/* + static void +msg_destroy_message(lua_message * msg) +{ + if (msg->msg) msg_release(msg->msg); + if (msg->mtype) { + int i; + for (i=0;i!=msg->mtype->nparameters;++i) { + if (msg->mtype->types[i]->release) { + msg->mtype->types[i]->release(msg->args[i]); + } + } + } +} +*/ +int msg_set_resource(lua_message * msg, const char *param, const char *resname) +{ + if (msg->mtype) { + int i = mtype_get_param(msg->mtype, param); + const resource_type * rtype; + if (i == msg->mtype->nparameters) { + return E_INVALID_PARAMETER_NAME; + } + if (strcmp(msg->mtype->types[i]->name, "resource") != 0) { + return E_INVALID_PARAMETER_TYPE; + } + + rtype = rt_find(resname); + if (rtype) { + msg->args[i].v = (void *)rtype; + } else { + return E_INVALID_PARAMETER_VALUE; + } + return E_OK; + } + return E_INVALID_MESSAGE; +} + +int msg_set_unit(lua_message * msg, const char *param, const unit * u) +{ + if (msg->mtype) { + int i = mtype_get_param(msg->mtype, param); + + if (i == msg->mtype->nparameters) { + return E_INVALID_PARAMETER_NAME; + } + if (strcmp(msg->mtype->types[i]->name, "unit") != 0) { + return E_INVALID_PARAMETER_TYPE; + } + + msg->args[i].v = (void *)u; + + return E_OK; + } + return E_INVALID_MESSAGE; +} + +int msg_set_region(lua_message * msg, const char *param, const region * r) +{ + if (msg->mtype) { + int i = mtype_get_param(msg->mtype, param); + + if (i == msg->mtype->nparameters) { + return E_INVALID_PARAMETER_NAME; + } + if (strcmp(msg->mtype->types[i]->name, "region") != 0) { + return E_INVALID_PARAMETER_TYPE; + } + + msg->args[i].v = (void *)r; + + return E_OK; + } + return E_INVALID_MESSAGE; +} + +int msg_set_string(lua_message * msg, const char *param, const char *value) +{ + if (msg->mtype) { + int i = mtype_get_param(msg->mtype, param); + variant var; + + if (i == msg->mtype->nparameters) { + return E_INVALID_PARAMETER_NAME; + } + if (strcmp(msg->mtype->types[i]->name, "string") != 0) { + return E_INVALID_PARAMETER_TYPE; + } + + var.v = (void *)value; + msg->args[i] = msg->mtype->types[i]->copy(var); + + return E_OK; + } + return E_INVALID_MESSAGE; +} + +int msg_set_int(lua_message * msg, const char *param, int value) +{ + if (msg->mtype) { + int i = mtype_get_param(msg->mtype, param); + if (i == msg->mtype->nparameters) { + return E_INVALID_PARAMETER_NAME; + } + if (strcmp(msg->mtype->types[i]->name, "int") != 0) { + return E_INVALID_PARAMETER_TYPE; + } + + msg->args[i].i = value; + + return E_OK; + } + return E_INVALID_MESSAGE; +} + +int msg_send_faction(lua_message * msg, faction * f) +{ + assert(f); + assert(msg); + + if (msg->mtype) { + if (msg->msg == NULL) { + msg->msg = msg_create(msg->mtype, msg->args); + } + add_message(&f->msgs, msg->msg); + return E_OK; + } + return E_INVALID_MESSAGE; +} + +int msg_send_region(lua_message * lmsg, region * r) +{ + if (lmsg->mtype) { + if (lmsg->msg == NULL) { + lmsg->msg = msg_create(lmsg->mtype, lmsg->args); + } + add_message(&r->msgs, lmsg->msg); + return E_OK; + } + return E_INVALID_MESSAGE; +} + +static int tolua_msg_create(lua_State * L) +{ + const char *type = tolua_tostring(L, 1, 0); + lua_message *lmsg = msg_create_message(type); + tolua_pushusertype(L, (void *)lmsg, TOLUA_CAST "message"); + return 1; +} + +static int tolua_msg_set_string(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + const char *param = tolua_tostring(L, 2, 0); + const char *value = tolua_tostring(L, 3, 0); + int result = msg_set_string(lmsg, param, value); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_msg_set_int(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + const char *param = tolua_tostring(L, 2, 0); + int value = (int)tolua_tonumber(L, 3, 0); + int result = msg_set_int(lmsg, param, value); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_msg_set_resource(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + const char *param = tolua_tostring(L, 2, 0); + const char *value = tolua_tostring(L, 3, 0); + int result = msg_set_resource(lmsg, param, value); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_msg_set_unit(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + const char *param = tolua_tostring(L, 2, 0); + unit *value = (unit *) tolua_tousertype(L, 3, 0); + int result = msg_set_unit(lmsg, param, value); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_msg_set_region(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + const char *param = tolua_tostring(L, 2, 0); + region *value = (region *) tolua_tousertype(L, 3, 0); + int result = msg_set_region(lmsg, param, value); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_msg_set(lua_State * L) +{ + tolua_Error err; + if (tolua_isnumber(L, 3, 0, &err)) { + return tolua_msg_set_int(L); + } else if (tolua_isusertype(L, 3, TOLUA_CAST "region", 0, &err)) { + return tolua_msg_set_region(L); + } else if (tolua_isusertype(L, 3, TOLUA_CAST "unit", 0, &err)) { + return tolua_msg_set_unit(L); + } + tolua_pushnumber(L, (lua_Number) - 1); + return 1; +} + +static int tolua_msg_send_region(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + region *r = (region *) tolua_tousertype(L, 2, 0); + int result = msg_send_region(lmsg, r); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_msg_report_action(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + region *r = (region *) tolua_tousertype(L, 2, 0); + unit *u = (unit *) tolua_tousertype(L, 3, 0); + int result, flags = (int)tolua_tonumber(L, 4, 0); + if (lmsg->msg == NULL) { + lmsg->msg = msg_create(lmsg->mtype, lmsg->args); + } + result = report_action(r, u, lmsg->msg, flags); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_msg_send_faction(lua_State * L) +{ + lua_message *lmsg = (lua_message *) tolua_tousertype(L, 1, 0); + faction *f = (faction *) tolua_tousertype(L, 2, 0); + if (f && lmsg) { + int result = msg_send_faction(lmsg, f); + tolua_pushnumber(L, (lua_Number) result); + return 1; + } + return 0; +} + +void tolua_message_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "message"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_function(L, TOLUA_CAST "message", tolua_msg_create); + + tolua_cclass(L, TOLUA_CAST "message", TOLUA_CAST "message", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "message"); + { + tolua_function(L, TOLUA_CAST "set", tolua_msg_set); + tolua_function(L, TOLUA_CAST "set_unit", tolua_msg_set_unit); + tolua_function(L, TOLUA_CAST "set_region", tolua_msg_set_region); + tolua_function(L, TOLUA_CAST "set_resource", tolua_msg_set_resource); + tolua_function(L, TOLUA_CAST "set_int", tolua_msg_set_int); + tolua_function(L, TOLUA_CAST "set_string", tolua_msg_set_string); + tolua_function(L, TOLUA_CAST "send_faction", tolua_msg_send_faction); + tolua_function(L, TOLUA_CAST "send_region", tolua_msg_send_region); + tolua_function(L, TOLUA_CAST "report_action", tolua_msg_report_action); + + tolua_function(L, TOLUA_CAST "create", tolua_msg_create); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_message.h b/core/src/bindings/bind_message.h new file mode 100644 index 000000000..ed9aab133 --- /dev/null +++ b/core/src/bindings/bind_message.h @@ -0,0 +1,22 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + void tolua_message_open(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_process.c b/core/src/bindings/bind_process.c new file mode 100755 index 000000000..83420578b --- /dev/null +++ b/core/src/bindings/bind_process.c @@ -0,0 +1,294 @@ +#include "bind_process.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PROC_LAND_REGION 0x0001 +#define PROC_LONG_ORDER 0x0002 + +static void process_cmd(keyword_t kwd, int (*callback)(unit *, order *), int flags) +{ + region * r; + for (r=regions; r; r=r->next) { + unit * u; + + /* look for shortcuts */ + if (flags&PROC_LAND_REGION) { + /* only execute when we are on solid terrain */ + while (r && (r->terrain->flags&LAND_REGION)==0) { + r = r->next; + } + if (!r) break; + } + + for (u=r->units; u; u=u->next) { + if (flags & PROC_LONG_ORDER) { + if (kwd == get_keyword(u->thisorder)) { + callback(u, u->thisorder); + } + } else { + order * ord; + for (ord=u->orders; ord; ord=ord->next) { + if (kwd == get_keyword(ord)) { + callback(u, ord); + } + } + } + } + } +} + +void process_produce(void) { + struct region *r; + for (r = regions; r; r = r->next) { + unit * u; + for (u=r->units; u; u=u->next) { + order * ord; + for (ord=u->orders; ord; ord=ord->next) { + if (K_MAKE == get_keyword(ord)) { + make_cmd(u, ord); + } + } + } + produce(r); + split_allocations(r); + } +} + +void process_battle(void) { + struct region *r; + for (r = regions; r; r = r->next) { + do_battle(r); + } +} + +void process_siege(void) { + process_cmd(K_BESIEGE, siege_cmd, PROC_LAND_REGION); +} + +void process_update_long_order(void) { + region * r; + for (r=regions; r; r=r->next) { + unit * u; + for (u=r->units; u; u=u->next) { + update_long_order(u); + } + } +} + +void process_markets(void) { + do_markets(); +} + +void process_make_temp(void) { + new_units(); +} + +void process_settings(void) { + region * r; + for (r=regions; r; r=r->next) { + unit * u; + for (u=r->units; u; u=u->next) { + order * ord; + for (ord=u->orders; ord; ord=ord->next) { + keyword_t kwd = get_keyword(ord); + if (kwd==K_BANNER) { + banner_cmd(u, ord); + } + else if (kwd==K_EMAIL) { + email_cmd(u, ord); + } + else if (kwd==K_SEND) { + send_cmd(u, ord); + } + else if (kwd==K_PASSWORD) { + password_cmd(u, ord); + } + } + } + } +} + +void process_ally(void) { + process_cmd(K_ALLY, ally_cmd, 0); +} + +void process_prefix(void) { + process_cmd(K_PREFIX, prefix_cmd, 0); +} + +void process_setstealth(void) { + process_cmd(K_SETSTEALTH, setstealth_cmd, 0); +} + +void process_status(void) { + process_cmd(K_STATUS, status_cmd, 0); +} + +void process_name(void) { + process_cmd(K_NAME, name_cmd, 0); + process_cmd(K_DISPLAY, display_cmd, 0); +} + +void process_group(void) { + process_cmd(K_GROUP, group_cmd, 0); +} + +void process_origin(void) { + process_cmd(K_URSPRUNG, origin_cmd, 0); +} + +void process_quit(void) { + process_cmd(K_QUIT, quit_cmd, 0); + quit(); +} + +void process_study(void) { + process_cmd(K_TEACH, teach_cmd, PROC_LONG_ORDER); + process_cmd(K_STUDY, learn_cmd, PROC_LONG_ORDER); +} + +void process_movement(void) { + region * r; + + movement(); + for (r=regions; r; r=r->next) { + if (r->ships) { + sinkships(r); + } + } +} + +void process_use(void) { + process_cmd(K_USE, use_cmd, 0); +} + +void process_leave(void) { + process_cmd(K_LEAVE, leave_cmd, 0); +} + +void process_promote(void) { + process_cmd(K_PROMOTION, promotion_cmd, 0); +} + +void process_renumber(void) { + process_cmd(K_NUMBER, renumber_cmd, 0); + renumber_factions(); +} + +void process_restack(void) { + restack_units(); +} + +void process_setspells(void) { + process_cmd(K_COMBATSPELL, combatspell_cmd, 0); +} + +void process_sethelp(void) { + process_cmd(K_ALLY, ally_cmd, 0); +} + +void process_contact(void) { + process_cmd(K_CONTACT, contact_cmd, 0); +} + +void process_magic(void) { + magic(); +} + +void process_give_control(void) { + process_cmd(K_CONTACT, give_control_cmd, 0); +} + +void process_guard_on(void) { + process_cmd(K_GUARD, guard_on_cmd, PROC_LAND_REGION); +} + +void process_explain(void) { + process_cmd(K_RESHOW, reshow_cmd, 0); +} + +void process_reserve(void) { + process_cmd(K_RESERVE, reserve_cmd, 0); +} + +void process_claim(void) { + process_cmd(K_CLAIM, claim_cmd, 0); +} + +void process_follow(void) { + struct region *r; + for (r = regions; r; r = r->next) { + unit * u; + for (u=r->units; u; u=u->next) { + follow_unit(u); + } + } +} + +void process_messages(void) { + process_cmd(K_MAIL, mail_cmd, 0); +} + +void process_guard_off(void) { + process_cmd(K_GUARD, guard_off_cmd, PROC_LAND_REGION); +} + +void process_regeneration(void) { + monthly_healing(); + regenerate_aura(); +} + +void process_enter(int final) { + region * r; + for (r=regions; r; r=r->next) { + do_enter(r, final); + } +} + +void process_maintenance(void) { + region * r; + for (r=regions; r; r=r->next) { + unit * u; + for (u=r->units; u; u=u->next) { + order * ord; + for (ord=u->orders; ord; ord=ord->next) { + keyword_t kwd = get_keyword(ord); + if (kwd==K_PAY) { + pay_cmd(u, ord); + } + } + } + maintain_buildings(r, 0); + } +} + +void process_alliance(void) { + alliance_cmd(); +} + +void process_idle(void) { + region * r; + for (r=regions; r; r=r->next) { + auto_work(r); + } +} + +void process_set_default(void) { + if (!global.disabled[K_DEFAULT]) { + defaultorders(); + } +} diff --git a/core/src/bindings/bind_process.h b/core/src/bindings/bind_process.h new file mode 100755 index 000000000..767c7eb77 --- /dev/null +++ b/core/src/bindings/bind_process.h @@ -0,0 +1,52 @@ +#ifndef BIND_ERESSEA_PROCESS_H +#define BIND_ERESSEA_PROCESS_H +#ifdef __cplusplus +extern "C" { +#endif + +void process_produce(void); +void process_markets(void); +void process_update_long_order(void); + +void process_make_temp(void); +void process_settings(void); +void process_ally(void); +void process_prefix(void); +void process_setstealth(void); +void process_status(void); +void process_name(void); +void process_group(void); +void process_origin(void); +void process_quit(void); +void process_study(void); +void process_movement(void); +void process_use(void); +void process_battle(void); +void process_siege(void); +void process_leave(void); +void process_maintenance(void); +void process_promote(void); +void process_renumber(void); +void process_restack(void); +void process_setspells(void); +void process_sethelp(void); +void process_contact(void); +void process_enter(int final); +void process_magic(void); +void process_give_control(void); +void process_regeneration(void); +void process_guard_on(void); +void process_guard_off(void); +void process_explain(void); +void process_messages(void); +void process_reserve(void); +void process_claim(void); +void process_follow(void); +void process_alliance(void); +void process_idle(void); +void process_set_default(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/bindings/bind_region.c b/core/src/bindings/bind_region.c new file mode 100644 index 000000000..b5fb16108 --- /dev/null +++ b/core/src/bindings/bind_region.c @@ -0,0 +1,713 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include "bind_region.h" +#include "bind_unit.h" +#include "bind_ship.h" +#include "bind_building.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +int tolua_regionlist_next(lua_State * L) +{ + region **region_ptr = (region **) lua_touserdata(L, lua_upvalueindex(1)); + region *r = *region_ptr; + if (r != NULL) { + tolua_pushusertype(L, (void *)r, TOLUA_CAST "region"); + *region_ptr = r->next; + return 1; + } else + return 0; /* no more values to return */ +} + +static int tolua_region_get_id(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->uid); + return 1; +} + +static int tolua_region_get_x(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->x); + return 1; +} + +static int tolua_region_get_y(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->y); + return 1; +} + +static int tolua_region_get_plane(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, rplane(r), TOLUA_CAST "plane"); + return 1; +} + +static int tolua_region_get_terrain(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, self->terrain->_name); + return 1; +} + +static int tolua_region_set_terrain(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + const char *tname = tolua_tostring(L, 2, 0); + if (tname) { + const terrain_type *terrain = get_terrain(tname); + if (terrain) { + terraform_region(r, terrain); + } + } + return 0; +} + +static int tolua_region_get_terrainname(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + attrib *a = a_find(self->attribs, &at_racename); + if (a) { + tolua_pushstring(L, get_racename(a)); + return 1; + } + return 0; +} + +static int tolua_region_set_owner(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + struct faction *f = (struct faction *)tolua_tousertype(L, 2, 0); + if (r) { + region_set_owner(r, f, turn); + } + return 0; +} + +static int tolua_region_get_owner(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + if (r) { + struct faction *f = region_get_owner(r); + tolua_pushusertype(L, f, TOLUA_CAST "faction"); + return 1; + } + return 0; +} + +static int tolua_region_set_terrainname(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + if (name == NULL) { + a_removeall(&self->attribs, &at_racename); + } else { + set_racename(&self->attribs, name); + } + return 0; +} + +static int tolua_region_get_info(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, region_getinfo(self)); + return 1; +} + +static int tolua_region_set_info(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + region_setinfo(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_region_get_name(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, region_getname(self)); + return 1; +} + +static int tolua_region_set_name(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + region_setname(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_region_get_morale(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, region_get_morale(r)); + return 1; +} + +static int tolua_region_set_morale(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + region_set_morale(r, (int)tolua_tonumber(L, 2, 0), turn); + return 0; +} + +static int tolua_region_get_adj(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + region *rn[MAXDIRECTIONS]; + int d, idx; + get_neighbours(r, rn); + + lua_createtable(L, MAXDIRECTIONS, 0); + for (d = 0, idx = 0; d != MAXDIRECTIONS; ++d) { + if (rn[d]) { + tolua_pushusertype(L, rn[d], TOLUA_CAST "region"); + lua_rawseti(L, -2, ++idx); + } + } + return 1; +} + +static int tolua_region_get_luxury(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + if (r->land) { + const item_type *lux = r_luxury(r); + if (lux) { + const char *name = lux->rtype->_name[0]; + tolua_pushstring(L, name); + return 1; + } + } + return 0; +} + +static int tolua_region_set_luxury(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + if (r->land && name) { + const item_type *lux = r_luxury(r); + const item_type *itype = it_find(name); + if (lux && itype && lux != itype) { + r_setdemand(r, lux->rtype->ltype, 1); + r_setdemand(r, itype->rtype->ltype, 0); + } + } + return 0; +} + +static int tolua_region_set_herb(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + if (r->land) { + const char *name = tolua_tostring(L, 2, 0); + const item_type *itype = it_find(name); + if (itype && (itype->flags & ITF_HERB)) { + r->land->herbtype = itype; + } + } + return 0; +} + +static int tolua_region_get_herb(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + if (r->land && r->land->herbtype) { + const char *name = r->land->herbtype->rtype->_name[0]; + tolua_pushstring(L, name); + return 1; + } + return 0; +} + +static int tolua_region_get_next(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + direction_t dir = (direction_t) tolua_tonumber(L, 2, 0); + + if (dir >= 0 && dir < MAXDIRECTIONS) { + tolua_pushusertype(L, (void *)r_connect(self, dir), TOLUA_CAST "region"); + return 1; + } + return 0; +} + +static int tolua_region_get_flag(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + int bit = (int)tolua_tonumber(L, 2, 0); + + lua_pushboolean(L, (self->flags & (1 << bit))); + return 1; +} + +static int tolua_region_set_flag(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + int bit = (int)tolua_tonumber(L, 2, 0); + int set = tolua_toboolean(L, 3, 1); + + if (set) + self->flags |= (1 << bit); + else + self->flags &= ~(1 << bit); + return 0; +} + +static int tolua_region_get_resourcelevel(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + const char *type = tolua_tostring(L, 2, 0); + const resource_type *rtype = rt_find(type); + if (rtype != NULL) { + const rawmaterial *rm; + for (rm = r->resources; rm; rm = rm->next) { + if (rm->type->rtype == rtype) { + tolua_pushnumber(L, (lua_Number) rm->level); + return 1; + } + } + } + return 0; +} + +#define LUA_ASSERT(c, s) if (!(c)) { log_error("%s(%d): %s\n", __FILE__, __LINE__, (s)); return 0; } + +static critbit_tree * special_resources(void) +{ + static critbit_tree cb = CRITBIT_TREE(); + if (!cb.root) { + const char * special[] = { "seed", "sapling", "tree", "grave", "chaos", 0 }; + char buffer[32]; + int i; + for (i=0;special[i];++i) { + size_t len = strlen(special[i]); + len = cb_new_kv(special[i], len, &i, sizeof(int), buffer); + cb_insert(&cb, buffer, len); + } + } + return &cb; +} + +static int tolua_region_get_resource(lua_State * L) +{ + region *r; + const char *type; + const resource_type *rtype; + int result = 0; + const void * matches; + critbit_tree * cb = special_resources(); + + r = (region *) tolua_tousertype(L, 1, 0); + LUA_ASSERT(r != NULL, "invalid parameter"); + type = tolua_tostring(L, 2, 0); + LUA_ASSERT(type != NULL, "invalid parameter"); + + if (cb_find_prefix(cb, type, strlen(type)+1, &matches, 1, 0)) { + cb_get_kv(matches, &result, sizeof(result)); + switch (result) { + case 0: + case 1: + case 2: + result = rtrees(r, result); + break; + case 3: + result = deathcount(r); + break; + case 4: + result = chaoscount(r); + break; + } + } else { + rtype = rt_find(type); + if (rtype) { + result = region_getresource(r, rtype); + } else { + result = -1; + } + } + + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_region_set_resource(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + const char *type = tolua_tostring(L, 2, 0); + int result, value = (int)tolua_tonumber(L, 3, 0); + critbit_tree * cb = special_resources(); + const void * matches; + + if (cb_find_prefix(cb, type, strlen(type)+1, &matches, 1, 0)) { + cb_get_kv(matches, &result, sizeof(result)); + switch (result) { + case 0: + case 1: + case 2: + rsettrees(r, result, value); + break; + case 3: + deathcounts(r, value - deathcount(r)); + break; + case 4: + chaoscounts(r, value - chaoscount(r)); + break; + } + } else { + const resource_type *rtype = rt_find(type); + if (rtype != NULL) { + region_setresource(r, rtype, value); + } + } + return 0; +} + +static int tolua_region_get_objects(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, (void *)&self->attribs, TOLUA_CAST "hashtable"); + return 1; +} + +static int tolua_region_destroy(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + remove_region(®ions, self); + return 0; +} + +static int tolua_region_create(lua_State * L) +{ + int x = (int)tolua_tonumber(L, 1, 0); + int y = (int)tolua_tonumber(L, 2, 0); + const char *tname = tolua_tostring(L, 3, 0); + if (tname) { + plane *pl = findplane(x, y); + const terrain_type *terrain = get_terrain(tname); + region *r, *result; + if (!terrain) { + return 0; + } + + assert(!pnormalize(&x, &y, pl)); + r = result = findregion(x, y); + + if (terrain == NULL && r != NULL && r->units != NULL) { + /* TODO: error message */ + result = NULL; + } else if (r == NULL) { + result = new_region(x, y, pl, 0); + } + if (result) { + terraform_region(result, terrain); + } + fix_demand(result); + + tolua_pushusertype(L, result, TOLUA_CAST "region"); + return 1; + } + return 0; +} + +static int tolua_region_get_units(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + unit **unit_ptr = (unit **) lua_newuserdata(L, sizeof(unit *)); + + luaL_getmetatable(L, "unit"); + lua_setmetatable(L, -2); + + *unit_ptr = self->units; + + lua_pushcclosure(L, tolua_unitlist_next, 1); + return 1; +} + +static int tolua_region_get_buildings(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + building **building_ptr = + (building **) lua_newuserdata(L, sizeof(building *)); + + luaL_getmetatable(L, "building"); + lua_setmetatable(L, -2); + + *building_ptr = self->buildings; + + lua_pushcclosure(L, tolua_buildinglist_next, 1); + return 1; +} + +static int tolua_region_get_ships(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + ship **ship_ptr = (ship **) lua_newuserdata(L, sizeof(ship *)); + + luaL_getmetatable(L, "ship"); + lua_setmetatable(L, -2); + + *ship_ptr = self->ships; + + lua_pushcclosure(L, tolua_shiplist_next, 1); + return 1; +} + +static int tolua_region_get_age(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + + if (self) { + lua_pushnumber(L, self->age); + return 1; + } + return 0; +} + +static int tolua_region_getkey(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + + int flag = atoi36(name); + attrib *a = find_key(self->attribs, flag); + lua_pushboolean(L, a != NULL); + + return 1; +} + +static int tolua_region_setkey(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + int value = tolua_toboolean(L, 3, 0); + + int flag = atoi36(name); + attrib *a = find_key(self->attribs, flag); + if (a == NULL && value) { + add_key(&self->attribs, flag); + } else if (a != NULL && !value) { + a_remove(&self->attribs, a); + } + return 0; +} + +static int tolua_region_tostring(lua_State * L) +{ + region *self = (region *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, regionname(self, NULL)); + return 1; +} + +static int tolua_plane_get(lua_State * L) +{ + int id = (int)tolua_tonumber(L, 1, 0); + plane *pl = getplanebyid(id); + + tolua_pushusertype(L, pl, TOLUA_CAST "plane"); + return 1; +} + +static int tolua_plane_create(lua_State * L) +{ + int id = (int)tolua_tonumber(L, 1, 0); + int x = (int)tolua_tonumber(L, 2, 0); + int y = (int)tolua_tonumber(L, 3, 0); + int width = (int)tolua_tonumber(L, 4, 0); + int height = (int)tolua_tonumber(L, 5, 0); + const char *name = tolua_tostring(L, 6, 0); + plane *pl; + + pl = create_new_plane(id, name, x, x + width - 1, y, y + height - 1, 0); + + tolua_pushusertype(L, pl, TOLUA_CAST "plane"); + return 1; +} + +static int tolua_plane_get_name(lua_State * L) +{ + plane *self = (plane *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, self->name); + return 1; +} + +static int tolua_plane_set_name(lua_State * L) +{ + plane *self = (plane *) tolua_tousertype(L, 1, 0); + const char *str = tolua_tostring(L, 2, 0); + free(self->name); + if (str) + self->name = strdup(str); + else + self->name = 0; + return 0; +} + +static int tolua_plane_get_id(lua_State * L) +{ + plane *self = (plane *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->id); + return 1; +} + +static int tolua_plane_normalize(lua_State * L) +{ + plane *self = (plane *) tolua_tousertype(L, 1, 0); + int x = (int)tolua_tonumber(L, 2, 0); + int y = (int)tolua_tonumber(L, 3, 0); + pnormalize(&x, &y, self); + tolua_pushnumber(L, (lua_Number) x); + tolua_pushnumber(L, (lua_Number) y); + return 2; +} + +static int tolua_plane_tostring(lua_State * L) +{ + plane *self = (plane *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, self->name); + return 1; +} + +static int tolua_plane_get_size(lua_State * L) +{ + plane *pl = (plane *) tolua_tousertype(L, 1, 0); + lua_pushnumber(L, plane_width(pl)); + lua_pushnumber(L, plane_height(pl)); + return 2; +} + +static int tolua_distance(lua_State * L) +{ + int x1 = (int)tolua_tonumber(L, 1, 0); + int y1 = (int)tolua_tonumber(L, 2, 0); + int x2 = (int)tolua_tonumber(L, 3, 0); + int y2 = (int)tolua_tonumber(L, 4, 0); + plane *pl = (plane *) tolua_tousertype(L, 5, 0); + int result; + + if (!pl) + pl = get_homeplane(); + pnormalize(&x1, &y1, pl); + pnormalize(&x2, &y2, pl); + result = koor_distance(x1, y1, x2, y2); + lua_pushnumber(L, result); + return 1; +} + +void tolua_region_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "region"); + tolua_usertype(L, TOLUA_CAST "plane"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_function(L, TOLUA_CAST "distance", tolua_distance); + + tolua_cclass(L, TOLUA_CAST "region", TOLUA_CAST "region", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "region"); + { + tolua_function(L, TOLUA_CAST "create", tolua_region_create); + tolua_function(L, TOLUA_CAST "destroy", tolua_region_destroy); + tolua_function(L, TOLUA_CAST "__tostring", tolua_region_tostring); + + tolua_variable(L, TOLUA_CAST "id", tolua_region_get_id, NULL); + tolua_variable(L, TOLUA_CAST "x", tolua_region_get_x, NULL); + tolua_variable(L, TOLUA_CAST "y", tolua_region_get_y, NULL); + tolua_variable(L, TOLUA_CAST "plane", tolua_region_get_plane, NULL); + tolua_variable(L, TOLUA_CAST "name", tolua_region_get_name, + tolua_region_set_name); + tolua_variable(L, TOLUA_CAST "morale", tolua_region_get_morale, + tolua_region_set_morale); + tolua_variable(L, TOLUA_CAST "info", tolua_region_get_info, + tolua_region_set_info); + tolua_variable(L, TOLUA_CAST "units", tolua_region_get_units, NULL); + tolua_variable(L, TOLUA_CAST "ships", tolua_region_get_ships, NULL); + tolua_variable(L, TOLUA_CAST "age", tolua_region_get_age, NULL); + tolua_variable(L, TOLUA_CAST "buildings", tolua_region_get_buildings, + NULL); + tolua_variable(L, TOLUA_CAST "terrain", tolua_region_get_terrain, + tolua_region_set_terrain); + tolua_function(L, TOLUA_CAST "get_resourcelevel", + tolua_region_get_resourcelevel); + tolua_function(L, TOLUA_CAST "get_resource", tolua_region_get_resource); + tolua_function(L, TOLUA_CAST "set_resource", tolua_region_set_resource); + tolua_function(L, TOLUA_CAST "get_flag", tolua_region_get_flag); + tolua_function(L, TOLUA_CAST "set_flag", tolua_region_set_flag); + tolua_function(L, TOLUA_CAST "next", tolua_region_get_next); + tolua_variable(L, TOLUA_CAST "adj", tolua_region_get_adj, NULL); + + tolua_variable(L, TOLUA_CAST "luxury", &tolua_region_get_luxury, + &tolua_region_set_luxury); + tolua_variable(L, TOLUA_CAST "herb", &tolua_region_get_herb, + &tolua_region_set_herb); + + tolua_variable(L, TOLUA_CAST "terrain_name", + &tolua_region_get_terrainname, &tolua_region_set_terrainname); + tolua_variable(L, TOLUA_CAST "owner", &tolua_region_get_owner, + &tolua_region_set_owner); + + tolua_function(L, TOLUA_CAST "get_key", tolua_region_getkey); + tolua_function(L, TOLUA_CAST "set_key", tolua_region_setkey); + + tolua_variable(L, TOLUA_CAST "objects", tolua_region_get_objects, 0); + } + tolua_endmodule(L); + + tolua_cclass(L, TOLUA_CAST "plane", TOLUA_CAST "plane", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "plane"); + { + tolua_function(L, TOLUA_CAST "create", tolua_plane_create); + tolua_function(L, TOLUA_CAST "get", tolua_plane_get); + tolua_function(L, TOLUA_CAST "__tostring", tolua_plane_tostring); + + tolua_function(L, TOLUA_CAST "size", tolua_plane_get_size); + tolua_variable(L, TOLUA_CAST "id", tolua_plane_get_id, NULL); + tolua_function(L, TOLUA_CAST "normalize", tolua_plane_normalize); + tolua_variable(L, TOLUA_CAST "name", tolua_plane_get_name, + tolua_plane_set_name); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_region.h b/core/src/bindings/bind_region.h new file mode 100644 index 000000000..f8d0c8e2a --- /dev/null +++ b/core/src/bindings/bind_region.h @@ -0,0 +1,23 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + void tolua_region_open(struct lua_State *L); + int tolua_regionlist_next(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_settings.c b/core/src/bindings/bind_settings.c new file mode 100755 index 000000000..a864a242f --- /dev/null +++ b/core/src/bindings/bind_settings.c @@ -0,0 +1,15 @@ +#include "bind_settings.h" + +#include +#include +#include + +const char * settings_get(const char *key) +{ + return get_param(global.parameters, key); +} + +void settings_set(const char *key, const char *value) +{ + set_param(&global.parameters, key, value); +} diff --git a/core/src/bindings/bind_settings.h b/core/src/bindings/bind_settings.h new file mode 100755 index 000000000..b935f33e0 --- /dev/null +++ b/core/src/bindings/bind_settings.h @@ -0,0 +1,13 @@ +#ifndef BIND_ERESSEA_PROCESS_H +#define BIND_ERESSEA_PROCESS_H +#ifdef __cplusplus +extern "C" { +#endif + +const char * settings_get(const char *key); +void settings_set(const char *key, const char *value); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/bindings/bind_ship.c b/core/src/bindings/bind_ship.c new file mode 100644 index 000000000..ddfa883ad --- /dev/null +++ b/core/src/bindings/bind_ship.c @@ -0,0 +1,201 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include "bind_ship.h" +#include "bind_unit.h" + +#include +#include +#include +#include +#include + +#include + +#include + +int tolua_shiplist_next(lua_State * L) +{ + ship **ship_ptr = (ship **) lua_touserdata(L, lua_upvalueindex(1)); + ship *u = *ship_ptr; + if (u != NULL) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "ship"); + *ship_ptr = u->next; + return 1; + } else + return 0; /* no more values to return */ +} + +static int tolua_ship_get_id(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->no); + return 1; +} + +static int tolua_ship_get_name(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, ship_getname(self)); + return 1; +} + +static int tolua_ship_get_region(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + if (self) { + tolua_pushusertype(L, self->region, TOLUA_CAST "region"); + return 1; + } + return 0; +} + +static int tolua_ship_set_region(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + region *r = (region *) tolua_tousertype(L, 1, 0); + if (self) { + move_ship(self, self->region, r, NULL); + } + return 0; +} + +static int tolua_ship_set_name(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + ship_setname(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_ship_get_units(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + unit **unit_ptr = (unit **) lua_newuserdata(L, sizeof(unit *)); + unit *u = self->region->units; + + while (u && u->ship != self) + u = u->next; + luaL_getmetatable(L, TOLUA_CAST "unit"); + lua_setmetatable(L, -2); + + *unit_ptr = u; + + lua_pushcclosure(L, tolua_unitlist_nexts, 1); + return 1; +} + +static int tolua_ship_get_objects(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, (void *)&self->attribs, TOLUA_CAST "hashtable"); + return 1; +} + +static int tolua_ship_create(lua_State * L) +{ + region *r = (region *) tolua_tousertype(L, 1, 0); + const char *sname = tolua_tostring(L, 2, 0); + if (sname) { + const ship_type *stype = st_find(sname); + if (stype) { + ship *sh = new_ship(stype, r, default_locale); + sh->size = stype->construction->maxsize; + tolua_pushusertype(L, (void *)sh, TOLUA_CAST "ship"); + return 1; + } + } + return 0; +} + +static int + tolua_ship_tostring(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, shipname(self)); + return 1; +} + +static int tolua_ship_get_flags(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->flags); + return 1; +} + +static int tolua_ship_set_flags(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + self->flags = (int)tolua_tonumber(L, 2, 0); + return 0; +} + +static int tolua_ship_set_coast(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + if (lua_isnil(L, 2)) { + self->coast = NODIRECTION; + } else if (lua_isnumber(L, 2)) { + self->coast = (direction_t) tolua_tonumber(L, 2, 0); + } + return 0; +} + +static int tolua_ship_get_coast(lua_State * L) +{ + ship *self = (ship *) tolua_tousertype(L, 1, 0); + if (self->coast) { + tolua_pushnumber(L, self->coast); + return 1; + } + return 0; +} + +void tolua_ship_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "ship"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "ship", TOLUA_CAST "ship", TOLUA_CAST "", NULL); + tolua_beginmodule(L, TOLUA_CAST "ship"); + { + tolua_function(L, TOLUA_CAST "__tostring", tolua_ship_tostring); + tolua_variable(L, TOLUA_CAST "id", tolua_ship_get_id, NULL); + tolua_variable(L, TOLUA_CAST "name", tolua_ship_get_name, + tolua_ship_set_name); + tolua_variable(L, TOLUA_CAST "units", tolua_ship_get_units, NULL); + tolua_variable(L, TOLUA_CAST "flags", &tolua_ship_get_flags, + tolua_ship_set_flags); + tolua_variable(L, TOLUA_CAST "region", tolua_ship_get_region, + tolua_ship_set_region); + tolua_variable(L, TOLUA_CAST "coast", tolua_ship_get_coast, + tolua_ship_set_coast); +#ifdef TODO + .property("type", &ship_gettype) + .property("weight", &ship_getweight) + .property("capacity", &ship_getcapacity) + .property("maxsize", &ship_maxsize) + .def_readwrite("damage", &ship::damage) + .def_readwrite("size", &ship::size) + .def_readwrite("coast", &ship::coast) +#endif + tolua_variable(L, TOLUA_CAST "objects", tolua_ship_get_objects, 0); + + tolua_function(L, TOLUA_CAST "create", tolua_ship_create); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_ship.h b/core/src/bindings/bind_ship.h new file mode 100644 index 000000000..7c9426fce --- /dev/null +++ b/core/src/bindings/bind_ship.h @@ -0,0 +1,23 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + int tolua_shiplist_next(struct lua_State *L); + void tolua_ship_open(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_sqlite.c b/core/src/bindings/bind_sqlite.c new file mode 100644 index 000000000..6f6c5ea29 --- /dev/null +++ b/core/src/bindings/bind_sqlite.c @@ -0,0 +1,94 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include + +#include "bind_unit.h" +#include "bindings.h" + +#include +#include + +#define LTYPE_DB TOLUA_CAST "db" + +extern int db_update_factions(sqlite3 * db, bool force); +static int tolua_db_update_factions(lua_State * L) +{ + sqlite3 *db = (sqlite3 *) tolua_tousertype(L, 1, 0); + db_update_factions(db, tolua_toboolean(L, 2, 0)); + return 0; +} + +extern int db_update_scores(sqlite3 * db, bool force); +static int tolua_db_update_scores(lua_State * L) +{ + sqlite3 *db = (sqlite3 *) tolua_tousertype(L, 1, 0); + db_update_scores(db, tolua_toboolean(L, 2, 0)); + return 0; +} + +static int tolua_db_execute(lua_State * L) +{ + sqlite3 *db = (sqlite3 *) tolua_tousertype(L, 1, 0); + const char *sql = tolua_tostring(L, 2, 0); + + int res = sqlite3_exec(db, sql, 0, 0, 0); + + tolua_pushnumber(L, (LUA_NUMBER) res); + return 1; +} + +static int tolua_db_close(lua_State * L) +{ + sqlite3 *db = (sqlite3 *) tolua_tousertype(L, 1, 0); + sqlite3_close(db); + return 0; +} + +static int tolua_db_create(lua_State * L) +{ + sqlite3 *db; + const char *dbname = tolua_tostring(L, 1, 0); + int result = sqlite3_open_v2(dbname, &db, SQLITE_OPEN_READWRITE, 0); + if (result == SQLITE_OK) { + tolua_pushusertype(L, (void *)db, LTYPE_DB); + return 1; + } + return 0; +} + +int tolua_sqlite_open(lua_State * L) +{ + /* register user types */ + + tolua_usertype(L, LTYPE_DB); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, LTYPE_DB, LTYPE_DB, TOLUA_CAST "", NULL); + tolua_beginmodule(L, LTYPE_DB); + { + tolua_function(L, TOLUA_CAST "open", &tolua_db_create); + tolua_function(L, TOLUA_CAST "close", &tolua_db_close); + + tolua_function(L, TOLUA_CAST "update_factions", + &tolua_db_update_factions); + tolua_function(L, TOLUA_CAST "update_scores", &tolua_db_update_scores); + tolua_function(L, TOLUA_CAST "execute", &tolua_db_execute); + } + tolua_endmodule(L); + + } + tolua_endmodule(L); + return 0; +} diff --git a/core/src/bindings/bind_storage.c b/core/src/bindings/bind_storage.c new file mode 100644 index 000000000..e2b83b670 --- /dev/null +++ b/core/src/bindings/bind_storage.c @@ -0,0 +1,139 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "bind_storage.h" + +#include +#include +#include +#include + +#include +#include + +#include + +static int tolua_storage_create(lua_State * L) +{ + const char *filename = tolua_tostring(L, 1, 0); + const char *type = tolua_tostring(L, 2, "rb"); + storage *store = 0; + int mode = IO_READ; + if (strchr(type, 't')) { + store = malloc(sizeof(text_store)); + memcpy(store, &text_store, sizeof(text_store)); + } else { + store = malloc(sizeof(binary_store)); + memcpy(store, &binary_store, sizeof(binary_store)); + } + if (strchr(type, 'r')) + mode = IO_READ; + if (strchr(type, 'w')) + mode = IO_WRITE; + store->open(store, filename, mode); + tolua_pushusertype(L, (void *)store, TOLUA_CAST "storage"); + return 1; +} + +static int tolua_storage_read_unit(lua_State * L) +{ + storage *self = (storage *) tolua_tousertype(L, 1, 0); + struct unit *u = read_unit(self); + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + return 1; +} + +static int tolua_storage_write_unit(lua_State * L) +{ + storage *store = (storage *) tolua_tousertype(L, 1, 0); + struct unit *u = (struct unit *)tolua_tousertype(L, 2, 0); + if (store->version) { + write_unit(store, u); + } + return 0; +} + +static int tolua_storage_read_float(lua_State * L) +{ + storage *self = (storage *) tolua_tousertype(L, 1, 0); + float num = self->r_flt(self); + tolua_pushnumber(L, (lua_Number) num); + return 1; +} + +static int tolua_storage_read_int(lua_State * L) +{ + storage *self = (storage *) tolua_tousertype(L, 1, 0); + int num = self->r_int(self); + tolua_pushnumber(L, (lua_Number) num); + return 1; +} + +static int tolua_storage_write(lua_State * L) +{ + storage *self = (storage *) tolua_tousertype(L, 1, 0); + if (self->version && tolua_isnumber(L, 2, 0, 0)) { + lua_Number num = tolua_tonumber(L, 2, 0); + double n; + if (modf(num, &n) == 0.0) { + self->w_int(self, (int)num); + } else { + self->w_flt(self, (float)num); + } + } + return 0; +} + +static int tolua_storage_tostring(lua_State * L) +{ + storage *self = (storage *) tolua_tousertype(L, 1, 0); + char name[64]; + snprintf(name, sizeof(name), "", self->encoding, + self->version); + lua_pushstring(L, name); + return 1; +} + +static int tolua_storage_close(lua_State * L) +{ + storage *self = (storage *) tolua_tousertype(L, 1, 0); + self->close(self); + return 0; +} + +void tolua_storage_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "storage"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "storage", TOLUA_CAST "storage", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "storage"); + { + tolua_function(L, TOLUA_CAST "__tostring", tolua_storage_tostring); + tolua_function(L, TOLUA_CAST "write", tolua_storage_write); + tolua_function(L, TOLUA_CAST "read_int", tolua_storage_read_int); + tolua_function(L, TOLUA_CAST "read_float", tolua_storage_read_float); + tolua_function(L, TOLUA_CAST "write_unit", tolua_storage_write_unit); + tolua_function(L, TOLUA_CAST "read_unit", tolua_storage_read_unit); + tolua_function(L, TOLUA_CAST "close", tolua_storage_close); + tolua_function(L, TOLUA_CAST "create", tolua_storage_create); + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_storage.h b/core/src/bindings/bind_storage.h new file mode 100644 index 000000000..fd79c7ca8 --- /dev/null +++ b/core/src/bindings/bind_storage.h @@ -0,0 +1,22 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + void tolua_storage_open(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bind_unit.c b/core/src/bindings/bind_unit.c new file mode 100755 index 000000000..e19684bdc --- /dev/null +++ b/core/src/bindings/bind_unit.c @@ -0,0 +1,1035 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include + +#include "bind_unit.h" +#ifdef BSON_ATTRIB +# include "bind_attrib.h" +#endif +#include "bindings.h" + +/* attributes includes */ +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static int tolua_unit_get_objects(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, (void *)&self->attribs, TOLUA_CAST "hashtable"); + return 1; +} + +#ifdef BSON_ATTRIB +static int tolua_unit_get_attribs(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + attrib **attrib_ptr = (attrib **) lua_newuserdata(L, sizeof(attrib *)); + attrib *a = tolua_get_lua_ext(self->attribs); + + luaL_getmetatable(L, "attrib"); + lua_setmetatable(L, -2); + + *attrib_ptr = a; + + lua_pushcclosure(L, tolua_attriblist_next, 1); + return 1; +} +#endif + +int tolua_unitlist_nextf(lua_State * L) +{ + unit **unit_ptr = (unit **) lua_touserdata(L, lua_upvalueindex(1)); + unit *u = *unit_ptr; + if (u != NULL) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + *unit_ptr = u->nextF; + return 1; + } else + return 0; /* no more values to return */ +} + +int tolua_unitlist_nextb(lua_State * L) +{ + unit **unit_ptr = (unit **) lua_touserdata(L, lua_upvalueindex(1)); + unit *u = *unit_ptr; + if (u != NULL) { + unit *unext = u->next; + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + + while (unext && unext->building != u->building) { + unext = unext->next; + } + *unit_ptr = unext; + + return 1; + } else + return 0; /* no more values to return */ +} + +int tolua_unitlist_nexts(lua_State * L) +{ + unit **unit_ptr = (unit **) lua_touserdata(L, lua_upvalueindex(1)); + unit *u = *unit_ptr; + if (u != NULL) { + unit *unext = u->next; + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + + while (unext && unext->ship != u->ship) { + unext = unext->next; + } + *unit_ptr = unext; + + return 1; + } else + return 0; /* no more values to return */ +} + +int tolua_unitlist_next(lua_State * L) +{ + unit **unit_ptr = (unit **) lua_touserdata(L, lua_upvalueindex(1)); + unit *u = *unit_ptr; + if (u != NULL) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + *unit_ptr = u->next; + return 1; + } else + return 0; /* no more values to return */ +} + +static int tolua_unit_get_group(lua_State * L) +{ + unit *u = (unit *) tolua_tousertype(L, 1, 0); + group *g = get_group(u); + if (g) { + tolua_pushstring(L, g->name); + return 1; + } + return 0; +} + +static int tolua_unit_set_group(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + int result = join_group(self, tolua_tostring(L, 2, 0)); + tolua_pushnumber(L, result); + return 1; +} + +static int tolua_unit_get_name(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, self->name); + return 1; +} + +static int tolua_unit_set_name(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + unit_setname(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_unit_get_info(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, unit_getinfo(self)); + return 1; +} + +static int tolua_unit_set_info(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + unit_setinfo(self, tolua_tostring(L, 2, 0)); + return 0; +} + +static int tolua_unit_get_id(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, unit_getid(self)); + return 1; +} + +static int tolua_unit_set_id(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + unit_setid(self, (int)tolua_tonumber(L, 2, 0)); + return 0; +} + +static int tolua_unit_get_hpmax(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, unit_max_hp(self)); + return 1; +} + +static int tolua_unit_get_hp(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, unit_gethp(self)); + return 1; +} + +static int tolua_unit_set_hp(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + unit_sethp(self, (int)tolua_tonumber(L, 2, 0)); + return 0; +} + +static int tolua_unit_get_number(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, self->number); + return 1; +} + +static int tolua_unit_set_number(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + int number = (int)tolua_tonumber(L, 2, 0); + if (self->number == 0) { + set_number(self, number); + self->hp = unit_max_hp(self) * number; + } else { + scale_number(self, number); + } + return 0; +} + +static int tolua_unit_get_flags(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, self->flags); + return 1; +} + +static int tolua_unit_set_flags(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + self->flags = (int)tolua_tonumber(L, 2, 0); + return 0; +} + +static const char *unit_getmagic(const unit * u) +{ + sc_mage *mage = get_mage(u); + return mage ? magic_school[mage->magietyp] : NULL; +} + +static int tolua_unit_get_magic(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, unit_getmagic(self)); + return 1; +} + +static void unit_setmagic(unit * u, const char *type) +{ + sc_mage *mage = get_mage(u); + magic_t mtype; + for (mtype = 0; mtype != MAXMAGIETYP; ++mtype) { + if (strcmp(magic_school[mtype], type) == 0) + break; + } + if (mtype == MAXMAGIETYP) + return; + if (mage == NULL) { + mage = create_mage(u, mtype); + } +} + +static int tolua_unit_set_magic(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *type = tolua_tostring(L, 2, 0); + unit_setmagic(self, type); + return 0; +} + +static int tolua_unit_get_aura(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, get_spellpoints(self)); + return 1; +} + +static int tolua_unit_set_aura(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + set_spellpoints(self, (int)tolua_tonumber(L, 2, 0)); + return 0; +} + +static int tolua_unit_get_age(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, self->age); + return 1; +} + +static int tolua_unit_set_age(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + self->age = (short)tolua_tonumber(L, 2, 0); + return 0; +} + +static int tolua_unit_get_status(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, unit_getstatus(self)); + return 1; +} + +static int tolua_unit_set_status(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + unit_setstatus(self, (status_t) tolua_tonumber(L, 2, 0)); + return 0; +} + +static int tolua_unit_get_item(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *iname = tolua_tostring(L, 2, 0); + int result = -1; + + if (iname != NULL) { + const item_type *itype = it_find(iname); + if (itype != NULL) { + result = i_get(self->items, itype); + } + } + lua_pushinteger(L, result); + return 1; +} + +static int tolua_unit_get_effect(lua_State * L) +{ + const unit *self = (unit *)tolua_tousertype(L, 1, 0); + const char *potion_name = tolua_tostring(L, 2, 0); + int result = -1; + const potion_type *pt_potion; + const item_type *it_potion = it_find(potion_name); + + if (it_potion != NULL) { + pt_potion = it_potion->rtype->ptype; + if (pt_potion != NULL) + result = get_effect(self, pt_potion); + } + + lua_pushinteger(L, result); + return 1; +} + +static int tolua_unit_add_item(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *iname = tolua_tostring(L, 2, 0); + int number = (int)tolua_tonumber(L, 3, 0); + int result = -1; + + if (iname != NULL) { + const item_type *itype = it_find(iname); + if (itype != NULL) { + item *i = i_change(&self->items, itype, number); + result = i ? i->number : 0; + } + } + lua_pushinteger(L, result); + return 1; +} + +static int tolua_unit_getskill(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *skname = tolua_tostring(L, 2, 0); + skill_t sk = sk_find(skname); + int value = -1; + if (sk != NOSKILL) { + skill *sv = get_skill(self, sk); + if (sv) { + value = sv->level; + } else + value = 0; + } + lua_pushinteger(L, value); + return 1; +} + +static int tolua_unit_effskill(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *skname = tolua_tostring(L, 2, 0); + skill_t sk = sk_find(skname); + int value = (sk == NOSKILL) ? -1 : eff_skill(self, sk, self->region); + lua_pushinteger(L, value); + return 1; +} + +typedef struct fctr_data { + unit *target; + int fhandle; +} fctr_data; + +typedef struct event { + struct event_arg *args; + char *msg; +} event; + +int fctr_handle(struct trigger *tp, void *data) +{ + trigger *t = tp; + event evt = { 0 }; + fctr_data *fd = (fctr_data *) t->data.v; + lua_State *L = (lua_State *) global.vm_state; + unit *u = fd->target; + + evt.args = (event_arg *) data; + lua_rawgeti(L, LUA_REGISTRYINDEX, fd->fhandle); + tolua_pushusertype(L, u, TOLUA_CAST "unit"); + tolua_pushusertype(L, &evt, TOLUA_CAST "event"); + if (lua_pcall(L, 2, 0, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("event (%s): %s\n", unitname(u), error); + lua_pop(L, 1); + tolua_error(L, TOLUA_CAST "event handler call failed", NULL); + } + + return 0; +} + +static void fctr_init(trigger * t) +{ + t->data.v = calloc(sizeof(fctr_data), 1); +} + +static void fctr_done(trigger * t) +{ + fctr_data *fd = (fctr_data *) t->data.v; + lua_State *L = (lua_State *) global.vm_state; + luaL_unref(L, LUA_REGISTRYINDEX, fd->fhandle); + free(fd); +} + +static struct trigger_type tt_lua = { + "lua_event", + fctr_init, + fctr_done, + fctr_handle +}; + +static trigger *trigger_lua(struct unit *u, int handle) +{ + trigger *t = t_new(&tt_lua); + fctr_data *td = (fctr_data *) t->data.v; + td->target = u; + td->fhandle = handle; + return t; +} + +static int tolua_unit_addhandler(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *ename = tolua_tostring(L, 2, 0); + int handle; + + lua_pushvalue(L, 3); + handle = luaL_ref(L, LUA_REGISTRYINDEX); + add_trigger(&self->attribs, ename, trigger_lua(self, handle)); + + return 0; +} + +static int tolua_unit_addnotice(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *str = tolua_tostring(L, 2, 0); + + addmessage(self->region, self->faction, str, MSG_MESSAGE, ML_IMPORTANT); + return 0; +} + +static void unit_castspell(unit * u, const char *name, int level) +{ + spell *sp = find_spell(name); + + if (sp) { + spellbook *book = unit_get_spellbook(u); + if (spellbook_get(book, sp)) { + if (!sp->cast) { + log_error("spell '%s' has no function.\n", sp->sname); + } else { + castorder co; + create_castorder(&co, u, 0, sp, u->region, level, level * MagicPower(), 0, 0, 0); + sp->cast(&co); + free_castorder(&co); + } + } + } +} + +static int tolua_unit_castspell(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *str = tolua_tostring(L, 2, 0); + int level = (int)tolua_tonumber(L, 3, 1); + + unit_castspell(self, str, level); + return 0; +} + +static int tolua_unit_addspell(lua_State * L) +{ + unit *u = (unit *) tolua_tousertype(L, 1, 0); + const char *str = tolua_tostring(L, 2, 0); + int level = (int)tolua_tonumber(L, 3, 1); + int err = 0; + spell *sp = find_spell(str); + + if (!sp) { + log_error("spell %s could not be found\n", str); + return EINVAL; + } else { + unit_add_spell(u, 0, sp, level); + } + + lua_pushinteger(L, err); + return 1; +} + +static int tolua_unit_set_racename(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *str = tolua_tostring(L, 2, 0); + + set_racename(&self->attribs, str); + return 0; +} + +static int tolua_unit_get_racename(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + attrib *a = a_find(self->attribs, &at_racename); + if (a) { + tolua_pushstring(L, get_racename(a)); + return 1; + } + return 0; +} + +static int tolua_unit_setskill(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *skname = tolua_tostring(L, 2, 0); + int level = (int)tolua_tonumber(L, 3, 0); + skill_t sk = sk_find(skname); + if (sk != NOSKILL) { + set_level(self, sk, level); + } else { + level = -1; + } + lua_pushinteger(L, level); + return 1; +} + +static int tolua_unit_use_pooled(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *iname = tolua_tostring(L, 2, 0); + int number = (int)tolua_tonumber(L, 3, 0); + const resource_type *rtype = rt_find(iname); + int result = -1; + if (rtype != NULL) { + result = use_pooled(self, rtype, GET_DEFAULT, number); + } + lua_pushinteger(L, result); + return 1; +} + +static int tolua_unit_get_pooled(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *iname = tolua_tostring(L, 2, 0); + const resource_type *rtype = rt_find(iname); + int result = -1; + if (rtype != NULL) { + result = get_pooled(self, rtype, GET_DEFAULT, INT_MAX); + } + lua_pushinteger(L, result); + return 1; +} + +static unit *unit_getfamiliar(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_familiar); + if (a != NULL) { + return (unit *) a->data.v; + } + return NULL; +} + +static int tolua_unit_get_familiar(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, unit_getfamiliar(self), TOLUA_CAST "unit"); + return 1; +} + +static int tolua_unit_set_familiar(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + create_newfamiliar(self, (unit *) tolua_tousertype(L, 2, 0)); + return 0; +} + +static int tolua_unit_get_building(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, self->building, TOLUA_CAST "building"); + return 1; +} + +static int tolua_unit_set_building(lua_State * L) +{ + unit *u = (unit *) tolua_tousertype(L, 1, 0); + if (u->faction) { + building * b = (building *) tolua_tousertype(L, 2, 0); + if (b!=u->building) { + leave(u, true); + if (b) { + if (u->region != b->region) { + move_unit(u, b->region, NULL); + } + u_set_building(u, b); + } + } + } + return 0; +} + +static int tolua_unit_get_ship(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, self->ship, TOLUA_CAST "ship"); + return 1; +} + +static void unit_setship(unit * u, ship * s) +{ + leave(u, true); + if (s && u->region != s->region) { + move_unit(u, s->region, NULL); + } + u_set_ship(u, s); +} + +static int tolua_unit_set_ship(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + if (self->faction) { + unit_setship(self, (ship *) tolua_tousertype(L, 2, 0)); + } + return 0; +} + +static int tolua_unit_get_region(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, self->region, TOLUA_CAST "region"); + return 1; +} + +static void unit_setregion(unit * u, region * r) +{ + move_unit(u, r, NULL); +} + +static int tolua_unit_set_region(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + unit_setregion(self, (region *) tolua_tousertype(L, 2, 0)); + return 0; +} + +static int tolua_unit_add_order(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *str = tolua_tostring(L, 2, 0); + order *ord = parse_order(str, self->faction->locale); + unit_addorder(self, ord); + return 0; +} + +static int tolua_unit_clear_orders(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + free_orders(&self->orders); + return 0; +} + +static int tolua_unit_get_items(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + + item **item_ptr = (item **) lua_newuserdata(L, sizeof(item *)); + + luaL_getmetatable(L, TOLUA_CAST "item"); + lua_setmetatable(L, -2); + + *item_ptr = self->items; + + lua_pushcclosure(L, tolua_itemlist_next, 1); + + return 1; +} + +#ifdef TODO /* spellbooks */ +static int tolua_unit_get_spells(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + sc_mage *mage = get_mage(self); + quicklist *slist = 0; + + if (mage) { + quicklist **slist_ptr = get_spelllist(mage, self->faction); + if (slist_ptr) { + slist = *slist_ptr; + } + } + + return tolua_quicklist_push(L, "spell_list", "spell", slist); +} + +static void unit_removespell(unit * u, spell * sp) +{ + quicklist **isptr; + + isptr = get_spelllist(get_mage(u), u->faction); + ql_set_remove(isptr, sp); +} + +static int tolua_unit_removespell(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + spell *sp = (spell *) tolua_tousertype(L, 2, 0); + unit_removespell(self, sp); + return 0; +} + +#endif + +static int tolua_unit_get_orders(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + + order **order_ptr = (order **) lua_newuserdata(L, sizeof(order *)); + + luaL_getmetatable(L, TOLUA_CAST "order"); + lua_setmetatable(L, -2); + + *order_ptr = self->orders; + + lua_pushcclosure(L, tolua_orderlist_next, 1); + + return 1; +} + +static int tolua_unit_get_flag(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + int flag = atoi36(name); + attrib *a = find_key(self->attribs, flag); + lua_pushboolean(L, (a != NULL)); + return 1; +} + +static int tolua_unit_set_flag(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + int value = (int)tolua_tonumber(L, 3, 0); + int flag = atoi36(name); + attrib *a = find_key(self->attribs, flag); + if (a == NULL && value) { + add_key(&self->attribs, flag); + } else if (a != NULL && !value) { + a_remove(&self->attribs, a); + } + return 0; +} + +static int tolua_unit_get_weight(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, unit_getweight(self)); + return 1; +} + +static int tolua_unit_get_capacity(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushinteger(L, unit_getcapacity(self)); + return 1; +} + +static int tolua_unit_get_faction(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushusertype(L, (void *)self->faction, TOLUA_CAST "faction"); + return 1; +} + +static int tolua_unit_set_faction(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + faction *f = (faction *) tolua_tousertype(L, 2, 0); + u_setfaction(self, f); + return 0; +} + +static int tolua_unit_get_race(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, u_race(self)->_name[0]); + return 1; +} + +static int tolua_unit_set_race(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + const char *rcname = tolua_tostring(L, 2, 0); + race *rc = rc_find(rcname); + if (rc != NULL) { + if (count_unit(self)) + --self->faction->no_units; + if (self->irace == u_race(self)) + self->irace = NULL; + u_setrace(self, rc); + if (count_unit(self)) + --self->faction->no_units; + } + return 0; +} + +static int tolua_unit_destroy(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + if (self) { + remove_unit(&self->region->units, self); + } + return 0; +} + +static int tolua_unit_create(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, 0); + region *r = (region *) tolua_tousertype(L, 2, 0); + int num = (int)tolua_tonumber(L, 3, 1); + if (f && r) { + const race *rc = f->race; + const char *rcname = tolua_tostring(L, 4, NULL); + if (rcname) + rc = rc_find(rcname); + if (rc) { + unit *u = create_unit(r, f, num, rc, 0, NULL, NULL); + tolua_pushusertype(L, u, TOLUA_CAST "unit"); + return 1; + } + } + return 0; +} + +static int tolua_unit_tostring(lua_State * L) +{ + unit *self = (unit *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, unitname(self)); + return 1; +} + +static int tolua_event_gettype(lua_State * L) +{ + event *self = (event *) tolua_tousertype(L, 1, 0); + int index = (int)tolua_tonumber(L, 2, 0); + lua_pushstring(L, self->args[index].type); + return 1; +} + +static int tolua_event_get(lua_State * L) +{ + struct event *self = (struct event *)tolua_tousertype(L, 1, 0); + int index = (int)tolua_tonumber(L, 2, 0); + + event_arg *arg = self->args + index; + + if (arg->type) { + if (strcmp(arg->type, "string") == 0) { + tolua_pushstring(L, (const char *)arg->data.v); + } else if (strcmp(arg->type, "int") == 0) { + lua_pushinteger(L, arg->data.i); + } else if (strcmp(arg->type, "float") == 0) { + lua_pushnumber(L, (lua_Number) arg->data.f); + } else { + /* this is pretty lazy */ + tolua_pushusertype(L, (void *)arg->data.v, TOLUA_CAST arg->type); + } + return 1; + } + tolua_error(L, TOLUA_CAST "invalid type argument for event", NULL); + return 0; +} + +void tolua_unit_open(lua_State * L) +{ + /* register user types */ + tolua_usertype(L, TOLUA_CAST "unit"); + + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "event", TOLUA_CAST "event", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "event"); + { + tolua_function(L, TOLUA_CAST "get_type", &tolua_event_gettype); + tolua_function(L, TOLUA_CAST "get", &tolua_event_get); + } + tolua_endmodule(L); + + tolua_cclass(L, TOLUA_CAST "unit", TOLUA_CAST "unit", TOLUA_CAST "", NULL); + tolua_beginmodule(L, TOLUA_CAST "unit"); + { + tolua_function(L, TOLUA_CAST "__tostring", &tolua_unit_tostring); + tolua_function(L, TOLUA_CAST "create", &tolua_unit_create); + tolua_function(L, TOLUA_CAST "destroy", &tolua_unit_destroy); + + tolua_variable(L, TOLUA_CAST "name", &tolua_unit_get_name, + tolua_unit_set_name); + tolua_variable(L, TOLUA_CAST "faction", &tolua_unit_get_faction, + tolua_unit_set_faction); + tolua_variable(L, TOLUA_CAST "id", tolua_unit_get_id, tolua_unit_set_id); + tolua_variable(L, TOLUA_CAST "group", tolua_unit_get_group, tolua_unit_set_group); + tolua_variable(L, TOLUA_CAST "info", tolua_unit_get_info, tolua_unit_set_info); + tolua_variable(L, TOLUA_CAST "hp", &tolua_unit_get_hp, tolua_unit_set_hp); + tolua_variable(L, TOLUA_CAST "status", &tolua_unit_get_status, + tolua_unit_set_status); + tolua_variable(L, TOLUA_CAST "familiar", &tolua_unit_get_familiar, + tolua_unit_set_familiar); + + tolua_variable(L, TOLUA_CAST "weight", &tolua_unit_get_weight, 0); + tolua_variable(L, TOLUA_CAST "capacity", &tolua_unit_get_capacity, 0); + + tolua_function(L, TOLUA_CAST "add_order", &tolua_unit_add_order); + tolua_function(L, TOLUA_CAST "clear_orders", &tolua_unit_clear_orders); + tolua_variable(L, TOLUA_CAST "orders", &tolua_unit_get_orders, 0); + + /* key-attributes for named flags: */ + tolua_function(L, TOLUA_CAST "set_flag", &tolua_unit_set_flag); + tolua_function(L, TOLUA_CAST "get_flag", &tolua_unit_get_flag); + tolua_variable(L, TOLUA_CAST "flags", &tolua_unit_get_flags, + &tolua_unit_set_flags); + tolua_variable(L, TOLUA_CAST "age", &tolua_unit_get_age, + tolua_unit_set_age); + + /* items: */ + tolua_function(L, TOLUA_CAST "get_item", &tolua_unit_get_item); + tolua_function(L, TOLUA_CAST "add_item", &tolua_unit_add_item); + tolua_variable(L, TOLUA_CAST "items", &tolua_unit_get_items, 0); + tolua_function(L, TOLUA_CAST "get_pooled", &tolua_unit_get_pooled); + tolua_function(L, TOLUA_CAST "use_pooled", &tolua_unit_use_pooled); + + /* effects */ + tolua_function(L, TOLUA_CAST "get_potion", &tolua_unit_get_effect); + + /* skills: */ + tolua_function(L, TOLUA_CAST "get_skill", &tolua_unit_getskill); + tolua_function(L, TOLUA_CAST "eff_skill", &tolua_unit_effskill); + tolua_function(L, TOLUA_CAST "set_skill", &tolua_unit_setskill); + + tolua_function(L, TOLUA_CAST "add_notice", &tolua_unit_addnotice); + + /* npc logic: */ + tolua_function(L, TOLUA_CAST "add_handler", &tolua_unit_addhandler); + + tolua_variable(L, TOLUA_CAST "race_name", &tolua_unit_get_racename, + &tolua_unit_set_racename); + tolua_function(L, TOLUA_CAST "add_spell", &tolua_unit_addspell); +#ifdef TODO /* spellbooks */ + tolua_function(L, TOLUA_CAST "remove_spell", &tolua_unit_removespell); + tolua_variable(L, TOLUA_CAST "spells", &tolua_unit_get_spells, 0); +#endif + tolua_function(L, TOLUA_CAST "cast_spell", &tolua_unit_castspell); + + tolua_variable(L, TOLUA_CAST "magic", &tolua_unit_get_magic, + tolua_unit_set_magic); + tolua_variable(L, TOLUA_CAST "aura", &tolua_unit_get_aura, + tolua_unit_set_aura); + tolua_variable(L, TOLUA_CAST "building", &tolua_unit_get_building, + tolua_unit_set_building); + tolua_variable(L, TOLUA_CAST "ship", &tolua_unit_get_ship, + tolua_unit_set_ship); + tolua_variable(L, TOLUA_CAST "region", &tolua_unit_get_region, + tolua_unit_set_region); + tolua_variable(L, TOLUA_CAST "number", &tolua_unit_get_number, + tolua_unit_set_number); + tolua_variable(L, TOLUA_CAST "race", &tolua_unit_get_race, + tolua_unit_set_race); + tolua_variable(L, TOLUA_CAST "hp_max", &tolua_unit_get_hpmax, 0); + + tolua_variable(L, TOLUA_CAST "objects", &tolua_unit_get_objects, 0); +#ifdef BSON_ATTRIB + tolua_variable(L, TOLUA_CAST "attribs", &tolua_unit_get_attribs, 0); +#endif + } + tolua_endmodule(L); + } + tolua_endmodule(L); +} diff --git a/core/src/bindings/bind_unit.h b/core/src/bindings/bind_unit.h new file mode 100644 index 000000000..767f4bfd6 --- /dev/null +++ b/core/src/bindings/bind_unit.h @@ -0,0 +1,26 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + int tolua_unitlist_nextb(struct lua_State *L); + int tolua_unitlist_nexts(struct lua_State *L); + int tolua_unitlist_nextf(struct lua_State *L); + int tolua_unitlist_next(struct lua_State *L); + void tolua_unit_open(struct lua_State *L); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/bindings.c b/core/src/bindings/bindings.c new file mode 100755 index 000000000..904e3e015 --- /dev/null +++ b/core/src/bindings/bindings.c @@ -0,0 +1,1229 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include "bindings.h" +#include "bind_unit.h" +#include "bind_storage.h" +#include "bind_building.h" +#include "bind_hashtable.h" +#include "bind_message.h" +#include "bind_building.h" +#include "bind_faction.h" +#include "bind_ship.h" +#include "bind_gmtool.h" +#include "bind_region.h" +#include "helpers.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define TOLUA_PKG(NAME) extern void tolua_##NAME##_open(lua_State * L) + +TOLUA_PKG(eressea); +TOLUA_PKG(process); +TOLUA_PKG(settings); + +int log_lua_error(lua_State * L) +{ + const char *error = lua_tostring(L, -1); + log_error("LUA call failed.\n%s\n", error); + lua_pop(L, 1); + return 1; +} + +int tolua_orderlist_next(lua_State * L) +{ + order **order_ptr = (order **) lua_touserdata(L, lua_upvalueindex(1)); + order *ord = *order_ptr; + if (ord != NULL) { + char cmd[8192]; + write_order(ord, cmd, sizeof(cmd)); + tolua_pushstring(L, cmd); + *order_ptr = ord->next; + return 1; + } + return 0; +} + +static int tolua_quicklist_iter(lua_State * L) +{ + quicklist **qlp = (quicklist **) lua_touserdata(L, lua_upvalueindex(1)); + quicklist *ql = *qlp; + if (ql != NULL) { + int index = lua_tointeger(L, lua_upvalueindex(2)); + const char *type = lua_tostring(L, lua_upvalueindex(3)); + void *data = ql_get(ql, index); + tolua_pushusertype(L, data, TOLUA_CAST type); + ql_advance(qlp, &index, 1); + tolua_pushnumber(L, index); + lua_replace(L, lua_upvalueindex(2)); + return 1; + } + return 0; +} + +int tolua_quicklist_push(struct lua_State *L, const char *list_type, + const char *elem_type, struct quicklist *list) +{ + if (list) { + quicklist **qlist_ptr = + (quicklist **) lua_newuserdata(L, sizeof(quicklist *)); + *qlist_ptr = list; + luaL_getmetatable(L, list_type); + lua_setmetatable(L, -2); + lua_pushnumber(L, 0); + lua_pushstring(L, elem_type); + lua_pushcclosure(L, tolua_quicklist_iter, 3); /* OBS: this closure has multiple upvalues (list, index, type_name) */ + } else { + lua_pushnil(L); + } + return 1; +} + +int tolua_itemlist_next(lua_State * L) +{ + item **item_ptr = (item **) lua_touserdata(L, lua_upvalueindex(1)); + item *itm = *item_ptr; + if (itm != NULL) { + tolua_pushstring(L, itm->type->rtype->_name[0]); + *item_ptr = itm->next; + return 1; + } + return 0; +} + +static int tolua_autoseed(lua_State * L) +{ + const char *filename = tolua_tostring(L, 1, 0); + int new_island = tolua_toboolean(L, 2, 0); + newfaction *players = read_newfactions(filename); + if (players != NULL) { + while (players) { + int n = listlen(players); + int k = (n + ISLANDSIZE - 1) / ISLANDSIZE; + k = n / k; + n = autoseed(&players, k, new_island ? 0 : TURNS_PER_ISLAND); + if (n == 0) { + break; + } + } + } + return 0; +} + +static int tolua_getkey(lua_State * L) +{ + const char *name = tolua_tostring(L, 1, 0); + int flag = atoi36(name); + attrib *a = find_key(global.attribs, flag); + lua_pushboolean(L, a != NULL); + return 1; +} + +static int tolua_translate(lua_State * L) +{ + const char *str = tolua_tostring(L, 1, 0); + const char *lang = tolua_tostring(L, 2, 0); + struct locale *loc = lang ? find_locale(lang) : default_locale; + if (loc) { + str = locale_string(loc, str); + tolua_pushstring(L, str); + return 1; + } + return 0; +} + +static int tolua_setkey(lua_State * L) +{ + const char *name = tolua_tostring(L, 1, 0); + int value = tolua_toboolean(L, 2, 0); + int flag = atoi36(name); + attrib *a = find_key(global.attribs, flag); + if (a == NULL && value) { + add_key(&global.attribs, flag); + } else if (a != NULL && !value) { + a_remove(&global.attribs, a); + } + return 0; +} + +static int tolua_rng_int(lua_State * L) +{ + lua_pushnumber(L, (lua_Number) rng_int()); + return 1; +} + +static int tolua_read_orders(lua_State * L) +{ + const char *filename = tolua_tostring(L, 1, 0); + int result = readorders(filename); + lua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_message_unit(lua_State * L) +{ + unit *sender = (unit *) tolua_tousertype(L, 1, 0); + unit *target = (unit *) tolua_tousertype(L, 2, 0); + const char *str = tolua_tostring(L, 3, 0); + if (!target) + tolua_error(L, TOLUA_CAST "target is nil", NULL); + if (!sender) + tolua_error(L, TOLUA_CAST "sender is nil", NULL); + deliverMail(target->faction, sender->region, sender, str, target); + return 0; +} + +static int tolua_message_faction(lua_State * L) +{ + unit *sender = (unit *) tolua_tousertype(L, 1, 0); + faction *target = (faction *) tolua_tousertype(L, 2, 0); + const char *str = tolua_tostring(L, 3, 0); + if (!target) + tolua_error(L, TOLUA_CAST "target is nil", NULL); + if (!sender) + tolua_error(L, TOLUA_CAST "sender is nil", NULL); + deliverMail(target, sender->region, sender, str, NULL); + return 0; +} + +static int tolua_message_region(lua_State * L) +{ + unit *sender = (unit *) tolua_tousertype(L, 1, 0); + const char *str = tolua_tostring(L, 2, 0); + if (!sender) + tolua_error(L, TOLUA_CAST "sender is nil", NULL); + ADDMSG(&sender->region->msgs, msg_message("mail_result", "unit message", + sender, str)); + return 0; +} + +static int tolua_update_guards(lua_State * L) +{ + update_guards(); + return 0; +} + +static int tolua_set_turn(lua_State * L) +{ + turn = (int)tolua_tonumber(L, 1, 0); + return 0; +} + +static int tolua_get_turn(lua_State * L) +{ + tolua_pushnumber(L, (lua_Number) turn); + return 1; +} + +static int tolua_atoi36(lua_State * L) +{ + const char *s = tolua_tostring(L, 1, 0); + tolua_pushnumber(L, (lua_Number) atoi36(s)); + return 1; +} + +static int tolua_itoa36(lua_State * L) +{ + int i = (int)tolua_tonumber(L, 1, 0); + tolua_pushstring(L, itoa36(i)); + return 1; +} + +static int tolua_dice_rand(lua_State * L) +{ + const char *s = tolua_tostring(L, 1, 0); + tolua_pushnumber(L, dice_rand(s)); + return 1; +} + +static int tolua_addequipment(lua_State * L) +{ + const char *eqname = tolua_tostring(L, 1, 0); + const char *iname = tolua_tostring(L, 2, 0); + const char *value = tolua_tostring(L, 3, 0); + int result = -1; + if (iname != NULL) { + const struct item_type *itype = it_find(iname); + if (itype != NULL) { + equipment_setitem(create_equipment(eqname), itype, value); + result = 0; + } + } + lua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_get_season(lua_State * L) +{ + int turnno = (int)tolua_tonumber(L, 1, 0); + gamedate gd; + get_gamedate(turnno, &gd); + tolua_pushstring(L, seasonnames[gd.season]); + return 1; +} + +static int tolua_create_curse(lua_State * L) +{ + unit *u = (unit *) tolua_tousertype(L, 1, 0); + tolua_Error tolua_err; + attrib **ap = NULL; + if (tolua_isusertype(L, 2, TOLUA_CAST "unit", 0, &tolua_err)) { + unit *target = (unit *) tolua_tousertype(L, 2, 0); + if (target) + ap = &target->attribs; + } else if (tolua_isusertype(L, 2, TOLUA_CAST "region", 0, &tolua_err)) { + region *target = (region *) tolua_tousertype(L, 2, 0); + if (target) + ap = &target->attribs; + } else if (tolua_isusertype(L, 2, TOLUA_CAST "ship", 0, &tolua_err)) { + ship *target = (ship *) tolua_tousertype(L, 2, 0); + if (target) + ap = &target->attribs; + } else if (tolua_isusertype(L, 2, TOLUA_CAST "building", 0, &tolua_err)) { + building *target = (building *) tolua_tousertype(L, 2, 0); + if (target) + ap = &target->attribs; + } + if (ap) { + const char *cname = tolua_tostring(L, 3, 0); + const curse_type *ctype = ct_find(cname); + if (ctype) { + double vigour = tolua_tonumber(L, 4, 0); + int duration = (int)tolua_tonumber(L, 5, 0); + double effect = tolua_tonumber(L, 6, 0); + int men = (int)tolua_tonumber(L, 7, 0); /* optional */ + curse *c = create_curse(u, ap, ctype, vigour, duration, effect, men); + if (c) { + tolua_pushboolean(L, true); + return 1; + } + } + } + tolua_pushboolean(L, false); + return 1; +} + +static int tolua_learn_skill(lua_State * L) +{ + unit *u = (unit *) tolua_tousertype(L, 1, 0); + const char *skname = tolua_tostring(L, 2, 0); + float chances = (float)tolua_tonumber(L, 3, 0); + skill_t sk = sk_find(skname); + if (sk != NOSKILL) { + learn_skill(u, sk, chances); + } + return 0; +} + +static int tolua_update_scores(lua_State * L) +{ + score(); + return 0; +} + +static int tolua_update_owners(lua_State * L) +{ + region *r; + for (r = regions; r; r = r->next) { + update_owners(r); + } + return 0; +} + +static int tolua_update_subscriptions(lua_State * L) +{ + update_subscriptions(); + return 0; +} + +static int tolua_remove_empty_units(lua_State * L) +{ + remove_empty_units(); + return 0; +} + +static int tolua_get_nmrs(lua_State * L) +{ + int result = -1; + int n = (int)tolua_tonumber(L, 1, 0); + if (n >= 0 && n <= NMRTimeout()) { + if (nmrs == NULL) { + update_nmrs(); + } + result = nmrs[n]; + } + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_equipunit(lua_State * L) +{ + unit *u = (unit *) tolua_tousertype(L, 1, 0); + const char *eqname = tolua_tostring(L, 2, 0); + equip_unit(u, get_equipment(eqname)); + return 0; +} + +static int tolua_equipment_setitem(lua_State * L) +{ + int result = -1; + const char *eqname = tolua_tostring(L, 1, 0); + const char *iname = tolua_tostring(L, 2, 0); + const char *value = tolua_tostring(L, 3, 0); + if (iname != NULL) { + const struct item_type *itype = it_find(iname); + if (itype != NULL) { + equipment_setitem(create_equipment(eqname), itype, value); + result = 0; + } + } + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_spawn_braineaters(lua_State * L) +{ + float chance = (float)tolua_tonumber(L, 1, 0); + spawn_braineaters(chance); + return 0; +} + +static int tolua_init_reports(lua_State * L) +{ + int result = init_reports(); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_write_report(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, 0); + time_t ltime = time(0); + int result = write_reports(f, ltime); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static int tolua_write_reports(lua_State * L) +{ + int result; + init_reports(); + result = reports(); + tolua_pushnumber(L, (lua_Number) result); + return 1; +} + +static void reset_game(void) +{ + region *r; + faction *f; + for (r = regions; r; r = r->next) { + unit *u; + building *b; + r->flags &= RF_SAVEMASK; + for (u = r->units; u; u = u->next) { + u->flags &= UFL_SAVEMASK; + } + for (b = r->buildings; b; b = b->next) { + b->flags &= BLD_SAVEMASK; + } + if (r->land && r->land->ownership && r->land->ownership->owner) { + faction *owner = r->land->ownership->owner; + if (owner == get_monsters()) { + /* some compat-fix, i believe. */ + owner = update_owners(r); + } + if (owner) { + fset(r, RF_GUARDED); + } + } + } + for (f = factions; f; f = f->next) { + f->flags &= FFL_SAVEMASK; + } +} + +static int tolua_process_orders(lua_State * L) +{ + ++turn; + reset_game(); + processorders(); + return 0; +} + +static int tolua_write_passwords(lua_State * L) +{ + int result = writepasswd(); + lua_pushnumber(L, (lua_Number) result); + return 0; +} + +static struct summary *sum_begin = 0; +static int tolua_init_summary(lua_State * L) +{ + sum_begin = make_summary(); + return 0; +} + +static int tolua_write_summary(lua_State * L) +{ + if (sum_begin) { + struct summary *sum_end = make_summary(); + report_summary(sum_end, sum_begin, false); + report_summary(sum_end, sum_begin, true); + return 0; + } + return 0; +} + +static int tolua_write_map(lua_State * L) +{ + const char *filename = tolua_tostring(L, 1, 0); + if (filename) { + crwritemap(filename); + } + return 0; +} + +static int tolua_read_turn(lua_State * L) +{ + int cturn = current_turn(); + tolua_pushnumber(L, (lua_Number) cturn); + return 1; +} + +static int tolua_get_faction(lua_State * L) +{ + int no = tolua_toid(L, 1, 0); + faction *f = findfaction(no); + tolua_pushusertype(L, f, TOLUA_CAST "faction"); + return 1; +} + +static int tolua_get_region(lua_State * L) +{ + int x = (int)tolua_tonumber(L, 1, 0); + int y = (int)tolua_tonumber(L, 2, 0); + region *r; + assert(!pnormalize(&x, &y, findplane(x, y))); + r = findregion(x, y); + tolua_pushusertype(L, r, TOLUA_CAST "region"); + return 1; +} + +static int tolua_get_region_byid(lua_State * L) +{ + int uid = (int)tolua_tonumber(L, 1, 0); + region *r = findregionbyid(uid); + tolua_pushusertype(L, r, TOLUA_CAST "region"); + return 1; +} + +static int tolua_get_building(lua_State * L) +{ + int no = tolua_toid(L, 1, 0); + building *b = findbuilding(no); + tolua_pushusertype(L, b, TOLUA_CAST "building"); + return 1; +} + +static int tolua_get_ship(lua_State * L) +{ + int no = tolua_toid(L, 1, 0); + ship *sh = findship(no); + tolua_pushusertype(L, sh, TOLUA_CAST "ship"); + return 1; +} + +static int tolua_get_alliance(lua_State * L) +{ + int no = tolua_toid(L, 1, 0); + alliance *f = findalliance(no); + tolua_pushusertype(L, f, TOLUA_CAST "alliance"); + return 1; +} + +static int tolua_get_unit(lua_State * L) +{ + int no = tolua_toid(L, 1, 0); + unit *u = findunit(no); + tolua_pushusertype(L, u, TOLUA_CAST "unit"); + return 1; +} + +static int tolua_alliance_create(lua_State * L) +{ + int id = (int)tolua_tonumber(L, 1, 0); + const char *name = tolua_tostring(L, 2, 0); + alliance *alli = makealliance(id, name); + tolua_pushusertype(L, alli, TOLUA_CAST "alliance"); + return 1; +} + +static int tolua_get_regions(lua_State * L) +{ + region **region_ptr = (region **) lua_newuserdata(L, sizeof(region *)); + luaL_getmetatable(L, "region"); + lua_setmetatable(L, -2); + *region_ptr = regions; + lua_pushcclosure(L, tolua_regionlist_next, 1); + return 1; +} + +static int tolua_get_factions(lua_State * L) +{ + faction **faction_ptr = (faction **) lua_newuserdata(L, sizeof(faction *)); + luaL_getmetatable(L, "faction"); + lua_setmetatable(L, -2); + *faction_ptr = factions; + lua_pushcclosure(L, tolua_factionlist_next, 1); + return 1; +} + +static int tolua_get_alliance_factions(lua_State * L) +{ + alliance *self = (alliance *) tolua_tousertype(L, 1, 0); + return tolua_quicklist_push(L, "faction_list", "faction", self->members); +} + +static int tolua_get_alliance_id(lua_State * L) +{ + alliance *self = (alliance *) tolua_tousertype(L, 1, 0); + tolua_pushnumber(L, (lua_Number) self->id); + return 1; +} + +static int tolua_get_alliance_name(lua_State * L) +{ + alliance *self = (alliance *) tolua_tousertype(L, 1, 0); + tolua_pushstring(L, self->name); + return 1; +} + +static int tolua_set_alliance_name(lua_State * L) +{ + alliance *self = (alliance *) tolua_tousertype(L, 1, 0); + alliance_setname(self, tolua_tostring(L, 2, 0)); + return 0; +} + +#ifdef WRITE_SPELLS +#include +#include +#include +#include +static int tolua_write_spells(lua_State * L) +{ + spell_f fun = (spell_f) get_function("lua_castspell"); + const char *filename = "magic.xml"; + xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); + xmlNodePtr root = xmlNewNode(NULL, BAD_CAST "spells"); + quicklist *ql; + int qi; + for (ql = spells, qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spell *sp = (spell *) ql_get(ql, qi); + if (sp->cast != fun) { + int combat = 0; + xmlNodePtr node = xmlNewNode(NULL, BAD_CAST "spell"); + xmlNewProp(node, BAD_CAST "name", BAD_CAST sp->sname); + xmlNewProp(node, BAD_CAST "type", BAD_CAST magic_school[sp->magietyp]); + xmlNewProp(node, BAD_CAST "rank", xml_i(sp->rank)); + xmlNewProp(node, BAD_CAST "level", xml_i(sp->level)); + xmlNewProp(node, BAD_CAST "index", xml_i(sp->id)); + if (sp->syntax) + xmlNewProp(node, BAD_CAST "syntax", BAD_CAST sp->syntax); + if (sp->parameter) + xmlNewProp(node, BAD_CAST "parameters", BAD_CAST sp->parameter); + if (sp->components) { + spell_component *comp = sp->components; + for (; comp->type != 0; ++comp) { + static const char *costs[] = { "fixed", "level", "linear" }; + xmlNodePtr cnode = xmlNewNode(NULL, BAD_CAST "resource"); + xmlNewProp(cnode, BAD_CAST "name", BAD_CAST comp->type->_name[0]); + xmlNewProp(cnode, BAD_CAST "amount", xml_i(comp->amount)); + xmlNewProp(cnode, BAD_CAST "cost", BAD_CAST costs[comp->cost]); + xmlAddChild(node, cnode); + }} + if (sp->sptyp & TESTCANSEE) { + xmlNewProp(node, BAD_CAST "los", BAD_CAST "true"); + } + if (sp->sptyp & ONSHIPCAST) { + xmlNewProp(node, BAD_CAST "ship", BAD_CAST "true"); + } + if (sp->sptyp & OCEANCASTABLE) { + xmlNewProp(node, BAD_CAST "ocean", BAD_CAST "true"); + } + if (sp->sptyp & FARCASTING) { + xmlNewProp(node, BAD_CAST "far", BAD_CAST "true"); + } + if (sp->sptyp & SPELLLEVEL) { + xmlNewProp(node, BAD_CAST "variable", BAD_CAST "true"); + } + if (sp->sptyp & POSTCOMBATSPELL) + combat = 3; + + else if (sp->sptyp & COMBATSPELL) + combat = 2; + + else if (sp->sptyp & PRECOMBATSPELL) + combat = 1; + if (combat) { + xmlNewProp(node, BAD_CAST "combat", xml_i(combat)); + } + xmlAddChild(root, node); + } + } + xmlDocSetRootElement(doc, root); + xmlKeepBlanksDefault(0); + xmlSaveFormatFileEnc(filename, doc, "utf-8", 1); + xmlFreeDoc(doc); + return 0; +} +#endif + +static int config_get_ships(lua_State * L) +{ + quicklist *ql; + int qi, i = 0; + lua_createtable(L, ql_length(shiptypes), 0); + for (qi = 0, ql = shiptypes; ql; ql_advance(&ql, &qi, 1)) { + ship_type *stype = (ship_type *) ql_get(ql, qi); + tolua_pushstring(L, TOLUA_CAST stype->name[0]); + lua_rawseti(L, -2, ++i); + } + return 1; +} + +static int config_get_buildings(lua_State * L) +{ + quicklist *ql; + int qi, i = 0; + lua_createtable(L, ql_length(buildingtypes), 0); + for (qi = 0, ql = buildingtypes; ql; ql_advance(&ql, &qi, 1)) { + building_type *btype = (building_type *) ql_get(ql, qi); + tolua_pushstring(L, TOLUA_CAST btype->_name); + lua_rawseti(L, -2, ++i); + } + return 1; +} + +static int config_get_locales(lua_State * L) +{ + const struct locale *lang; + int i = 0, n = 0; + for (lang = locales; lang; lang = nextlocale(lang)) + ++n; + lua_createtable(L, n, 0); + for (lang = locales; lang; lang = nextlocale(lang)) { + tolua_pushstring(L, TOLUA_CAST locale_name(lang)); + lua_rawseti(L, -2, ++i); + } + return 1; +} + +static int config_get_resource(lua_State * L) +{ + const char *name = tolua_tostring(L, 1, 0); + if (name) { + const struct item_type *itype = it_find(name); + if (itype) { + lua_newtable(L); + lua_pushstring(L, "weight"); + lua_pushinteger(L, itype->weight); + lua_settable(L, -3); + if (itype->capacity > 0) { + lua_pushstring(L, "capacity"); + lua_pushinteger(L, itype->capacity); + lua_settable(L, -3); + } + if (itype->construction) { + int i; + lua_pushstring(L, "build_skill_min"); + lua_pushinteger(L, itype->construction->minskill); + lua_settable(L, -3); + lua_pushstring(L, "build_skill_name"); + lua_pushstring(L, skillnames[itype->construction->skill]); + lua_settable(L, -3); + if (itype->construction->materials) { + lua_pushstring(L, "materials"); + lua_newtable(L); + for (i = 0; itype->construction->materials[i].number; ++i) { + lua_pushstring(L, + itype->construction->materials[i].rtype->_name[0]); + lua_pushinteger(L, itype->construction->materials[i].number); + lua_settable(L, -3); + } + lua_settable(L, -3); + } + } + return 1; + } + } + return 0; +} + +static int config_get_btype(lua_State * L) +{ + const char *name = tolua_tostring(L, 1, 0); + + if (name) { + const struct building_type *btype = bt_find(name); + if (btype) { + lua_newtable(L); + lua_pushstring(L, "flags"); + lua_pushinteger(L, btype->flags); + lua_settable(L, -3); + if (btype->capacity > 0) { + lua_pushstring(L, "capacity"); + lua_pushinteger(L, btype->capacity); + lua_settable(L, -3); + } + if (btype->maxcapacity > 0) { + lua_pushstring(L, "maxcapacity"); + lua_pushinteger(L, btype->maxcapacity); + lua_settable(L, -3); + } + if (btype->maxsize > 0) { + lua_pushstring(L, "maxsize"); + lua_pushinteger(L, btype->maxsize); + lua_settable(L, -3); + } + if (btype->maintenance) { + int i; + lua_pushstring(L, "maintenance"); + lua_newtable(L); + for (i = 0; btype->maintenance[i].number; ++i) { + lua_pushstring(L, + btype->maintenance[i].rtype->_name[0]); + lua_pushinteger(L, btype->maintenance[i].number); + lua_settable(L, -3); + } + lua_settable(L, -3); + } + if (btype->construction) { + int i; + lua_pushstring(L, "build_skill_min"); + lua_pushinteger(L, btype->construction->minskill); + lua_settable(L, -3); + lua_pushstring(L, "build_skill_name"); + lua_pushstring(L, skillnames[btype->construction->skill]); + lua_settable(L, -3); + if (btype->construction->materials) { + lua_pushstring(L, "materials"); + lua_newtable(L); + for (i = 0; btype->construction->materials[i].number; ++i) { + lua_pushstring(L, + btype->construction->materials[i].rtype->_name[0]); + lua_pushinteger(L, btype->construction->materials[i].number); + lua_settable(L, -3); + } + lua_settable(L, -3); + } + } + return 1; + } + } + return 0; +} + +static int config_get_stype(lua_State * L) +{ + const char *name = tolua_tostring(L, 1, 0); + + if (name) { + const struct ship_type *stype = st_find(name); + if (stype) { + lua_newtable(L); + lua_pushstring(L, "range"); + lua_pushinteger(L, stype->range); + lua_settable(L, -3); + lua_pushstring(L, "cabins"); + lua_pushinteger(L, stype->cabins); + lua_settable(L, -3); + lua_pushstring(L, "cargo"); + lua_pushinteger(L, stype->cargo); + lua_settable(L, -3); + lua_pushstring(L, "cptskill"); + lua_pushinteger(L, stype->cptskill); + lua_settable(L, -3); + lua_pushstring(L, "minskill"); + lua_pushinteger(L, stype->minskill); + lua_settable(L, -3); + lua_pushstring(L, "sumskill"); + lua_pushinteger(L, stype->sumskill); + lua_settable(L, -3); + lua_pushstring(L, "fishing"); + lua_pushinteger(L, stype->fishing); + lua_settable(L, -3); + if (stype->coasts) { + const terrain_type *coast = *stype->coasts; + lua_pushstring(L, "coasts"); + lua_newtable(L); + while(coast) { + lua_pushstring(L, coast->_name); + lua_pushinteger(L, 1); + lua_settable(L, -3); + coast = coast->next; + } + } + if (stype->construction) { + int i; + lua_pushstring(L, "build_skill_min"); + lua_pushinteger(L, stype->construction->minskill); + lua_settable(L, -3); + lua_pushstring(L, "build_skill_name"); + lua_pushstring(L, skillnames[stype->construction->skill]); + lua_settable(L, -3); + if (stype->construction->materials) { + lua_pushstring(L, "materials"); + lua_newtable(L); + for (i = 0; stype->construction->materials[i].number; ++i) { + lua_pushstring(L, + stype->construction->materials[i].rtype->_name[0]); + lua_pushinteger(L, stype->construction->materials[i].number); + lua_settable(L, -3); + } + lua_settable(L, -3); + } + } + return 1; + } + } + return 0; +} + +static int tolua_get_spell_text(lua_State * L) +{ + const struct locale *loc = default_locale; + spell *self = (spell *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, spell_info(self, loc)); + return 1; +} + +#ifdef TODO +static int tolua_get_spell_school(lua_State * L) +{ + spell *self = (spell *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, magic_school[self->magietyp]); + return 1; +} + +static int tolua_get_spell_level(lua_State * L) +{ + spell *self = (spell *) tolua_tousertype(L, 1, 0); + lua_pushnumber(L, self->level); + return 1; +} +#endif + +static int tolua_get_spell_name(lua_State * L) +{ + const struct locale *lang = default_locale; + spell *self = (spell *) tolua_tousertype(L, 1, 0); + lua_pushstring(L, spell_name(self, lang)); + return 1; +} + +static int tolua_get_spells(lua_State * L) +{ + return tolua_quicklist_push(L, "spell_list", "spell", spells); +} + +int tolua_read_xml(lua_State * L) +{ + const char *filename = tolua_tostring(L, 1, 0); + const char *catalog = tolua_tostring(L, 2, 0); + init_data(filename, catalog); + return 0; +} + +typedef struct event_args { + int hfunction; + int hargs; + const char *sendertype; +} event_args; + +static int tolua_report_unit(lua_State * L) +{ + char buffer[512]; + unit *u = (unit *) tolua_tousertype(L, 1, 0); + faction *f = (faction *) tolua_tousertype(L, 2, 0); + bufunit(f, u, 0, see_unit, buffer, sizeof(buffer)); + tolua_pushstring(L, buffer); + return 1; +} + +static void parse_inifile(lua_State * L, dictionary * d, const char *section) +{ + int i; + size_t len = strlen(section); + for (i = 0; d && i != d->n; ++i) { + const char *key = d->key[i]; + if (strncmp(section, key, len) == 0 && key[len] == ':') { + const char *str_value = d->val[i]; + char *endp; + double num_value = strtod(str_value, &endp); + lua_pushstring(L, key + len + 1); + if (*endp) { + tolua_pushstring(L, str_value); + } else { + tolua_pushnumber(L, num_value); + } + lua_rawset(L, -3); + } + } + + /* special case */ + lua_pushstring(L, "basepath"); + lua_pushstring(L, basepath()); + lua_rawset(L, -3); + lua_pushstring(L, "reportpath"); + lua_pushstring(L, reportpath()); + lua_rawset(L, -3); +} + +int tolua_bindings_open(lua_State * L) +{ + tolua_open(L); + + tolua_eressea_open(L); + tolua_process_open(L); + tolua_settings_open(L); + + /* register user types */ + tolua_usertype(L, TOLUA_CAST "spell"); + tolua_usertype(L, TOLUA_CAST "spell_list"); + tolua_usertype(L, TOLUA_CAST "order"); + tolua_usertype(L, TOLUA_CAST "item"); + tolua_usertype(L, TOLUA_CAST "alliance"); + tolua_usertype(L, TOLUA_CAST "event"); + tolua_usertype(L, TOLUA_CAST "faction_list"); + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_cclass(L, TOLUA_CAST "alliance", TOLUA_CAST "alliance", + TOLUA_CAST "", NULL); + tolua_beginmodule(L, TOLUA_CAST "alliance"); + { + tolua_variable(L, TOLUA_CAST "name", tolua_get_alliance_name, + tolua_set_alliance_name); + tolua_variable(L, TOLUA_CAST "id", tolua_get_alliance_id, NULL); + tolua_variable(L, TOLUA_CAST "factions", &tolua_get_alliance_factions, + NULL); + tolua_function(L, TOLUA_CAST "create", tolua_alliance_create); + } tolua_endmodule(L); + tolua_cclass(L, TOLUA_CAST "spell", TOLUA_CAST "spell", TOLUA_CAST "", + NULL); + tolua_beginmodule(L, TOLUA_CAST "spell"); + { + tolua_function(L, TOLUA_CAST "__tostring", tolua_get_spell_name); + tolua_variable(L, TOLUA_CAST "name", tolua_get_spell_name, 0); +#ifdef TODO + tolua_variable(L, TOLUA_CAST "school", tolua_get_spell_school, 0); + tolua_variable(L, TOLUA_CAST "level", tolua_get_spell_level, 0); +#endif + tolua_variable(L, TOLUA_CAST "text", tolua_get_spell_text, 0); + } tolua_endmodule(L); + tolua_module(L, TOLUA_CAST "report", 1); + tolua_beginmodule(L, TOLUA_CAST "report"); + { + tolua_function(L, TOLUA_CAST "report_unit", &tolua_report_unit); + } tolua_endmodule(L); + tolua_module(L, TOLUA_CAST "config", 1); + tolua_beginmodule(L, TOLUA_CAST "config"); + { + parse_inifile(L, global.inifile, "config"); + tolua_variable(L, TOLUA_CAST "locales", &config_get_locales, 0); + tolua_function(L, TOLUA_CAST "get_resource", &config_get_resource); + tolua_variable(L, TOLUA_CAST "buildings", &config_get_buildings, 0); + tolua_function(L, TOLUA_CAST "get_building", &config_get_btype); + tolua_variable(L, TOLUA_CAST "ships", &config_get_ships, 0); + tolua_function(L, TOLUA_CAST "get_ship", &config_get_stype); + } tolua_endmodule(L); + tolua_function(L, TOLUA_CAST "get_region_by_id", tolua_get_region_byid); + tolua_function(L, TOLUA_CAST "get_faction", tolua_get_faction); + tolua_function(L, TOLUA_CAST "get_unit", tolua_get_unit); + tolua_function(L, TOLUA_CAST "get_alliance", tolua_get_alliance); + tolua_function(L, TOLUA_CAST "get_ship", tolua_get_ship); + tolua_function(L, TOLUA_CAST "get_building", tolua_get_building); + tolua_function(L, TOLUA_CAST "get_region", tolua_get_region); + tolua_function(L, TOLUA_CAST "factions", tolua_get_factions); + tolua_function(L, TOLUA_CAST "regions", tolua_get_regions); + tolua_function(L, TOLUA_CAST "read_turn", tolua_read_turn); + tolua_function(L, TOLUA_CAST "write_map", &tolua_write_map); + tolua_function(L, TOLUA_CAST "read_orders", tolua_read_orders); + tolua_function(L, TOLUA_CAST "process_orders", tolua_process_orders); + tolua_function(L, TOLUA_CAST "init_reports", tolua_init_reports); + tolua_function(L, TOLUA_CAST "write_reports", tolua_write_reports); + tolua_function(L, TOLUA_CAST "write_report", tolua_write_report); + tolua_function(L, TOLUA_CAST "init_summary", tolua_init_summary); + tolua_function(L, TOLUA_CAST "write_summary", tolua_write_summary); + tolua_function(L, TOLUA_CAST "write_passwords", tolua_write_passwords); + tolua_function(L, TOLUA_CAST "message_unit", tolua_message_unit); + tolua_function(L, TOLUA_CAST "message_faction", tolua_message_faction); + tolua_function(L, TOLUA_CAST "message_region", tolua_message_region); + + /* scripted monsters */ + tolua_function(L, TOLUA_CAST "spawn_braineaters", tolua_spawn_braineaters); + + tolua_function(L, TOLUA_CAST "update_guards", tolua_update_guards); + tolua_function(L, TOLUA_CAST "set_turn", &tolua_set_turn); + tolua_function(L, TOLUA_CAST "get_turn", &tolua_get_turn); + tolua_function(L, TOLUA_CAST "get_season", tolua_get_season); + tolua_function(L, TOLUA_CAST "equipment_setitem", tolua_equipment_setitem); + tolua_function(L, TOLUA_CAST "equip_unit", tolua_equipunit); + tolua_function(L, TOLUA_CAST "add_equipment", tolua_addequipment); + tolua_function(L, TOLUA_CAST "atoi36", tolua_atoi36); + tolua_function(L, TOLUA_CAST "itoa36", tolua_itoa36); + tolua_function(L, TOLUA_CAST "dice_roll", tolua_dice_rand); + tolua_function(L, TOLUA_CAST "get_nmrs", tolua_get_nmrs); + tolua_function(L, TOLUA_CAST "remove_empty_units", tolua_remove_empty_units); + tolua_function(L, TOLUA_CAST "update_subscriptions", tolua_update_subscriptions); + tolua_function(L, TOLUA_CAST "update_scores", tolua_update_scores); + tolua_function(L, TOLUA_CAST "update_owners", tolua_update_owners); + tolua_function(L, TOLUA_CAST "learn_skill", tolua_learn_skill); + tolua_function(L, TOLUA_CAST "create_curse", tolua_create_curse); + tolua_function(L, TOLUA_CAST "autoseed", tolua_autoseed); + tolua_function(L, TOLUA_CAST "get_key", tolua_getkey); + tolua_function(L, TOLUA_CAST "set_key", tolua_setkey); + tolua_function(L, TOLUA_CAST "translate", &tolua_translate); + tolua_function(L, TOLUA_CAST "rng_int", tolua_rng_int); + tolua_function(L, TOLUA_CAST "spells", tolua_get_spells); +#ifdef WRITE_SPELLS + tolua_function(L, TOLUA_CAST "write_spells", tolua_write_spells); +#endif + tolua_function(L, TOLUA_CAST "read_xml", tolua_read_xml); + } tolua_endmodule(L); + return 1; +} + +static void openlibs(lua_State * L) +{ + luaL_openlibs(L); +} + +void lua_done(lua_State * L) { + lua_close(L); +} + +lua_State *lua_init(void) { + lua_State *L = luaL_newstate(); + + openlibs(L); +#ifdef BINDINGS_TOLUA + register_tolua_helpers(); + tolua_bindings_open(L); + tolua_eressea_open(L); + tolua_sqlite_open(L); + tolua_unit_open(L); + tolua_building_open(L); + tolua_ship_open(L); + tolua_region_open(L); + tolua_faction_open(L); +#ifdef BSON_ATTRIB + tolua_attrib_open(L); +#endif + tolua_unit_open(L); + tolua_message_open(L); + tolua_hashtable_open(L); + tolua_gmtool_open(L); + tolua_storage_open(L); +#endif + return L; +} + +int eressea_run(lua_State *L, const char *luafile, const char *entry_point) +{ + int err = 0; + + global.vm_state = L; + /* run the main script */ + if (luafile) { + log_debug("executing script %s\n", luafile); + lua_getglobal(L, "dofile"); + lua_pushstring(L, luafile); + err = lua_pcall(L, 1, 0, 0); + if (err != 0) { + log_lua_error(L); + abort(); + return err; + } + } + if (entry_point) { + lua_getglobal(L, entry_point); + if (lua_isfunction(L, -1)) { + log_debug("calling entry-point: %s\n", entry_point); + err = lua_pcall(L, 0, 1, 0); + if (err != 0) { + log_lua_error(L); + } + return err; + } else { + log_error("unknown entry-point: %s\n", entry_point); + } + } + return lua_console(L); +} diff --git a/core/src/bindings/bindings.h b/core/src/bindings/bindings.h new file mode 100755 index 000000000..1494a1d7c --- /dev/null +++ b/core/src/bindings/bindings.h @@ -0,0 +1,36 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + struct quicklist; + + int tolua_sqlite_open(struct lua_State *L); + int tolua_bindings_open(struct lua_State *L); + int tolua_spelllist_next(struct lua_State *L); + int tolua_itemlist_next(struct lua_State *L); + int tolua_orderlist_next(struct lua_State *L); + int tolua_quicklist_push(struct lua_State *L, const char *list_type, + const char *elem_type, struct quicklist *list); + + int log_lua_error(struct lua_State *L); + + void lua_done(struct lua_State *L); + struct lua_State *lua_init(void); + int eressea_run(struct lua_State *L, const char *luafile, const char *entry_point); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/eressea.pkg b/core/src/bindings/eressea.pkg new file mode 100755 index 000000000..78740c485 --- /dev/null +++ b/core/src/bindings/eressea.pkg @@ -0,0 +1,8 @@ +$#include + +module eressea { + void eressea_free_game @ free_game(void); + int eressea_read_game @ read_game(const char * filename); + int eressea_write_game @ write_game(const char * filename); + int eressea_read_orders @ read_orders(const char * filename); +} diff --git a/core/src/bindings/helpers.c b/core/src/bindings/helpers.c new file mode 100644 index 000000000..96407ca07 --- /dev/null +++ b/core/src/bindings/helpers.c @@ -0,0 +1,635 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include "helpers.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +static int +lua_giveitem(unit * s, unit * d, const item_type * itype, int n, struct order *ord) +{ + lua_State *L = (lua_State *) global.vm_state; + char fname[64]; + int result = -1; + const char *iname = itype->rtype->_name[0]; + + assert(s != NULL); + strlcpy(fname, iname, sizeof(fname)); + strlcat(fname, "_give", sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, s, TOLUA_CAST "unit"); + tolua_pushusertype(L, d, TOLUA_CAST "unit"); + tolua_pushstring(L, iname); + tolua_pushnumber(L, (lua_Number) n); + + if (lua_pcall(L, 4, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("unit %s calling '%s': %s.\n", unitname(s), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("unit %s trying to call '%s' : not a function.\n", unitname(s), fname); + lua_pop(L, 1); + } + + return result; +} + +static int limit_resource(const region * r, const resource_type * rtype) +{ + char fname[64]; + int result = -1; + lua_State *L = (lua_State *) global.vm_state; + + strlcpy(fname, rtype->_name[0], sizeof(fname)); + strlcat(fname, "_limit", sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)r, TOLUA_CAST "region"); + + if (lua_pcall(L, 1, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("limit(%s) calling '%s': %s.\n", regionname(r, NULL), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("limit(%s) calling '%s': not a function.\n", regionname(r, NULL), fname); + lua_pop(L, 1); + } + + return result; +} + +static void +produce_resource(region * r, const resource_type * rtype, int norders) +{ + lua_State *L = (lua_State *) global.vm_state; + char fname[64]; + + strlcpy(fname, rtype->_name[0], sizeof(fname)); + strlcat(fname, "_produce", sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)r, TOLUA_CAST "region"); + tolua_pushnumber(L, (lua_Number) norders); + + if (lua_pcall(L, 2, 0, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("produce(%s) calling '%s': %s.\n", regionname(r, NULL), fname, error); + lua_pop(L, 1); + } + } else { + log_error("produce(%s) calling '%s': not a function.\n", regionname(r, NULL), fname); + lua_pop(L, 1); + } +} + +static int lc_age(struct attrib *a) +{ + building_action *data = (building_action *) a->data.v; + const char *fname = data->fname; + const char *fparam = data->param; + building *b = data->b; + int result = -1; + + assert(b != NULL); + if (fname != NULL) { + lua_State *L = (lua_State *) global.vm_state; + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)b, TOLUA_CAST "building"); + if (fparam) { + tolua_pushstring(L, fparam); + } + + if (lua_pcall(L, fparam ? 2 : 1, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("lc_age(%s) calling '%s': %s.\n", buildingname(b), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("lc_age(%s) calling '%s': not a function.\n", buildingname(b), fname); + lua_pop(L, 1); + } + } + return (result != 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +static void push_param(lua_State * L, char c, spllprm * param) +{ + if (c == 'u') + tolua_pushusertype(L, param->data.u, "unit"); + else if (c == 'b') + tolua_pushusertype(L, param->data.b, "building"); + else if (c == 's') + tolua_pushusertype(L, param->data.sh, "ship"); + else if (c == 'r') + tolua_pushusertype(L, param->data.sh, "region"); + else if (c == 'c') + tolua_pushstring(L, param->data.s); + else { + log_error("unsupported syntax %c.\n", c); + lua_pushnil(L); + } +} + +/** callback to use lua for spell functions */ +static int lua_callspell(castorder * co) +{ + lua_State *L = (lua_State *) global.vm_state; + const char *fname = co->sp->sname; + unit *caster = co_get_caster(co); + region * r = co_get_region(co); + int result = -1; + const char *hashpos = strchr(fname, '#'); + char fbuf[64]; + + if (hashpos != NULL) { + ptrdiff_t len = hashpos - fname; + assert(len < (ptrdiff_t) sizeof(fbuf)); + strncpy(fbuf, fname, len); + fbuf[len] = '\0'; + fname = fbuf; + } + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + int nparam = 4; + tolua_pushusertype(L, r, TOLUA_CAST "region"); + tolua_pushusertype(L, caster, TOLUA_CAST "unit"); + tolua_pushnumber(L, (lua_Number) co->level); + tolua_pushnumber(L, (lua_Number) co->force); + if (co->sp->parameter && co->par->length) { + const char *synp = co->sp->parameter; + int i = 0; + ++nparam; + lua_newtable(L); + while (*synp && i < co->par->length) { + spllprm *param = co->par->param[i]; + char c = *synp; + if (c == '+') { + push_param(L, *(synp - 1), param); + } else { + push_param(L, c, param); + ++synp; + } + lua_rawseti(L, -2, ++i); + } + } + + if (lua_pcall(L, nparam, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("spell(%s) calling '%s': %s.\n", unitname(caster), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + int ltype = lua_type(L, -1); + log_error("spell(%s) calling '%s': not a function, has type %d.\n", unitname(caster), fname, ltype); + lua_pop(L, 1); + } + + return result; +} + +/** callback to initialize a familiar from lua. */ +static int lua_initfamiliar(unit * u) +{ + lua_State *L = (lua_State *) global.vm_state; + char fname[64]; + int result = -1; + + strlcpy(fname, "initfamiliar_", sizeof(fname)); + strlcat(fname, u_race(u)->_name[0], sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, u, TOLUA_CAST "unit"); + + if (lua_pcall(L, 1, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("familiar(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_warning("familiar(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + + create_mage(u, M_GRAY); + + strlcpy(fname, u_race(u)->_name[0], sizeof(fname)); + strlcat(fname, "_familiar", sizeof(fname)); + equip_unit(u, get_equipment(fname)); + return result; +} + +static int +lua_changeresource(unit * u, const struct resource_type *rtype, int delta) +{ + lua_State *L = (lua_State *) global.vm_state; + int result = -1; + char fname[64]; + + strlcpy(fname, rtype->_name[0], sizeof(fname)); + strlcat(fname, "_changeresource", sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, u, TOLUA_CAST "unit"); + tolua_pushnumber(L, (lua_Number) delta); + + if (lua_pcall(L, 2, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("change(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("change(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + + return result; +} + +static int lua_getresource(unit * u, const struct resource_type *rtype) +{ + lua_State *L = (lua_State *) global.vm_state; + int result = -1; + char fname[64]; + + strlcpy(fname, rtype->_name[0], sizeof(fname)); + strlcat(fname, "_getresource", sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, u, TOLUA_CAST "unit"); + + if (lua_pcall(L, 1, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("get(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("get(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + + return result; +} + +static bool lua_canuse_item(const unit * u, const struct item_type *itype) +{ + static int function_exists = 1; + bool result = true; + + if (function_exists) { + lua_State *L = (lua_State *) global.vm_state; + const char *fname = "item_canuse"; + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + tolua_pushstring(L, itype->rtype->_name[0]); + + if (lua_pcall(L, 2, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("get(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = lua_toboolean(L, -1); + lua_pop(L, 1); + } + } else { + function_exists = 0; + log_error("get(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + } + return result; +} + +static int +lua_wage(const region * r, const faction * f, const race * rc, int in_turn) +{ + lua_State *L = (lua_State *) global.vm_state; + const char *fname = "wage"; + int result = -1; + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)r, TOLUA_CAST "region"); + tolua_pushusertype(L, (void *)f, TOLUA_CAST "faction"); + tolua_pushstring(L, rc ? rc->_name[0] : 0); + tolua_pushnumber(L, (lua_Number) in_turn); + + if (lua_pcall(L, 3, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("wage(%s) calling '%s': %s.\n", regionname(r, NULL), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("wage(%s) calling '%s': not a function.\n", regionname(r, NULL), fname); + lua_pop(L, 1); + } + + return result; +} + +static void lua_agebuilding(building * b) +{ + lua_State *L = (lua_State *) global.vm_state; + char fname[64]; + + strlcpy(fname, "age_", sizeof(fname)); + strlcat(fname, b->type->_name, sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)b, TOLUA_CAST "building"); + + if (lua_pcall(L, 1, 0, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("agebuilding(%s) calling '%s': %s.\n", buildingname(b), fname, error); + lua_pop(L, 1); + } + } else { + log_error("agebuilding(%s) calling '%s': not a function.\n", buildingname(b), fname); + lua_pop(L, 1); + } +} + +static int lua_building_protection(building * b, unit * u) +{ + lua_State *L = (lua_State *) global.vm_state; + const char *fname = "building_protection"; + int result = 0; + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)b, TOLUA_CAST "building"); + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + + if (lua_pcall(L, 2, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("building_protection(%s, %s) calling '%s': %s.\n", buildingname(b), unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("building_protection(%s, %s) calling '%s': not a function.\n", buildingname(b), unitname(u), fname); + lua_pop(L, 1); + } + return result; +} + +static double lua_building_taxes(building * b, int level) +{ + lua_State *L = (lua_State *) global.vm_state; + const char *fname = "building_taxes"; + double result = 0.0F; + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)b, TOLUA_CAST "building"); + tolua_pushnumber(L, level); + + if (lua_pcall(L, 2, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("building_taxes(%s) calling '%s': %s.\n", buildingname(b), fname, error); + lua_pop(L, 1); + } else { + result = (double)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("building_taxes(%s) calling '%s': not a function.\n", buildingname(b), fname); + lua_pop(L, 1); + } + return result; +} + +static int lua_maintenance(const unit * u) +{ + lua_State *L = (lua_State *) global.vm_state; + const char *fname = "maintenance"; + int result = -1; + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + + if (lua_pcall(L, 1, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("maintenance(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("maintenance(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + + return result; +} + +static int lua_equipmentcallback(const struct equipment *eq, unit * u) +{ + lua_State *L = (lua_State *) global.vm_state; + char fname[64]; + int result = -1; + + strlcpy(fname, "equip_", sizeof(fname)); + strlcat(fname, eq->name, sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + + if (lua_pcall(L, 1, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("equip(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("equip(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + return result; +} + +/** callback for an item-use function written in lua. */ +int +lua_useitem(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + lua_State *L = (lua_State *) global.vm_state; + int result = 0; + char fname[64]; + + strlcpy(fname, "use_", sizeof(fname)); + strlcat(fname, itype->rtype->_name[0], sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + tolua_pushnumber(L, (lua_Number) amount); + + if (lua_pcall(L, 2, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("use(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("use(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + + return result; +} + +static int lua_recruit(struct unit *u, const struct archetype *arch, int amount) +{ + lua_State *L = (lua_State *) global.vm_state; + int result = 0; + char fname[64]; + + strlcpy(fname, "recruit_", sizeof(fname)); + strlcat(fname, arch->name[0], sizeof(fname)); + + lua_getglobal(L, fname); + if (lua_isfunction(L, -1)) { + tolua_pushusertype(L, (void *)u, TOLUA_CAST "unit"); + tolua_pushnumber(L, (lua_Number) amount); + + if (lua_pcall(L, 2, 1, 0) != 0) { + const char *error = lua_tostring(L, -1); + log_error("use(%s) calling '%s': %s.\n", unitname(u), fname, error); + lua_pop(L, 1); + } else { + result = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + log_error("use(%s) calling '%s': not a function.\n", unitname(u), fname); + lua_pop(L, 1); + } + return result; +} + +int tolua_toid(lua_State * L, int idx, int def) +{ + int no = 0; + int type = lua_type(L, idx); + if (type == LUA_TNUMBER) { + no = (int)tolua_tonumber(L, idx, def); + } else { + const char *str = tolua_tostring(L, idx, NULL); + no = str ? atoi36(str) : def; + } + return no; +} + +void register_tolua_helpers(void) +{ + at_building_action.age = lc_age; + + register_function((pf_generic) & lua_building_protection, + TOLUA_CAST "lua_building_protection"); + register_function((pf_generic) & lua_building_taxes, + TOLUA_CAST "lua_building_taxes"); + register_function((pf_generic) & lua_agebuilding, + TOLUA_CAST "lua_agebuilding"); + register_function((pf_generic) & lua_recruit, TOLUA_CAST "lua_recruit"); + register_function((pf_generic) & lua_callspell, TOLUA_CAST "lua_castspell"); + register_function((pf_generic) & lua_initfamiliar, + TOLUA_CAST "lua_initfamiliar"); + register_item_use(&lua_useitem, TOLUA_CAST "lua_useitem"); + register_function((pf_generic) & lua_getresource, + TOLUA_CAST "lua_getresource"); + register_function((pf_generic) & lua_canuse_item, + TOLUA_CAST "lua_canuse_item"); + register_function((pf_generic) & lua_changeresource, + TOLUA_CAST "lua_changeresource"); + register_function((pf_generic) & lua_equipmentcallback, + TOLUA_CAST "lua_equip"); + + register_function((pf_generic) & lua_wage, TOLUA_CAST "lua_wage"); + register_function((pf_generic) & lua_maintenance, + TOLUA_CAST "lua_maintenance"); + + register_function((pf_generic) produce_resource, + TOLUA_CAST "lua_produceresource"); + register_function((pf_generic) limit_resource, + TOLUA_CAST "lua_limitresource"); + register_item_give(lua_giveitem, TOLUA_CAST "lua_giveitem"); +} diff --git a/core/src/bindings/helpers.h b/core/src/bindings/helpers.h new file mode 100644 index 000000000..acf0d0d73 --- /dev/null +++ b/core/src/bindings/helpers.h @@ -0,0 +1,23 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + void register_tolua_helpers(void); + int tolua_toid(struct lua_State *L, int idx, int def); + +#ifdef __cplusplus +} +#endif diff --git a/core/src/bindings/process.pkg b/core/src/bindings/process.pkg new file mode 100755 index 000000000..fb68e3a42 --- /dev/null +++ b/core/src/bindings/process.pkg @@ -0,0 +1,46 @@ +$#include + +module eressea { + module process { + void process_update_long_order @ update_long_order(void); + void process_markets @ markets(void); /* operate the e3 markets */ + void process_produce @ produce(void); /* MAKE+BUY/SELL/ENTERTAIN/WORK/TAX/STEAL/SPY/SABOTAGE/PLANT/BREED/RESEARCH */ + void process_make_temp @ make_temp(void); /* MAKE TEMP */ + void process_settings @ settings(void); /* EMAIL/PASSWORD/BANNER/OPTION */ + void process_ally @ set_allies(void); /* HELP */ + void process_prefix @ set_prefix(void); /* PREFIX */ + void process_setstealth @ set_stealth(void); /* STEALTH */ + void process_status @ set_status(void); /* STATUS */ + void process_name @ set_name(void); /* NAME/DISPLAY */ + void process_group @ set_group(void); /* GROUP */ + void process_origin @ set_origin(void); /* ORIGIN */ + void process_quit @ quit(void); /* QUIT */ + void process_study @ study(void); /* LEARN/TEACH */ + void process_movement @ movement(void); /* MOVE/FOLLOW/ROUTE */ + void process_use @ use(void); /* USE */ + void process_battle @ battle(void); /* ATTACK */ + void process_siege @ siege(void); /* SIEGE */ + void process_leave @ leave(void); /* LEAVE */ + void process_maintenance @ maintenance(void); /* PAY */ + void process_promote @ promote(void); /* PROMOTE */ + void process_renumber @ renumber(void); /* RENUMBER */ + void process_restack @ restack(void); /* SORT */ + void process_setspells @ set_spells(void); /* COMBATSPELL */ + void process_sethelp @ set_help(void); /* HELP */ + void process_contact @ contact(void); /* CONTACT */ + void process_enter @ enter(int message); /* ENTER */ + void process_magic @ magic(void); /* CAST */ + void process_give_control @ give_control(void); /* GIVE CONTROL */ + void process_regeneration @ regeneration(void); /* regen health & aura */ + void process_guard_on @ guard_on(void); /* GUARD */ + void process_guard_off @ guard_off(void); /* GUARD NOT */ + void process_explain @ explain(void); /* SHOW */ + void process_messages @ messages(void); /* MESSAGE */ + void process_reserve @ reserve(void); /* RESERVE */ + void process_claim @ claim(void); /* CLAIM */ + void process_follow @ follow(void); /* FOLLOW */ + void process_alliance @ alliance(void); /* FOLLOW */ + void process_idle @ idle(void); /* work.auto */ + void process_set_default @ set_default(void); /* work.auto */ + } +} diff --git a/core/src/bindings/settings.pkg b/core/src/bindings/settings.pkg new file mode 100755 index 000000000..8269dbd58 --- /dev/null +++ b/core/src/bindings/settings.pkg @@ -0,0 +1,8 @@ +$#include + +module eressea { + module settings { + void settings_set @ set(const char *key, const char *value); + const char * settings_get @ get(const char *key); + } +} diff --git a/core/src/build/atoi36.c b/core/src/build/atoi36.c new file mode 100644 index 000000000..4f896a1e7 --- /dev/null +++ b/core/src/build/atoi36.c @@ -0,0 +1,6 @@ +#include "common/settings.h" +#include "common/config.h" +#include "stdafx.h" + +#include "common/util/base36.c" +#include "tools/atoi36.c" diff --git a/core/src/build/dependencies.c b/core/src/build/dependencies.c new file mode 100644 index 000000000..47c3314a8 --- /dev/null +++ b/core/src/build/dependencies.c @@ -0,0 +1,26 @@ +#include +#include +#include "stdafx.h" + +#pragma warning(push) +#pragma warning(disable: 4706) /* '__STDC_VERSION__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +#pragma warning(disable: 4244) /* '-=' : conversion from 'int' to 'u16', possible loss of data */ +#pragma warning(disable: 4127) /* conditional expression is constant */ +#pragma warning(disable: 4820) /* 'sqlite3_index_constraint' : '2' bytes padding added after data member 'usable' */ +#pragma warning(disable: 4668) /* is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +#pragma warning(disable: 4242) /* '=' : conversion from 'int' to 'ynVar', possible loss of data */ +#include +#pragma warning(pop) + +#pragma warning(push) +#pragma warning(disable: 4668) /* is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +#include +#pragma warning(pop) + +#include + +#include + +#ifndef DISABLE_TESTS +#include +#endif diff --git a/core/src/build/gamecode.c b/core/src/build/gamecode.c new file mode 100644 index 000000000..db0797b27 --- /dev/null +++ b/core/src/build/gamecode.c @@ -0,0 +1,77 @@ +#include +#include "stdafx.h" + +#ifdef BINDINGS_TOLUA +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/core/src/build/kernel.c b/core/src/build/kernel.c new file mode 100644 index 000000000..22e8992b8 --- /dev/null +++ b/core/src/build/kernel.c @@ -0,0 +1,54 @@ +#include +#include +#include "stdafx.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/core/src/build/lib.c b/core/src/build/lib.c new file mode 100644 index 000000000..a5e6ff13d --- /dev/null +++ b/core/src/build/lib.c @@ -0,0 +1,14 @@ +#include +#include +#include "stdafx.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4668) +#include +#pragma warning(pop) +#endif + +#include +#include +#include diff --git a/core/src/build/stdafx.c b/core/src/build/stdafx.c new file mode 100644 index 000000000..da835cf2b --- /dev/null +++ b/core/src/build/stdafx.c @@ -0,0 +1,2 @@ +#pragma warning(disable: 4206) +#include "stdafx.h" diff --git a/core/src/build/stdafx.h b/core/src/build/stdafx.h new file mode 100644 index 000000000..b67bed8c1 --- /dev/null +++ b/core/src/build/stdafx.h @@ -0,0 +1,14 @@ +#ifdef _MSC_VER +#define VC_EXTRALEAN +#define WIN32_LEAN_AND_MEAN +#pragma warning(disable: 4820) +#pragma warning(push) +#pragma warning(disable: 4668) +#pragma warning(disable: 4255) +#include +#include +#pragma warning(pop) +#endif /* _MSC_VER */ + +#include +#include diff --git a/core/src/build/util.c b/core/src/build/util.c new file mode 100644 index 000000000..3fa1e4f1c --- /dev/null +++ b/core/src/build/util.c @@ -0,0 +1,34 @@ +#include +#include +#include "stdafx.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef HAVE_INLINE +#include +#endif diff --git a/core/src/eressea.c b/core/src/eressea.c new file mode 100755 index 000000000..d56fdfc9a --- /dev/null +++ b/core/src/eressea.c @@ -0,0 +1,86 @@ +#include +#include "settings.h" +#include "eressea.h" + +#include +#include + +#if MUSEUM_MODULE +#include +#endif +#if ARENA_MODULE +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void game_done(void) +{ +#ifdef CLEANUP_CODE + /* Diese Routine enfernt allen allokierten Speicher wieder. Das ist nur + * zum Debugging interessant, wenn man Leak Detection hat, und nach + * nicht freigegebenem Speicher sucht, der nicht bis zum Ende benötigt + * wird (temporäre Hilsstrukturen) */ + + free_game(); + + creport_cleanup(); +#ifdef REPORT_FORMAT_NR + report_cleanup(); +#endif + calendar_cleanup(); +#endif + kernel_done(); +} + +void game_init(void) +{ + kernel_init(); + register_triggers(); + register_xmas(); + + register_reports(); + register_nr(); + register_cr(); + register_xr(); + + register_names(); + register_resources(); + register_buildings(); + register_itemfunctions(); +#if DUNGEON_MODULE + register_dungeon(); +#endif +#if MUSEUM_MODULE + register_museum(); +#endif +#if ARENA_MODULE + register_arena(); +#endif + register_wormholes(); + + register_itemtypes(); + register_xmlreader(); + register_archetypes(); + enable_xml_gamecode(); + + register_attributes(); + register_gmcmd(); + +} diff --git a/core/src/eressea.h b/core/src/eressea.h new file mode 100755 index 000000000..3944bb090 --- /dev/null +++ b/core/src/eressea.h @@ -0,0 +1,13 @@ +#ifndef H_ERESSEA_LIB +#define H_ERESSEA_LIB +#ifdef __cplusplus +extern "C" { +#endif + + void game_init(void); + void game_done(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/archetype.c b/core/src/gamecode/archetype.c new file mode 100644 index 000000000..942e1eca4 --- /dev/null +++ b/core/src/gamecode/archetype.c @@ -0,0 +1,161 @@ +#include +#include +#include "archetype.h" + +/* kernel includes */ +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include + +/* libxml includes */ +#include +#include +#include + +/* libc includes */ +#include +#include + +static struct archetype *archetypes; + +struct attrib_type at_recruit = { + "recruit", NULL, NULL, NULL, NULL, NULL, ATF_UNIQUE +}; + +const struct archetype *find_archetype(const char *s, const struct locale *lang) +{ + void **tokens = get_translations(lang, UT_ARCHETYPES); + variant token; + + if (tokens && findtoken(*tokens, s, &token) == E_TOK_SUCCESS) { + return (const struct archetype *)token.v; + } + return NULL; +} + +void register_archetype(archetype * arch) +{ + arch->next = archetypes; + archetypes = arch; +} + +void init_archetypes(void) +{ + const struct locale *lang = locales; + for (; lang; lang = nextlocale(lang)) { + variant var; + archetype *arch = archetypes; + void *tokens = get_translations(lang, UT_ARCHETYPES); + for (; arch; arch = arch->next) { + const char *s1, *s2; + var.v = arch; + + s1 = LOC(lang, arch->name[0]); + addtoken(tokens, s1, var); + s2 = LOC(lang, arch->name[1]); + if (strcmp(s2, s1) != 0) { + addtoken(tokens, s2, var); + } + } + } +} + +static int parse_archetypes(xmlDocPtr doc) +{ + char zName[64]; + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr result = + xmlXPathEvalExpression(BAD_CAST "/eressea/archetypes/archetype", xpath); + xmlNodeSetPtr nodes = result->nodesetval; + + xmlChar *propValue; + if (nodes) { + int i; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + xmlNodePtr child; + archetype *arch = calloc(1, sizeof(archetype)); + xmlXPathObjectPtr sub; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + arch->name[0] = strdup((const char *)propValue); + sprintf(zName, "%s_p", arch->name[0]); + arch->name[1] = strdup(zName); + xmlFree(propValue); + + propValue = xmlGetProp(node, BAD_CAST "equip"); + if (propValue != NULL) { + arch->equip = get_equipment((const char *)propValue); + xmlFree(propValue); + } else { + arch->equip = get_equipment(arch->name[0]); + } + + propValue = xmlGetProp(node, BAD_CAST "building"); + if (propValue != NULL) { + arch->btype = bt_find((const char *)propValue); + xmlFree(propValue); + } + + arch->size = xml_ivalue(node, "cost", 0); + + for (child = node->children; child; child = child->next) { + if (strcmp((const char *)child->name, "function") == 0) { + xmlChar *propName = xmlGetProp(child, BAD_CAST "name"); + xmlChar *propValue = xmlGetProp(child, BAD_CAST "value"); + if (strcmp((const char *)propName, "create")) { + pf_generic foo = get_function((const char *)propValue); + arch->exec = (archetype_function) foo; + } + xmlFree(propValue); + xmlFree(propName); + } + } + xpath->node = node; + sub = xmlXPathEvalExpression(BAD_CAST "allow|deny", xpath); + if (sub->nodesetval && sub->nodesetval->nodeNr) { + int k; + arch->rules = calloc(sub->nodesetval->nodeNr + 1, sizeof(rule)); + for (k = 0; k != sub->nodesetval->nodeNr; ++k) { + xmlNodePtr rule = sub->nodesetval->nodeTab[k]; + arch->rules[k].allow = (rule->name[0] == 'a'); + + propValue = xmlGetProp(rule, BAD_CAST "property"); + arch->rules[k].property = strdup((const char *)propValue); + xmlFree(propValue); + + propValue = xmlGetProp(rule, BAD_CAST "value"); + arch->rules[k].value = strdup((const char *)propValue); + xmlFree(propValue); + } + } + xmlXPathFreeObject(sub); + + xpath->node = node; + sub = xmlXPathEvalExpression(BAD_CAST "construction", xpath); + if (sub->nodesetval) { + xml_readconstruction(xpath, sub->nodesetval, &arch->ctype); + } + xmlXPathFreeObject(sub); + register_archetype(arch); + } + } + xmlXPathFreeObject(result); + + xmlXPathFreeContext(xpath); + return 0; +} + +void register_archetypes(void) +{ + xml_register_callback(parse_archetypes); +} diff --git a/core/src/gamecode/archetype.h b/core/src/gamecode/archetype.h new file mode 100644 index 000000000..55c6896cc --- /dev/null +++ b/core/src/gamecode/archetype.h @@ -0,0 +1,52 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2007 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifndef H_GC_ARCHETYPE +#define H_GC_ARCHETYPE + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct rule { + bool allow; + char *property; + char *value; + } rule; + + struct archetype; + typedef int (*archetype_function) (struct unit * u, const struct archetype *, + int); + + typedef struct archetype { + struct archetype *next; + char *name[2]; + int size; + struct building_type *btype; + struct equipment *equip; + struct construction *ctype; + struct rule *rules; + archetype_function exec; + } archetype; + + extern const struct archetype *find_archetype(const char *s, + const struct locale *lang); + extern void init_archetypes(void); + extern void register_archetype(struct archetype *arch); + extern void register_archetypes(void); + + extern struct attrib_type at_recruit; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/creation.c b/core/src/gamecode/creation.c new file mode 100644 index 000000000..4f0eb9b98 --- /dev/null +++ b/core/src/gamecode/creation.c @@ -0,0 +1,72 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "creation.h" +#include "monster.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +faction *createmonsters(int no) +{ + faction *f = findfaction(no); + + if (f) { + puts("* Fehler! Die Monster Partei gibt es schon."); + return f; + } + f = (faction *) calloc(1, sizeof(faction)); + f->no = no; + /* alles ist auf null gesetzt, ausser dem folgenden. achtung - partei + * no 0 muss keine orders einreichen! */ + + f->email = strdup("monsters@eressea.de"); + f->name = strdup("Monster"); + f->alive = 1; + f->options = (char)(1 << O_REPORT); + addlist(&factions, f); + fhash(f); + return f; +} diff --git a/core/src/gamecode/creation.h b/core/src/gamecode/creation.h new file mode 100644 index 000000000..47146d303 --- /dev/null +++ b/core/src/gamecode/creation.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_GC_CREATION +#define H_GC_CREATION +#ifdef __cplusplus +extern "C" { +#endif + + struct faction *createmonsters(int no); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/creport.c b/core/src/gamecode/creport.c new file mode 100644 index 000000000..b7fe57358 --- /dev/null +++ b/core/src/gamecode/creport.c @@ -0,0 +1,1688 @@ +/* vi: set ts=2: ++-------------------+ Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel ++-------------------+ +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "creport.h" + +/* tweakable features */ +#define RENDER_CRMESSAGES +#define BUFFERSIZE 32768 +#define RESOURCECOMPAT + +/* modules include */ +#include + +/* attributes include */ +#include +#include +#include +#include +#include + +/* gamecode includes */ +#include "laws.h" +#include "economy.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include + +/* imports */ +extern int verbosity; +bool opt_cr_absolute_coords = false; + +/* globals */ +#define C_REPORT_VERSION 66 + +#define TAG_LOCALE "de" +#ifdef TAG_LOCALE +static const char *crtag(const char *key) +{ + static const struct locale *lang = NULL; + if (!lang) + lang = find_locale(TAG_LOCALE); + return locale_string(lang, key); +} +#else +#define crtag(x) (x) +#endif +/* + * translation table + */ +typedef struct translation { + struct translation *next; + char *key; + const char *value; +} translation; + +#define TRANSMAXHASH 257 +static translation *translation_table[TRANSMAXHASH]; +static translation *junkyard; + +static const char *add_translation(const char *key, const char *value) +{ + int kk = ((key[0] << 5) + key[0]) % TRANSMAXHASH; + translation *t = translation_table[kk]; + while (t && strcmp(t->key, key) != 0) + t = t->next; + if (!t) { + if (junkyard) { + t = junkyard; + junkyard = junkyard->next; + } else + t = malloc(sizeof(translation)); + t->key = strdup(key); + t->value = value; + t->next = translation_table[kk]; + translation_table[kk] = t; + } + return crtag(key); +} + +static void write_translations(FILE * F) +{ + int i; + fputs("TRANSLATION\n", F); + for (i = 0; i != TRANSMAXHASH; ++i) { + translation *t = translation_table[i]; + while (t) { + fprintf(F, "\"%s\";%s\n", t->value, crtag(t->key)); + t = t->next; + } + } +} + +static void reset_translations(void) +{ + int i; + for (i = 0; i != TRANSMAXHASH; ++i) { + translation *t = translation_table[i]; + while (t) { + translation *c = t->next; + free(t->key); + t->next = junkyard; + junkyard = t; + t = c; + } + translation_table[i] = 0; + } +} + +#include + +static void print_items(FILE * F, item * items, const struct locale *lang) +{ + item *itm; + + for (itm = items; itm; itm = itm->next) { + int in = itm->number; + const char *ic = resourcename(itm->type->rtype, 0); + if (itm == items) + fputs("GEGENSTAENDE\n", F); + fprintf(F, "%d;%s\n", in, add_translation(ic, LOC(lang, ic))); + } +} + +static void +cr_output_curses(FILE * F, const faction * viewer, const void *obj, objtype_t typ) +{ + bool header = false; + attrib *a = NULL; + int self = 0; + region *r; + + /* Die Sichtbarkeit eines Zaubers und die Zaubermeldung sind bei + * Gebäuden und Schiffen je nach, ob man Besitzer ist, verschieden. + * Bei Einheiten sieht man Wirkungen auf eigene Einheiten immer. + * Spezialfälle (besonderes Talent, verursachender Magier usw. werde + * bei jedem curse gesondert behandelt. */ + if (typ == TYP_SHIP) { + ship *sh = (ship *) obj; + unit *owner = ship_owner(sh); + a = sh->attribs; + r = sh->region; + if (owner != NULL) { + if (owner->faction == viewer) { + self = 2; + } else { /* steht eine person der Partei auf dem Schiff? */ + unit *u = NULL; + for (u = r->units; u; u = u->next) { + if (u->ship == sh) { + self = 1; + break; + } + } + } + } + } else if (typ == TYP_BUILDING) { + building *b = (building *) obj; + unit *owner = building_owner(b); + a = b->attribs; + r = b->region; + if (owner != NULL) { + if (owner->faction == viewer) { + self = 2; + } else { /* steht eine Person der Partei in der Burg? */ + unit *u = NULL; + for (u = r->units; u; u = u->next) { + if (u->building == b) { + self = 1; + break; + } + } + } + } + } else if (typ == TYP_UNIT) { + unit *u = (unit *) obj; + a = u->attribs; + r = u->region; + if (u->faction == viewer) { + self = 2; + } + } else if (typ == TYP_REGION) { + r = (region *) obj; + a = r->attribs; + } else { + /* fehler */ + } + + while (a) { + if (fval(a->type, ATF_CURSE)) { + curse *c = (curse *) a->data.v; + message *msg; + + if (c->type->cansee) { + self = c->type->cansee(viewer, obj, typ, c, self); + } + msg = msg_curse(c, obj, typ, self); + + if (msg) { + char buf[BUFFERSIZE]; + if (!header) { + header = 1; + fputs("EFFECTS\n", F); + } + nr_render(msg, viewer->locale, buf, sizeof(buf), viewer); + fprintf(F, "\"%s\"\n", buf); + msg_release(msg); + } + } else if (a->type == &at_effect && self) { + effect_data *data = (effect_data *) a->data.v; + if (data->value > 0) { + const char *key = resourcename(data->type->itype->rtype, 0); + if (!header) { + header = 1; + fputs("EFFECTS\n", F); + } + fprintf(F, "\"%d %s\"\n", data->value, add_translation(key, + locale_string(default_locale, key))); + } + } + a = a->next; + } +} + +static int cr_unit(variant var, char *buffer, const void *userdata) +{ + unit *u = (unit *) var.v; + sprintf(buffer, "%d", u ? u->no : -1); + return 0; +} + +static int cr_ship(variant var, char *buffer, const void *userdata) +{ + ship *u = (ship *) var.v; + sprintf(buffer, "%d", u ? u->no : -1); + return 0; +} + +static int cr_building(variant var, char *buffer, const void *userdata) +{ + building *u = (building *) var.v; + sprintf(buffer, "%d", u ? u->no : -1); + return 0; +} + +static int cr_faction(variant var, char *buffer, const void *userdata) +{ + faction *f = (faction *) var.v; + sprintf(buffer, "%d", f ? f->no : -1); + return 0; +} + +static int cr_region(variant var, char *buffer, const void *userdata) +{ + const faction *report = (const faction *)userdata; + region *r = (region *) var.v; + if (r) { + plane *pl = rplane(r); + int nx = r->x, ny = r->y; + pnormalize(&nx, &ny, pl); + adjust_coordinates(report, &nx, &ny, pl, r); + sprintf(buffer, "%d %d %d", nx, ny, plane_id(pl)); + return 0; + } + return -1; +} + +static int cr_resource(variant var, char *buffer, const void *userdata) +{ + const faction *report = (const faction *)userdata; + const resource_type *r = (const resource_type *)var.v; + if (r) { + const char *key = resourcename(r, 0); + sprintf(buffer, "\"%s\"", + add_translation(key, locale_string(report->locale, key))); + return 0; + } + return -1; +} + +static int cr_race(variant var, char *buffer, const void *userdata) +{ + const faction *report = (const faction *)userdata; + const struct race *rc = (const race *)var.v; + const char *key = rc_name(rc, 0); + sprintf(buffer, "\"%s\"", + add_translation(key, locale_string(report->locale, key))); + return 0; +} + +static int cr_alliance(variant var, char *buffer, const void *userdata) +{ + const alliance *al = (const alliance *)var.v; + if (al != NULL) { + sprintf(buffer, "%d", al->id); + } + unused(userdata); + return 0; +} + +static int cr_skill(variant var, char *buffer, const void *userdata) +{ + const faction *report = (const faction *)userdata; + skill_t sk = (skill_t) var.i; + if (sk != NOSKILL) + sprintf(buffer, "\"%s\"", + add_translation(mkname("skill", skillnames[sk]), skillname(sk, + report->locale))); + else + strcpy(buffer, "\"\""); + return 0; +} + +static int cr_order(variant var, char *buffer, const void *userdata) +{ + order *ord = (order *) var.v; + if (ord != NULL) { + char *wp = buffer; + char *cmd = getcommand(ord); + const char *rp = cmd; + + *wp++ = '\"'; + while (*rp) { + switch (*rp) { + case '\"': + case '\\': + *wp++ = '\\'; + default: + *wp++ = *rp++; + } + } + *wp++ = '\"'; + *wp++ = 0; + + free(cmd); + } else + strcpy(buffer, "\"\""); + return 0; +} + +static int cr_resources(variant var, char *buffer, const void *userdata) +{ + faction *f = (faction *) userdata; + resource *rlist = (resource *) var.v; + char *wp = buffer; + if (rlist != NULL) { + const char *name = resourcename(rlist->type, rlist->number != 1); + wp += + sprintf(wp, "\"%d %s", rlist->number, add_translation(name, LOC(f->locale, + name))); + for (;;) { + rlist = rlist->next; + if (rlist == NULL) + break; + name = resourcename(rlist->type, rlist->number != 1); + wp += + sprintf(wp, ", %d %s", rlist->number, add_translation(name, + LOC(f->locale, name))); + } + strcat(wp, "\""); + } + return 0; +} + +static int cr_regions(variant var, char *buffer, const void *userdata) +{ + faction *f = (faction *) userdata; + const arg_regions *rdata = (const arg_regions *)var.v; + + if (rdata != NULL && rdata->nregions > 0) { + region *r = rdata->regions[0]; + plane *pl = rplane(r); + int i, z = plane_id(pl); + char *wp = buffer; + int nx = r->x, ny = r->y; + + pnormalize(&nx, &ny, pl); + adjust_coordinates(f, &nx, &ny, pl, r); + wp += sprintf(wp, "\"%d %d %d", nx, ny, z); + for (i = 1; i != rdata->nregions; ++i) { + r = rdata->regions[i]; + pl = rplane(r); + z = plane_id(pl); + wp += sprintf(wp, ", %d %d %d", nx, ny, z); + } + strcat(wp, "\""); + } else { + strcpy(buffer, "\"\""); + } + return 0; +} + +static int cr_spell(variant var, char *buffer, const void *userdata) +{ + const faction *report = (const faction *)userdata; + spell *sp = (spell *) var.v; + if (sp != NULL) + sprintf(buffer, "\"%s\"", spell_name(sp, report->locale)); + else + strcpy(buffer, "\"\""); + return 0; +} + +static int cr_curse(variant var, char *buffer, const void *userdata) +{ + const faction *report = (const faction *)userdata; + const curse_type *ctype = (const curse_type *)var.v; + if (ctype != NULL) { + sprintf(buffer, "\"%s\"", curse_name(ctype, report->locale)); + } else + strcpy(buffer, "\"\""); + return 0; +} + +/*static int msgno; */ + +#define MTMAXHASH 1021 + +static struct known_mtype { + const struct message_type *mtype; + struct known_mtype *nexthash; +} *mtypehash[MTMAXHASH]; + +static void report_crtypes(FILE * F, const struct locale *lang) +{ + int i; + for (i = 0; i != MTMAXHASH; ++i) { + struct known_mtype *kmt; + for (kmt = mtypehash[i]; kmt; kmt = kmt->nexthash) { + const struct nrmessage_type *nrt = nrt_find(lang, kmt->mtype); + if (nrt) { + unsigned int hash = kmt->mtype->key; + fprintf(F, "MESSAGETYPE %d\n", hash); + fputc('\"', F); + fputs(escape_string(nrt_string(nrt), NULL, 0), F); + fputs("\";text\n", F); + fprintf(F, "\"%s\";section\n", nrt_section(nrt)); + } + } + while (mtypehash[i]) { + kmt = mtypehash[i]; + mtypehash[i] = mtypehash[i]->nexthash; + free(kmt); + } + } +} + +static unsigned int messagehash(const struct message *msg) +{ + variant var; + var.v = (void *)msg; + return (unsigned int)var.i; +} + +static void render_messages(FILE * F, faction * f, message_list * msgs) +{ + struct mlist *m = msgs->begin; + while (m) { + char crbuffer[BUFFERSIZE]; /* gross, wegen spionage-messages :-( */ + bool printed = false; + const struct message_type *mtype = m->msg->type; + unsigned int hash = mtype->key; +#ifdef RENDER_CRMESSAGES + char nrbuffer[1024 * 32]; + nrbuffer[0] = '\0'; + if (nr_render(m->msg, f->locale, nrbuffer, sizeof(nrbuffer), f) > 0) { + fprintf(F, "MESSAGE %u\n", messagehash(m->msg)); + fprintf(F, "%d;type\n", hash); + fwritestr(F, nrbuffer); + fputs(";rendered\n", F); + printed = true; + } +#endif + crbuffer[0] = '\0'; + if (cr_render(m->msg, crbuffer, (const void *)f) == 0) { + if (crbuffer[0]) { + if (!printed) { + fprintf(F, "MESSAGE %u\n", messagehash(m->msg)); + } + fputs(crbuffer, F); + } + } else { + log_error("could not render cr-message %p: %s\n", m->msg, m->msg->type->name); + } + if (printed) { + unsigned int ihash = hash % MTMAXHASH; + struct known_mtype *kmt = mtypehash[ihash]; + while (kmt && kmt->mtype != mtype) + kmt = kmt->nexthash; + if (kmt == NULL) { + kmt = (struct known_mtype *)malloc(sizeof(struct known_mtype)); + kmt->nexthash = mtypehash[ihash]; + kmt->mtype = mtype; + mtypehash[ihash] = kmt; + } + } + m = m->next; + } +} + +static void cr_output_messages(FILE * F, message_list * msgs, faction * f) +{ + if (msgs) + render_messages(F, f, msgs); +} + +/* prints a building */ +static void +cr_output_building(FILE * F, building * b, const unit * owner, int fno, + faction * f) +{ + const char *bname, *billusion; + + fprintf(F, "BURG %d\n", b->no); + + report_building(b, &bname, &billusion); + if (billusion) { + fprintf(F, "\"%s\";Typ\n", add_translation(billusion, LOC(f->locale, + billusion))); + if (owner && owner->faction == f) { + fprintf(F, "\"%s\";wahrerTyp\n", add_translation(bname, LOC(f->locale, + bname))); + } + } else { + fprintf(F, "\"%s\";Typ\n", add_translation(bname, LOC(f->locale, bname))); + } + fprintf(F, "\"%s\";Name\n", b->name); + if (b->display && b->display[0]) + fprintf(F, "\"%s\";Beschr\n", b->display); + if (b->size) + fprintf(F, "%d;Groesse\n", b->size); + if (owner) + fprintf(F, "%d;Besitzer\n", owner ? owner->no : -1); + if (fno >= 0) + fprintf(F, "%d;Partei\n", fno); + if (b->besieged) + fprintf(F, "%d;Belagerer\n", b->besieged); + cr_output_curses(F, f, b, TYP_BUILDING); +} + +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ + +/* prints a ship */ +static void +cr_output_ship(FILE * F, const ship * sh, const unit * u, int fcaptain, + const faction * f, const region * r) +{ + int w = 0; + assert(sh); + fprintf(F, "SCHIFF %d\n", sh->no); + fprintf(F, "\"%s\";Name\n", sh->name); + if (sh->display && sh->display[0]) + fprintf(F, "\"%s\";Beschr\n", sh->display); + fprintf(F, "\"%s\";Typ\n", add_translation(sh->type->name[0], + locale_string(f->locale, sh->type->name[0]))); + fprintf(F, "%d;Groesse\n", sh->size); + if (sh->damage) { + int percent = + (sh->damage * 100 + DAMAGE_SCALE - 1) / (sh->size * DAMAGE_SCALE); + fprintf(F, "%d;Schaden\n", percent); + } + if (u) + fprintf(F, "%d;Kapitaen\n", u ? u->no : -1); + if (fcaptain >= 0) + fprintf(F, "%d;Partei\n", fcaptain); + + /* calculate cargo */ + if (u && (u->faction == f || omniscient(f))) { + int n = 0, p = 0; + int mweight = shipcapacity(sh); + getshipweight(sh, &n, &p); + + fprintf(F, "%d;capacity\n", mweight); + fprintf(F, "%d;cargo\n", n); + fprintf(F, "%d;speed\n", shipspeed(sh, u)); + } + /* shore */ + w = NODIRECTION; + if (!fval(r->terrain, SEA_REGION)) + w = sh->coast; + if (w != NODIRECTION) + fprintf(F, "%d;Kueste\n", w); + + cr_output_curses(F, f, sh, TYP_SHIP); +} + +static void +fwriteorder(FILE * F, const struct order *ord, const struct locale *lang, + bool escape) +{ + char ebuf[1024]; + char obuf[1024]; + const char *str = obuf; + fputc('"', F); + write_order(ord, obuf, sizeof(obuf)); + if (escape) { + str = escape_string(obuf, ebuf, sizeof(ebuf)); + } + if (str[0]) + fputs(str, F); + fputc('"', F); +} + +static void cr_output_spells(FILE * F, const unit * u, int maxlevel) +{ + spellbook * book = unit_get_spellbook(u); + + if (book) { + const faction * f = u->faction; + quicklist *ql; + int qi, header = 0; + + for (ql = book->spells, qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry * sbe = (spellbook_entry *)ql_get(ql, qi); + if (sbe->level <= maxlevel) { + spell * sp = sbe->sp; + const char *name = add_translation(mkname("spell", sp->sname), spell_name(sp, f->locale)); + if (!header) { + fputs("SPRUECHE\n", F); + header = 1; + } + fprintf(F, "\"%s\"\n", name); + } + } + } +} + +/* prints all that belongs to a unit */ +static void cr_output_unit(FILE * F, const region * r, const faction * f, /* observers faction */ + const unit * u, int mode) +{ + /* Race attributes are always plural and item attributes always + * singular */ + const char *str; + const item_type *lasttype; + int pr; + item *itm, *show; + building *b; + const char *pzTmp; + skill *sv; + const attrib *a_fshidden = NULL; + bool itemcloak = false; + static const curse_type *itemcloak_ct = 0; + static bool init = false; + item result[MAX_INVENTORY]; + + if (fval(u_race(u), RCF_INVISIBLE)) + return; + + if (!init) { + init = true; + itemcloak_ct = ct_find("itemcloak"); + } + if (itemcloak_ct != NULL) { + itemcloak = curse_active(get_curse(u->attribs, itemcloak_ct)); + } + + assert(u && u->number); + + fprintf(F, "EINHEIT %d\n", u->no); + fprintf(F, "\"%s\";Name\n", u->name); + str = u_description(u, f->locale); + if (str) { + fprintf(F, "\"%s\";Beschr\n", str); + } + { + /* print faction information */ + const faction *sf = visible_faction(f, u); + const char *prefix = raceprefix(u); + if (u->faction == f || omniscient(f)) { + const attrib *a_otherfaction = a_find(u->attribs, &at_otherfaction); + const faction *otherfaction = + a_otherfaction ? get_otherfaction(a_otherfaction) : NULL; + /* my own faction, full info */ + const attrib *a = NULL; + unit *mage; + + if (fval(u, UFL_GROUP)) + a = a_find(u->attribs, &at_group); + if (a != NULL) { + const group *g = (const group *)a->data.v; + fprintf(F, "%d;gruppe\n", g->gid); + } + fprintf(F, "%d;Partei\n", u->faction->no); + if (sf != u->faction) + fprintf(F, "%d;Verkleidung\n", sf->no); + if (fval(u, UFL_ANON_FACTION)) + fprintf(F, "%d;Parteitarnung\n", i2b(fval(u, UFL_ANON_FACTION))); + if (otherfaction) { + if (otherfaction != u->faction) { + fprintf(F, "%d;Anderepartei\n", otherfaction->no); + } + } + mage = get_familiar_mage(u); + if (mage) { + fprintf(F, "%u;familiarmage\n", mage->no); + } + } else { + if (fval(u, UFL_ANON_FACTION)) { + /* faction info is hidden */ + fprintf(F, "%d;Parteitarnung\n", i2b(fval(u, UFL_ANON_FACTION))); + } else { + const attrib *a_otherfaction = a_find(u->attribs, &at_otherfaction); + const faction *otherfaction = + a_otherfaction ? get_otherfaction(a_otherfaction) : NULL; + /* other unit. show visible faction, not u->faction */ + fprintf(F, "%d;Partei\n", sf->no); + if (sf == f) { + fprintf(F, "1;Verraeter\n"); + } + if (a_otherfaction) { + if (otherfaction != u->faction) { + if (alliedunit(u, f, HELP_FSTEALTH)) { + fprintf(F, "%d;Anderepartei\n", otherfaction->no); + } + } + } + } + } + if (prefix) { + prefix = mkname("prefix", prefix); + fprintf(F, "\"%s\";typprefix\n", add_translation(prefix, LOC(f->locale, + prefix))); + } + } + if (u->faction != f && a_fshidden + && a_fshidden->data.ca[0] == 1 && effskill(u, SK_STEALTH) >= 6) { + fprintf(F, "-1;Anzahl\n"); + } else { + fprintf(F, "%d;Anzahl\n", u->number); + } + + pzTmp = get_racename(u->attribs); + if (pzTmp) { + fprintf(F, "\"%s\";Typ\n", pzTmp); + if (u->faction == f && fval(u_race(u), RCF_SHAPESHIFTANY)) { + const char *zRace = rc_name(u_race(u), 1); + fprintf(F, "\"%s\";wahrerTyp\n", + add_translation(zRace, locale_string(f->locale, zRace))); + } + } else { + const race *irace = u_irace(u); + const char *zRace = rc_name(irace, 1); + fprintf(F, "\"%s\";Typ\n", + add_translation(zRace, locale_string(f->locale, zRace))); + if (u->faction == f && irace != u_race(u)) { + assert(skill_enabled[SK_STEALTH] + || !"we're resetting this on load, so.. ircase should never be used"); + zRace = rc_name(u_race(u), 1); + fprintf(F, "\"%s\";wahrerTyp\n", + add_translation(zRace, locale_string(f->locale, zRace))); + } + } + + if (u->building) { + assert(u->building->region); + fprintf(F, "%d;Burg\n", u->building->no); + } + if (u->ship) { + assert(u->ship->region); + fprintf(F, "%d;Schiff\n", u->ship->no); + } + if (is_guard(u, GUARD_ALL) != 0) { + fprintf(F, "%d;bewacht\n", 1); + } + if ((b = usiege(u)) != NULL) { + fprintf(F, "%d;belagert\n", b->no); + } + /* additional information for own units */ + if (u->faction == f || omniscient(f)) { + order *ord; + const char *xc; + const char *c; + int i; + sc_mage *mage; + + i = ualias(u); + if (i > 0) + fprintf(F, "%d;temp\n", i); + else if (i < 0) + fprintf(F, "%d;alias\n", -i); + i = get_money(u); + fprintf(F, "%d;Kampfstatus\n", u->status); + fprintf(F, "%d;weight\n", weight(u)); + if (fval(u, UFL_NOAID)) { + fputs("1;unaided\n", F); + } + if (fval(u, UFL_STEALTH)) { + i = u_geteffstealth(u); + if (i >= 0) { + fprintf(F, "%d;Tarnung\n", i); + } + } + xc = uprivate(u); + if (xc) { + fprintf(F, "\"%s\";privat\n", xc); + } + c = hp_status(u); + if (c && *c && (u->faction == f || omniscient(f))) { + fprintf(F, "\"%s\";hp\n", add_translation(c, + locale_string(u->faction->locale, c))); + } + if (fval(u, UFL_HERO)) { + fputs("1;hero\n", F); + } + + if (fval(u, UFL_HUNGER) && (u->faction == f)) { + fputs("1;hunger\n", F); + } + if (is_mage(u)) { + fprintf(F, "%d;Aura\n", get_spellpoints(u)); + fprintf(F, "%d;Auramax\n", max_spellpoints(u->region, u)); + } + /* default commands */ + fprintf(F, "COMMANDS\n"); + for (ord = u->old_orders; ord; ord = ord->next) { + /* this new order will replace the old defaults */ + if (is_persistent(ord)) { + fwriteorder(F, ord, f->locale, true); + fputc('\n', F); + } + } + for (ord = u->orders; ord; ord = ord->next) { + if (u->old_orders && is_repeated(ord)) + continue; /* unit has defaults */ + if (is_persistent(ord)) { + fwriteorder(F, ord, f->locale, true); + fputc('\n', F); + } + } + + /* talents */ + pr = 0; + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level > 0) { + skill_t sk = sv->id; + int esk = eff_skill(u, sk, r); + if (!pr) { + pr = 1; + fprintf(F, "TALENTE\n"); + } + fprintf(F, "%d %d;%s\n", u->number * level_days(sv->level), esk, + add_translation(mkname("skill", skillnames[sk]), skillname(sk, + f->locale))); + } + } + + /* spells that this unit can cast */ + mage = get_mage(u); + if (mage) { + int i, maxlevel = effskill(u, SK_MAGIC); + cr_output_spells(F, u, maxlevel); + + for (i = 0; i != MAXCOMBATSPELLS; ++i) { + const spell *sp = mage->combatspells[i].sp; + if (sp) { + const char *name = + add_translation(mkname("spell", sp->sname), spell_name(sp, + f->locale)); + fprintf(F, "KAMPFZAUBER %d\n", i); + fprintf(F, "\"%s\";name\n", name); + fprintf(F, "%d;level\n", mage->combatspells[i].level); + } + } + } + } + /* items */ + pr = 0; + if (f == u->faction || omniscient(f)) { + show = u->items; + } else if (!itemcloak && mode >= see_unit && !(a_fshidden + && a_fshidden->data.ca[1] == 1 && effskill(u, SK_STEALTH) >= 3)) { + int n = report_items(u->items, result, MAX_INVENTORY, u, f); + assert(n >= 0); + if (n > 0) + show = result; + else + show = NULL; + } else { + show = NULL; + } + lasttype = NULL; + for (itm = show; itm; itm = itm->next) { + const char *ic; + int in; + assert(itm->type != lasttype + || !"error: list contains two objects of the same item"); + report_item(u, itm, f, NULL, &ic, &in, true); + if (in == 0) + continue; + if (!pr) { + pr = 1; + fputs("GEGENSTAENDE\n", F); + } + fprintf(F, "%d;%s\n", in, add_translation(ic, locale_string(f->locale, + ic))); + } + + cr_output_curses(F, f, u, TYP_UNIT); +} + +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ + +/* prints allies */ +static void show_allies_cr(FILE * F, const faction * f, const ally * sf) +{ + for (; sf; sf = sf->next) + if (sf->faction) { + int mode = alliedgroup(NULL, f, sf->faction, sf, HELP_ALL); + if (mode != 0 && sf->status > 0) { + fprintf(F, "ALLIANZ %d\n", sf->faction->no); + fprintf(F, "\"%s\";Parteiname\n", sf->faction->name); + fprintf(F, "%d;Status\n", sf->status & HELP_ALL); + } + } +} + +/* prints allies */ +static void show_alliances_cr(FILE * F, const faction * f) +{ + alliance *al = f_get_alliance(f); + if (al) { + faction *lead = alliance_get_leader(al); + assert(lead); + fprintf(F, "ALLIANCE %d\n", al->id); + fprintf(F, "\"%s\";name\n", al->name); + fprintf(F, "%d;leader\n", lead->no); + } +} + +/* prints all visible spells in a region */ +static void show_active_spells(const region * r) +{ + char fogwall[MAXDIRECTIONS]; +#ifdef TODO /* alte Regionszauberanzeigen umstellen */ + unit *u; + int env = 0; +#endif + memset(fogwall, 0, sizeof(char) * MAXDIRECTIONS); + +} + +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ + +/* this is a copy of laws.c->find_address output changed. */ +static void cr_find_address(FILE * F, const faction * uf, quicklist * addresses) +{ + int i = 0; + quicklist *flist = addresses; + while (flist) { + const faction *f = (const faction *)ql_get(flist, i); + if (uf != f) { + fprintf(F, "PARTEI %d\n", f->no); + fprintf(F, "\"%s\";Parteiname\n", f->name); + if (f->email) + fprintf(F, "\"%s\";email\n", f->email); + if (f->banner) + fprintf(F, "\"%s\";banner\n", f->banner); + fprintf(F, "\"%s\";locale\n", locale_name(f->locale)); + if (f->alliance && f->alliance == uf->alliance) { + fprintf(F, "%d;alliance\n", f->alliance->id); + } + } + ql_advance(&flist, &i, 1); + } +} + +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ + +static void cr_reportspell(FILE * F, spell * sp, int level, const struct locale *lang) +{ + int k; + const char *name = + add_translation(mkname("spell", sp->sname), spell_name(sp, lang)); + + fprintf(F, "ZAUBER %d\n", hashstring(sp->sname)); + fprintf(F, "\"%s\";name\n", name); + fprintf(F, "%d;level\n", level); + fprintf(F, "%d;rank\n", sp->rank); + fprintf(F, "\"%s\";info\n", spell_info(sp, lang)); + if (sp->parameter) + fprintf(F, "\"%s\";syntax\n", sp->parameter); + else + fputs("\"\";syntax\n", F); + + if (sp->sptyp & PRECOMBATSPELL) + fputs("\"precombat\";class\n", F); + else if (sp->sptyp & COMBATSPELL) + fputs("\"combat\";class\n", F); + else if (sp->sptyp & POSTCOMBATSPELL) + fputs("\"postcombat\";class\n", F); + else + fputs("\"normal\";class\n", F); + + if (sp->sptyp & FARCASTING) + fputs("1;far\n", F); + if (sp->sptyp & OCEANCASTABLE) + fputs("1;ocean\n", F); + if (sp->sptyp & ONSHIPCAST) + fputs("1;ship\n", F); + if (!(sp->sptyp & NOTFAMILIARCAST)) + fputs("1;familiar\n", F); + fputs("KOMPONENTEN\n", F); + + for (k = 0; sp->components[k].type; ++k) { + const resource_type *rtype = sp->components[k].type; + int itemanz = sp->components[k].amount; + int costtyp = sp->components[k].cost; + if (itemanz > 0) { + const char *name = resourcename(rtype, 0); + fprintf(F, "%d %d;%s\n", itemanz, costtyp == SPC_LEVEL + || costtyp == SPC_LINEAR, add_translation(name, LOC(lang, name))); + } + } +} + +static char *cr_output_resource(char *buf, const char *name, + const struct locale *loc, int amount, int level) +{ + buf += sprintf(buf, "RESOURCE %u\n", hashstring(name)); + buf += sprintf(buf, "\"%s\";type\n", add_translation(name, LOC(loc, name))); + if (amount >= 0) { + if (level >= 0) + buf += sprintf(buf, "%d;skill\n", level); + buf += sprintf(buf, "%d;number\n", amount); + } + return buf; +} + +static void +cr_borders(seen_region ** seen, const region * r, const faction * f, + int seemode, FILE * F) +{ + direction_t d; + int g = 0; + for (d = 0; d != MAXDIRECTIONS; d++) { /* Nachbarregionen, die gesehen werden, ermitteln */ + const region *r2 = rconnect(r, d); + const connection *b; + if (!r2) + continue; + if (seemode == see_neighbour) { + seen_region *sr = find_seen(seen, r2); + if (sr == NULL || sr->mode <= see_neighbour) + continue; + } + b = get_borders(r, r2); + while (b) { + bool cs = b->type->fvisible(b, f, r); + + if (!cs) { + cs = b->type->rvisible(b, r); + if (!cs) { + unit *us = r->units; + while (us && !cs) { + if (us->faction == f) { + cs = b->type->uvisible(b, us); + if (cs) + break; + } + us = us->next; + } + } + } + if (cs) { + const char *bname = mkname("border", b->type->name(b, r, f, GF_PURE)); + fprintf(F, "GRENZE %d\n", ++g); + fprintf(F, "\"%s\";typ\n", LOC(default_locale, bname)); + fprintf(F, "%d;richtung\n", d); + if (!b->type->transparent(b, f)) + fputs("1;opaque\n", F); + /* hack: */ + if (b->type == &bt_road && r->terrain->max_road) { + int p = rroad(r, d) * 100 / r->terrain->max_road; + fprintf(F, "%d;prozent\n", p); + } + } + b = b->next; + } + } +} + +static void +cr_output_resources(FILE * F, report_context * ctx, seen_region * sr) +{ + char cbuf[BUFFERSIZE], *pos = cbuf; + region *r = sr->r; + faction *f = ctx->f; + resource_report result[MAX_RAWMATERIALS]; + int n, size = report_resources(sr, result, MAX_RAWMATERIALS, f); + +#ifdef RESOURCECOMPAT + int trees = rtrees(r, 2); + int saplings = rtrees(r, 1); + + if (trees > 0) + fprintf(F, "%d;Baeume\n", trees); + if (saplings > 0) + fprintf(F, "%d;Schoesslinge\n", saplings); + if (fval(r, RF_MALLORN) && (trees > 0 || saplings > 0)) + fprintf(F, "1;Mallorn\n"); + for (n = 0; n < size; ++n) { + if (result[n].level >= 0 && result[n].number >= 0) { + fprintf(F, "%d;%s\n", result[n].number, crtag(result[n].name)); + } + } +#endif + + for (n = 0; n < size; ++n) { + if (result[n].number >= 0) { + pos = + cr_output_resource(pos, result[n].name, f->locale, result[n].number, + result[n].level); + } + } + if (pos != cbuf) + fputs(cbuf, F); +} + +static void +cr_region_header(FILE * F, int plid, int nx, int ny, unsigned int uid) +{ + if (plid == 0) { + fprintf(F, "REGION %d %d\n", nx, ny); + } else { + fprintf(F, "REGION %d %d %d\n", nx, ny, plid); + } + if (uid) + fprintf(F, "%d;id\n", uid); +} + +static void cr_output_region(FILE * F, report_context * ctx, seen_region * sr) +{ + faction *f = ctx->f; + region *r = sr->r; + plane *pl = rplane(r); + int plid = plane_id(pl), nx, ny; + const char *tname; + int oc[4][2], o = 0; + int uid = r->uid; + +#ifdef OCEAN_NEIGHBORS_GET_NO_ID + if (sr->mode <= see_neighbour && !r->land) { + uid = 0; + } +#endif + + if (opt_cr_absolute_coords) { + nx = r->x; + ny = r->y; + } else { + nx = r->x, ny = r->y; + pnormalize(&nx, &ny, pl); + adjust_coordinates(f, &nx, &ny, pl, r); + } + + if (pl) { + if (ny == pl->maxy) { + oc[o][0] = nx; + oc[o++][1] = pl->miny - 1; + } + if (nx == pl->maxx) { + oc[o][0] = pl->minx - 1; + oc[o++][1] = ny; + } + if (ny == pl->miny) { + oc[o][0] = nx; + oc[o++][1] = pl->maxy + 1; + } + if (nx == pl->minx) { + oc[o][0] = pl->maxx + 1; + oc[o++][1] = ny; + } + } + while (o--) { + cr_region_header(F, plid, oc[o][0], oc[o][1], uid); + fputs("\"wrap\";visibility\n", F); + } + + cr_region_header(F, plid, nx, ny, uid); + + if (r->land) { + const char *str = rname(r, f->locale); + if (str && str[0]) { + fprintf(F, "\"%s\";Name\n", str); + } + } + tname = terrain_name(r); + + fprintf(F, "\"%s\";Terrain\n", add_translation(tname, locale_string(f->locale, + tname))); + if (sr->mode != see_unit) + fprintf(F, "\"%s\";visibility\n", visibility[sr->mode]); + if (sr->mode == see_neighbour) { + cr_borders(ctx->seen, r, f, sr->mode, F); + } else { + building *b; + ship *sh; + unit *u; + int stealthmod = stealth_modifier(sr->mode); + + if (r->display && r->display[0]) + fprintf(F, "\"%s\";Beschr\n", r->display); + if (fval(r->terrain, LAND_REGION)) { + fprintf(F, "%d;Bauern\n", rpeasants(r)); + if (fval(r, RF_ORCIFIED)) { + fprintf(F, "1;Verorkt\n"); + } + fprintf(F, "%d;Pferde\n", rhorses(r)); + + if (sr->mode >= see_unit) { + if (rule_region_owners()) { + faction *owner = region_get_owner(r); + if (owner) { + fprintf(F, "%d;owner\n", owner->no); + } + } + fprintf(F, "%d;Silber\n", rmoney(r)); + if (skill_enabled[SK_ENTERTAINMENT]) { + fprintf(F, "%d;Unterh\n", entertainmoney(r)); + } + if (is_cursed(r->attribs, C_RIOT, 0)) { + fputs("0;Rekruten\n", F); + } else { + fprintf(F, "%d;Rekruten\n", rpeasants(r) / RECRUITFRACTION); + } + if (production(r)) { + int p_wage = wage(r, NULL, NULL, turn + 1); + fprintf(F, "%d;Lohn\n", p_wage); + if (is_mourning(r, turn + 1)) { + fputs("1;mourning\n", F); + } + } + if (r->land->ownership) { + fprintf(F, "%d;morale\n", r->land->morale); + } + } + + /* this writes both some tags (RESOURCECOMPAT) and a block. + * must not write any blocks before it */ + cr_output_resources(F, ctx, sr); + + if (sr->mode >= see_unit) { + /* trade */ + if (markets_module() && r->land) { + const item_type *lux = r_luxury(r); + const item_type *herb = r->land->herbtype; + if (lux || herb) { + fputs("PREISE\n", F); + if (lux) { + const char *ch = resourcename(lux->rtype, 0); + fprintf(F, "%d;%s\n", 1, add_translation(ch, + locale_string(f->locale, ch))); + } + if (herb) { + const char *ch = resourcename(herb->rtype, 0); + fprintf(F, "%d;%s\n", 1, add_translation(ch, + locale_string(f->locale, ch))); + } + } + } else if (rpeasants(r) / TRADE_FRACTION > 0) { + struct demand *dmd = r->land->demands; + fputs("PREISE\n", F); + while (dmd) { + const char *ch = resourcename(dmd->type->itype->rtype, 0); + fprintf(F, "%d;%s\n", (dmd->value + ? dmd->value * dmd->type->price + : -dmd->type->price), + add_translation(ch, locale_string(f->locale, ch))); + dmd = dmd->next; + } + } + } + } + if (r->land) { + print_items(F, r->land->items, f->locale); + } + cr_output_curses(F, f, r, TYP_REGION); + cr_borders(ctx->seen, r, f, sr->mode, F); + if (sr->mode == see_unit && is_astral(r) + && !is_cursed(r->attribs, C_ASTRALBLOCK, 0)) { + /* Sonderbehandlung Teleport-Ebene */ + region_list *rl = astralregions(r, inhabitable); + + if (rl) { + region_list *rl2 = rl; + while (rl2) { + region *r = rl2->data; + int nx = r->x, ny = r->y; + plane *plx = rplane(r); + + pnormalize(&nx, &ny, plx); + adjust_coordinates(f, &nx, &ny, plx, r); + fprintf(F, "SCHEMEN %d %d\n", nx, ny); + fprintf(F, "\"%s\";Name\n", rname(r, f->locale)); + rl2 = rl2->next; + } + free_regionlist(rl); + } + } + + /* describe both passed and inhabited regions */ + show_active_spells(r); + if (fval(r, RF_TRAVELUNIT)) { + bool seeunits = false, seeships = false; + const attrib *ru; + /* show units pulled through region */ + for (ru = a_find(r->attribs, &at_travelunit); + ru && ru->type == &at_travelunit; ru = ru->next) { + unit *u = (unit *) ru->data.v; + if (cansee_durchgezogen(f, r, u, 0) && r != u->region) { + if (u->ship && ship_owner(u->ship)==u) { + if (!seeships) { + fprintf(F, "DURCHSCHIFFUNG\n"); + } + seeships = true; + fprintf(F, "\"%s\"\n", shipname(u->ship)); + } + } + } + for (ru = a_find(r->attribs, &at_travelunit); + ru && ru->type == &at_travelunit; ru = ru->next) { + unit *u = (unit *) ru->data.v; + if (cansee_durchgezogen(f, r, u, 0) && r != u->region) { + if (!u->ship) { + if (!seeunits) { + fprintf(F, "DURCHREISE\n"); + } + seeunits = true; + fprintf(F, "\"%s\"\n", unitname(u)); + } + } + } + } + cr_output_messages(F, r->msgs, f); + { + message_list *mlist = r_getmessages(r, f); + if (mlist) + cr_output_messages(F, mlist, f); + } + /* buildings */ + for (b = rbuildings(r); b; b = b->next) { + int fno = -1; + u = building_owner(b); + if (u && !fval(u, UFL_ANON_FACTION)) { + const faction *sf = visible_faction(f, u); + fno = sf->no; + } + cr_output_building(F, b, u, fno, f); + } + + /* ships */ + for (sh = r->ships; sh; sh = sh->next) { + int fno = -1; + u = ship_owner(sh); + if (u && !fval(u, UFL_ANON_FACTION)) { + const faction *sf = visible_faction(f, u); + fno = sf->no; + } + + cr_output_ship(F, sh, u, fno, f, r); + } + + /* visible units */ + for (u = r->units; u; u = u->next) { + + if (u->building || u->ship || (stealthmod > INT_MIN + && cansee(f, r, u, stealthmod))) { + cr_output_unit(F, r, f, u, sr->mode); + } + } + } +} + +/* main function of the creport. creates the header and traverses all regions */ +static int +report_computer(const char *filename, report_context * ctx, const char *charset) +{ + static int era = -1; + int i; + faction *f = ctx->f; + const char *prefix; + region *r; + const char *mailto = locale_string(f->locale, "mailto"); + const attrib *a; + seen_region *sr = NULL; +#if SCORE_MODULE + int score = 0, avgscore = 0; +#endif + int enc = xmlParseCharEncoding(charset); + FILE *F = fopen(filename, "wt"); + + if (era < 0) { + era = get_param_int(global.parameters, "world.era", 2); + } + if (F == NULL) { + perror(filename); + return -1; + } else if (enc == XML_CHAR_ENCODING_UTF8) { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + fwrite(utf8_bom, 1, 3, F); + } + + /* must call this to get all the neighbour regions */ + /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ + /* initialisations, header and lists */ + + fprintf(F, "VERSION %d\n", C_REPORT_VERSION); + fprintf(F, "\"%s\";charset\n", charset); + fprintf(F, "\"%s\";locale\n", locale_name(f->locale)); + fprintf(F, "%d;noskillpoints\n", 1); + fprintf(F, "%ld;date\n", ctx->report_time); + fprintf(F, "\"%s\";Spiel\n", global.gamename); + fprintf(F, "\"%s\";Konfiguration\n", "Standard"); + fprintf(F, "\"%s\";Koordinaten\n", "Hex"); + fprintf(F, "%d;Basis\n", 36); + fprintf(F, "%d;Runde\n", turn); + fprintf(F, "%d;Zeitalter\n", era); + if (mailto != NULL) { + fprintf(F, "\"%s\";mailto\n", mailto); + fprintf(F, "\"%s\";mailcmd\n", locale_string(f->locale, "mailcmd")); + } + + show_alliances_cr(F, f); + + fprintf(F, "PARTEI %d\n", f->no); + fprintf(F, "\"%s\";locale\n", locale_name(f->locale)); + if (f_get_alliance(f)) { + fprintf(F, "%d;alliance\n", f->alliance->id); + fprintf(F, "%d;joined\n", f->alliance_joindate); + } + fprintf(F, "%d;age\n", f->age); + fprintf(F, "%d;Optionen\n", f->options); +#if SCORE_MODULE + if (f->options & want(O_SCORE) && f->age > DISPLAYSCORE) { + score = f->score; + avgscore = average_score_of_age(f->age, f->age / 24 + 1); + } + fprintf(F, "%d;Punkte\n", score); + fprintf(F, "%d;Punktedurchschnitt\n", avgscore); +#endif + { + const char *zRace = rc_name(f->race, 1); + fprintf(F, "\"%s\";Typ\n", add_translation(zRace, LOC(f->locale, zRace))); + } + prefix = get_prefix(f->attribs); + if (prefix != NULL) { + prefix = mkname("prefix", prefix); + fprintf(F, "\"%s\";typprefix\n", + add_translation(prefix, LOC(f->locale, prefix))); + } + fprintf(F, "%d;Rekrutierungskosten\n", f->race->recruitcost); + fprintf(F, "%d;Anzahl Personen\n", count_all(f)); + fprintf(F, "\"%s\";Magiegebiet\n", magic_school[f->magiegebiet]); + + if (f->race == new_race[RC_HUMAN]) { + fprintf(F, "%d;Anzahl Immigranten\n", count_migrants(f)); + fprintf(F, "%d;Max. Immigranten\n", count_maxmigrants(f)); + } + + i = countheroes(f); + if (i > 0) + fprintf(F, "%d;heroes\n", i); + i = maxheroes(f); + if (i > 0) + fprintf(F, "%d;max_heroes\n", i); + + if (f->age > 1 && f->lastorders != turn) { + fprintf(F, "%d;nmr\n", turn - f->lastorders); + } + + fprintf(F, "\"%s\";Parteiname\n", f->name); + fprintf(F, "\"%s\";email\n", f->email); + if (f->banner) + fprintf(F, "\"%s\";banner\n", f->banner); + print_items(F, f->items, f->locale); + fputs("OPTIONEN\n", F); + for (i = 0; i != MAXOPTIONS; ++i) { + int flag = want(i); + if (options[i]) { + fprintf(F, "%d;%s\n", (f->options & flag) ? 1 : 0, options[i]); + } else if (f->options & flag) { + f->options &= (~flag); + } + } + show_allies_cr(F, f, f->allies); + { + group *g; + for (g = f->groups; g; g = g->next) { + + fprintf(F, "GRUPPE %d\n", g->gid); + fprintf(F, "\"%s\";name\n", g->name); + prefix = get_prefix(g->attribs); + if (prefix != NULL) { + prefix = mkname("prefix", prefix); + fprintf(F, "\"%s\";typprefix\n", + add_translation(prefix, LOC(f->locale, prefix))); + } + show_allies_cr(F, f, g->allies); + } + } + + cr_output_messages(F, f->msgs, f); + { + struct bmsg *bm; + for (bm = f->battles; bm; bm = bm->next) { + plane *pl = rplane(bm->r); + int plid = plane_id(pl); + region *r = bm->r; + int nx = r->x, ny = r->y; + + pnormalize(&nx, &ny, pl); + adjust_coordinates(f, &nx, &ny, pl, r); + if (!plid) + fprintf(F, "BATTLE %d %d\n", nx, ny); + else { + fprintf(F, "BATTLE %d %d %d\n", nx, ny, plid); + } + cr_output_messages(F, bm->msgs, f); + } + } + + cr_find_address(F, f, ctx->addresses); + a = a_find(f->attribs, &at_reportspell); + while (a && a->type == &at_reportspell) { + spellbook_entry *sbe = (spellbook_entry *) a->data.v; + cr_reportspell(F, sbe->sp, sbe->level, f->locale); + a = a->next; + } + for (a = a_find(f->attribs, &at_showitem); a && a->type == &at_showitem; + a = a->next) { + const potion_type *ptype = + resource2potion(((const item_type *)a->data.v)->rtype); + const char *ch; + const char *description = NULL; + + if (ptype == NULL) + continue; + ch = resourcename(ptype->itype->rtype, 0); + fprintf(F, "TRANK %d\n", hashstring(ch)); + fprintf(F, "\"%s\";Name\n", add_translation(ch, locale_string(f->locale, + ch))); + fprintf(F, "%d;Stufe\n", ptype->level); + + if (description == NULL) { + const char *pname = resourcename(ptype->itype->rtype, 0); + const char *potiontext = mkname("potion", pname); + description = LOC(f->locale, potiontext); + } + + fprintf(F, "\"%s\";Beschr\n", description); + if (ptype->itype->construction) { + requirement *m = ptype->itype->construction->materials; + + fprintf(F, "ZUTATEN\n"); + + while (m->number) { + ch = resourcename(m->rtype, 0); + fprintf(F, "\"%s\"\n", add_translation(ch, locale_string(f->locale, + ch))); + m++; + } + } + } + + /* traverse all regions */ + for (r = ctx->first; sr == NULL && r != ctx->last; r = r->next) { + sr = find_seen(ctx->seen, r); + } + for (; sr != NULL; sr = sr->next) { + cr_output_region(F, ctx, sr); + } + report_crtypes(F, f->locale); + write_translations(F); + reset_translations(); + fclose(F); + return 0; +} + +int crwritemap(const char *filename) +{ + FILE *F = fopen(filename, "w"); + region *r; + + fprintf(F, "VERSION %d\n", C_REPORT_VERSION); + fputs("\"UTF-8\";charset\n", F); + + for (r = regions; r; r = r->next) { + plane *pl = rplane(r); + int plid = plane_id(pl); + if (plid) { + fprintf(F, "REGION %d %d %d\n", r->x, r->y, plid); + } else { + fprintf(F, "REGION %d %d\n", r->x, r->y); + } + fprintf(F, "\"%s\";Name\n\"%s\";Terrain\n", rname(r, default_locale), + LOC(default_locale, terrain_name(r))); + } + fclose(F); + return 0; +} + +void register_cr(void) +{ + tsf_register("report", &cr_ignore); + tsf_register("string", &cr_string); + tsf_register("order", &cr_order); + tsf_register("spell", &cr_spell); + tsf_register("curse", &cr_curse); + tsf_register("int", &cr_int); + tsf_register("unit", &cr_unit); + tsf_register("region", &cr_region); + tsf_register("faction", &cr_faction); + tsf_register("ship", &cr_ship); + tsf_register("building", &cr_building); + tsf_register("skill", &cr_skill); + tsf_register("resource", &cr_resource); + tsf_register("race", &cr_race); + tsf_register("direction", &cr_int); + tsf_register("alliance", &cr_alliance); + tsf_register("resources", &cr_resources); + tsf_register("items", &cr_resources); + tsf_register("regions", &cr_regions); + + if (!nocr) + register_reporttype("cr", &report_computer, 1 << O_COMPUTER); +} + +void creport_cleanup(void) +{ + while (junkyard) { + translation *t = junkyard; + junkyard = junkyard->next; + free(t); + } + junkyard = 0; +} diff --git a/core/src/gamecode/creport.h b/core/src/gamecode/creport.h new file mode 100644 index 000000000..1f3145b4f --- /dev/null +++ b/core/src/gamecode/creport.h @@ -0,0 +1,28 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#ifndef H_GC_CREPORT +#define H_GC_CREPORT +#ifdef __cplusplus +extern "C" { +#endif + +#include + + extern void creport_cleanup(void); + extern void register_cr(void); + + extern int crwritemap(const char *filename); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/economy.c b/core/src/gamecode/economy.c new file mode 100644 index 000000000..236e4fe1c --- /dev/null +++ b/core/src/gamecode/economy.c @@ -0,0 +1,3512 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#pragma region includes + +#include +#include +#include +#include "economy.h" + +/* gamecode includes */ +#include "archetype.h" +#include "give.h" +#include "laws.h" +#include "randenc.h" +#include "spy.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* libs includes */ +#include +#include +#include +#include +#include + +#pragma endregion + +typedef struct request { + struct request *next; + struct unit *unit; + struct order *ord; + int qty; + int no; + union { + bool goblin; /* stealing */ + const struct luxury_type *ltype; /* trading */ + } type; +} request; + +static int working; + +static request entertainers[1024]; +static request *nextentertainer; +static int entertaining; + +static int norders; +static request *oa; + +#define RECRUIT_MERGE 1 +#define RECRUIT_CLASSIC 2 +#define RECRUIT_ARCHETYPES 4 +static int rules_recruit = -1; + +static void recruit_init(void) +{ + if (rules_recruit < 0) { + rules_recruit = 0; + if (get_param_int(global.parameters, "recruit.allow_merge", 1)) { + rules_recruit |= RECRUIT_MERGE; + } + if (get_param_int(global.parameters, "recruit.classic", 1)) { + rules_recruit |= RECRUIT_CLASSIC; + } + if (get_param_int(global.parameters, "recruit.archetype", 0)) { + rules_recruit |= RECRUIT_ARCHETYPES; + } + } +} + +int income(const unit * u) +{ + switch (old_race(u_race(u))) { + case RC_FIREDRAGON: + return 150 * u->number; + case RC_DRAGON: + return 1000 * u->number; + case RC_WYRM: + return 5000 * u->number; + default: + return 20 * u->number; + } +} + +static void scramble(void *data, int n, size_t width) +{ + int j; + char temp[64]; + assert(width <= sizeof(temp)); + for (j = 0; j != n; ++j) { + int k = rng_int() % n; + if (k == j) + continue; + memcpy(temp, (char *)data + j * width, width); + memcpy((char *)data + j * width, (char *)data + k * width, width); + memcpy((char *)data + k * width, temp, width); + } +} + +static void expandorders(region * r, request * requests) +{ + unit *u; + request *o; + + /* Alle Units ohne request haben ein -1, alle units mit orders haben ein + * 0 hier stehen */ + + for (u = r->units; u; u = u->next) + u->n = -1; + + norders = 0; + + for (o = requests; o; o = o->next) { + if (o->qty > 0) { + norders += o->qty; + } + } + + if (norders > 0) { + int i = 0; + oa = (request *) calloc(norders, sizeof(request)); + for (o = requests; o; o = o->next) { + if (o->qty > 0) { + int j; + for (j = o->qty; j; j--) { + oa[i] = *o; + oa[i].unit->n = 0; + i++; + } + } + } + scramble(oa, norders, sizeof(request)); + } else { + oa = NULL; + } + while (requests) { + request *o = requests->next; + free_order(requests->ord); + free(requests); + requests = o; + } +} + +/* ------------------------------------------------------------- */ + +static void change_level(unit * u, skill_t sk, int bylevel) +{ + skill *sv = get_skill(u, sk); + assert(bylevel > 0); + if (sv == 0) + sv = add_skill(u, sk); + sk_set(sv, sv->level + bylevel); +} + +typedef struct recruitment { + struct recruitment *next; + faction *f; + request *requests; + int total, assigned; +} recruitment; + +/** Creates a list of recruitment structs, one for each faction. Adds every quantifyable request + * to the faction's struct and to total. + */ +static recruitment *select_recruitment(request ** rop, + int (*quantify) (const struct race *, int), int *total) +{ + recruitment *recruits = NULL; + + while (*rop) { + recruitment *rec = recruits; + request *ro = *rop; + unit *u = ro->unit; + const race *rc = u_race(u); + int qty = quantify(rc, ro->qty); + + if (qty < 0) { + rop = &ro->next; /* skip this one */ + } else { + *rop = ro->next; /* remove this one */ + while (rec && rec->f != u->faction) + rec = rec->next; + if (rec == NULL) { + rec = malloc(sizeof(recruitment)); + rec->f = u->faction; + rec->total = 0; + rec->assigned = 0; + rec->requests = NULL; + rec->next = recruits; + recruits = rec; + } + *total += qty; + rec->total += qty; + ro->next = rec->requests; + rec->requests = ro; + } + } + return recruits; +} + +static void add_recruits(unit * u, int number, int wanted) +{ + region *r = u->region; + assert(number <= wanted); + if (number > 0) { + unit *unew; + char equipment[64]; + + if (u->number == 0) { + set_number(u, number); + u->hp = number * unit_max_hp(u); + unew = u; + } else { + unew = create_unit(r, u->faction, number, u_race(u), 0, NULL, u); + } + + strlcpy(equipment, "new_", sizeof(equipment)); + strlcat(equipment, u_race(u)->_name[0], sizeof(equipment)); + strlcat(equipment, "_unit", sizeof(equipment)); + equip_unit(unew, get_equipment(equipment)); + + if (u_race(unew)->ec_flags & ECF_REC_HORSES) { + change_level(unew, SK_RIDING, 1); + } + + if (unew != u) { + transfermen(unew, u, unew->number); + remove_unit(&r->units, unew); + } + } + if (number < wanted) { + ADDMSG(&u->faction->msgs, msg_message("recruit", + "unit region amount want", u, r, number, wanted)); + } +} + +static int any_recruiters(const struct race *rc, int qty) +{ + return (int)(qty * 2 * rc->recruit_multi); +} + +/*static int peasant_recruiters(const struct race *rc, int qty) +{ + if (rc->ec_flags & ECF_REC_ETHEREAL) + return -1; + if (rc->ec_flags & ECF_REC_HORSES) + return -1; + return (int)(qty * 2 * rc->recruit_multi); +}*/ + +static int horse_recruiters(const struct race *rc, int qty) +{ + if (rc->ec_flags & ECF_REC_ETHEREAL) + return -1; + if (rc->ec_flags & ECF_REC_HORSES) + return (int)(qty * 2 * rc->recruit_multi); + return -1; +} + +static int do_recruiting(recruitment * recruits, int available) +{ + recruitment *rec; + int recruited = 0; + + /* try to assign recruits to factions fairly */ + while (available > 0) { + int n = 0; + int rest, mintotal = INT_MAX; + + /* find smallest request */ + for (rec = recruits; rec != NULL; rec = rec->next) { + int want = rec->total - rec->assigned; + if (want > 0) { + if (mintotal > want) + mintotal = want; + ++n; + } + } + if (n == 0) + break; + if (mintotal * n > available) { + mintotal = available / n; + } + rest = available - mintotal * n; + + /* assign size of smallest request for everyone if possible; in the end roll dice to assign + * small rest */ + for (rec = recruits; rec != NULL; rec = rec->next) { + int want = rec->total - rec->assigned; + + if (want > 0) { + int get = mintotal; + if (want > mintotal && rest < n && (rng_int() % n) < rest) { + --rest; + ++get; + } + assert(get <= want); + available -= get; + rec->assigned += get; + } + } + } + + /* do actual recruiting */ + for (rec = recruits; rec != NULL; rec = rec->next) { + request *req; + int get = rec->assigned; + + for (req = rec->requests; req; req = req->next) { + unit *u = req->unit; + const race *rc = u_race(u); /* race is set in recruit() */ + int number, dec; + float multi = 2.0F * rc->recruit_multi; + + number = MIN(req->qty, (int)(get / multi)); + if (rc->recruitcost) { + int afford = get_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, + number * rc->recruitcost) / rc->recruitcost; + number = MIN(number, afford); + } + if (u->number + number > UNIT_MAXSIZE) { + ADDMSG(&u->faction->msgs, msg_feedback(u, req->ord, "error_unit_size", + "maxsize", UNIT_MAXSIZE)); + number = UNIT_MAXSIZE - u->number; + assert(number >= 0); + } + if (rc->recruitcost) { + use_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, + rc->recruitcost * number); + } + add_recruits(u, number, req->qty); + dec = (int)(number * multi); + if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) { + recruited += dec; + } + + get -= dec; + } + } + return recruited; +} + +static void feedback_give_not_allowed(unit * u, order * ord) +{ + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_give_forbidden", + "")); +} + +static bool check_give(unit * u, unit * u2, const item_type * itype, + int mask) +{ + if (u2) { + if (u->faction != u2->faction) { + int rule = rule_give(); + if (itype) { + assert(mask == 0); + if (itype->rtype->ltype) + mask |= GIVE_LUXURIES; + else if (fval(itype, ITF_HERB)) + mask |= GIVE_HERBS; + else + mask |= GIVE_GOODS; + } + return (rule & mask) != 0; + } + } else { + int rule = rule_give(); + return (rule & GIVE_PEASANTS) != 0; + } + return true; +} + +void free_recruitments(recruitment * recruits) +{ + while (recruits) { + recruitment *rec = recruits; + recruits = rec->next; + while (rec->requests) { + request *req = rec->requests; + rec->requests = req->next; + free(req); + } + free(rec); + } +} + +/* Rekrutierung */ +static void expandrecruit(region * r, request * recruitorders) +{ + recruitment *recruits = NULL; + + int orc_total = 0; + + /* centaurs: */ + recruits = select_recruitment(&recruitorders, horse_recruiters, &orc_total); + if (recruits) { + int recruited, horses = rhorses(r) * 2; + if (orc_total < horses) + horses = orc_total; + recruited = do_recruiting(recruits, horses); + rsethorses(r, (horses - recruited) / 2); + free_recruitments(recruits); + } + + /* peasant limited: */ + recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total); + if (recruits) { + int orc_recruited, orc_peasants = rpeasants(r) * 2; + int orc_frac = orc_peasants / RECRUITFRACTION; /* anzahl orks. 2 ork = 1 bauer */ + if (orc_total < orc_frac) + orc_frac = orc_total; + orc_recruited = do_recruiting(recruits, orc_frac); + assert(orc_recruited <= orc_frac); + rsetpeasants(r, (orc_peasants - orc_recruited) / 2); + free_recruitments(recruits); + } + + /* no limit: */ + recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total); + if (recruits) { + int recruited, peasants = rpeasants(r) * 2; + recruited = do_recruiting(recruits, INT_MAX); + if (recruited > 0) { + rsetpeasants(r, (peasants - recruited) / 2); + } + free_recruitments(recruits); + } + + assert(recruitorders == NULL); +} + +static int recruit_cost(const faction * f, const race * rc) +{ + if (is_monsters(f) || f->race == rc) { + return rc->recruitcost; + } else if (valid_race(f, rc)) { + return rc->recruitcost; +/* return get_param_int(f->race->parameters, "other_cost", -1); */ + } + return -1; +} + +static void recruit(unit * u, struct order *ord, request ** recruitorders) +{ + int n; + region *r = u->region; + plane *pl; + request *o; + int recruitcost = -1; + const faction *f = u->faction; + const struct race *rc = u_race(u); + const char *str; + + init_tokens(ord); + skip_token(); + n = getuint(); + + if (u->number == 0) { + str = getstrtoken(); + if (str && str[0]) { + /* Monster dürfen REKRUTIERE 15 dracoid machen + * also: secondary race */ + rc = findrace(str, f->locale); + if (rc != NULL) { + recruitcost = recruit_cost(f, rc); + } + } + } + if (recruitcost < 0) { + rc = u_race(u); + recruitcost = recruit_cost(f, rc); + if (recruitcost < 0) { + recruitcost = INT_MAX; + } + } + assert(rc); + u_setrace(u, rc); + +#if GUARD_DISABLES_RECRUIT + /* this is a very special case because the recruiting unit may be empty + * at this point and we have to look at the creating unit instead. This + * is done in cansee, which is called indirectly by is_guarded(). */ + if (is_guarded(r, u, GUARD_RECRUIT)) { + cmistake(u, ord, 70, MSG_EVENT); + return; + } +#endif + + if (rc == new_race[RC_INSECT]) { + gamedate date; + get_gamedate(turn, &date); + if (date.season == 0 && r->terrain != newterrain(T_DESERT)) { +#ifdef INSECT_POTION + bool usepotion = false; + unit *u2; + + for (u2 = r->units; u2; u2 = u2->next) + if (fval(u2, UFL_WARMTH)) { + usepotion = true; + break; + } + if (!usepotion) +#endif + { + cmistake(u, ord, 98, MSG_EVENT); + return; + } + } + /* in Gletschern, Eisbergen gar nicht rekrutieren */ + if (r_insectstalled(r)) { + cmistake(u, ord, 97, MSG_EVENT); + return; + } + } + if (is_cursed(r->attribs, C_RIOT, 0)) { + /* Die Region befindet sich in Aufruhr */ + cmistake(u, ord, 237, MSG_EVENT); + return; + } + + if (!(rc->ec_flags & ECF_REC_HORSES) && fval(r, RF_ORCIFIED)) { + if (rc != new_race[RC_ORC]) { + cmistake(u, ord, 238, MSG_EVENT); + return; + } + } + + if (recruitcost) { + pl = getplane(r); + if (pl && fval(pl, PFL_NORECRUITS)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_pflnorecruit", "")); + return; + } + + if (get_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, + recruitcost) < recruitcost) { + cmistake(u, ord, 142, MSG_EVENT); + return; + } + } + if (!playerrace(rc) || idle(u->faction)) { + cmistake(u, ord, 139, MSG_EVENT); + return; + } + + if (has_skill(u, SK_MAGIC)) { + /* error158;de;{unit} in {region}: '{command}' - Magier arbeiten + * grundsätzlich nur alleine! */ + cmistake(u, ord, 158, MSG_EVENT); + return; + } + if (has_skill(u, SK_ALCHEMY) + && count_skill(u->faction, SK_ALCHEMY) + n > + skill_limit(u->faction, SK_ALCHEMY)) { + cmistake(u, ord, 156, MSG_EVENT); + return; + } + if (recruitcost > 0) { + int pooled = + get_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, recruitcost * n); + n = MIN(n, pooled / recruitcost); + } + + u->wants = n; + + if (!n) { + cmistake(u, ord, 142, MSG_EVENT); + return; + } + + o = (request *) calloc(1, sizeof(request)); + o->qty = n; + o->unit = u; + o->ord = copy_order(ord); + addlist(recruitorders, o); +} + +static void friendly_takeover(region * r, faction * f) +{ + int morale = region_get_morale(r); + region_set_owner(r, f, turn); + if (morale > 0) { + morale = MAX(0, morale - MORALE_TRANSFER); + region_set_morale(r, morale, turn); + } +} + +void give_control(unit * u, unit * u2) +{ + if (u->building) { + if (u->faction != u2->faction && rule_region_owners()) { + region *r = u->region; + faction *f = region_get_owner(r); + + assert(u->building==u2->building); + if (f == u->faction) { + building *b = largestbuilding(r, &cmp_current_owner, false); + if (b == u->building) { + friendly_takeover(r, u2->faction); + } + } + } + building_set_owner(u2); + } + if (u->ship) { + assert(u->ship==u2->ship); + ship_set_owner(u2); + } +} + +int give_control_cmd(unit * u, order * ord) +{ + region *r = u->region; + unit *u2; + const char *s; + param_t p; + + init_tokens(ord); + skip_token(); + u2 = getunit(r, u->faction); + s = getstrtoken(); + p = findparam(s, u->faction->locale); + + /* first, do all the ones that do not require HELP_GIVE or CONTACT */ + if (p == P_CONTROL) { + message *msg = 0; + + if (!u2 || u2->number == 0) { + msg = msg_feedback(u, ord, "feedback_unit_not_found", ""); + ADDMSG(&u->faction->msgs, msg); + } else if (!u->building && !u->ship) { + msg = cmistake(u, ord, 140, MSG_EVENT); + } else if (u->building) { + if (u!=building_owner(u->building)) { + msg = cmistake(u, ord, 49, MSG_EVENT); + } else if (u2->building != u->building) { + msg = cmistake(u, ord, 33, MSG_EVENT); + } + } else if (u->ship) { + if (u!=ship_owner(u->ship)) { + msg = cmistake(u, ord, 49, MSG_EVENT); + } else if (u2->ship != u->ship) { + msg = cmistake(u, ord, 32, MSG_EVENT); + } + } + if (!msg) { + give_control(u, u2); + msg = msg_message("givecommand", "unit recipient", u, u2); + add_message(&u->faction->msgs, msg); + if (u->faction != u2->faction) { + add_message(&u2->faction->msgs, msg); + } + msg_release(msg); + } + } + return 0; +} + +static void give_cmd(unit * u, order * ord) +{ + region *r = u->region; + unit *u2; + const char *s; + int n; + const item_type *itype; + param_t p; + plane *pl; + + init_tokens(ord); + skip_token(); + u2 = getunit(r, u->faction); + s = getstrtoken(); + n = atoip(s); + p = (n>0) ? NOPARAM : findparam(s, u->faction->locale); + + /* first, do all the ones that do not require HELP_GIVE or CONTACT */ + if (p == P_CONTROL) { + /* handled in give_control_cmd */ + return; + } + + if (!u2 && !getunitpeasants) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + return; + } + + if (!check_give(u, u2, NULL, GIVE_ALLITEMS)) { + feedback_give_not_allowed(u, ord); + return; + } + + /* Damit Tarner nicht durch die Fehlermeldung enttarnt werden können */ + if (u2 && !alliedunit(u2, u->faction, HELP_GIVE) + && !cansee(u->faction, r, u2, 0) && !ucontact(u2, u) + && !fval(u2, UFL_TAKEALL)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + return; + } + if (u == u2) { + cmistake(u, ord, 8, MSG_COMMERCE); + return; + } + + /* UFL_TAKEALL ist ein grober Hack. Generalisierung tut not, ist aber nicht + * wirklich einfach. */ + pl = rplane(r); + if (pl && fval(pl, PFL_NOGIVE) && (!u2 || !fval(u2, UFL_TAKEALL))) { + cmistake(u, ord, 268, MSG_COMMERCE); + return; + } + + if (u2 && u_race(u2) == new_race[RC_SPELL]) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + return; + } + + else if (u2 && !alliedunit(u2, u->faction, HELP_GIVE) && !ucontact(u2, u)) { + cmistake(u, ord, 40, MSG_COMMERCE); + return; + } + + else if (p == P_HERBS) { + bool given = false; + if (!(u_race(u)->ec_flags & GIVEITEM) && u2 != NULL) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_nogive", "race", u_race(u))); + return; + } + if (!check_give(u, u2, NULL, GIVE_HERBS)) { + feedback_give_not_allowed(u, ord); + return; + } + if (u2 && !(u_race(u2)->ec_flags & GETITEM)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_notake", "race", u_race(u2))); + return; + } + if (!u2) { + if (!getunitpeasants) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "feedback_unit_not_found", "")); + return; + } + } + if (u->items) { + item **itmp = &u->items; + while (*itmp) { + item *itm = *itmp; + const item_type *itype = itm->type; + if (fval(itype, ITF_HERB) && itm->number > 0) { + /* give_item ändert im fall,das man alles übergibt, die + * item-liste der unit, darum continue vor pointerumsetzten */ + if (give_item(itm->number, itm->type, u, u2, ord) == 0) { + given = true; + if (*itmp != itm) + continue; + continue; + } + } + itmp = &itm->next; + } + } + if (!given) + cmistake(u, ord, 38, MSG_COMMERCE); + return; + } + + else if (p == P_ZAUBER) { + cmistake(u, ord, 7, MSG_COMMERCE); + /* geht nimmer */ + return; + } + + else if (p == P_UNIT) { /* Einheiten uebergeben */ + if (!(u_race(u)->ec_flags & GIVEUNIT)) { + cmistake(u, ord, 167, MSG_COMMERCE); + return; + } + + give_unit(u, u2, ord); + return; + } + + else if (p == P_ANY) { + const char *s; + + if (!check_give(u, u2, NULL, GIVE_ALLITEMS)) { + feedback_give_not_allowed(u, ord); + return; + } + s = getstrtoken(); + if (*s == 0) { /* GIVE ALL items that you have */ + + /* do these checks once, not for each item we have: */ + if (!(u_race(u)->ec_flags & GIVEITEM) && u2 != NULL) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_nogive", "race", u_race(u))); + return; + } + if (u2 && !(u_race(u2)->ec_flags & GETITEM)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_notake", "race", u_race(u2))); + return; + } + + /* für alle items einmal prüfen, ob wir mehr als von diesem Typ + * reserviert ist besitzen und diesen Teil dann übergeben */ + if (u->items) { + item **itmp = &u->items; + while (*itmp) { + item *itm = *itmp; + const item_type *itype = itm->type; + if (itm->number > 0 + && itm->number - get_reservation(u, itype->rtype) > 0) { + n = itm->number - get_reservation(u, itype->rtype); + if (give_item(n, itype, u, u2, ord) == 0) { + if (*itmp != itm) + continue; + } + } + itmp = &itm->next; + } + } + } else { + if (isparam(s, u->faction->locale, P_PERSON)) { + if (!(u_race(u)->ec_flags & GIVEPERSON)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_noregroup", "race", u_race(u))); + } else { + n = u->number; + give_men(n, u, u2, ord); + } + } else if (!(u_race(u)->ec_flags & GIVEITEM) && u2 != NULL) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_nogive", "race", u_race(u))); + } else if (u2 && !(u_race(u2)->ec_flags & GETITEM)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_notake", "race", u_race(u2))); + } else { + itype = finditemtype(s, u->faction->locale); + if (itype != NULL) { + item *i = *i_find(&u->items, itype); + if (i != NULL) { + if (check_give(u, u2, itype, 0)) { + n = i->number - get_reservation(u, itype->rtype); + give_item(n, itype, u, u2, ord); + } else { + feedback_give_not_allowed(u, ord); + } + } + } + } + } + return; + } else if (p == P_EACH) { + if (u2 == NULL) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "peasants_give_invalid", "")); + return; + } + s = getstrtoken(); /* skip one ahead to get the amount. */ + n = atoip(s); /* n: Anzahl */ + n *= u2->number; + } + s = getstrtoken(); + + if (s == NULL) { + cmistake(u, ord, 113, MSG_COMMERCE); + return; + } + + if (isparam(s, u->faction->locale, P_PERSON)) { + if (!(u_race(u)->ec_flags & GIVEPERSON)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_noregroup", "race", u_race(u))); + return; + } + give_men(n, u, u2, ord); + return; + } + + if (u2 != NULL) { + if (!(u_race(u)->ec_flags & GIVEITEM) && u2 != NULL) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_nogive", "race", u_race(u))); + return; + } + if (!(u_race(u2)->ec_flags & GETITEM)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_notake", "race", u_race(u2))); + return; + } + } + + itype = finditemtype(s, u->faction->locale); + if (itype != NULL) { + if (check_give(u, u2, itype, 0)) { + give_item(n, itype, u, u2, ord); + } else { + feedback_give_not_allowed(u, ord); + } + return; + } + cmistake(u, ord, 123, MSG_COMMERCE); +} + +static int forget_cmd(unit * u, order * ord) +{ + skill_t sk; + const char *s; + + if (is_cursed(u->attribs, C_SLAVE, 0)) { + /* charmed units shouldn't be losing their skills */ + return 0; + } + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + if ((sk = findskill(s, u->faction->locale)) != NOSKILL) { + ADDMSG(&u->faction->msgs, msg_message("forget", "unit skill", u, sk)); + set_level(u, sk, 0); + } + return 0; +} + +void add_spende(faction * f1, faction * f2, int amount, region * r) +{ + donation *sp; + + sp = r->donations; + + while (sp) { + if (sp->f1 == f1 && sp->f2 == f2) { + sp->amount += amount; + return; + } + sp = sp->next; + } + + sp = calloc(1, sizeof(donation)); + sp->f1 = f1; + sp->f2 = f2; + sp->amount = amount; + sp->next = r->donations; + r->donations = sp; +} + +static bool maintain(building * b, bool first) +/* first==false -> take money from wherever you can */ +{ + int c; + region *r = b->region; + bool paid = true, work = first; + unit *u; + if (fval(b, BLD_MAINTAINED) || b->type == NULL || b->type->maintenance == NULL + || is_cursed(b->attribs, C_NOCOST, 0)) { + fset(b, BLD_MAINTAINED); + fset(b, BLD_WORKING); + return true; + } + if (fval(b, BLD_DONTPAY)) { + return false; + } + u = building_owner(b); + if (u == NULL) + return false; + for (c = 0; b->type->maintenance[c].number; ++c) { + const maintenance *m = b->type->maintenance + c; + int need = m->number; + + if (fval(m, MTF_VARIABLE)) + need = need * b->size; + if (u) { + /* first ist im ersten versuch true, im zweiten aber false! Das + * bedeutet, das in der Runde in die Region geschafften Resourcen + * nicht genutzt werden können, weil die reserviert sind! */ + if (!first) + need -= get_pooled(u, m->rtype, GET_ALL, need); + else + need -= get_pooled(u, m->rtype, GET_DEFAULT, need); + if (!first && need > 0) { + unit *ua; + for (ua = r->units; ua; ua = ua->next) + freset(ua->faction, FFL_SELECT); + fset(u->faction, FFL_SELECT); /* hat schon */ + for (ua = r->units; ua; ua = ua->next) { + if (!fval(ua->faction, FFL_SELECT) && (ua->faction == u->faction + || alliedunit(ua, u->faction, HELP_MONEY))) { + need -= get_pooled(ua, m->rtype, GET_ALL, need); + fset(ua->faction, FFL_SELECT); + if (need <= 0) + break; + } + } + } + if (need > 0) { + if (!fval(m, MTF_VITAL)) + work = false; + else { + paid = false; + break; + } + } + } + } + if (paid && c > 0) { + /* TODO: wieviel von was wurde bezahlt */ + if (first) { + ADDMSG(&u->faction->msgs, msg_message("maintenance", "unit building", u, + b)); + } else { + ADDMSG(&u->faction->msgs, msg_message("maintenance_late", "building", b)); + } + fset(b, BLD_MAINTAINED); + if (work) { + fset(b, BLD_WORKING); + } + for (c = 0; b->type->maintenance[c].number; ++c) { + const maintenance *m = b->type->maintenance + c; + int cost = m->number; + + if (!fval(m, MTF_VITAL) && !work) + continue; + if (fval(m, MTF_VARIABLE)) + cost = cost * b->size; + + if (!first) + cost -= use_pooled(u, m->rtype, GET_ALL, cost); + else + cost -= + use_pooled(u, m->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + cost); + if (!first && cost > 0) { + unit *ua; + for (ua = r->units; ua; ua = ua->next) + freset(ua->faction, FFL_SELECT); + fset(u->faction, FFL_SELECT); /* hat schon */ + for (ua = r->units; ua; ua = ua->next) { + if (!fval(ua->faction, FFL_SELECT) + && alliedunit(ua, u->faction, HELP_MONEY)) { + int give = use_pooled(ua, m->rtype, GET_ALL, cost); + if (!give) + continue; + cost -= give; + fset(ua->faction, FFL_SELECT); + if (m->rtype == r_silver) + add_spende(ua->faction, u->faction, give, r); + if (cost <= 0) + break; + } + } + } + assert(cost == 0); + } + } else { + ADDMSG(&u->faction->msgs, + msg_message("maintenancefail", "unit building", u, b)); + return false; + } + return true; +} + +void maintain_buildings(region * r, bool crash) +{ + building **bp = &r->buildings; + while (*bp) { + building *b = *bp; + bool maintained = maintain(b, !crash); + + /* the second time, send a message */ + if (crash) { + if (!fval(b, BLD_WORKING)) { + unit *u = building_owner(b); + const char *msgtype = + maintained ? "maintenance_nowork" : "maintenance_none"; + struct message *msg = msg_message(msgtype, "building", b); + + if (u) { + add_message(&u->faction->msgs, msg); + r_addmessage(r, u->faction, msg); + } else { + add_message(&r->msgs, msg); + } + msg_release(msg); + } + } + bp = &b->next; + } +} + +static int recruit_archetype(unit * u, order * ord) +{ + bool merge = (u->number > 0); + int want; + const char *s; + + if (rules_recruit < 0) + recruit_init(); + + init_tokens(ord); + skip_token(); + want = getuint(); + s = getstrtoken(); + if (want > 0 && s && s[0]) { + int n = want; + const archetype *arch = find_archetype(s, u->faction->locale); + attrib *a = NULL; + + if ((rules_recruit & RECRUIT_MERGE) == 0 && merge) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "unit_must_be_new", "")); + /* TODO: error message */ + return 0; + } + + if (arch == NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "unknown_archetype", + "name", s)); + /* TODO: error message */ + return 0; + } + if (arch->rules) { + /* Simple allow/deny style restrictions for archetypes (let only humans + * recruit gamedesigners, etc). These need to be more powerful to be + * useful, and the current way they are implemented is not, but the + * general idea strikes me as good. Also, feedback should be configurable + * for each failed rule. + */ + int k; + for (k = 0; arch->rules[k].property; ++k) { + bool match = false; + if (arch->rules[k].value[0] == '*') + match = true; + else if (strcmp(arch->rules[k].property, "race") == 0) { + const race *rc = rc_find(arch->rules[k].value); + assert(rc); + if (rc == u_race(u)) + match = true; + } else if (strcmp(arch->rules[k].property, "building") == 0) { + const building_type *btype = bt_find(arch->rules[k].value); + assert(btype); + if (u->building && u->building->type == btype) + match = true; + } + if (match) { + if (arch->rules[k].allow) + break; + else { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "recruit_rule_fail", + "property value", arch->rules[k].property, + arch->rules[k].value)); + /* TODO: error message */ + return 0; + } + } + } + } + if (arch->btype) { + if (u->building == NULL || u->building->type != arch->btype) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "unit_must_be_in_building", "type", arch->btype)); + /* TODO: error message */ + return 0; + } + if (arch->size) { + int maxsize = u->building->size; + attrib *a = a_find(u->building->attribs, &at_recruit); + if (a != NULL) { + maxsize -= a->data.i; + } + n = MIN(maxsize / arch->size, n); + if (n <= 0) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "recruit_capacity_exhausted", "building", u->building)); + /* TODO: error message */ + return 0; + } + } + } + + n = build(u, arch->ctype, 0, n); + if (n > 0) { + unit *u2; + if (merge) { + u2 = create_unit(u->region, u->faction, 0, u_race(u), 0, 0, u); + } else { + u2 = u; + } + if (arch->exec) { + n = arch->exec(u2, arch, n); + } else { + set_number(u2, n); + equip_unit(u2, arch->equip); + u2->hp = n * unit_max_hp(u2); + if (arch->size) { + if (a == NULL) + a = a_add(&u->building->attribs, a_new(&at_recruit)); + a->data.i += n * arch->size; + } + } + ADDMSG(&u->faction->msgs, msg_message("recruit_archetype", + "unit amount archetype", u, n, arch->name[n == 1])); + if (u != u2 && u_race(u) == u_race(u2)) { + transfermen(u2, u, u2->number); + } + return n; + } else + switch (n) { + case ENOMATERIALS: + ADDMSG(&u->faction->msgs, msg_materials_required(u, ord, arch->ctype, + want)); + break; + case ELOWSKILL: + case ENEEDSKILL: + /* no skill, or not enough skill points to build */ + cmistake(u, ord, 50, MSG_PRODUCE); + break; + case EBUILDINGREQ: + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "building_needed", "building", + arch->ctype->btype->_name)); + break; + default: + assert(!"unhandled return value from build() in recruit_archetype"); + } + return 0; + } + return -1; +} + +int recruit_archetypes(void) +{ + if (rules_recruit < 0) + recruit_init(); + return (rules_recruit & RECRUIT_ARCHETYPES) != 0; +} + +void economics(region * r) +{ + unit *u; + request *recruitorders = NULL; + + /* Geben vor Selbstmord (doquit)! Hier alle unmittelbaren Befehle. + * Rekrutieren vor allen Einnahmequellen. Bewachen JA vor Steuern + * eintreiben. */ + + for (u = r->units; u; u = u->next) { + order *ord; + bool destroyed = false; + if (u->number > 0) { + for (ord = u->orders; ord; ord = ord->next) { + keyword_t kwd = get_keyword(ord); + if (kwd == K_DESTROY) { + if (!destroyed) { + if (destroy_cmd(u, ord) != 0) + ord = NULL; + destroyed = true; + } + } else if (kwd == K_GIVE) { + give_cmd(u, ord); + } else if (kwd == K_FORGET) { + forget_cmd(u, ord); + } + if (u->orders == NULL) { + break; + } + } + } + } + /* RECRUIT orders */ + + if (rules_recruit < 0) + recruit_init(); + for (u = r->units; u; u = u->next) { + order *ord; + + if ((rules_recruit & RECRUIT_MERGE) || u->number == 0) { + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == K_RECRUIT) { + if (rules_recruit & RECRUIT_ARCHETYPES) { + if (recruit_archetype(u, ord) >= 0) { + continue; + } + } + if (rules_recruit & RECRUIT_CLASSIC) { + recruit(u, ord, &recruitorders); + } + break; + } + } + } + } + + if (recruitorders) + expandrecruit(r, recruitorders); + remove_empty_units_in_region(r); +} + +/* ------------------------------------------------------------- */ + +static void manufacture(unit * u, const item_type * itype, int want) +{ + int n; + int skill; + int minskill = itype->construction->minskill; + skill_t sk = itype->construction->skill; + + skill = effskill(u, sk); + skill = + skillmod(itype->rtype->attribs, u, u->region, sk, skill, SMF_PRODUCTION); + + if (skill < 0) { + /* an error occured */ + int err = -skill; + cmistake(u, u->thisorder, err, MSG_PRODUCE); + return; + } + + if (want == 0) { + want = maxbuild(u, itype->construction); + } + n = build(u, itype->construction, 0, want); + switch (n) { + case ENEEDSKILL: + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "skill_needed", "skill", sk)); + return; + case EBUILDINGREQ: + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "building_needed", "building", + itype->construction->btype->_name)); + return; + case ELOWSKILL: + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "manufacture_skills", + "skill minskill product", sk, minskill, itype->rtype, 1)); + return; + case ENOMATERIALS: + ADDMSG(&u->faction->msgs, msg_materials_required(u, u->thisorder, + itype->construction, want)); + return; + } + if (n > 0) { + i_change(&u->items, itype, n); + if (want == INT_MAX) + want = n; + ADDMSG(&u->faction->msgs, msg_message("manufacture", + "unit region amount wanted resource", u, u->region, n, want, + itype->rtype)); + } else { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, "error_cannotmake", + "")); + } +} + +typedef struct allocation { + struct allocation *next; + int want, get; + double save; + unsigned int flags; + unit *unit; +} allocation; +#define new_allocation() calloc(sizeof(allocation), 1) +#define free_allocation(a) free(a) + +typedef struct allocation_list { + struct allocation_list *next; + allocation *data; + const resource_type *type; +} allocation_list; + +static allocation_list *allocations; + +static bool can_guard(const unit * guard, const unit * u) +{ + if (fval(guard, UFL_ISNEW)) + return false; + if (guard->number <= 0 || !cansee(guard->faction, guard->region, u, 0)) + return false; + if (besieged(guard) || !(fval(u_race(guard), RCF_UNARMEDGUARD) + || armedmen(guard, true))) + return false; + + return !alliedunit(guard, u->faction, HELP_GUARD); +} + +enum { + AFL_DONE = 1 << 0, + AFL_LOWSKILL = 1 << 1 +}; + +static void allocate_resource(unit * u, const resource_type * rtype, int want) +{ + const item_type *itype = resource2item(rtype); + region *r = u->region; + int dm = 0; + allocation_list *alist; + allocation *al; + attrib *a = a_find(rtype->attribs, &at_resourcelimit); + resource_limit *rdata = (resource_limit *) a->data.v; + int amount, skill; + + /* momentan kann man keine ressourcen abbauen, wenn man dafür + * Materialverbrauch hat: */ + assert(itype != NULL && (itype->construction == NULL + || itype->construction->materials == NULL)); + assert(rdata != NULL); + + if (rdata->limit != NULL) { + int avail = rdata->limit(r, rtype); + if (avail <= 0) { + cmistake(u, u->thisorder, 121, MSG_PRODUCE); + return; + } + } + + if (besieged(u)) { + cmistake(u, u->thisorder, 60, MSG_PRODUCE); + return; + } + + if (rdata->modifiers) { + resource_mod *mod = rdata->modifiers; + for (; mod->flags != 0; ++mod) { + if (mod->flags & RMF_REQUIREDBUILDING) { + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + if (mod->btype && mod->btype != btype) { + cmistake(u, u->thisorder, 104, MSG_PRODUCE); + return; + } + } + } + } + + if (rdata->guard != 0) { + unit *u2; + for (u2 = r->units; u2; u2 = u2->next) { + if (is_guard(u2, rdata->guard) != 0 && can_guard(u2, u)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "region_guarded", "guard", u2)); + return; + } + } + } + + /* Bergwächter können Abbau von Eisen/Laen durch Bewachen verhindern. + * Als magische Wesen 'sehen' Bergwächter alles und werden durch + * Belagerung nicht aufgehalten. (Ansonsten wie oben bei Elfen anpassen). + */ + if (itype == olditemtype[I_IRON] || itype == olditemtype[I_LAEN]) { + unit *u2; + for (u2 = r->units; u2; u2 = u2->next) { + if (is_guard(u, GUARD_MINING) + && !fval(u2, UFL_ISNEW) + && u2->number && !alliedunit(u2, u->faction, HELP_GUARD)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "region_guarded", "guard", u2)); + return; + } + } + } + + assert(itype->construction->skill != 0 + || "limited resource needs a required skill for making it"); + skill = eff_skill(u, itype->construction->skill, u->region); + if (skill == 0) { + skill_t sk = itype->construction->skill; + add_message(&u->faction->msgs, + msg_feedback(u, u->thisorder, "skill_needed", "skill", sk)); + return; + } + if (skill < itype->construction->minskill) { + skill_t sk = itype->construction->skill; + add_message(&u->faction->msgs, + msg_feedback(u, u->thisorder, "manufacture_skills", + "skill minskill product", sk, itype->construction->minskill, + itype->rtype)); + return; + } else { + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + + if (rdata->modifiers) { + resource_mod *mod = rdata->modifiers; + for (; mod->flags != 0; ++mod) { + if (mod->flags & RMF_SKILL) { + if (mod->btype == NULL || mod->btype == btype) { + if (mod->race == NULL || mod->race == u_race(u)) { + skill += mod->value.i; + } + } + } + } + } + } + amount = skill * u->number; + /* nun ist amount die Gesamtproduktion der Einheit (in punkten) */ + + /* mit Flinkfingerring verzehnfacht sich die Produktion */ + amount += + skill * MIN(u->number, get_item(u, + I_RING_OF_NIMBLEFINGER)) * (roqf_factor() - 1); + + /* Schaffenstrunk: */ + if ((dm = get_effect(u, oldpotiontype[P_DOMORE])) != 0) { + dm = MIN(dm, u->number); + change_effect(u, oldpotiontype[P_DOMORE], -dm); + amount += dm * skill; /* dm Personen produzieren doppelt */ + } + + amount /= itype->construction->minskill; + + /* Limitierung durch Parameter m. */ + if (want > 0 && want < amount) + amount = want; + + alist = allocations; + while (alist && alist->type != rtype) + alist = alist->next; + if (!alist) { + alist = calloc(sizeof(struct allocation_list), 1); + alist->next = allocations; + alist->type = rtype; + allocations = alist; + } + al = new_allocation(); + al->want = amount; + al->save = 1.0; + al->next = alist->data; + al->unit = u; + alist->data = al; + + if (rdata->modifiers) { + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + + resource_mod *mod = rdata->modifiers; + for (; mod->flags != 0; ++mod) { + if (mod->flags & RMF_SAVEMATERIAL) { + if (mod->btype == NULL || mod->btype == btype) { + if (mod->race == NULL || mod->race == u_race(u)) { + al->save *= mod->value.f; + } + } + } + } + } +} + +static int required(int want, double save) +{ + int norders = (int)(want * save); + if (norders < want * save) + ++norders; + return norders; +} + +static void +leveled_allocation(const resource_type * rtype, region * r, allocation * alist) +{ + const item_type *itype = resource2item(rtype); + rawmaterial *rm = rm_get(r, rtype); + int need; + bool first = true; + + if (rm != NULL) { + do { + int avail = rm->amount; + int norders = 0; + allocation *al; + + if (avail <= 0) { + for (al = alist; al; al = al->next) { + al->get = 0; + } + break; + } + + assert(avail > 0); + + for (al = alist; al; al = al->next) + if (!fval(al, AFL_DONE)) { + int req = required(al->want - al->get, al->save); + assert(al->get <= al->want && al->get >= 0); + if (eff_skill(al->unit, itype->construction->skill, r) + >= rm->level + itype->construction->minskill - 1) { + if (req) { + norders += req; + } else { + fset(al, AFL_DONE); + } + } else { + fset(al, AFL_DONE); + if (first) + fset(al, AFL_LOWSKILL); + } + } + need = norders; + + avail = MIN(avail, norders); + if (need > 0) { + int use = 0; + for (al = alist; al; al = al->next) + if (!fval(al, AFL_DONE)) { + if (avail > 0) { + int want = required(al->want - al->get, al->save); + int x = avail * want / norders; + /* Wenn Rest, dann würfeln, ob ich was bekomme: */ + if (rng_int() % norders < (avail * want) % norders) + ++x; + avail -= x; + use += x; + norders -= want; + need -= x; + al->get = MIN(al->want, al->get + (int)(x / al->save)); + } + } + if (use) { + assert(use <= rm->amount); + rm->type->use(rm, r, use); + } + assert(avail == 0 || norders == 0); + } + first = false; + } while (need > 0); + } +} + +static void +attrib_allocation(const resource_type * rtype, region * r, allocation * alist) +{ + allocation *al; + int norders = 0; + attrib *a = a_find(rtype->attribs, &at_resourcelimit); + resource_limit *rdata = (resource_limit *) a->data.v; + int avail = rdata->value; + + for (al = alist; al; al = al->next) { + norders += required(al->want, al->save); + } + + if (rdata->limit) { + avail = rdata->limit(r, rtype); + if (avail < 0) + avail = 0; + } + + avail = MIN(avail, norders); + for (al = alist; al; al = al->next) { + if (avail > 0) { + int want = required(al->want, al->save); + int x = avail * want / norders; + /* Wenn Rest, dann würfeln, ob ich was bekomme: */ + if (rng_int() % norders < (avail * want) % norders) + ++x; + avail -= x; + norders -= want; + al->get = MIN(al->want, (int)(x / al->save)); + if (rdata->produce) { + int use = required(al->get, al->save); + if (use) + rdata->produce(r, rtype, use); + } + } + } + assert(avail == 0 || norders == 0); +} + +typedef void (*allocate_function) (const resource_type *, struct region *, + struct allocation *); + +static allocate_function get_allocator(const struct resource_type *rtype) +{ + attrib *a = a_find(rtype->attribs, &at_resourcelimit); + + if (a != NULL) { + resource_limit *rdata = (resource_limit *) a->data.v; + if (rdata->value > 0 || rdata->limit != NULL) { + return attrib_allocation; + } + return leveled_allocation; + } + return NULL; +} + +void split_allocations(region * r) +{ + allocation_list **p_alist = &allocations; + freset(r, RF_SELECT); + while (*p_alist) { + allocation_list *alist = *p_alist; + const resource_type *rtype = alist->type; + allocate_function alloc = get_allocator(rtype); + const item_type *itype = resource2item(rtype); + allocation **p_al = &alist->data; + + freset(r, RF_SELECT); + alloc(rtype, r, alist->data); + + while (*p_al) { + allocation *al = *p_al; + if (al->get) { + assert(itype || !"not implemented for non-items"); + i_change(&al->unit->items, itype, al->get); + produceexp(al->unit, itype->construction->skill, al->unit->number); + fset(r, RF_SELECT); + } + if (al->want == INT_MAX) + al->want = al->get; + ADDMSG(&al->unit->faction->msgs, msg_message("produce", + "unit region amount wanted resource", + al->unit, al->unit->region, al->get, al->want, rtype)); + *p_al = al->next; + free_allocation(al); + } + *p_alist = alist->next; + free(alist); + } + allocations = NULL; +} + +static void create_potion(unit * u, const potion_type * ptype, int want) +{ + int built; + + if (want == 0) { + want = maxbuild(u, ptype->itype->construction); + } + built = build(u, ptype->itype->construction, 0, want); + switch (built) { + case ELOWSKILL: + case ENEEDSKILL: + /* no skill, or not enough skill points to build */ + cmistake(u, u->thisorder, 50, MSG_PRODUCE); + break; + case EBUILDINGREQ: + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "building_needed", "building", + ptype->itype->construction->btype->_name)); + break; + case ECOMPLETE: + assert(0); + break; + case ENOMATERIALS: + /* something missing from the list of materials */ + ADDMSG(&u->faction->msgs, msg_materials_required(u, u->thisorder, + ptype->itype->construction, want)); + return; + break; + default: + i_change(&u->items, ptype->itype, built); + if (want == INT_MAX) + want = built; + ADDMSG(&u->faction->msgs, msg_message("manufacture", + "unit region amount wanted resource", u, u->region, built, want, + ptype->itype->rtype)); + break; + } +} + +static void create_item(unit * u, const item_type * itype, int want) +{ + if (itype->construction && fval(itype->rtype, RTF_LIMITED)) { +#if GUARD_DISABLES_PRODUCTION == 1 + if (is_guarded(u->region, u, GUARD_PRODUCE)) { + cmistake(u, u->thisorder, 70, MSG_EVENT); + return; + } +#endif + allocate_resource(u, itype->rtype, want); + } else { + const potion_type *ptype = resource2potion(itype->rtype); + if (ptype != NULL) + create_potion(u, ptype, want); + else if (itype->construction && itype->construction->materials) + manufacture(u, itype, want); + else { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "error_cannotmake", "")); + } + } +} + +int make_cmd(unit * u, struct order *ord) +{ + region *r = u->region; + const building_type *btype; + const ship_type *stype; + param_t p; + int m; + const item_type *itype; + const char *s; + const struct locale *lang = u->faction->locale; + char ibuf[16]; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + m = atoi((const char *)s); + sprintf(ibuf, "%d", m); + if (!strcmp(ibuf, (const char *)s)) { + /* first came a want-paramter */ + s = getstrtoken(); + } else { + m = INT_MAX; + } + + p = findparam(s, u->faction->locale); + + /* MACHE TEMP kann hier schon gar nicht auftauchen, weil diese nicht in + * thisorder abgespeichert werden - und auf den ist getstrtoken() beim + * aufruf von make geeicht */ + + if (p == P_ROAD) { + plane *pl = rplane(r); + if (pl && fval(pl, PFL_NOBUILD)) { + cmistake(u, ord, 275, MSG_PRODUCE); + } else { + direction_t d = finddirection(getstrtoken(), u->faction->locale); + if (d != NODIRECTION) { + build_road(r, u, m, d); + } else { + /* Die Richtung wurde nicht erkannt */ + cmistake(u, ord, 71, MSG_PRODUCE); + } + } + return 0; + } else if (p == P_SHIP) { + plane *pl = rplane(r); + if (pl && fval(pl, PFL_NOBUILD)) { + cmistake(u, ord, 276, MSG_PRODUCE); + } else { + continue_ship(r, u, m); + } + return 0; + } else if (p == P_HERBS) { + herbsearch(r, u, m); + return 0; + } + + /* since the string can match several objects, like in 'academy' and + * 'academy of arts', we need to figure out what the player meant. + * This is not 100% safe. + */ + stype = findshiptype(s, lang); + btype = findbuildingtype(s, lang); + itype = finditemtype(s, lang); + + if (itype != NULL && (btype != NULL || stype != NULL)) { + if (itype->construction == NULL) { + /* if the item cannot be made, we probably didn't mean to make it */ + itype = NULL; + } else if (stype != NULL) { + const char *sname = LOC(lang, stype->name[0]); + const char *iname = LOC(lang, resourcename(itype->rtype, 0)); + if (strlen(iname) < strlen(sname)) + stype = NULL; + else + itype = NULL; + } else { + const char *bname = LOC(lang, btype->_name); + const char *iname = LOC(lang, resourcename(itype->rtype, 0)); + if (strlen(iname) < strlen(bname)) + btype = NULL; + else + itype = NULL; + } + } + + if (btype != NULL && stype != NULL) { + const char *bname = LOC(lang, btype->_name); + const char *sname = LOC(lang, stype->name[0]); + if (strlen(sname) < strlen(bname)) + btype = NULL; + else + stype = NULL; + } + + if (stype != NOSHIP) { + plane *pl = rplane(r); + if (pl && fval(pl, PFL_NOBUILD)) { + cmistake(u, ord, 276, MSG_PRODUCE); + } else { + create_ship(r, u, stype, m, ord); + } + } else if (btype != NOBUILDING) { + plane *pl = rplane(r); + if (pl && fval(pl, PFL_NOBUILD)) { + cmistake(u, ord, 94, MSG_PRODUCE); + } else { + build_building(u, btype, m, ord); + } + } else if (itype != NULL) { + create_item(u, itype, m); + } else { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_cannotmake", "")); + } + + return 0; +} + +/* ------------------------------------------------------------- */ + +static void free_luxuries(struct attrib *a) +{ + item *itm = (item *) a->data.v; + a->data.v = NULL; + i_freeall(&itm); +} + +const attrib_type at_luxuries = { + "luxuries", NULL, free_luxuries, NULL, NULL, NULL +}; + +static void expandbuying(region * r, request * buyorders) +{ + int max_products; + unit *u; + static struct trade { + const luxury_type *type; + int number; + int multi; + } *trades, *trade; + static int ntrades = 0; + int i, j; + const luxury_type *ltype; + + if (ntrades == 0) { + for (ltype = luxurytypes; ltype; ltype = ltype->next) + ++ntrades; + trades = gc_add(calloc(sizeof(struct trade), ntrades)); + for (i = 0, ltype = luxurytypes; i != ntrades; ++i, ltype = ltype->next) + trades[i].type = ltype; + } + for (i = 0; i != ntrades; ++i) { + trades[i].number = 0; + trades[i].multi = 1; + } + + if (!buyorders) + return; + + /* Initialisation. multiplier ist der Multiplikator auf den + * Verkaufspreis. Für max_products Produkte kauft man das Produkt zum + * einfachen Verkaufspreis, danach erhöht sich der Multiplikator um 1. + * counter ist ein Zähler, der die gekauften Produkte zählt. money + * wird für die debug message gebraucht. */ + + max_products = rpeasants(r) / TRADE_FRACTION; + + /* Kauf - auch so programmiert, daß er leicht erweiterbar auf mehrere + * Güter pro Monat ist. j sind die Befehle, i der Index des + * gehandelten Produktes. */ + if (max_products > 0) { + expandorders(r, buyorders); + if (!norders) + return; + + for (j = 0; j != norders; j++) { + int price, multi; + ltype = oa[j].type.ltype; + trade = trades; + while (trade->type != ltype) + ++trade; + multi = trade->multi; + price = ltype->price * multi; + + if (get_pooled(oa[j].unit, oldresourcetype[R_SILVER], GET_DEFAULT, + price) >= price) { + unit *u = oa[j].unit; + item *items; + + /* litems zählt die Güter, die verkauft wurden, u->n das Geld, das + * verdient wurde. Dies muß gemacht werden, weil der Preis ständig sinkt, + * man sich also das verdiente Geld und die verkauften Produkte separat + * merken muß. */ + attrib *a = a_find(u->attribs, &at_luxuries); + if (a == NULL) + a = a_add(&u->attribs, a_new(&at_luxuries)); + + items = a->data.v; + i_change(&items, ltype->itype, 1); + a->data.v = items; + i_change(&oa[j].unit->items, ltype->itype, 1); + use_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, price); + if (u->n < 0) + u->n = 0; + u->n += price; + + rsetmoney(r, rmoney(r) + price); + + /* Falls mehr als max_products Bauern ein Produkt verkauft haben, steigt + * der Preis Multiplikator für das Produkt um den Faktor 1. Der Zähler + * wird wieder auf 0 gesetzt. */ + if (++trade->number == max_products) { + trade->number = 0; + ++trade->multi; + } + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + } + } + free(oa); + + /* Ausgabe an Einheiten */ + + for (u = r->units; u; u = u->next) { + attrib *a = a_find(u->attribs, &at_luxuries); + item *itm; + if (a == NULL) + continue; + ADDMSG(&u->faction->msgs, msg_message("buy", "unit money", u, u->n)); + for (itm = (item *) a->data.v; itm; itm = itm->next) { + if (itm->number) { + ADDMSG(&u->faction->msgs, msg_message("buyamount", + "unit amount resource", u, itm->number, itm->type->rtype)); + } + } + a_remove(&u->attribs, a); + } + } +} + +attrib_type at_trades = { + "trades", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ +}; + +static void buy(unit * u, request ** buyorders, struct order *ord) +{ + region *r = u->region; + int n, k; + request *o; + attrib *a; + const item_type *itype = NULL; + const luxury_type *ltype = NULL; + if (u->ship && is_guarded(r, u, GUARD_CREWS)) { + cmistake(u, ord, 69, MSG_INCOME); + return; + } + if (u->ship && is_guarded(r, u, GUARD_CREWS)) { + cmistake(u, ord, 69, MSG_INCOME); + return; + } + /* Im Augenblick kann man nur 1 Produkt kaufen. expandbuying ist aber + * schon dafür ausgerüstet, mehrere Produkte zu kaufen. */ + + init_tokens(ord); + skip_token(); + n = getuint(); + if (!n) { + cmistake(u, ord, 26, MSG_COMMERCE); + return; + } + if (besieged(u)) { + /* Belagerte Einheiten können nichts kaufen. */ + cmistake(u, ord, 60, MSG_COMMERCE); + return; + } + + if (u_race(u) == new_race[RC_INSECT]) { + /* entweder man ist insekt, oder... */ + if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT) + && !rbuildings(r)) { + cmistake(u, ord, 119, MSG_COMMERCE); + return; + } + } else { + /* ...oder in der Region muß es eine Burg geben. */ + building *b; + static const struct building_type *bt_castle; + if (!bt_castle) + bt_castle = bt_find("castle"); + for (b = r->buildings; b; b = b->next) { + if (b->type == bt_castle && b->size >= 2) + break; + } + if (b == NULL) { + cmistake(u, ord, 119, MSG_COMMERCE); + return; + } + } + + /* Ein Händler kann nur 10 Güter pro Talentpunkt handeln. */ + k = u->number * 10 * eff_skill(u, SK_TRADE, r); + + /* hat der Händler bereits gehandelt, muss die Menge der bereits + * verkauften/gekauften Güter abgezogen werden */ + a = a_find(u->attribs, &at_trades); + if (!a) { + a = a_add(&u->attribs, a_new(&at_trades)); + } else { + k -= a->data.i; + } + + n = MIN(n, k); + + if (!n) { + cmistake(u, ord, 102, MSG_COMMERCE); + return; + } + + assert(n >= 0); + /* die Menge der verkauften Güter merken */ + a->data.i += n; + + itype = finditemtype(getstrtoken(), u->faction->locale); + if (itype != NULL) { + ltype = resource2luxury(itype->rtype); + if (ltype == NULL) { + cmistake(u, ord, 124, MSG_COMMERCE); + return; + } + } + if (r_demand(r, ltype)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "luxury_notsold", "")); + return; + } + o = (request *) calloc(1, sizeof(request)); + o->type.ltype = ltype; /* sollte immer gleich sein */ + + o->unit = u; + o->qty = n; + addlist(buyorders, o); +} + +/* ------------------------------------------------------------- */ + +/* Steuersätze in % bei Burggröße */ +static int tax_per_size[7] = { 0, 6, 12, 18, 24, 30, 36 }; + +static void expandselling(region * r, request * sellorders, int limit) +{ + int money, price, j, max_products; + /* int m, n = 0; */ + int maxsize = 0, maxeffsize = 0; + int taxcollected = 0; + int hafencollected = 0; + unit *maxowner = (unit *) NULL; + building *maxb = (building *) NULL; + building *b; + unit *u; + unit *hafenowner; + static int *counter; + static int ncounter = 0; + + if (ncounter == 0) { + const luxury_type *ltype; + for (ltype = luxurytypes; ltype; ltype = ltype->next) + ++ncounter; + counter = (int *)gc_add(calloc(sizeof(int), ncounter)); + } else { + memset(counter, 0, sizeof(int) * ncounter); + } + + if (!sellorders) { /* NEIN, denn Insekten können in || !r->buildings) */ + return; /* Sümpfen und Wüsten auch so handeln */ + } + /* Stelle Eigentümer der größten Burg fest. Bekommt Steuern aus jedem + * Verkauf. Wenn zwei Burgen gleicher Größe bekommt gar keiner etwas. */ + + for (b = rbuildings(r); b; b = b->next) { + if (b->size > maxsize && building_owner(b) != NULL + && b->type == bt_find("castle")) { + maxb = b; + maxsize = b->size; + maxowner = building_owner(b); + } else if (b->size == maxsize && b->type == bt_find("castle")) { + maxb = (building *) NULL; + maxowner = (unit *) NULL; + } + } + + hafenowner = owner_buildingtyp(r, bt_find("harbour")); + + if (maxb != (building *) NULL && maxowner != (unit *) NULL) { + maxeffsize = buildingeffsize(maxb, false); + if (maxeffsize == 0) { + maxb = (building *) NULL; + maxowner = (unit *) NULL; + } + } + /* Die Region muss genug Geld haben, um die Produkte kaufen zu können. */ + + money = rmoney(r); + + /* max_products sind 1/100 der Bevölkerung, falls soviele Produkte + * verkauft werden - counter[] - sinkt die Nachfrage um 1 Punkt. + * multiplier speichert r->demand für die debug message ab. */ + + max_products = rpeasants(r) / TRADE_FRACTION; + if (max_products <= 0) + return; + + if (r->terrain == newterrain(T_DESERT) + && buildingtype_exists(r, bt_find("caravan"), true)) { + max_products = rpeasants(r) * 2 / TRADE_FRACTION; + } + /* Verkauf: so programmiert, dass er leicht auf mehrere Gueter pro + * Runde erweitert werden kann. */ + + expandorders(r, sellorders); + if (!norders) + return; + + for (j = 0; j != norders; j++) { + static const luxury_type *search = NULL; + const luxury_type *ltype = oa[j].type.ltype; + int multi = r_demand(r, ltype); + static int i = -1; + int use = 0; + if (search != ltype) { + i = 0; + for (search = luxurytypes; search != ltype; search = search->next) + ++i; + } + if (counter[i] >= limit) + continue; + if (counter[i] + 1 > max_products && multi > 1) + --multi; + price = ltype->price * multi; + + if (money >= price) { + int abgezogenhafen = 0; + int abgezogensteuer = 0; + unit *u = oa[j].unit; + item *itm; + attrib *a = a_find(u->attribs, &at_luxuries); + if (a == NULL) + a = a_add(&u->attribs, a_new(&at_luxuries)); + itm = (item *) a->data.v; + i_change(&itm, ltype->itype, 1); + a->data.v = itm; + ++use; + if (u->n < 0) + u->n = 0; + + if (hafenowner != NULL) { + if (hafenowner->faction != u->faction) { + abgezogenhafen = price / 10; + hafencollected += abgezogenhafen; + price -= abgezogenhafen; + money -= abgezogenhafen; + } + } + if (maxb != NULL) { + if (maxowner->faction != u->faction) { + abgezogensteuer = price * tax_per_size[maxeffsize] / 100; + taxcollected += abgezogensteuer; + price -= abgezogensteuer; + money -= abgezogensteuer; + } + } + u->n += price; + change_money(u, price); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + + /* r->money -= price; --- dies wird eben nicht ausgeführt, denn die + * Produkte können auch als Steuern eingetrieben werden. In der Region + * wurden Silberstücke gegen Luxusgüter des selben Wertes eingetauscht! + * Falls mehr als max_products Kunden ein Produkt gekauft haben, sinkt + * die Nachfrage für das Produkt um 1. Der Zähler wird wieder auf 0 + * gesetzt. */ + + if (++counter[i] > max_products) { + int d = r_demand(r, ltype); + if (d > 1) { + r_setdemand(r, ltype, d - 1); + } + counter[i] = 0; + } + } + if (use > 0) { +#ifdef NDEBUG + use_pooled(oa[j].unit, ltype->itype->rtype, GET_DEFAULT, use); +#else + /* int i = */ use_pooled(oa[j].unit, ltype->itype->rtype, GET_DEFAULT, + use); + /* assert(i==use); */ +#endif + } + } + free(oa); + + /* Steuern. Hier werden die Steuern dem Besitzer der größten Burg gegeben. */ + + if (maxowner) { + if (taxcollected > 0) { + change_money(maxowner, (int)taxcollected); + add_income(maxowner, IC_TRADETAX, taxcollected, taxcollected); + /* TODO: Meldung + * "%s verdient %d Silber durch den Handel in %s.", + * unitname(maxowner), (int) taxcollected, regionname(r)); */ + } + } + if (hafenowner) { + if (hafencollected > 0) { + change_money(hafenowner, (int)hafencollected); + add_income(hafenowner, IC_TRADETAX, hafencollected, hafencollected); + } + } + /* Berichte an die Einheiten */ + + for (u = r->units; u; u = u->next) { + + attrib *a = a_find(u->attribs, &at_luxuries); + item *itm; + if (a == NULL) + continue; + for (itm = (item *) a->data.v; itm; itm = itm->next) { + if (itm->number) { + ADDMSG(&u->faction->msgs, msg_message("sellamount", + "unit amount resource", u, itm->number, itm->type->rtype)); + } + } + a_remove(&u->attribs, a); + add_income(u, IC_TRADE, u->n, u->n); + } +} + +static bool sell(unit * u, request ** sellorders, struct order *ord) +{ + bool unlimited = true; + const item_type *itype; + const luxury_type *ltype = NULL; + int n; + region *r = u->region; + const char *s; + + if (u->ship && is_guarded(r, u, GUARD_CREWS)) { + cmistake(u, ord, 69, MSG_INCOME); + return false; + } + /* sellorders sind KEIN array, weil für alle items DIE SELBE resource + * (das geld der region) aufgebraucht wird. */ + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + if (findparam(s, u->faction->locale) == P_ANY) { + unlimited = false; + n = rpeasants(r) / TRADE_FRACTION; + if (r->terrain == newterrain(T_DESERT) + && buildingtype_exists(r, bt_find("caravan"), true)) + n *= 2; + if (n == 0) { + cmistake(u, ord, 303, MSG_COMMERCE); + return false; + } + } else { + n = atoi((const char *)s); + if (n == 0) { + cmistake(u, ord, 27, MSG_COMMERCE); + return false; + } + } + /* Belagerte Einheiten können nichts verkaufen. */ + + if (besieged(u)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error60", "")); + return false; + } + /* In der Region muß es eine Burg geben. */ + + if (u_race(u) == new_race[RC_INSECT]) { + if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT) + && !rbuildings(r)) { + cmistake(u, ord, 119, MSG_COMMERCE); + return false; + } + } else { + /* ...oder in der Region muß es eine Burg geben. */ + building *b; + static const struct building_type *bt_castle; + if (!bt_castle) + bt_castle = bt_find("castle"); + for (b = r->buildings; b; b = b->next) { + if (b->type == bt_castle && b->size >= 2) + break; + } + if (b == NULL) { + cmistake(u, ord, 119, MSG_COMMERCE); + return false; + } + } + + /* Ein Händler kann nur 10 Güter pro Talentpunkt verkaufen. */ + + n = MIN(n, u->number * 10 * eff_skill(u, SK_TRADE, r)); + + if (!n) { + cmistake(u, ord, 54, MSG_COMMERCE); + return false; + } + s = getstrtoken(); + itype = finditemtype(s, u->faction->locale); + if (itype != NULL) + ltype = resource2luxury(itype->rtype); + if (ltype == NULL) { + cmistake(u, ord, 126, MSG_COMMERCE); + return false; + } else { + attrib *a; + request *o; + int k, available; + + if (!r_demand(r, ltype)) { + cmistake(u, ord, 263, MSG_COMMERCE); + return false; + } + available = get_pooled(u, itype->rtype, GET_DEFAULT, INT_MAX); + + /* Wenn andere Einheiten das selbe verkaufen, muß ihr Zeug abgezogen + * werden damit es nicht zweimal verkauft wird: */ + for (o = *sellorders; o; o = o->next) { + if (o->type.ltype == ltype && o->unit->faction == u->faction) { + int fpool = + o->qty - get_pooled(o->unit, itype->rtype, GET_RESERVE, INT_MAX); + available -= MAX(0, fpool); + } + } + + n = MIN(n, available); + + if (n <= 0) { + cmistake(u, ord, 264, MSG_COMMERCE); + return false; + } + /* Hier wird request->type verwendet, weil die obere limit durch + * das silber gegeben wird (region->money), welches für alle + * (!) produkte als summe gilt, als nicht wie bei der + * produktion, wo für jedes produkt einzeln eine obere limite + * existiert, so dass man arrays von orders machen kann. */ + + /* Ein Händler kann nur 10 Güter pro Talentpunkt handeln. */ + k = u->number * 10 * eff_skill(u, SK_TRADE, r); + + /* hat der Händler bereits gehandelt, muss die Menge der bereits + * verkauften/gekauften Güter abgezogen werden */ + a = a_find(u->attribs, &at_trades); + if (!a) { + a = a_add(&u->attribs, a_new(&at_trades)); + } else { + k -= a->data.i; + } + + n = MIN(n, k); + assert(n >= 0); + /* die Menge der verkauften Güter merken */ + a->data.i += n; + o = (request *) calloc(1, sizeof(request)); + o->unit = u; + o->qty = n; + o->type.ltype = ltype; + addlist(sellorders, o); + + return unlimited; + } +} + +/* ------------------------------------------------------------- */ + +static void expandstealing(region * r, request * stealorders) +{ + int i; + + expandorders(r, stealorders); + if (!norders) + return; + + /* Für jede unit in der Region wird Geld geklaut, wenn sie Opfer eines + * Beklauen-Orders ist. Jedes Opfer muß einzeln behandelt werden. + * + * u ist die beklaute unit. oa.unit ist die klauende unit. + */ + + for (i = 0; i != norders && oa[i].unit->n <= oa[i].unit->wants; i++) { + unit *u = findunitg(oa[i].no, r); + int n = 0; + if (u && u->region == r) { + n = get_pooled(u, r_silver, GET_ALL, INT_MAX); + } +#ifndef GOBLINKILL + if (oa[i].type.goblin) { /* Goblin-Spezialklau */ + int uct = 0; + unit *u2; + assert(effskill(oa[i].unit, SK_STEALTH) >= 4 + || !"this goblin\'s skill is too low"); + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction == u->faction) { + uct += maintenance_cost(u2); + } + } + n -= uct * 2; + } +#endif + if (n > 10 && rplane(r) && (rplane(r)->flags & PFL_NOALLIANCES)) { + /* In Questen nur reduziertes Klauen */ + n = 10; + } + if (n > 0) { + n = MIN(n, oa[i].unit->wants); + use_pooled(u, r_silver, GET_ALL, n); + oa[i].unit->n = n; + change_money(oa[i].unit, n); + ADDMSG(&u->faction->msgs, msg_message("stealeffect", "unit region amount", + u, u->region, n)); + } + add_income(oa[i].unit, IC_STEAL, oa[i].unit->wants, oa[i].unit->n); + fset(oa[i].unit, UFL_LONGACTION | UFL_NOTMOVING); + } + free(oa); +} + +/* ------------------------------------------------------------- */ +static void plant(region * r, unit * u, int raw) +{ + int n, i, skill, planted = 0; + const item_type *itype; + static const resource_type *rt_water = NULL; + if (rt_water == NULL) + rt_water = rt_find("p2"); + + assert(rt_water != NULL); + if (!fval(r->terrain, LAND_REGION)) { + return; + } + if (rherbtype(r) == NULL) { + cmistake(u, u->thisorder, 108, MSG_PRODUCE); + return; + } + + /* Skill prüfen */ + skill = eff_skill(u, SK_HERBALISM, r); + itype = rherbtype(r); + if (skill < 6) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "plant_skills", + "skill minskill product", SK_HERBALISM, 6, itype->rtype, 1)); + return; + } + /* Wasser des Lebens prüfen */ + if (get_pooled(u, rt_water, GET_DEFAULT, 1) == 0) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "resource_missing", "missing", rt_water)); + return; + } + n = get_pooled(u, itype->rtype, GET_DEFAULT, skill * u->number); + /* Kräuter prüfen */ + if (n == 0) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "resource_missing", "missing", + itype->rtype)); + return; + } + + n = MIN(skill * u->number, n); + n = MIN(raw, n); + /* Für jedes Kraut Talent*10% Erfolgschance. */ + for (i = n; i > 0; i--) { + if (rng_int() % 10 < skill) + planted++; + } + produceexp(u, SK_HERBALISM, u->number); + + /* Alles ok. Abziehen. */ + use_pooled(u, rt_water, GET_DEFAULT, 1); + use_pooled(u, itype->rtype, GET_DEFAULT, n); + rsetherbs(r, rherbs(r) + planted); + ADDMSG(&u->faction->msgs, msg_message("plant", "unit region amount herb", + u, r, planted, itype->rtype)); +} + +static void planttrees(region * r, unit * u, int raw) +{ + int n, i, skill, planted = 0; + const resource_type *rtype; + + if (!fval(r->terrain, LAND_REGION)) { + return; + } + + /* Mallornbäume kann man nur in Mallornregionen züchten */ + if (fval(r, RF_MALLORN)) { + rtype = rt_mallornseed; + } else { + rtype = rt_seed; + } + + /* Skill prüfen */ + skill = eff_skill(u, SK_HERBALISM, r); + if (skill < 6) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "plant_skills", + "skill minskill product", SK_HERBALISM, 6, rtype, 1)); + return; + } + if (fval(r, RF_MALLORN) && skill < 7) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "plant_skills", + "skill minskill product", SK_HERBALISM, 7, rtype, 1)); + return; + } + + /* wenn eine Anzahl angegeben wurde, nur soviel verbrauchen */ + raw = MIN(raw, skill * u->number); + n = get_pooled(u, rtype, GET_DEFAULT, raw); + if (n == 0) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "resource_missing", "missing", rtype)); + return; + } + n = MIN(raw, n); + + /* Für jeden Samen Talent*10% Erfolgschance. */ + for (i = n; i > 0; i--) { + if (rng_int() % 10 < skill) + planted++; + } + rsettrees(r, 0, rtrees(r, 0) + planted); + + /* Alles ok. Abziehen. */ + produceexp(u, SK_HERBALISM, u->number); + use_pooled(u, rtype, GET_DEFAULT, n); + + ADDMSG(&u->faction->msgs, msg_message("plant", + "unit region amount herb", u, r, planted, rtype)); +} + +/* züchte bäume */ +static void breedtrees(region * r, unit * u, int raw) +{ + int n, i, skill, planted = 0; + const resource_type *rtype; + static int gamecookie = -1; + static int current_season; + + if (gamecookie != global.cookie) { + gamedate date; + get_gamedate(turn, &date); + current_season = date.season; + gamecookie = global.cookie; + } + + /* Bäume züchten geht nur im Frühling */ + if (current_season != SEASON_SPRING) { + planttrees(r, u, raw); + return; + } + + if (!fval(r->terrain, LAND_REGION)) { + return; + } + + /* Mallornbäume kann man nur in Mallornregionen züchten */ + if (fval(r, RF_MALLORN)) { + rtype = rt_mallornseed; + } else { + rtype = rt_seed; + } + + /* Skill prüfen */ + skill = eff_skill(u, SK_HERBALISM, r); + if (skill < 12) { + planttrees(r, u, raw); + return; + } + + /* wenn eine Anzahl angegeben wurde, nur soviel verbrauchen */ + raw = MIN(skill * u->number, raw); + n = get_pooled(u, rtype, GET_DEFAULT, raw); + /* Samen prüfen */ + if (n == 0) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, u->thisorder, "resource_missing", "missing", rtype)); + return; + } + n = MIN(raw, n); + + /* Für jeden Samen Talent*5% Erfolgschance. */ + for (i = n; i > 0; i--) { + if (rng_int() % 100 < skill * 5) + planted++; + } + rsettrees(r, 1, rtrees(r, 1) + planted); + + /* Alles ok. Abziehen. */ + produceexp(u, SK_HERBALISM, u->number); + use_pooled(u, rtype, GET_DEFAULT, n); + + ADDMSG(&u->faction->msgs, msg_message("plant", + "unit region amount herb", u, r, planted, rtype)); +} + +/* züchte pferde */ +static void breedhorses(region * r, unit * u) +{ + int n, c; + int gezuechtet = 0; + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + + if (btype != bt_find("stables")) { + cmistake(u, u->thisorder, 122, MSG_PRODUCE); + return; + } + if (get_item(u, I_HORSE) < 2) { + cmistake(u, u->thisorder, 107, MSG_PRODUCE); + return; + } + n = MIN(u->number * eff_skill(u, SK_HORSE_TRAINING, r), get_item(u, I_HORSE)); + + for (c = 0; c < n; c++) { + if (rng_int() % 100 < eff_skill(u, SK_HORSE_TRAINING, r)) { + i_change(&u->items, olditemtype[I_HORSE], 1); + gezuechtet++; + } + } + + produceexp(u, SK_HORSE_TRAINING, u->number); + + ADDMSG(&u->faction->msgs, msg_message("raised", + "unit amount", u, gezuechtet)); +} + +static void breed_cmd(unit * u, struct order *ord) +{ + int m; + const char *s; + param_t p; + region *r = u->region; + const resource_type *rtype = NULL; + + if (r->land == NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_onlandonly", "")); + return; + } + + /* züchte [] */ + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + m = atoi((const char *)s); + if (m != 0) { + /* first came a want-paramter */ + s = getstrtoken(); + } else { + m = INT_MAX; + } + + if (!s[0]) { + p = P_ANY; + } else { + p = findparam(s, u->faction->locale); + } + + switch (p) { + case P_HERBS: + plant(r, u, m); + break; + case P_TREES: + breedtrees(r, u, m); + break; + default: + if (p != P_ANY) { + rtype = findresourcetype(s, u->faction->locale); + if (rtype == rt_mallornseed || rtype == rt_seed) { + breedtrees(r, u, m); + break; + } else if (rtype != oldresourcetype[R_HORSE]) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_cannotmake", "")); + break; + } + } + breedhorses(r, u); + break; + } +} + +static const char *rough_amount(int a, int m) +{ + int p = (a * 100) / m; + + if (p < 10) { + return "sehr wenige"; + } else if (p < 30) { + return "wenige"; + } else if (p < 60) { + return "relativ viele"; + } else if (p < 90) { + return "viele"; + } + return "sehr viele"; +} + +static void research_cmd(unit * u, struct order *ord) +{ + region *r = u->region; + + init_tokens(ord); + skip_token(); + /* + const char *s = getstrtoken(); + + if (findparam(s, u->faction->locale) == P_HERBS) { */ + + if (eff_skill(u, SK_HERBALISM, r) < 7) { + cmistake(u, ord, 227, MSG_EVENT); + return; + } + + produceexp(u, SK_HERBALISM, u->number); + + if (rherbs(r) > 0) { + const item_type *itype = rherbtype(r); + + if (itype != NULL) { + ADDMSG(&u->faction->msgs, msg_message("researchherb", + "unit region amount herb", + u, r, rough_amount(rherbs(r), 100), itype->rtype)); + } else { + ADDMSG(&u->faction->msgs, msg_message("researchherb_none", + "unit region", u, r)); + } + } else { + ADDMSG(&u->faction->msgs, msg_message("researchherb_none", + "unit region", u, r)); + } +} + +static int max_skill(region * r, faction * f, skill_t sk) +{ + unit *u; + int w = 0; + + for (u = r->units; u; u = u->next) { + if (u->faction == f) { + if (eff_skill(u, sk, r) > w) { + w = eff_skill(u, sk, r); + } + } + } + + return w; +} + +static void steal_cmd(unit * u, struct order *ord, request ** stealorders) +{ + int n, i, id; + bool goblin = false; + request *o; + unit *u2 = NULL; + region *r = u->region; + faction *f = NULL; + plane *pl; + + assert(skill_enabled[SK_PERCEPTION] && skill_enabled[SK_STEALTH]); + + if (!fval(u_race(u), RCF_CANSTEAL)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "race_nosteal", "race", + u_race(u))); + return; + } + + if (fval(r->terrain, SEA_REGION) && u_race(u) != new_race[RC_AQUARIAN]) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_onlandonly", "")); + return; + } + + pl = rplane(r); + if (pl && fval(pl, PFL_NOATTACK)) { + cmistake(u, ord, 270, MSG_INCOME); + return; + } + + init_tokens(ord); + skip_token(); + id = read_unitid(u->faction, r); + u2 = findunitr(r, id); + + if (u2 && u2->region == u->region) { + f = u2->faction; + } else { + f = dfindhash(id); + } + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction == f && cansee(u->faction, r, u2, 0)) + break; + } + + if (!u2) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + return; + } + + if (IsImmune(u2->faction)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "newbie_immunity_error", "turns", NewbieImmunity())); + return; + } + + if (u->faction->alliance && u->faction->alliance == u2->faction->alliance) { + cmistake(u, ord, 47, MSG_INCOME); + return; + } + + assert(u->region == u2->region); + if (!can_contact(r, u, u2)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error60", "")); + return; + } + + n = eff_skill(u, SK_STEALTH, r) - max_skill(r, f, SK_PERCEPTION); + + if (n <= 0) { + /* Wahrnehmung == Tarnung */ + if (u_race(u) != new_race[RC_GOBLIN] || eff_skill(u, SK_STEALTH, r) <= 3) { + ADDMSG(&u->faction->msgs, msg_message("stealfail", "unit target", u, u2)); + if (n == 0) { + ADDMSG(&u2->faction->msgs, msg_message("stealdetect", "unit", u2)); + } else { + ADDMSG(&u2->faction->msgs, msg_message("thiefdiscover", "unit target", + u, u2)); + } + return; + } else { + ADDMSG(&u->faction->msgs, msg_message("stealfatal", "unit target", u, + u2)); + ADDMSG(&u2->faction->msgs, msg_message("thiefdiscover", "unit target", u, + u2)); + n = 1; + goblin = true; + } + } + + i = MIN(u->number, get_item(u, I_RING_OF_NIMBLEFINGER)); + if (i > 0) { + n *= STEALINCOME * (u->number + i * (roqf_factor() - 1)); + } else { + n *= u->number * STEALINCOME; + } + + u->wants = n; + + /* wer dank unsichtbarkeitsringen klauen kann, muss nicht unbedingt ein + * guter dieb sein, schliesslich macht man immer noch sehr viel laerm */ + + o = (request *) calloc(1, sizeof(request)); + o->unit = u; + o->qty = 1; /* Betrag steht in u->wants */ + o->no = u2->no; + o->type.goblin = goblin; /* Merken, wenn Goblin-Spezialklau */ + addlist(stealorders, o); + + /* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */ + + produceexp(u, SK_STEALTH, MIN(n, u->number)); +} + +/* ------------------------------------------------------------- */ + +static void expandentertainment(region * r) +{ + unit *u; + int m = entertainmoney(r); + request *o; + + for (o = &entertainers[0]; o != nextentertainer; ++o) { + double part = m / (double)entertaining; + u = o->unit; + if (entertaining <= m) + u->n = o->qty; + else + u->n = (int)(o->qty * part); + change_money(u, u->n); + rsetmoney(r, rmoney(r) - u->n); + m -= u->n; + entertaining -= o->qty; + + /* Nur soviel PRODUCEEXP wie auch tatsächlich gemacht wurde */ + produceexp(u, SK_ENTERTAINMENT, MIN(u->n, u->number)); + add_income(u, IC_ENTERTAIN, o->qty, u->n); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + } +} + +void entertain_cmd(unit * u, struct order *ord) +{ + region *r = u->region; + int max_e; + request *o; + static int entertainbase = 0; + static int entertainperlevel = 0; + + if (!entertainbase) { + const char *str = get_param(global.parameters, "entertain.base"); + entertainbase = str ? atoi(str) : 0; + } + if (!entertainperlevel) { + const char *str = get_param(global.parameters, "entertain.perlevel"); + entertainperlevel = str ? atoi(str) : 0; + } + if (fval(u, UFL_WERE)) { + cmistake(u, ord, 58, MSG_INCOME); + return; + } + if (!effskill(u, SK_ENTERTAINMENT)) { + cmistake(u, ord, 58, MSG_INCOME); + return; + } + if (besieged(u)) { + cmistake(u, ord, 60, MSG_INCOME); + return; + } + if (u->ship && is_guarded(r, u, GUARD_CREWS)) { + cmistake(u, ord, 69, MSG_INCOME); + return; + } + if (is_cursed(r->attribs, C_DEPRESSION, 0)) { + cmistake(u, ord, 28, MSG_INCOME); + return; + } + + u->wants = u->number * (entertainbase + effskill(u, SK_ENTERTAINMENT) + * entertainperlevel); + + init_tokens(ord); + skip_token(); + max_e = getuint(); + if (max_e != 0) { + u->wants = MIN(u->wants, max_e); + } + o = nextentertainer++; + o->unit = u; + o->qty = u->wants; + entertaining += o->qty; +} + +/** + * \return number of working spaces taken by players + */ +static void +expandwork(region * r, request * work_begin, request * work_end, int maxwork) +{ + int earnings; + /* n: verbleibende Einnahmen */ + /* fishes: maximale Arbeiter */ + int jobs = maxwork; + int p_wage = wage(r, NULL, NULL, turn); + int money = rmoney(r); + request *o; + + for (o = work_begin; o != work_end; ++o) { + unit *u = o->unit; + int workers; + + if (u->number == 0) + continue; + + if (jobs >= working) + workers = u->number; + else { + workers = u->number * jobs / working; + if (rng_int() % working < (u->number * jobs) % working) + workers++; + } + + assert(workers >= 0); + + u->n = workers * wage(u->region, u->faction, u_race(u), turn); + + jobs -= workers; + assert(jobs >= 0); + + change_money(u, u->n); + working -= o->unit->number; + add_income(u, IC_WORK, o->qty, u->n); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + } + + if (jobs > rpeasants(r)) { + jobs = rpeasants(r); + } + earnings = jobs * p_wage; + if (rule_blessed_harvest() == HARVEST_TAXES) { + /* E3 rules */ + static const curse_type *blessedharvest_ct; + if (!blessedharvest_ct) { + blessedharvest_ct = ct_find("blessedharvest"); + } + if (blessedharvest_ct && r->attribs) { + int happy = + (int)curse_geteffect(get_curse(r->attribs, blessedharvest_ct)); + happy = MIN(happy, jobs); + earnings += happy; + } + } + rsetmoney(r, money + earnings); +} + +static int do_work(unit * u, order * ord, request * o) +{ + if (playerrace(u_race(u))) { + region *r = u->region; + int w; + + if (fval(u, UFL_WERE)) { + if (ord) + cmistake(u, ord, 313, MSG_INCOME); + return -1; + } + if (besieged(u)) { + if (ord) + cmistake(u, ord, 60, MSG_INCOME); + return -1; + } + if (u->ship && is_guarded(r, u, GUARD_CREWS)) { + if (ord) + cmistake(u, ord, 69, MSG_INCOME); + return -1; + } + w = wage(r, u->faction, u_race(u), turn); + u->wants = u->number * w; + o->unit = u; + o->qty = u->number * w; + working += u->number; + return 0; + } else if (ord && !is_monsters(u->faction)) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "race_cantwork", "race", u_race(u))); + } + return -1; +} + +static void expandtax(region * r, request * taxorders) +{ + unit *u; + int i; + + expandorders(r, taxorders); + if (!norders) + return; + + for (i = 0; i != norders && rmoney(r) > TAXFRACTION; i++) { + change_money(oa[i].unit, TAXFRACTION); + oa[i].unit->n += TAXFRACTION; + rsetmoney(r, rmoney(r) - TAXFRACTION); + } + free(oa); + + for (u = r->units; u; u = u->next) { + if (u->n >= 0) { + add_income(u, IC_TAX, u->wants, u->n); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + } + } +} + +void tax_cmd(unit * u, struct order *ord, request ** taxorders) +{ + /* Steuern werden noch vor der Forschung eingetrieben */ + region *r = u->region; + unit *u2; + int n; + request *o; + int max; + + if (!humanoidrace(u_race(u)) && !is_monsters(u->faction)) { + cmistake(u, ord, 228, MSG_INCOME); + return; + } + + if (fval(u, UFL_WERE)) { + cmistake(u, ord, 228, MSG_INCOME); + return; + } + + if (besieged(u)) { + cmistake(u, ord, 60, MSG_INCOME); + return; + } + n = armedmen(u, false); + + if (!n) { + cmistake(u, ord, 48, MSG_INCOME); + return; + } + + init_tokens(ord); + skip_token(); + max = getuint(); + + if (max == 0) + max = INT_MAX; + if (!playerrace(u_race(u))) { + u->wants = MIN(income(u), max); + } else { + u->wants = MIN(n * eff_skill(u, SK_TAXING, r) * 20, max); + } + + u2 = is_guarded(r, u, GUARD_TAX); + if (u2) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "region_guarded", "guard", u2)); + return; + } + + /* die einnahmen werden in fraktionen von 10 silber eingeteilt: diese + * fraktionen werden dann bei eintreiben unter allen eintreibenden + * einheiten aufgeteilt. */ + + o = (request *) calloc(1, sizeof(request)); + o->qty = u->wants / TAXFRACTION; + o->unit = u; + addlist(taxorders, o); + return; +} + +#define MAX_WORKERS 2048 +void auto_work(region * r) +{ + request workers[MAX_WORKERS]; + request *nextworker = workers; + unit *u; + + for (u = r->units; u; u = u->next) { + if (!(u->flags & UFL_LONGACTION) && !is_monsters(u->faction)) { + if (do_work(u, NULL, nextworker) == 0) { + assert(nextworker - workers < MAX_WORKERS); + ++nextworker; + } + } + } + if (nextworker != workers) { + expandwork(r, workers, nextworker, maxworkingpeasants(r)); + } +} + +static void peasant_taxes(region * r) +{ + faction *f; + unit *u; + building *b; + int money; + int maxsize; + + f = region_get_owner(r); + if (f == NULL || is_mourning(r, turn)) { + return; + } + money = rmoney(r); + if (money <= 0) + return; + + b = largestbuilding(r, cmp_taxes, false); + if (b == NULL) + return; + + u = building_owner(b); + if (u == NULL || u->faction != f) + return; + + maxsize = buildingeffsize(b, false); + if (maxsize > 0) { + double taxfactor = money * b->type->taxes(b, maxsize); + double morale = money * region_get_morale(r) * MORALE_TAX_FACTOR; + if (taxfactor > morale) + taxfactor = morale; + if (taxfactor > 0) { + int taxmoney = (int)taxfactor; + change_money(u, taxmoney); + rsetmoney(r, money - taxmoney); + ADDMSG(&u->faction->msgs, msg_message("income_tax", + "unit region amount", u, r, taxmoney)); + } + } +} + +void produce(struct region *r) +{ + request workers[MAX_WORKERS]; + request *taxorders, *sellorders, *stealorders, *buyorders; + unit *u; + int todo; + static int rule_autowork = -1; + bool limited = true; + request *nextworker = workers; + assert(r); + + /* das sind alles befehle, die 30 tage brauchen, und die in thisorder + * stehen! von allen 30-tage befehlen wird einfach der letzte verwendet + * (dosetdefaults). + * + * kaufen vor einnahmequellen. da man in einer region dasselbe produkt + * nicht kaufen und verkaufen kann, ist die reihenfolge wegen der + * produkte egal. nicht so wegen dem geld. + * + * lehren vor lernen. */ + + if (rule_autowork < 0) { + rule_autowork = get_param_int(global.parameters, "work.auto", 0); + } + + assert(rmoney(r) >= 0); + assert(rpeasants(r) >= 0); + + if (r->land && rule_auto_taxation()) { + /* new taxation rules, region owners make money based on morale and building */ + peasant_taxes(r); + } + + buyorders = 0; + sellorders = 0; + working = 0; + nextentertainer = &entertainers[0]; + entertaining = 0; + taxorders = 0; + stealorders = 0; + + for (u = r->units; u; u = u->next) { + order *ord; + bool trader = false; + + if (u_race(u) == new_race[RC_SPELL] || fval(u, UFL_LONGACTION)) + continue; + + if (u_race(u) == new_race[RC_INSECT] && r_insectstalled(r) && + !is_cursed(u->attribs, C_KAELTESCHUTZ, 0)) + continue; + + if (fval(u, UFL_LONGACTION) && u->thisorder == NULL) { + /* this message was already given in laws.setdefaults + cmistake(u, u->thisorder, 52, MSG_PRODUCE); + */ + continue; + } + + for (ord = u->orders; ord; ord = ord->next) { + keyword_t kwd = get_keyword(ord); + if (kwd == K_BUY) { + buy(u, &buyorders, ord); + trader = true; + } else if (kwd == K_SELL) { + /* sell returns true if the sale is not limited + * by the region limit */ + limited &= !sell(u, &sellorders, ord); + trader = true; + } + } + if (trader) { + attrib *a = a_find(u->attribs, &at_trades); + if (a && a->data.i) { + produceexp(u, SK_TRADE, u->number); + } + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + continue; + } + + todo = get_keyword(u->thisorder); + if (todo == NOKEYWORD) + continue; + + if (fval(r->terrain, SEA_REGION) && u_race(u) != new_race[RC_AQUARIAN] + && !(u_race(u)->flags & RCF_SWIM) + && todo != K_STEAL && todo != K_SPY && todo != K_SABOTAGE) + continue; + + switch (todo) { + + case K_ENTERTAIN: + entertain_cmd(u, u->thisorder); + break; + + case K_WORK: + if (!rule_autowork && do_work(u, u->thisorder, nextworker) == 0) { + assert(nextworker - workers < MAX_WORKERS); + ++nextworker; + } + break; + + case K_TAX: + tax_cmd(u, u->thisorder, &taxorders); + break; + + case K_STEAL: + steal_cmd(u, u->thisorder, &stealorders); + break; + + case K_SPY: + spy_cmd(u, u->thisorder); + break; + + case K_SABOTAGE: + sabotage_cmd(u, u->thisorder); + break; + + case K_PLANT: + case K_BREED: + breed_cmd(u, u->thisorder); + break; + + case K_RESEARCH: + research_cmd(u, u->thisorder); + break; + } + } + + /* Entertainment (expandentertainment) und Besteuerung (expandtax) vor den + * Befehlen, die den Bauern mehr Geld geben, damit man aus den Zahlen der + * letzten Runde berechnen kann, wieviel die Bauern für Unterhaltung + * auszugeben bereit sind. */ + if (entertaining) + expandentertainment(r); + if (!rule_autowork) { + expandwork(r, workers, nextworker, maxworkingpeasants(r)); + } + if (taxorders) + expandtax(r, taxorders); + + /* An erster Stelle Kaufen (expandbuying), die Bauern so Geld bekommen, um + * nachher zu beim Verkaufen (expandselling) den Spielern abkaufen zu + * können. */ + + if (buyorders) + expandbuying(r, buyorders); + + if (sellorders) { + int limit = rpeasants(r) / TRADE_FRACTION; + if (r->terrain == newterrain(T_DESERT) + && buildingtype_exists(r, bt_find("caravan"), true)) + limit *= 2; + expandselling(r, sellorders, limited ? limit : INT_MAX); + } + + /* Die Spieler sollen alles Geld verdienen, bevor sie beklaut werden + * (expandstealing). */ + + if (stealorders) + expandstealing(r, stealorders); + + assert(rmoney(r) >= 0); + assert(rpeasants(r) >= 0); +} diff --git a/core/src/gamecode/economy.h b/core/src/gamecode/economy.h new file mode 100644 index 000000000..8b44df380 --- /dev/null +++ b/core/src/gamecode/economy.h @@ -0,0 +1,63 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_GC_ECONOMY +#define H_GC_ECONOMY +#ifdef __cplusplus +extern "C" { +#endif + +/* Welchen Teil des Silbers die Bauern fuer Unterhaltung ausgeben (1/20), und + * wiviel Silber ein Unterhalter pro Talentpunkt bekommt. */ + +/* Wieviele Silbermuenzen jeweils auf einmal "getaxed" werden. */ + +#define TAXFRACTION 10 + +/* Wieviel Silber pro Talentpunkt geklaut wird. */ + +#define STEALINCOME 50 + +/* Teil der Bauern, welche Luxusgueter kaufen und verkaufen (1/100). */ + +#define TRADE_FRACTION 100 + + extern int income(const struct unit *u); + +/* Wieviel Fremde eine Partei pro Woche aufnehmen kann */ +#define MAXNEWBIES 5 + + void economics(struct region *r); + void produce(struct region *r); + void auto_work(struct region *r); + + enum { IC_WORK, IC_ENTERTAIN, IC_TAX, IC_TRADE, IC_TRADETAX, IC_STEAL, + IC_MAGIC }; + void maintain_buildings(struct region *r, bool crash); + extern void add_spende(struct faction *f1, struct faction *f2, int betrag, + struct region *r); + extern int make_cmd(struct unit *u, struct order *ord); + extern void split_allocations(struct region *r); + extern int recruit_archetypes(void); + extern int give_control_cmd(struct unit *u, struct order *ord); + extern void give_control(struct unit * u, struct unit * u2); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/economy_test.c b/core/src/gamecode/economy_test.c new file mode 100644 index 000000000..61294b1da --- /dev/null +++ b/core/src/gamecode/economy_test.c @@ -0,0 +1,61 @@ +#include +#include +#include "economy.h" + +#include +#include +#include +#include + +#include +#include + +static void test_give_control_building(CuTest * tc) +{ + unit *u1, *u2; + building *b; + struct faction *f; + region *r; + + test_cleanup(); + test_create_world(); + f = test_create_faction(0); + r = findregion(0, 0); + b = test_create_building(r, 0); + u1 = test_create_unit(f, r); + u_set_building(u1, b); + u2 = test_create_unit(f, r); + u_set_building(u2, b); + CuAssertPtrEquals(tc, u1, building_owner(b)); + give_control(u1, u2); + CuAssertPtrEquals(tc, u2, building_owner(b)); +} + +static void test_give_control_ship(CuTest * tc) +{ + unit *u1, *u2; + ship *sh; + struct faction *f; + region *r; + + test_cleanup(); + test_create_world(); + f = test_create_faction(0); + r = findregion(0, 0); + sh = test_create_ship(r, 0); + u1 = test_create_unit(f, r); + u_set_ship(u1, sh); + u2 = test_create_unit(f, r); + u_set_ship(u2, sh); + CuAssertPtrEquals(tc, u1, ship_owner(sh)); + give_control(u1, u2); + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +CuSuite *get_economy_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_give_control_building); + SUITE_ADD_TEST(suite, test_give_control_ship); + return suite; +} diff --git a/core/src/gamecode/give.c b/core/src/gamecode/give.c new file mode 100644 index 000000000..a2d0a83b3 --- /dev/null +++ b/core/src/gamecode/give.c @@ -0,0 +1,456 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ +#include +#include +#include "give.h" + +#include "economy.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* attributes includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +/* Wieviel Fremde eine Partei pro Woche aufnehmen kangiven */ +#define MAXNEWBIES 5 +#define RESERVE_DONATIONS /* shall we reserve objects given to us by other factions? */ +#define RESERVE_GIVE /* reserve anything that's given from one unit to another? */ + +static int GiveRestriction(void) +{ + static int value = -1; + if (value < 0) { + const char *str = get_param(global.parameters, "GiveRestriction"); + value = str ? atoi(str) : 0; + } + return value; +} + +static void +add_give(unit * u, unit * u2, int given, int received, + const resource_type * rtype, struct order *ord, int error) +{ + if (error) { + cmistake(u, ord, error, MSG_COMMERCE); + } else if (u2 == NULL) { + ADDMSG(&u->faction->msgs, + msg_message("give_peasants", "unit resource amount", u, rtype, given)); + } else if (u2->faction != u->faction) { + message *msg; + + msg = + msg_message("give", "unit target resource amount", u, u2, rtype, given); + add_message(&u->faction->msgs, msg); + msg_release(msg); + + msg = + msg_message("receive", "unit target resource amount", u, u2, rtype, + received); + add_message(&u2->faction->msgs, msg); + msg_release(msg); + } +} + +static bool limited_give(const item_type * type) +{ + /* trade only money 2:1, if at all */ + return (type == i_silver); +} + +int give_quota(const unit * src, const unit * dst, const item_type * type, + int n) +{ + static float divisor = -1; + + if (divisor == 0 || !limited_give(type)) { + return n; + } + if (dst && src && src->faction != dst->faction) { + if (divisor < 0) { + divisor = get_param_flt(global.parameters, "rules.items.give_divisor", 1); + assert(divisor == 0 || divisor >= 1); + } + if (divisor >= 1) { + /* predictable > correct: */ + int x = (int)(n / divisor); + return x; + } + } + return n; +} + +int +give_item(int want, const item_type * itype, unit * src, unit * dest, + struct order *ord) +{ + short error = 0; + int n, r; + + assert(itype != NULL); + n = get_pooled(src, item2resource(itype), GET_DEFAULT, want); + n = MIN(want, n); + r = n; + if (dest && src->faction != dest->faction + && src->faction->age < GiveRestriction()) { + if (ord != NULL) { + ADDMSG(&src->faction->msgs, msg_feedback(src, ord, "giverestriction", + "turns", GiveRestriction())); + } + return -1; + } else if (n == 0) { + int reserve = get_reservation(src, itype->rtype); + if (reserve) { + msg_feedback(src, ord, "nogive_reserved", "resource reservation", + itype->rtype, reserve); + return -1; + } + error = 36; + } else if (itype->flags & ITF_CURSED) { + error = 25; + } else if (itype->give == NULL || itype->give(src, dest, itype, n, ord) != 0) { + int use = use_pooled(src, item2resource(itype), GET_SLACK, n); + if (use < n) + use += + use_pooled(src, item2resource(itype), GET_RESERVE | GET_POOLED_SLACK, + n - use); + if (dest) { + r = give_quota(src, dest, itype, n); + i_change(&dest->items, itype, r); +#ifdef RESERVE_GIVE +#ifdef RESERVE_DONATIONS + change_reservation(dest, item2resource(itype), r); +#else + if (src->faction == dest->faction) { + change_reservation(dest, item2resource(itype), r); + } +#endif +#endif +#if MUSEUM_MODULE && defined(TODO) + /* TODO: use a trigger for the museum warden! */ + if (a_find(dest->attribs, &at_warden)) { + warden_add_give(src, dest, itype, r); + } +#endif + handle_event(dest->attribs, "receive", src); + } + handle_event(src->attribs, "give", dest); + } + add_give(src, dest, n, r, item2resource(itype), ord, error); + if (error) + return -1; + return 0; +} + +void give_men(int n, unit * u, unit * u2, struct order *ord) +{ + ship *sh; + int k = 0; + int error = 0; + + if (u2 && u->faction != u2->faction && u->faction->age < GiveRestriction()) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "giverestriction", + "turns", GiveRestriction())); + return; + } else if (u == u2) { + error = 10; + } else if (!u2 && u_race(u) == new_race[RC_SNOTLING]) { + /* snotlings may not be given to the peasants. */ + error = 307; + } else if (u2 && u2->number && (fval(u, UFL_HERO) != fval(u2, UFL_HERO))) { + /* heroes may not be given to non-heroes and vice versa */ + error = 75; + } else if (unit_has_cursed_item(u) || (u2 && unit_has_cursed_item(u2))) { + error = 78; + } else if (fval(u, UFL_LOCKED) || is_cursed(u->attribs, C_SLAVE, 0)) { + error = 74; + } else if (u2 && fval(u, UFL_HUNGER)) { + /* hungry people cannot be given away */ + error = 73; + } else if (u2 && (fval(u2, UFL_LOCKED) || is_cursed(u2->attribs, C_SLAVE, 0))) { + error = 75; + } else if (u2 && !ucontact(u2, u)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_no_contact", + "target", u2)); + error = -1; + } else if (u2 && (has_skill(u, SK_MAGIC) || has_skill(u2, SK_MAGIC))) { + /* cannot give units to and from magicians */ + error = 158; + } else if (u2 && (fval(u, UFL_WERE) != fval(u2, UFL_WERE))) { + /* werewolves can't be given to non-werewolves and vice-versa */ + error = 312; + } else if (u2 && u2->number != 0 && u_race(u2) != u_race(u)) { + log_warning("faction %s attempts to give %s to %s.\n", itoa36(u->faction->no), u_race(u)->_name[0], u_race(u2)->_name[1]); + error = 139; + } else if (u2 != NULL && (get_racename(u2->attribs) + || get_racename(u->attribs))) { + error = 139; + } else if (u2 && u2->faction != u->faction && !rule_transfermen()) { + error = 74; + } else { + if (n > u->number) + n = u->number; + if (u2 && n + u2->number > UNIT_MAXSIZE) { + n = UNIT_MAXSIZE - u2->number; + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_unit_size", + "maxsize", UNIT_MAXSIZE)); + assert(n >= 0); + } + if (n == 0) { + error = 96; + } else if (u2 && u->faction != u2->faction) { + if (u2->faction->newbies + n > MAXNEWBIES) { + error = 129; + } else if (u_race(u) != u2->faction->race) { + if (u2->faction->race != new_race[RC_HUMAN]) { + error = 120; + } else if (count_migrants(u2->faction) + n > + count_maxmigrants(u2->faction)) { + error = 128; + } else if (has_limited_skills(u) || has_limited_skills(u2)) { + error = 154; + } else if (u2->number != 0) { + error = 139; + } + } + } + } + + if (u2 && (has_skill(u, SK_ALCHEMY) || has_skill(u2, SK_ALCHEMY))) { + k = count_skill(u2->faction, SK_ALCHEMY); + + /* Falls die Zieleinheit keine Alchemisten sind, werden sie nun + * welche. */ + if (!has_skill(u2, SK_ALCHEMY) && has_skill(u, SK_ALCHEMY)) + k += u2->number; + + /* Wenn in eine Alchemisteneinheit Personen verschoben werden */ + if (has_skill(u2, SK_ALCHEMY) && !has_skill(u, SK_ALCHEMY)) + k += n; + + /* Wenn Parteigrenzen überschritten werden */ + if (u2->faction != u->faction) + k += n; + + /* wird das Alchemistenmaximum ueberschritten ? */ + + if (k > skill_limit(u2->faction, SK_ALCHEMY)) { + error = 156; + } + } + + if (error == 0) { + if (u2 && u2->number == 0) { + set_racename(&u2->attribs, get_racename(u->attribs)); + u_setrace(u2, u_race(u)); + u2->irace = u->irace; + if (fval(u, UFL_HERO)) + fset(u2, UFL_HERO); + else + freset(u2, UFL_HERO); + } + + if (u2) { + if (u2->number != 0 && recruit_archetypes()) { + /* must have same set of skills */ + bool okay = false; + if (u->skill_size == u2->skill_size) { + int i; + for (i = 0; i != u->skill_size; ++i) { + int j; + for (j = 0; j != u2->skill_size; ++j) { + if (u->skills[i].id == u2->skills[j].id) + break; + } + if (j != u2->skill_size) + break; + } + if (i == u->skill_size) + okay = true; + } + if (!okay) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "give_cannot_merge", + "")); + } + } + + /* Einheiten von Schiffen können nicht NACH in von + * Nicht-alliierten bewachten Regionen ausführen */ + sh = leftship(u); + if (sh) { + set_leftship(u2, sh); + } + transfermen(u, u2, n); + if (u->faction != u2->faction) { + u2->faction->newbies += n; + } + } else { + if (getunitpeasants) { +#ifdef ORCIFICATION + if (u_race(u) == new_race[RC_SNOTLING] && !fval(u->region, RF_ORCIFIED)) { + attrib *a = a_find(u->region->attribs, &at_orcification); + if (!a) + a = a_add(&u->region->attribs, a_new(&at_orcification)); + a->data.i += n; + } +#endif + transfermen(u, NULL, n); + } else { + error = 159; + } + } + } + if (error > 0) { + cmistake(u, ord, error, MSG_COMMERCE); + } else if (!u2) { + ADDMSG(&u->faction->msgs, + msg_message("give_person_peasants", "unit amount", u, n)); + } else if (u2->faction != u->faction) { + message *msg = msg_message("give_person", "unit target amount", u, u2, n); + add_message(&u->faction->msgs, msg); + add_message(&u2->faction->msgs, msg); + msg_release(msg); + } +} + +void give_unit(unit * u, unit * u2, order * ord) +{ + region *r = u->region; + int n = u->number; + + if (!rule_transfermen() && u->faction != u2->faction) { + cmistake(u, ord, 74, MSG_COMMERCE); + return; + } + + if (u && unit_has_cursed_item(u)) { + cmistake(u, ord, 78, MSG_COMMERCE); + return; + } + + if (fval(u, UFL_HERO)) { + cmistake(u, ord, 75, MSG_COMMERCE); + return; + } + if (fval(u, UFL_LOCKED) || fval(u, UFL_HUNGER)) { + cmistake(u, ord, 74, MSG_COMMERCE); + return; + } + + if (u2 == NULL) { + if (fval(r->terrain, SEA_REGION)) { + cmistake(u, ord, 152, MSG_COMMERCE); + } else if (getunitpeasants) { + unit *u3; + + for (u3 = r->units; u3; u3 = u3->next) + if (u3->faction == u->faction && u != u3) + break; + + if (u3) { + while (u->items) { + item *iold = i_remove(&u->items, u->items); + item *inew = *i_find(&u3->items, iold->type); + if (inew == NULL) + i_add(&u3->items, iold); + else { + inew->number += iold->number; + i_free(iold); + } + } + } + give_men(u->number, u, NULL, ord); + cmistake(u, ord, 153, MSG_COMMERCE); + } else { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + } + return; + } + + if (!alliedunit(u2, u->faction, HELP_GIVE) && ucontact(u2, u) == 0) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_no_contact", + "target", u2)); + return; + } + if (u->number == 0) { + cmistake(u, ord, 105, MSG_COMMERCE); + return; + } + if (u2->faction->newbies + n > MAXNEWBIES) { + cmistake(u, ord, 129, MSG_COMMERCE); + return; + } + if (u_race(u) != u2->faction->race) { + if (u2->faction->race != new_race[RC_HUMAN]) { + cmistake(u, ord, 120, MSG_COMMERCE); + return; + } + if (count_migrants(u2->faction) + u->number > + count_maxmigrants(u2->faction)) { + cmistake(u, ord, 128, MSG_COMMERCE); + return; + } + if (has_limited_skills(u)) { + cmistake(u, ord, 154, MSG_COMMERCE); + return; + } + } + if (has_skill(u, SK_MAGIC)) { + sc_mage *mage; + if (count_skill(u2->faction, SK_MAGIC) + u->number > + skill_limit(u2->faction, SK_MAGIC)) { + cmistake(u, ord, 155, MSG_COMMERCE); + return; + } + mage = get_mage(u); + if (!mage || u2->faction->magiegebiet != mage->magietyp) { + cmistake(u, ord, 157, MSG_COMMERCE); + return; + } + } + if (has_skill(u, SK_ALCHEMY) + && count_skill(u2->faction, SK_ALCHEMY) + u->number > + skill_limit(u2->faction, SK_ALCHEMY)) { + cmistake(u, ord, 156, MSG_COMMERCE); + return; + } + add_give(u, u2, 1, 1, r_unit, ord, 0); + u_setfaction(u, u2->faction); + u2->faction->newbies += n; +} diff --git a/core/src/gamecode/give.h b/core/src/gamecode/give.h new file mode 100644 index 000000000..9d2553e5e --- /dev/null +++ b/core/src/gamecode/give.h @@ -0,0 +1,28 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ +#ifndef H_GC_GIVE +#define H_GC_GIVE +#ifdef __cplusplus +extern "C" { +#endif + + extern int give_item(int want, const struct item_type *itype, + struct unit *src, struct unit *dest, struct order *ord); + extern void give_men(int n, struct unit *u, struct unit *u2, + struct order *ord); + extern void give_unit(struct unit *u, struct unit *u2, struct order *ord); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/items.c b/core/src/gamecode/items.c new file mode 100644 index 000000000..8c6ec2688 --- /dev/null +++ b/core/src/gamecode/items.c @@ -0,0 +1,276 @@ +#include +#include +#include "items.h" + +#include "study.h" +#include "curses.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +/* BEGIN studypotion */ +#define MAXGAIN 15 +static int +use_studypotion(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + if (get_keyword(u->thisorder) == K_STUDY) { + skill_t sk; + skill *sv; + + init_tokens(u->thisorder); + skip_token(); + sk = findskill(getstrtoken(), u->faction->locale); + sv = get_skill(u, sk); + + if (sv && sv->level > 2) { + /* TODO: message */ + } else if (study_cost(u, sk) > 0) { + /* TODO: message */ + } else { + attrib *a = a_find(u->attribs, &at_learning); + teaching_info *teach; + if (a == NULL) { + a = a_add(&u->attribs, a_new(&at_learning)); + } + teach = (teaching_info *) a->data.v; + if (amount > MAXGAIN) + amount = MAXGAIN; + teach->value += amount * 30; + if (teach->value > MAXGAIN * 30) { + teach->value = MAXGAIN * 30; + } + i_change(&u->items, itype, -amount); + return 0; + } + } + return EUNUSABLE; +} + +/* END studypotion */ + +/* BEGIN speedsail */ +#define SPEEDSAIL_EFFECT 1 +static int +use_speedsail(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + curse *c; + double effect; + ship *sh = u->ship; + if (!sh) { + cmistake(u, ord, 20, MSG_MOVE); + return -1; + } + + effect = SPEEDSAIL_EFFECT; + c = + create_curse(u, &sh->attribs, ct_find("shipspeedup"), 20, INT_MAX, effect, + 0); + c_setflag(c, CURSE_NOAGE); + + ADDMSG(&u->faction->msgs, msg_message("use_speedsail", "unit speed", u, + SPEEDSAIL_EFFECT)); + use_pooled(u, itype->rtype, GET_DEFAULT, 1); + + return 0; +} + +/* END speedsail */ + +/* ------------------------------------------------------------- */ +/* Kann auch von Nichtmagiern benutzt werden, erzeugt eine +* Antimagiezone, die zwei Runden bestehen bleibt */ +static int +use_antimagiccrystal(unit * u, const struct item_type *itype, int amount, + struct order *ord) +{ + region *r = u->region; + const resource_type *rt_crystal = NULL; + int i; + + if (rt_crystal == NULL) { + rt_crystal = rt_find("antimagic"); + assert(rt_crystal != NULL); + } + for (i = 0; i != amount; ++i) { + int effect, duration = 2; + double force; + spell *sp = find_spell("antimagiczone"); + attrib **ap = &r->attribs; + unused(ord); + assert(sp); + + /* Reduziert die Stärke jedes Spruchs um effect */ + effect = 5; + + /* Hält Sprüche bis zu einem summierten Gesamtlevel von power aus. + * Jeder Zauber reduziert die 'Lebenskraft' (vigour) der Antimagiezone + * um seine Stufe */ + force = effect * 20; /* Stufe 5 =~ 100 */ + + /* Regionszauber auflösen */ + while (*ap && force > 0) { + curse *c; + attrib *a = *ap; + if (!fval(a->type, ATF_CURSE)) { + do { + ap = &(*ap)->next; + } while (*ap && a->type == (*ap)->type); + continue; + } + c = (curse *) a->data.v; + + /* Immunität prüfen */ + if (c_flags(c) & CURSE_IMMUNE) { + do { + ap = &(*ap)->next; + } while (*ap && a->type == (*ap)->type); + continue; + } + + force = destr_curse(c, effect, force); + if (c->vigour <= 0) { + a_remove(&r->attribs, a); + } + if (*ap) + ap = &(*ap)->next; + } + + if (force > 0) { + create_curse(u, &r->attribs, ct_find("antimagiczone"), force, duration, + effect, 0); + } + } + use_pooled(u, rt_crystal, GET_DEFAULT, amount); + ADDMSG(&u->faction->msgs, msg_message("use_antimagiccrystal", + "unit region", u, r)); + return 0; +} + +static int +use_instantartsculpture(struct unit *u, const struct item_type *itype, + int amount, struct order *ord) +{ + building *b; + + if (u->region->land == NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_onlandonly", "")); + return -1; + } + + b = new_building(bt_find("artsculpture"), u->region, u->faction->locale); + b->size = 100; + + ADDMSG(&u->region->msgs, msg_message("artsculpture_create", "unit region", + u, u->region)); + + use_pooled(u, itype->rtype, GET_DEFAULT, 1); + + return 0; +} + +static int +use_instantartacademy(struct unit *u, const struct item_type *itype, + int amount, struct order *ord) +{ + building *b; + + if (u->region->land == NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_onlandonly", "")); + return -1; + } + + b = new_building(bt_find("artacademy"), u->region, u->faction->locale); + b->size = 100; + + ADDMSG(&u->region->msgs, msg_message("artacademy_create", "unit region", u, + u->region)); + + use_pooled(u, itype->rtype, GET_DEFAULT, 1); + + return 0; +} + +#define BAGPIPEFRACTION dice_rand("2d4+2") +#define BAGPIPEDURATION dice_rand("2d10+4") + +static int +use_bagpipeoffear(struct unit *u, const struct item_type *itype, + int amount, struct order *ord) +{ + int money; + + if (get_curse(u->region->attribs, ct_find("depression"))) { + cmistake(u, ord, 58, MSG_MAGIC); + return -1; + } + + money = entertainmoney(u->region) / BAGPIPEFRACTION; + change_money(u, money); + rsetmoney(u->region, rmoney(u->region) - money); + + create_curse(u, &u->region->attribs, ct_find("depression"), + 20, BAGPIPEDURATION, 0.0, 0); + + ADDMSG(&u->faction->msgs, msg_message("bagpipeoffear_faction", + "unit region command money", u, u->region, ord, money)); + + ADDMSG(&u->region->msgs, msg_message("bagpipeoffear_region", + "unit money", u, money)); + + return 0; +} + +static int +use_aurapotion50(struct unit *u, const struct item_type *itype, + int amount, struct order *ord) +{ + if (!is_mage(u)) { + cmistake(u, ord, 214, MSG_MAGIC); + return -1; + } + + change_spellpoints(u, 50); + + ADDMSG(&u->faction->msgs, msg_message("aurapotion50", + "unit region command", u, u->region, ord)); + + use_pooled(u, itype->rtype, GET_DEFAULT, 1); + + return 0; +} + +void register_itemfunctions(void) +{ + register_demonseye(); + register_item_use(use_antimagiccrystal, "use_antimagiccrystal"); + register_item_use(use_instantartsculpture, "use_instantartsculpture"); + register_item_use(use_studypotion, "use_studypotion"); + register_item_use(use_speedsail, "use_speedsail"); + register_item_use(use_instantartacademy, "use_instantartacademy"); + register_item_use(use_bagpipeoffear, "use_bagpipeoffear"); + register_item_use(use_aurapotion50, "use_aurapotion50"); +} diff --git a/core/src/gamecode/items.h b/core/src/gamecode/items.h new file mode 100644 index 000000000..5de50e512 --- /dev/null +++ b/core/src/gamecode/items.h @@ -0,0 +1,24 @@ +/* vi: set ts=2: ++-------------------+ Christian Schlittchen +| | Enno Rehling +| Eressea PBEM host | Katja Zedel +| (c) 1998 - 2003 | Henning Peters +| | Ingo Wilken ++-------------------+ Stefan Reich + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifndef H_KRNL_ITEMS +#define H_KRNL_ITEMS +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_itemfunctions(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/laws.c b/core/src/gamecode/laws.c new file mode 100755 index 000000000..bd8eb859f --- /dev/null +++ b/core/src/gamecode/laws.c @@ -0,0 +1,4737 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#pragma region includes + +#include +#include +#include "laws.h" + +#include +#include + +/* gamecode includes */ +#include "economy.h" +#include "archetype.h" +#include "monster.h" +#include "randenc.h" +#include "spy.h" +#include "study.h" +#include "market.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for volcanoes in emigration (needs a flag) */ +#include + +/* attributes includes */ +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#pragma endregion + +/* chance that a peasant dies of starvation: */ +#define PEASANT_STARVATION_CHANCE 0.9F +/* Pferdevermehrung */ +#define HORSEGROWTH 4 +/* Wanderungschance pro Pferd */ +#define HORSEMOVE 3 +/* Vermehrungschance pro Baum */ +#define FORESTGROWTH 10000 /* In Millionstel */ + +/** Ausbreitung und Vermehrung */ +#define MAXDEMAND 25 +#define DMRISE 0.1F /* weekly chance that demand goes up */ +#define DMRISEHAFEN 0.2F /* weekly chance that demand goes up with harbor */ + +/* - exported global symbols ----------------------------------- */ + +static int RemoveNMRNewbie(void) +{ + static int value = -1; + static int gamecookie = -1; + + if (value < 0 || gamecookie != global.cookie) { + value = get_param_int(global.parameters, "nmr.removenewbie", 0); + gamecookie = global.cookie; + } + return value; +} + +static void checkorders(void) +{ + faction *f; + + log_info(" - Warne spaete Spieler..."); + for (f = factions; f; f = f->next) + if (!is_monsters(f) && turn - f->lastorders == NMRTimeout() - 1) + ADDMSG(&f->msgs, msg_message("turnreminder", "")); +} + +static bool help_money(const unit * u) +{ + if (u_race(u)->ec_flags & GIVEITEM) + return true; + return false; +} + +static void help_feed(unit * donor, unit * u, int *need_p) +{ + int need = *need_p; + int give = get_money(donor) - lifestyle(donor); + give = MIN(need, give); + + if (give > 0) { + change_money(donor, -give); + change_money(u, give); + need -= give; + add_spende(donor->faction, u->faction, give, donor->region); + } + *need_p = need; +} + +enum { + FOOD_FROM_PEASANTS = 1, + FOOD_FROM_OWNER = 2, + FOOD_IS_FREE = 4 +}; + +void get_food(region * r) +{ + plane *pl = rplane(r); + unit *u; + int peasantfood = rpeasants(r) * 10; + static int food_rules = -1; + static int gamecookie = -1; + + if (food_rules < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + food_rules = get_param_int(global.parameters, "rules.economy.food", 0); + } + + if (food_rules & FOOD_IS_FREE) { + return; + } + /* 1. Versorgung von eigenen Einheiten. Das vorhandene Silber + * wird zunächst so auf die Einheiten aufgeteilt, dass idealerweise + * jede Einheit genug Silber für ihren Unterhalt hat. */ + + for (u = r->units; u; u = u->next) { + int need = lifestyle(u); + + /* Erstmal zurücksetzen */ + freset(u, UFL_HUNGER); + + if (u->ship && (u->ship->flags & SF_FISHING)) { + unit *v; + int c = 2; + for (v = u; c > 0 && v; v = v->next) { + if (v->ship == u->ship) { + int get = 0; + if (v->number <= c) { + get = lifestyle(v); + } else { + get = lifestyle(v) * c / v->number; + } + if (get) { + change_money(v, get); + } + } + c -= v->number; + } + u->ship->flags -= SF_FISHING; + } + + if (food_rules & FOOD_FROM_PEASANTS) { + faction *owner = region_get_owner(r); + /* if the region is owned, and the owner is nice, then we'll get + * food from the peasants - should not be used with WORK */ + if (owner != NULL && (get_alliance(owner, u->faction) & HELP_MONEY)) { + int rm = rmoney(r); + int use = MIN(rm, need); + rsetmoney(r, rm - use); + need -= use; + } + } + + need -= get_money(u); + if (need > 0) { + unit *v; + + for (v = r->units; need && v; v = v->next) { + if (v->faction == u->faction && help_money(v)) { + int give = get_money(v) - lifestyle(v); + give = MIN(need, give); + if (give > 0) { + change_money(v, -give); + change_money(u, give); + need -= give; + } + } + } + } + } + + /* 2. Versorgung durch Fremde. Das Silber alliierter Einheiten wird + * entsprechend verteilt. */ + for (u = r->units; u; u = u->next) { + int need = lifestyle(u); + faction *f = u->faction; + + need -= MAX(0, get_money(u)); + + if (need > 0) { + unit *v; + + if (food_rules & FOOD_FROM_OWNER) { + /* the owner of the region is the first faction to help out when you're hungry */ + faction *owner = region_get_owner(r); + if (owner && owner != u->faction) { + for (v = r->units; v; v = v->next) { + if (v->faction == owner && alliedunit(v, f, HELP_MONEY) + && help_money(v)) { + help_feed(v, u, &need); + break; + } + } + } + } + for (v = r->units; need && v; v = v->next) { + if (v->faction != f && alliedunit(v, f, HELP_MONEY) + && help_money(v)) { + help_feed(v, u, &need); + } + } + + /* Die Einheit hat nicht genug Geld zusammengekratzt und + * nimmt Schaden: */ + if (need > 0) { + int lspp = lifestyle(u) / u->number; + if (lspp > 0) { + int number = (need + lspp - 1) / lspp; + if (hunger(number, u)) + fset(u, UFL_HUNGER); + } + } + } + } + + /* 3. bestimmen, wie viele Bauern gefressen werden. + * bei fehlenden Bauern den Dämon hungern lassen + */ + for (u = r->units; u; u = u->next) { + if (u_race(u) == new_race[RC_DAEMON]) { + int hungry = u->number; + + /* use peasantblood before eating the peasants themselves */ + static const struct potion_type *pt_blood; + if (pt_blood == NULL) { + const item_type *it_blood = it_find("peasantblood"); + if (it_blood) + pt_blood = it_blood->rtype->ptype; + } + if (pt_blood != NULL) { + /* always start with the unit itself, then the first known unit that may have some blood */ + unit *donor = u; + while (donor != NULL && hungry > 0) { + int blut = get_effect(donor, pt_blood); + blut = MIN(blut, hungry); + change_effect(donor, pt_blood, -blut); + hungry -= blut; + if (donor == u) + donor = r->units; + while (donor != NULL) { + if (u_race(donor) == new_race[RC_DAEMON] && donor!=u) { + if (get_effect(donor, pt_blood)) { + /* if he's in our faction, drain him: */ + if (donor->faction == u->faction) + break; + } + } + donor = donor->next; + } + } + } + /* remaining demons feed on peasants */ + if (pl == NULL || !fval(pl, PFL_NOFEED)) { + if (peasantfood >= hungry) { + peasantfood -= hungry; + hungry = 0; + } else { + hungry -= peasantfood; + peasantfood = 0; + } + if (hungry > 0) { + static int demon_hunger = -1; + if (demon_hunger < 0) { + demon_hunger = get_param_int(global.parameters, "hunger.demons", 0); + } + if (demon_hunger == 0) { + /* demons who don't feed are hungry */ + if (hunger(hungry, u)) + fset(u, UFL_HUNGER); + } else { + /* no damage, but set the hungry-flag */ + fset(u, UFL_HUNGER); + } + } + } + } + } + rsetpeasants(r, peasantfood / 10); + + /* 3. Von den überlebenden das Geld abziehen: */ + for (u = r->units; u; u = u->next) { + int need = MIN(get_money(u), lifestyle(u)); + change_money(u, -need); + } +} + +static void age_unit(region * r, unit * u) +{ + if (u_race(u) == new_race[RC_SPELL]) { + if (--u->age <= 0) { + remove_unit(&r->units, u); + } + } else { + ++u->age; + if (u->number > 0 && u_race(u)->age) { + u_race(u)->age(u); + } + } +#ifdef ASTRAL_ITEM_RESTRICTIONS + if (u->region && is_astral(u->region)) { + item **itemp = &u->items; + while (*itemp) { + item *itm = *itemp; + if ((itm->type->flags & ITF_NOTLOST) == 0) { + if (itm->type->flags & (ITF_BIG | ITF_ANIMAL | ITF_CURSED)) { + ADDMSG(&u->faction->msgs, msg_message("itemcrumble", + "unit region item amount", + u, u->region, itm->type->rtype, itm->number)); + i_free(i_remove(itemp, itm)); + continue; + } + } + itemp = &itm->next; + } + } +#endif +} + +static void live(region * r) +{ + unit **up = &r->units; + + get_food(r); + + while (*up) { + unit *u = *up; + /* IUW: age_unit() kann u loeschen, u->next ist dann + * undefiniert, also muessen wir hier schon das nächste + * Element bestimmen */ + + int effect = get_effect(u, oldpotiontype[P_FOOL]); + if (effect > 0) { /* Trank "Dumpfbackenbrot" */ + skill *sv = u->skills, *sb = NULL; + while (sv != u->skills + u->skill_size) { + if (sb == NULL || skill_compare(sv, sb) > 0) { + sb = sv; + } + ++sv; + } + /* bestes Talent raussuchen */ + if (sb != NULL) { + int weeks = MIN(effect, u->number); + reduce_skill(u, sb, weeks); + ADDMSG(&u->faction->msgs, msg_message("dumbeffect", + "unit weeks skill", u, weeks, (skill_t) sb->id)); + } /* sonst Glück gehabt: wer nix weiß, kann nix vergessen... */ + change_effect(u, oldpotiontype[P_FOOL], -effect); + } + age_unit(r, u); + if (*up == u) + up = &u->next; + } +} + +/* + * This procedure calculates the number of emigrating peasants for the given + * region r. There are two incentives for peasants to emigrate: + * 1) They prefer the less crowded areas. + * Example: mountains, 700 peasants (max 1000), 70% inhabited + * plain, 5000 peasants (max 10000), 50% inhabited + * 700*(PEASANTSWANDER_WEIGHT/100)*((70-50)/100) peasants wander + * from mountains to plain. + * Effect : peasents will leave densely populated regions. + * 2) Peasants prefer richer neighbour regions. + * Example: region A, 700 peasants, wealth $10500, $15 per head + * region B, 2500 peasants, wealth $25000, $10 per head + * Some peasants will emigrate from B to A because $15 > $10 + * exactly: 2500*(PEASANTSGREED_WEIGHT1/100)*((15-10)/100) + * Not taken in consideration: + * - movement because of monsters. + * - movement because of wars + * - movement because of low loyalty relating to present parties. + */ + +#define MAX_EMIGRATION(p) ((p)/MAXDIRECTIONS) +#define MAX_IMMIGRATION(p) ((p)*2/3) + +static void calculate_emigration(region * r) +{ + int i; + int maxp = maxworkingpeasants(r); + int rp = rpeasants(r); + int max_immigrants = MAX_IMMIGRATION(maxp - rp); + + if (r->terrain == newterrain(T_VOLCANO) + || r->terrain == newterrain(T_VOLCANO_SMOKING)) { + max_immigrants = max_immigrants / 10; + } + + for (i = 0; max_immigrants > 0 && i != MAXDIRECTIONS; i++) { + int dir = (turn + i) % MAXDIRECTIONS; + region *rc = rconnect(r, (direction_t) dir); + + if (rc != NULL && fval(rc->terrain, LAND_REGION)) { + int rp2 = rpeasants(rc); + int maxp2 = maxworkingpeasants(rc); + int max_emigration = MAX_EMIGRATION(rp2 - maxp2); + + if (max_emigration > 0) { + max_emigration = MIN(max_emigration, max_immigrants); + r->land->newpeasants += max_emigration; + rc->land->newpeasants -= max_emigration; + max_immigrants -= max_emigration; + } + } + } +} + +/** Bauern vermehren sich */ + +static void peasants(region * r) +{ + int peasants = rpeasants(r); + int money = rmoney(r); + int maxp = production(r) * MAXPEASANTS_PER_AREA; + int n, satiated; + int dead = 0; + + /* Bis zu 1000 Bauern können Zwillinge bekommen oder 1000 Bauern + * wollen nicht! */ + + if (peasants > 0) { + int glueck = 0; + double fraction = peasants * 0.0001F * PEASANTGROWTH; + int births = (int)fraction; + attrib *a = a_find(r->attribs, &at_peasantluck); + + if (rng_double() < (fraction - births)) { + /* because we don't want regions that never grow pga. rounding. */ + ++births; + } + if (a != NULL) { + glueck = a->data.i * 1000; + } + + for (n = peasants; n; --n) { + int chances = 0; + + if (glueck > 0) { + --glueck; + chances += PEASANTLUCK; + } + + while (chances--) { + if (rng_int() % 10000 < PEASANTGROWTH) { + /* Only raise with 75% chance if peasants have + * reached 90% of maxpopulation */ + if (peasants / (float)maxp < 0.9 || chance(PEASANTFORCE)) { + ++births; + } + } + } + } + peasants += births; + } + + /* Alle werden satt, oder halt soviele für die es auch Geld gibt */ + + satiated = MIN(peasants, money / maintenance_cost(NULL)); + rsetmoney(r, money - satiated * maintenance_cost(NULL)); + + /* Von denjenigen, die nicht satt geworden sind, verhungert der + * Großteil. dead kann nie größer als rpeasants(r) - satiated werden, + * so dass rpeasants(r) >= 0 bleiben muß. */ + + /* Es verhungert maximal die unterernährten Bevölkerung. */ + + n = MIN(peasants - satiated, rpeasants(r)); + dead += (int)(0.5F + n * PEASANT_STARVATION_CHANCE); + + if (dead > 0) { + message *msg = add_message(&r->msgs, msg_message("phunger", "dead", dead)); + msg_release(msg); + peasants -= dead; + } + + rsetpeasants(r, peasants); +} + +/* ------------------------------------------------------------- */ + +typedef struct migration { + struct migration *next; + region *r; + int horses; + int trees; +} migration; + +#define MSIZE 1023 +migration *migrants[MSIZE]; +migration *free_migrants; + +static migration *get_migrants(region * r) +{ + int key = reg_hashkey(r); + int index = key % MSIZE; + migration *m = migrants[index]; + while (m && m->r != r) + m = m->next; + if (m == NULL) { + /* Es gibt noch keine Migration. Also eine erzeugen + */ + m = free_migrants; + if (!m) + m = calloc(1, sizeof(migration)); + else { + free_migrants = free_migrants->next; + m->horses = 0; + m->trees = 0; + } + m->r = r; + m->next = migrants[index]; + migrants[index] = m; + } + return m; +} + +static void migrate(region * r) +{ + int key = reg_hashkey(r); + int index = key % MSIZE; + migration **hp = &migrants[index]; + fset(r, RF_MIGRATION); + while (*hp && (*hp)->r != r) + hp = &(*hp)->next; + if (*hp) { + migration *m = *hp; + rsethorses(r, rhorses(r) + m->horses); + /* Was macht das denn hier? + * Baumwanderung wird in trees() gemacht. + * wer fragt das? Die Baumwanderung war abhängig von der + * Auswertungsreihenfolge der regionen, + * das hatte ich geändert. jemand hat es wieder gelöscht, toll. + * ich habe es wieder aktiviert, muß getestet werden. + */ + *hp = m->next; + m->next = free_migrants; + free_migrants = m; + } +} + +static void horses(region * r) +{ + int horses, maxhorses; + direction_t n; + + /* Logistisches Wachstum, Optimum bei halbem Maximalbesatz. */ + maxhorses = maxworkingpeasants(r) / 10; + maxhorses = MAX(0, maxhorses); + horses = rhorses(r); + if (horses > 0) { + if (is_cursed(r->attribs, C_CURSED_BY_THE_GODS, 0)) { + rsethorses(r, (int)(horses * 0.9F)); + } else if (maxhorses) { + int i; + double growth = + (RESOURCE_QUANTITY * HORSEGROWTH * 200 * (maxhorses - + horses)) / maxhorses; + + if (growth > 0) { + if (a_find(r->attribs, &at_horseluck)) + growth *= 2; + /* printf("Horses: <%d> %d -> ", growth, horses); */ + i = (int)(0.5F + (horses * 0.0001F) * growth); + /* printf("%d\n", horses); */ + rsethorses(r, horses + i); + } + } + } + + /* Pferde wandern in Nachbarregionen. + * Falls die Nachbarregion noch berechnet + * werden muß, wird eine migration-Struktur gebildet, + * die dann erst in die Berechnung der Nachbarstruktur einfließt. + */ + + for (n = 0; n != MAXDIRECTIONS; n++) { + region *r2 = rconnect(r, n); + if (r2 && fval(r2->terrain, WALK_INTO)) { + int pt = (rhorses(r) * HORSEMOVE) / 100; + pt = (int)normalvariate(pt, pt / 4.0); + pt = MAX(0, pt); + if (fval(r2, RF_MIGRATION)) + rsethorses(r2, rhorses(r2) + pt); + else { + migration *nb; + /* haben wir die Migration schonmal benutzt? + * wenn nicht, müssen wir sie suchen. + * Wandernde Pferde vermehren sich nicht. + */ + nb = get_migrants(r2); + nb->horses += pt; + } + /* Wandernde Pferde sollten auch abgezogen werden */ + rsethorses(r, rhorses(r) - pt); + } + } + assert(rhorses(r) >= 0); +} + +static int count_race(const region * r, const race * rc) +{ + unit *u; + int c = 0; + + for (u = r->units; u; u = u->next) + if (u_race(u) == rc) + c += u->number; + + return c; +} + +extern struct attrib_type at_germs; + +static void +growing_trees_e3(region * r, const int current_season, + const int last_weeks_season) +{ + static const int transform[4][3] = { + {-1, -1, 0}, + {TREE_SEED, TREE_SAPLING, 2}, + {TREE_SAPLING, TREE_TREE, 2}, + {TREE_TREE, TREE_SEED, 2} + }; + + if (r->land && current_season != last_weeks_season + && transform[current_season][2]) { + int src_type = transform[current_season][0]; + int dst_type = transform[current_season][1]; + int src = rtrees(r, src_type); + int dst = rtrees(r, dst_type); + int grow = src / transform[current_season][2]; + if (grow > 0) { + if (src_type != TREE_TREE) { + rsettrees(r, src_type, src - grow); + } + rsettrees(r, dst_type, dst + grow); + + if (dst_type == TREE_SEED && r->terrain->size) { + region *rn[MAXDIRECTIONS]; + int d; + double fgrow = grow / (double)MAXDIRECTIONS; + + get_neighbours(r, rn); + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rx = rn[d]; + if (rx && rx->land) { + double scale = 1.0; + int g; + double fg, ch; + int seeds = rtrees(rx, dst_type); + + if (r->terrain->size > rx->terrain->size) { + scale = (scale * rx->terrain->size) / r->terrain->size; + } + fg = scale * fgrow; + g = (int)fg; + ch = fg - g; + if (chance(ch)) + ++g; + if (g > 0) { + rsettrees(rx, dst_type, seeds + g); + } + } + } + } + } + } +} + +static void +growing_trees(region * r, const int current_season, const int last_weeks_season) +{ + int growth, grownup_trees, i, seeds, sprout; + direction_t d; + attrib *a; + + if (current_season == SEASON_SUMMER || current_season == SEASON_AUTUMN) { + double seedchance = 0.01F * RESOURCE_QUANTITY; + int elves = count_race(r, new_race[RC_ELF]); + + a = a_find(r->attribs, &at_germs); + if (a && last_weeks_season == SEASON_SPRING) { + /* ungekeimte Samen bleiben erhalten, Sprößlinge wachsen */ + sprout = MIN(a->data.sa[1], rtrees(r, 1)); + /* aus dem gesamt Sprößlingepool abziehen */ + rsettrees(r, 1, rtrees(r, 1) - sprout); + /* zu den Bäumen hinzufügen */ + rsettrees(r, 2, rtrees(r, 2) + sprout); + + a_removeall(&r->attribs, &at_germs); + } + + if (is_cursed(r->attribs, C_CURSED_BY_THE_GODS, 0)) { + rsettrees(r, 1, (int)(rtrees(r, 1) * 0.9)); + rsettrees(r, 2, (int)(rtrees(r, 2) * 0.9)); + return; + } + + if (production(r) <= 0) + return; + + /* Grundchance 1.0% */ + /* Jeder Elf in der Region erhöht die Chance marginal */ + elves = MIN(elves, (production(r) * MAXPEASANTS_PER_AREA) / 8); + if (elves) { + seedchance += 1.0 - pow(0.99999, elves * RESOURCE_QUANTITY); + } + grownup_trees = rtrees(r, 2); + seeds = 0; + + if (grownup_trees > 0) { + double remainder = seedchance * grownup_trees; + seeds = (int)(remainder); + remainder -= seeds; + if (chance(remainder)) { + ++seeds; + } + if (seeds > 0) { + seeds += rtrees(r, 0); + rsettrees(r, 0, seeds); + } + } + + /* Bäume breiten sich in Nachbarregionen aus. */ + + /* Gesamtzahl der Samen: + * bis zu 6% (FORESTGROWTH*3) der Bäume samen in die Nachbarregionen */ + seeds = (rtrees(r, 2) * FORESTGROWTH * 3) / 1000000; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *r2 = rconnect(r, d); + if (r2 && fval(r2->terrain, LAND_REGION) && r2->terrain->size) { + /* Eine Landregion, wir versuchen Samen zu verteilen: + * Die Chance, das Samen ein Stück Boden finden, in dem sie + * keimen können, hängt von der Bewuchsdichte und der + * verfügbaren Fläche ab. In Gletschern gibt es weniger + * Möglichkeiten als in Ebenen. */ + sprout = 0; + seedchance = (1000 * maxworkingpeasants(r2)) / r2->terrain->size; + for (i = 0; i < seeds / MAXDIRECTIONS; i++) { + if (rng_int() % 10000 < seedchance) + sprout++; + } + rsettrees(r2, 0, rtrees(r2, 0) + sprout); + } + } + + } else if (current_season == SEASON_SPRING) { + + if (is_cursed(r->attribs, C_CURSED_BY_THE_GODS, 0)) + return; + + /* in at_germs merken uns die Zahl der Samen und Sprößlinge, die + * dieses Jahr älter werden dürfen, damit nicht ein Same im selben + * Zyklus zum Baum werden kann */ + a = a_find(r->attribs, &at_germs); + if (!a) { + a = a_add(&r->attribs, a_new(&at_germs)); + a->data.sa[0] = (short)rtrees(r, 0); + a->data.sa[1] = (short)rtrees(r, 1); + } + /* wir haben 6 Wochen zum wachsen, jeder Same/Sproß hat 18% Chance + * zu wachsen, damit sollten nach 5-6 Wochen alle gewachsen sein */ + growth = 1800; + + /* Samenwachstum */ + + /* Raubbau abfangen, es dürfen nie mehr Samen wachsen, als aktuell + * in der Region sind */ + seeds = MIN(a->data.sa[0], rtrees(r, 0)); + sprout = 0; + + for (i = 0; i < seeds; i++) { + if (rng_int() % 10000 < growth) + sprout++; + } + /* aus dem Samenpool dieses Jahres abziehen */ + a->data.sa[0] = (short)(seeds - sprout); + /* aus dem gesamt Samenpool abziehen */ + rsettrees(r, 0, rtrees(r, 0) - sprout); + /* zu den Sprößlinge hinzufügen */ + rsettrees(r, 1, rtrees(r, 1) + sprout); + + /* Baumwachstum */ + + /* hier gehen wir davon aus, das Jungbäume nicht ohne weiteres aus + * der Region entfernt werden können, da Jungbäume in der gleichen + * Runde nachwachsen, wir also nicht mehr zwischen diesjährigen und + * 'alten' Jungbäumen unterscheiden könnten */ + sprout = MIN(a->data.sa[1], rtrees(r, 1)); + grownup_trees = 0; + + for (i = 0; i < sprout; i++) { + if (rng_int() % 10000 < growth) + grownup_trees++; + } + /* aus dem Sprößlingepool dieses Jahres abziehen */ + a->data.sa[1] = (short)(sprout - grownup_trees); + /* aus dem gesamt Sprößlingepool abziehen */ + rsettrees(r, 1, rtrees(r, 1) - grownup_trees); + /* zu den Bäumen hinzufügen */ + rsettrees(r, 2, rtrees(r, 2) + grownup_trees); + } +} + +static void +growing_herbs(region * r, const int current_season, const int last_weeks_season) +{ + /* Jetzt die Kräutervermehrung. Vermehrt wird logistisch: + * + * Jedes Kraut hat eine Wahrscheinlichkeit von (100-(vorhandene + * Kräuter))% sich zu vermehren. */ + if (current_season != SEASON_WINTER) { + int i; + for (i = rherbs(r); i > 0; i--) { + if (rng_int() % 100 < (100 - rherbs(r))) + rsetherbs(r, (short)(rherbs(r) + 1)); + } + } +} + +void demographics(void) +{ + region *r; + static int last_weeks_season = -1; + static int current_season = -1; + + if (current_season < 0) { + gamedate date; + get_gamedate(turn, &date); + current_season = date.season; + get_gamedate(turn - 1, &date); + last_weeks_season = date.season; + } + + for (r = regions; r; r = r->next) { + ++r->age; /* also oceans. no idea why we didn't always do that */ + live(r); + /* check_split_dragons(); */ + + if (!fval(r->terrain, SEA_REGION)) { + /* die Nachfrage nach Produkten steigt. */ + struct demand *dmd; + if (r->land) { + static int plant_rules = -1; + + if (plant_rules < 0) { + plant_rules = + get_param_int(global.parameters, "rules.economy.grow", 0); + } + for (dmd = r->land->demands; dmd; dmd = dmd->next) { + if (dmd->value > 0 && dmd->value < MAXDEMAND) { + float rise = DMRISE; + if (buildingtype_exists(r, bt_find("harbour"), true)) + rise = DMRISEHAFEN; + if (rng_double() < rise) + ++dmd->value; + } + } + /* Seuchen erst nachdem die Bauern sich vermehrt haben + * und gewandert sind */ + + calculate_emigration(r); + peasants(r); + if (r->age > 20) { + plagues(r, false); + } + horses(r); + if (plant_rules == 0) { /* E1 */ + growing_trees(r, current_season, last_weeks_season); + growing_herbs(r, current_season, last_weeks_season); + } else { /* E3 */ + growing_trees_e3(r, current_season, last_weeks_season); + } + } + + update_resources(r); + if (r->land) + migrate(r); + } + } + while (free_migrants) { + migration *m = free_migrants->next; + free(free_migrants); + free_migrants = m; + }; + if (verbosity >= 1) + putchar('\n'); + + remove_empty_units(); + + log_info(" - Einwanderung..."); + for (r = regions; r; r = r->next) { + if (r->land && r->land->newpeasants) { + int rp = rpeasants(r) + r->land->newpeasants; + rsetpeasants(r, MAX(0, rp)); + } + } + + checkorders(); +} + +/* ------------------------------------------------------------- */ + +static int modify(int i) +{ + int c; + + c = i * 2 / 3; + + if (c >= 1) { + return (c + rng_int() % c); + } else { + return (i); + } +} + +static void inactivefaction(faction * f) +{ + FILE *inactiveFILE; + char zText[128]; + + sprintf(zText, "%s/%s", datapath(), "inactive"); + inactiveFILE = fopen(zText, "a"); + + if (inactiveFILE) { + fprintf(inactiveFILE, "%s:%s:%d:%d\n", + factionid(f), + LOC(default_locale, rc_name(f->race, 1)), + modify(count_all(f)), turn - f->lastorders); + + fclose(inactiveFILE); + } +} + +static void transfer_faction(faction * f, faction * f2) +{ + unit *u, *un; + + for (u = f->units; u;) { + un = u->nextF; + if (!unit_has_cursed_item(u) + && !has_skill(u, SK_MAGIC) && !has_skill(u, SK_ALCHEMY)) { + u_setfaction(u, f2); + } + u = un; + } +} + +/* test if the unit can slip through a siege undetected. + * returns 0 if siege is successful, or 1 if the building is either + * not besieged or the unit can slip through the siege due to better stealth. + */ +static int slipthru(const region * r, const unit * u, const building * b) +{ + unit *u2; + int n, o; + + /* b ist die burg, in die man hinein oder aus der man heraus will. */ + if (b == NULL || b->besieged < b->size * SIEGEFACTOR) { + return 1; + } + + /* u wird am hinein- oder herausschluepfen gehindert, wenn STEALTH <= + * OBSERVATION +2 der belagerer u2 ist */ + n = eff_skill(u, SK_STEALTH, r); + + for (u2 = r->units; u2; u2 = u2->next) { + if (usiege(u2) == b) { + + if (invisible(u, u2) >= u->number) + continue; + + o = eff_skill(u2, SK_PERCEPTION, r); + + if (o + 2 >= n) { + return 0; /* entdeckt! */ + } + } + } + return 1; +} + +int can_contact(const region * r, const unit * u, const unit * u2) { + + /* hier geht es nur um die belagerung von burgen */ + + if (u->building == u2->building) { + return 1; + } + + /* unit u is trying to contact u2 - unasked for contact. wenn u oder u2 + * nicht in einer burg ist, oder die burg nicht belagert ist, ist + * slipthru () == 1. ansonsten ist es nur 1, wenn man die belagerer */ + + if (slipthru(u->region, u, u->building) && slipthru(u->region, u2, u2->building)) { + return 1; + } + + return (alliedunit(u, u2->faction, HELP_GIVE)); +} + +int contact_cmd(unit * u, order * ord) +{ + /* unit u kontaktiert unit u2. Dies setzt den contact einfach auf 1 - + * ein richtiger toggle ist (noch?) nicht noetig. die region als + * parameter ist nur deswegen wichtig, weil er an getunit () + * weitergegeben wird. dies wird fuer das auffinden von tempunits in + * getnewunit () verwendet! */ + unit *u2; + region *r = u->region; + + init_tokens(ord); + skip_token(); + u2 = getunitg(r, u->faction); + + if (u2 != NULL) { + if (!can_contact(r, u, u2)) { + cmistake(u, u->thisorder, 23, MSG_EVENT); + return -1; + } + usetcontact(u, u2); + } + return 0; +} + +int leave_cmd(unit * u, struct order *ord) +{ + region *r = u->region; + + if (fval(u, UFL_ENTER)) { + /* if we just entered this round, then we don't leave again */ + return 0; + } + + if (fval(r->terrain, SEA_REGION) && u->ship) { + if (!fval(u_race(u), RCF_SWIM)) { + cmistake(u, ord, 11, MSG_MOVE); + return 0; + } + if (has_horses(u)) { + cmistake(u, ord, 231, MSG_MOVE); + return 0; + } + } + if (!slipthru(r, u, u->building)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, "entrance_besieged", + "building", u->building)); + } else { + leave(u, true); + } + return 0; +} + +static bool EnhancedQuit(void) +{ + static int value = -1; + if (value < 0) { + const char *str = get_param(global.parameters, "alliance.transferquit"); + value = (str != 0 && strcmp(str, "true") == 0); + } + return value; +} + +int quit_cmd(unit * u, struct order *ord) +{ + faction *f = u->faction; + const char *passwd; + + init_tokens(ord); + skip_token(); /* skip keyword */ + + passwd = getstrtoken(); + if (checkpasswd(f, (const char *)passwd, false)) { + if (EnhancedQuit()) { + int f2_id = getid(); + if (f2_id > 0) { + faction *f2 = findfaction(f2_id); + + if (f2 == NULL) { + cmistake(u, ord, 66, MSG_EVENT); + return 0; + } else if (!u->faction->alliance + || u->faction->alliance != f2->alliance) { + cmistake(u, ord, 315, MSG_EVENT); + return 0; + } else if (!alliedfaction(NULL, f, f2, HELP_MONEY)) { + cmistake(u, ord, 316, MSG_EVENT); + return 0; + } else { + variant var; + var.i = f2_id; + a_add(&f->attribs, object_create("quit", TINTEGER, var)); + } + } + } + fset(f, FFL_QUIT); + } else { + char buffer[64]; + write_order(ord, buffer, sizeof(buffer)); + cmistake(u, ord, 86, MSG_EVENT); + log_warning("QUIT with illegal password for faction %s: %s\n", factionid(f), buffer); + } + return 0; +} + +static bool mayenter(region * r, unit * u, building * b) +{ + unit *u2; + if (fval(b, BLD_UNGUARDED)) + return true; + u2 = building_owner(b); + + if (u2 == NULL || ucontact(u2, u) + || alliedunit(u2, u->faction, HELP_GUARD)) + return true; + + return false; +} + +static int mayboard(const unit * u, ship * sh) +{ + unit *u2 = ship_owner(sh); + + return (!u2 || ucontact(u2, u) || alliedunit(u2, u->faction, HELP_GUARD)); +} + +static bool CheckOverload(void) +{ + static int value = -1; + if (value < 0) { + value = get_param_int(global.parameters, "rules.check_overload", 0); + } + return value; +} + +int enter_ship(unit * u, struct order *ord, int id, int report) +{ + region *r = u->region; + ship *sh; + + /* Muss abgefangen werden, sonst koennten Schwimmer an + * Bord von Schiffen an Land gelangen. */ + if (!fval(u_race(u), RCF_CANSAIL) || (!fval(u_race(u), RCF_WALK) + && !fval(u_race(u), RCF_FLY))) { + cmistake(u, ord, 233, MSG_MOVE); + return 0; + } + + sh = findship(id); + if (sh == NULL || sh->region != r) { + if (report) + cmistake(u, ord, 20, MSG_MOVE); + return 0; + } + if (sh == u->ship) { + return 1; + } + if (!mayboard(u, sh)) { + if (report) + cmistake(u, ord, 34, MSG_MOVE); + return 0; + } + if (CheckOverload()) { + int sweight, scabins; + int mweight = shipcapacity(sh); + int mcabins = sh->type->cabins; + + if (mweight > 0) { + getshipweight(sh, &sweight, &scabins); + sweight += weight(u); + if (mcabins) { + int pweight = u->number * u_race(u)->weight; + /* weight goes into number of cabins, not cargo */ + scabins += pweight; + sweight -= pweight; + } + + if (sweight > mweight || (mcabins && (scabins > mcabins))) { + if (report) + cmistake(u, ord, 34, MSG_MOVE); + return 0; + } + } + } + + if (leave(u, false)) { + u_set_ship(u, sh); + fset(u, UFL_ENTER); + } else if (report) { + cmistake(u, ord, 150, MSG_MOVE); + } + return 1; +} + +int enter_building(unit * u, order * ord, int id, int report) +{ + region *r = u->region; + building *b; + + /* Schwimmer können keine Gebäude betreten, außer diese sind + * auf dem Ozean */ + if (!fval(u_race(u), RCF_WALK) && !fval(u_race(u), RCF_FLY)) { + if (!fval(r->terrain, SEA_REGION)) { + if (report) { + cmistake(u, ord, 232, MSG_MOVE); + } + return 0; + } + } + + b = findbuilding(id); + if (b == NULL || b->region != r) { + if (report) { + cmistake(u, ord, 6, MSG_MOVE); + } + return 0; + } + if (!mayenter(r, u, b)) { + if (report) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "entrance_denied", + "building", b)); + } + return 0; + } + if (!slipthru(r, u, b)) { + if (report) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "entrance_besieged", + "building", b)); + } + return 0; + } + + if (leave(u, 0)) { + fset(u, UFL_ENTER); + u_set_building(u, b); + return 1; + } else if (report) { + cmistake(u, ord, 150, MSG_MOVE); + } + return 0; +} + +static void do_contact(region * r) +{ + unit * u; + for (u = r->units; u; u = u->next) { + order *ord; + for (ord = u->orders; ord; ord = ord->next) { + keyword_t kwd = get_keyword(ord); + if (kwd == K_CONTACT) { + contact_cmd(u, ord); + } + } + } +} + +void do_enter(struct region *r, bool is_final_attempt) +{ + unit **uptr; + + for (uptr = &r->units; *uptr;) { + unit *u = *uptr; + order **ordp = &u->orders; + + while (*ordp) { + order *ord = *ordp; + if (get_keyword(ord) == K_ENTER) { + param_t p; + int id; + unit *ulast = NULL; + const char * s; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + p = findparam_ex(s, u->faction->locale); + id = getid(); + + switch (p) { + case P_BUILDING: + case P_GEBAEUDE: + if (u->building && u->building->no == id) + break; + if (enter_building(u, ord, id, is_final_attempt)) { + unit *ub; + for (ub = u; ub; ub = ub->next) { + if (ub->building == u->building) { + ulast = ub; + } + } + } + break; + + case P_SHIP: + if (u->ship && u->ship->no == id) + break; + if (enter_ship(u, ord, id, is_final_attempt)) { + unit *ub; + ulast = u; + for (ub = u; ub; ub = ub->next) { + if (ub->ship == u->ship) { + ulast = ub; + } + } + } + break; + + default: + if (is_final_attempt) { + cmistake(u, ord, 79, MSG_MOVE); + } + } + if (ulast != NULL) { + /* Wenn wir hier angekommen sind, war der Befehl + * erfolgreich und wir löschen ihn, damit er im + * zweiten Versuch nicht nochmal ausgeführt wird. */ + *ordp = ord->next; + ord->next = NULL; + free_order(ord); + + if (ulast != u) { + /* put u behind ulast so it's the last unit in the building */ + *uptr = u->next; + u->next = ulast->next; + ulast->next = u; + } + break; + } + } + if (*ordp == ord) + ordp = &ord->next; + } + if (*uptr == u) + uptr = &u->next; + } +} + +int dropouts[2]; +int *age = NULL; + +static void nmr_death(faction * f) +{ + static int rule = -1; + if (rule < 0) + rule = get_param_int(global.parameters, "rules.nmr.destroy", 0); + if (rule) { + unit *u; + for (u = f->units; u; u = u->nextF) { + if (u->building && building_owner(u->building)==u) { + remove_building(&u->region->buildings, u->building); + } + } + } +} + +static void remove_idle_players(void) +{ + faction *f; + + log_info(" - beseitige Spieler, die sich zu lange nicht mehr gemeldet haben..."); + + for (f = factions; f; f = f->next) { + if (fval(f, FFL_NOIDLEOUT)) { + f->lastorders = turn; + } + if (NMRTimeout() > 0 && turn - f->lastorders >= NMRTimeout()) { + nmr_death(f); + destroyfaction(f); + continue; + } + if (fval(f, FFL_OVERRIDE)) { + free(f->override); + f->override = strdup(itoa36(rng_int())); + freset(f, FFL_OVERRIDE); + } + if (turn != f->lastorders) { + char info[256]; + sprintf(info, "%d Einheiten, %d Personen, %d Silber", + f->no_units, f->num_total, f->money); + if (f->subscription) { + sql_print( + ("UPDATE subscriptions SET lastturn=%d, password='%s', info='%s' WHERE id=%u;\n", + f->lastorders, f->override, info, f->subscription)); + } + } else { + if (f->subscription) { + sql_print( + ("UPDATE subscriptions SET status='ACTIVE', lastturn=%d, firstturn=greatest(firstturn,%d), password='%s' WHERE id=%u;\n", + f->lastorders, f->lastorders - f->age, f->override, + f->subscription)); + } + } + + if (NMRTimeout() > 0 && turn - f->lastorders >= (NMRTimeout() - 1)) { + inactivefaction(f); + continue; + } + } + log_info(" - beseitige Spieler, die sich nach der Anmeldung nicht gemeldet haben..."); + + age = calloc(MAX(4, turn + 1), sizeof(int)); + for (f = factions; f; f = f->next) + if (!is_monsters(f)) { + if (RemoveNMRNewbie() && !fval(f, FFL_NOIDLEOUT)) { + if (f->age >= 0 && f->age <= turn) + ++age[f->age]; + if (f->age == 2 || f->age == 3) { + if (f->lastorders == turn - 2) { + destroyfaction(f); + ++dropouts[f->age - 2]; + continue; + } + } + } + } +} + +void quit(void) +{ + faction **fptr = &factions; + while (*fptr) { + faction *f = *fptr; + if (f->flags & FFL_QUIT) { + if (EnhancedQuit()) { + /* this doesn't work well (use object_name()) */ + attrib *a = a_find(f->attribs, &at_object); + if (a) { + variant var; + object_type type; + var.i = 0; + object_get(a, &type, &var); + assert(var.i && type == TINTEGER); + if (var.i) { + int f2_id = var.i; + faction *f2 = findfaction(f2_id); + + assert(f2_id > 0); + assert(f2 != NULL); + transfer_faction(f, f2); + } + } + } + destroyfaction(f); + } else { + ++f->age; + if (f->age + 1 < NewbieImmunity()) { + ADDMSG(&f->msgs, msg_message("newbieimmunity", "turns", + NewbieImmunity() - f->age - 1)); + } + } + if (*fptr == f) { + fptr = &f->next; + } + } + remove_idle_players(); + remove_empty_units(); +} + +/* ------------------------------------------------------------- */ + +/* HELFE partei [] [NICHT] */ + +int ally_cmd(unit * u, struct order *ord) +{ + ally *sf, **sfp; + faction *f; + int keyword, not_kw; + const char *s; + + init_tokens(ord); + skip_token(); + f = getfaction(); + + if (f == NULL || is_monsters(f)) { + cmistake(u, ord, 66, MSG_EVENT); + return 0; + } + if (f == u->faction) + return 0; + + s = getstrtoken(); + + if (!s[0]) + keyword = P_ANY; + else + keyword = findparam(s, u->faction->locale); + + sfp = &u->faction->allies; + if (fval(u, UFL_GROUP)) { + attrib *a = a_find(u->attribs, &at_group); + if (a) + sfp = &((group *) a->data.v)->allies; + } + for (sf = *sfp; sf; sf = sf->next) + if (sf->faction == f) + break; /* Gleich die passende raussuchen, wenn vorhanden */ + + not_kw = getparam(u->faction->locale); /* HELFE partei [modus] NICHT */ + + if (!sf) { + if (keyword == P_NOT || not_kw == P_NOT) { + /* Wir helfen der Partei gar nicht... */ + return 0; + } else { + sf = calloc(1, sizeof(ally)); + sf->faction = f; + sf->status = 0; + addlist(sfp, sf); + } + } + switch (keyword) { + case P_NOT: + sf->status = 0; + break; + + case NOPARAM: + cmistake(u, ord, 137, MSG_EVENT); + return 0; + + case P_ANY: + if (not_kw == P_NOT) + sf->status = 0; + else + sf->status = HELP_ALL; + break; + + case P_TRAVEL: + if (not_kw == P_NOT) + sf->status = sf->status & (HELP_ALL - HELP_TRAVEL); + else + sf->status = sf->status | HELP_TRAVEL; + break; + + case P_GIVE: + if (not_kw == P_NOT) + sf->status = sf->status & (HELP_ALL - HELP_GIVE); + else + sf->status = sf->status | HELP_GIVE; + break; + + case P_MONEY: + if (not_kw == P_NOT) + sf->status = sf->status & (HELP_ALL - HELP_MONEY); + else + sf->status = sf->status | HELP_MONEY; + break; + + case P_FIGHT: + if (not_kw == P_NOT) + sf->status = sf->status & (HELP_ALL - HELP_FIGHT); + else + sf->status = sf->status | HELP_FIGHT; + break; + + case P_FACTIONSTEALTH: + if (not_kw == P_NOT) + sf->status = sf->status & (HELP_ALL - HELP_FSTEALTH); + else + sf->status = sf->status | HELP_FSTEALTH; + break; + + case P_GUARD: + if (not_kw == P_NOT) + sf->status = sf->status & (HELP_ALL - HELP_GUARD); + else + sf->status = sf->status | HELP_GUARD; + break; + } + + sf->status &= HelpMask(); + + if (sf->status == 0) { /* Alle HELPs geloescht */ + removelist(sfp, sf); + } + return 0; +} + +static struct local_names *pnames; + +static void init_prefixnames(void) +{ + int i; + for (i = 0; localenames[i]; ++i) { + const struct locale *lang = find_locale(localenames[i]); + bool exist = false; + struct local_names *in = pnames; + + while (in != NULL) { + if (in->lang == lang) { + exist = true; + break; + } + in = in->next; + } + if (in == NULL) + in = calloc(sizeof(local_names), 1); + in->next = pnames; + in->lang = lang; + + if (!exist) { + int key; + for (key = 0; race_prefixes[key]; ++key) { + variant var; + const char *pname = + locale_string(lang, mkname("prefix", race_prefixes[key])); + if (findtoken(in->names, pname, &var) == E_TOK_NOMATCH || var.i != key) { + var.i = key; + addtoken(&in->names, pname, var); + addtoken(&in->names, locale_string(lang, mkname("prefix", + race_prefixes[key])), var); + } + } + } + pnames = in; + } +} + +int prefix_cmd(unit * u, struct order *ord) +{ + attrib **ap; + const char *s; + local_names *in = pnames; + variant var; + const struct locale *lang = u->faction->locale; + + while (in != NULL) { + if (in->lang == lang) + break; + in = in->next; + } + if (in == NULL) { + init_prefixnames(); + for (in = pnames; in->lang != lang; in = in->next) ; + } + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + if (!*s) { + attrib *a = NULL; + if (fval(u, UFL_GROUP)) { + a = a_find(u->attribs, &at_group); + } + if (a) { + group *g = (group *) a->data.v; + a_removeall(&g->attribs, &at_raceprefix); + } else { + a_removeall(&u->faction->attribs, &at_raceprefix); + } + return 0; + } + + if (findtoken(in->names, s, &var) == E_TOK_NOMATCH) { + return 0; + } else if (race_prefixes[var.i] == NULL) { + cmistake(u, ord, 299, MSG_EVENT); + } else { + ap = &u->faction->attribs; + if (fval(u, UFL_GROUP)) { + attrib *a = a_find(u->attribs, &at_group); + group *g = (group *) a->data.v; + if (a) + ap = &g->attribs; + } + set_prefix(ap, race_prefixes[var.i]); + } + return 0; +} + +static cmp_building_cb get_cmp_region_owner(void) +{ + if (rule_region_owners()) { + return &cmp_current_owner; + } else { + return &cmp_wage; + } +} + +int display_cmd(unit * u, struct order *ord) +{ + char **s = NULL; + const char *str; + region *r = u->region; + + init_tokens(ord); + skip_token(); + + str = getstrtoken(); + switch (findparam_ex(str, u->faction->locale)) { + case P_BUILDING: + case P_GEBAEUDE: + if (!u->building) { + cmistake(u, ord, 145, MSG_PRODUCE); + break; + } + if (building_owner(u->building)!=u) { + cmistake(u, ord, 5, MSG_PRODUCE); + break; + } + if (!fval(u->building->type, BTF_NAMECHANGE) && u->building->display && u->building->display[0]) { + cmistake(u, ord, 278, MSG_EVENT); + break; + } + s = &u->building->display; + break; + + case P_SHIP: + if (!u->ship) { + cmistake(u, ord, 144, MSG_PRODUCE); + break; + } + if (ship_owner(u->ship)!=u) { + cmistake(u, ord, 12, MSG_PRODUCE); + break; + } + s = &u->ship->display; + break; + + case P_UNIT: + s = &u->display; + break; + + case P_PRIVAT: + { + const char *d = getstrtoken(); + if (d == NULL || *d == 0) { + usetprivate(u, NULL); + } else { + usetprivate(u, d); + } + } + break; + + case P_REGION: + if (!u->building) { + cmistake(u, ord, 145, MSG_EVENT); + break; + } + if (building_owner(u->building)!=u) { + cmistake(u, ord, 148, MSG_EVENT); + break; + } + if (u->building != largestbuilding(r, get_cmp_region_owner(), false)) { + cmistake(u, ord, 147, MSG_EVENT); + break; + } + s = &r->display; + break; + + default: + cmistake(u, ord, 110, MSG_EVENT); + break; + } + + if (s != NULL) { + const char *s2 = getstrtoken(); + + free(*s); + *s = strdup(s2); + if (strlen(s2) >= DISPLAYSIZE) { + (*s)[DISPLAYSIZE] = 0; + } + } + + return 0; +} + +bool renamed_building(const building * b) +{ + const struct locale *lang = locales; + size_t len = strlen(b->name); + for (; lang; lang = nextlocale(lang)) { + const char *bdname = LOC(lang, b->type->_name); + if (bdname) { + size_t bdlen = strlen(bdname); + if (len >= bdlen && strncmp(b->name, bdname, bdlen) == 0) { + return false; + } + } + } + return true; +} + +static int rename_cmd(unit * u, order * ord, char **s, const char *s2) +{ + if (!s2[0]) { + cmistake(u, ord, 84, MSG_EVENT); + return 0; + } + + /* TODO: Validate to make sure people don't have illegal characters in + * names, phishing-style? () come to mind. */ + + free(*s); + *s = strdup(s2); + if (strlen(s2) >= NAMESIZE) { + (*s)[NAMESIZE] = 0; + } + return 0; +} + +int +rename_building(unit * u, order * ord, building * b, const char *name) +{ + unit *owner = b ? building_owner(b) : 0; + bool foreign = !(owner && owner->faction == u->faction); + + if (!b) { + cmistake(u, ord, u->building ? 6 : 145, MSG_EVENT); + return -1; + } + + if (!fval(b->type, BTF_NAMECHANGE) && renamed_building(b)) { + cmistake(u, ord, 278, MSG_EVENT); + return -1; + } + + if (foreign) { + if (renamed_building(b)) { + cmistake(u, ord, 246, MSG_EVENT); + return -1; + } + + if (owner) { + if (cansee(owner->faction, u->region, u, 0)) { + ADDMSG(&owner->faction->msgs, + msg_message("renamed_building_seen", + "building renamer region", b, u, u->region)); + } else { + ADDMSG(&owner->faction->msgs, + msg_message("renamed_building_notseen", + "building region", b, u->region)); + } + } + } else { + if (owner!=u) { + cmistake(u, ord, 148, MSG_PRODUCE); + return -1; + } + } + + return rename_cmd(u, ord, &b->name, name); +} + +int name_cmd(struct unit *u, struct order *ord) +{ + building *b = u->building; + region *r = u->region; + char **s = NULL; + param_t p; + bool foreign = false; + const char *str; + + init_tokens(ord); + skip_token(); + str = getstrtoken(); + p = findparam_ex(str, u->faction->locale); + + if (p == P_FOREIGN) { + str = getstrtoken(); + foreign = true; + p = findparam_ex(str, u->faction->locale); + } + + switch (p) { + case P_ALLIANCE: + if (!foreign && f_get_alliance(u->faction)) { + alliance *al = u->faction->alliance; + faction *lead = alliance_get_leader(al); + if (lead == u->faction) { + s = &al->name; + } + } + break; + case P_BUILDING: + case P_GEBAEUDE: + if (foreign) { + b = getbuilding(u->region); + } + + return rename_building(u, ord, b, getstrtoken()); + + case P_FACTION: + if (foreign) { + faction *f; + + f = getfaction(); + if (!f) { + cmistake(u, ord, 66, MSG_EVENT); + break; + } + if (f->age < 10) { + cmistake(u, ord, 248, MSG_EVENT); + break; + } else { + const struct locale *lang = locales; + for (; lang; lang = nextlocale(lang)) { + const char *fdname = LOC(lang, "factiondefault"); + size_t fdlen = strlen(fdname); + if (strlen(f->name) >= fdlen && strncmp(f->name, fdname, fdlen) == 0) { + break; + } + } + if (lang == NULL) { + cmistake(u, ord, 247, MSG_EVENT); + break; + } + } + if (cansee(f, r, u, 0)) { + ADDMSG(&f->msgs, + msg_message("renamed_faction_seen", "unit region", u, r)); + } else { + ADDMSG(&f->msgs, msg_message("renamed_faction_notseen", "", r)); + } + s = &f->name; + } else { + s = &u->faction->name; + } + break; + + case P_SHIP: + if (foreign) { + ship *sh = getship(r); + unit *uo; + + if (!sh) { + cmistake(u, ord, 20, MSG_EVENT); + break; + } else { + const struct locale *lang = locales; + for (; lang; lang = nextlocale(lang)) { + const char *sdname = LOC(lang, sh->type->name[0]); + size_t sdlen = strlen(sdname); + if (strlen(sh->name) >= sdlen + && strncmp(sh->name, sdname, sdlen) == 0) { + break; + } + + sdname = LOC(lang, parameters[P_SHIP]); + sdlen = strlen(sdname); + if (strlen(sh->name) >= sdlen + && strncmp(sh->name, sdname, sdlen) == 0) { + break; + } + + } + if (lang == NULL) { + cmistake(u, ord, 245, MSG_EVENT); + break; + } + } + uo = ship_owner(sh); + if (uo) { + if (cansee(uo->faction, r, u, 0)) { + ADDMSG(&uo->faction->msgs, + msg_message("renamed_ship_seen", "ship renamer region", sh, u, r)); + } else { + ADDMSG(&uo->faction->msgs, + msg_message("renamed_ship_notseen", "ship region", sh, r)); + } + } + s = &sh->name; + } else { + if (!u->ship) { + cmistake(u, ord, 144, MSG_PRODUCE); + break; + } + if (ship_owner(u->ship)!=u) { + cmistake(u, ord, 12, MSG_PRODUCE); + break; + } + s = &u->ship->name; + } + break; + + case P_UNIT: + if (foreign) { + unit *u2 = getunit(r, u->faction); + + if (!u2 || !cansee(u->faction, r, u2, 0)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "feedback_unit_not_found", "")); + break; + } else { + const char *udefault = LOC(u2->faction->locale, "unitdefault"); + size_t udlen = strlen(udefault); + size_t unlen = strlen(u2->name); + if (unlen >= udlen && strncmp(u2->name, udefault, udlen) != 0) { + cmistake(u2, ord, 244, MSG_EVENT); + break; + } + } + if (cansee(u2->faction, r, u, 0)) { + ADDMSG(&u2->faction->msgs, msg_message("renamed_seen", + "renamer renamed region", u, u2, r)); + } else { + ADDMSG(&u2->faction->msgs, msg_message("renamed_notseen", + "renamed region", u2, r)); + } + s = &u2->name; + } else { + s = &u->name; + } + break; + + case P_REGION: + if (!b) { + cmistake(u, ord, 145, MSG_EVENT); + break; + } + if (building_owner(b)!=u) { + cmistake(u, ord, 148, MSG_EVENT); + break; + } + + if (b != largestbuilding(r, get_cmp_region_owner(), false)) { + cmistake(u, ord, 147, MSG_EVENT); + break; + } + s = &r->land->name; + break; + + case P_GROUP: + { + attrib *a = NULL; + if (fval(u, UFL_GROUP)) + a = a_find(u->attribs, &at_group); + if (a) { + group *g = (group *) a->data.v; + s = &g->name; + break; + } else { + cmistake(u, ord, 109, MSG_EVENT); + break; + } + } + break; + default: + cmistake(u, ord, 109, MSG_EVENT); + break; + } + + if (s != NULL) { + return rename_cmd(u, ord, s, getstrtoken()); + } + + return 0; +} + +/* ------------------------------------------------------------- */ + +void +deliverMail(faction * f, region * r, unit * u, const char *s, unit * receiver) +{ + if (!cansee(f, r, u, 0)) { + u = NULL; + } + if (!receiver) { /* BOTSCHAFT an PARTEI */ + ADDMSG(&f->msgs, + msg_message("regionmessage", "region sender string", r, u, s)); + } else { /* BOTSCHAFT an EINHEIT */ + ADDMSG(&f->msgs, + msg_message("unitmessage", "region unit sender string", r, + receiver, u, s)); + } +} + +static void +mailunit(region * r, unit * u, int n, struct order *ord, const char *s) +{ + unit *u2 = findunitr(r, n); + + if (u2 && cansee(u->faction, r, u2, 0)) { + deliverMail(u2->faction, r, u, s, u2); + /* now done in prepare_mail_cmd */ + } else { + /* Immer eine Meldung - sonst koennte man so getarnte EHs enttarnen: + * keine Meldung -> EH hier. */ + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "feedback_unit_not_found", "")); + } +} + +static void mailfaction(unit * u, int n, struct order *ord, const char *s) +{ + faction *f; + + f = findfaction(n); + if (f && n > 0) + deliverMail(f, u->region, u, s, NULL); + else + cmistake(u, ord, 66, MSG_MESSAGE); +} + +int mail_cmd(unit * u, struct order *ord) +{ + region *r = u->region; + unit *u2; + const char *s; + int n, cont; + + init_tokens(ord); + skip_token(); /* skip the keyword */ + s = getstrtoken(); + + /* Falls kein Parameter, ist das eine Einheitsnummer; + * das Füllwort "AN" muß wegfallen, da gültige Nummer! */ + + do { + cont = 0; + switch (findparam_ex(s, u->faction->locale)) { + case P_REGION: + /* können alle Einheiten in der Region sehen */ + s = getstrtoken(); + if (!s[0]) { + cmistake(u, ord, 30, MSG_MESSAGE); + break; + } else { + ADDMSG(&r->msgs, msg_message("mail_result", "unit message", u, s)); + return 0; + } + + case P_FACTION: + { + bool see = false; + + n = getfactionid(); + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction->no == n && seefaction(u->faction, r, u2, 0)) { + see = true; + break; + } + } + + if (!see) { + cmistake(u, ord, 66, MSG_MESSAGE); + break; + } + + s = getstrtoken(); + if (!s[0]) { + cmistake(u, ord, 30, MSG_MESSAGE); + break; + } + mailfaction(u, n, ord, s); + return 0; + } + + case P_UNIT: + { + bool see = false; + n = getid(); + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->no == n && cansee(u->faction, r, u2, 0)) { + see = true; + break; + } + } + + if (!see) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "feedback_unit_not_found", "")); + return 0; + } + + s = getstrtoken(); + if (!s[0]) { + cmistake(u, ord, 30, MSG_MESSAGE); + break; + } else { + attrib *a = a_find(u2->attribs, &at_eventhandler); + if (a != NULL) { + event_arg args[3]; + args[0].data.v = (void *)s; + args[0].type = "string"; + args[1].data.v = (void *)u; + args[1].type = "unit"; + args[2].type = NULL; + handle_event(a, "message", args); + } + + mailunit(r, u, n, ord, s); + } + return 0; + } + + case P_BUILDING: + case P_GEBAEUDE: + { + building *b = getbuilding(r); + + if (!b) { + cmistake(u, ord, 6, MSG_MESSAGE); + break; + } + + s = getstrtoken(); + + if (!s[0]) { + cmistake(u, ord, 30, MSG_MESSAGE); + break; + } + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->building == b && !fval(u2->faction, FFL_SELECT) + && cansee(u->faction, r, u2, 0)) { + mailunit(r, u, u2->no, ord, s); + fset(u2->faction, FFL_SELECT); + } + } + return 0; + } + + case P_SHIP: + { + ship *sh = getship(r); + + if (!sh) { + cmistake(u, ord, 20, MSG_MESSAGE); + break; + } + + s = getstrtoken(); + + if (!s[0]) { + cmistake(u, ord, 30, MSG_MESSAGE); + break; + } + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->ship == sh && !fval(u2->faction, FFL_SELECT) + && cansee(u->faction, r, u2, 0)) { + mailunit(r, u, u2->no, ord, s); + fset(u2->faction, FFL_SELECT); + } + } + return 0; + } + + default: + /* possibly filler token? */ + s = getstrtoken(); + if (s && *s) + cont = 1; + break; + } + } + while (cont); + cmistake(u, ord, 149, MSG_MESSAGE); + return 0; +} + +/* ------------------------------------------------------------- */ + +int banner_cmd(unit * u, struct order *ord) +{ + init_tokens(ord); + skip_token(); + + free(u->faction->banner); + u->faction->banner = strdup(getstrtoken()); + add_message(&u->faction->msgs, msg_message("changebanner", "value", + u->faction->banner)); + + return 0; +} + +int email_cmd(unit * u, struct order *ord) +{ + const char *s; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + if (!s[0]) { + cmistake(u, ord, 85, MSG_EVENT); + } else { + faction *f = u->faction; + if (set_email(&f->email, (const char *)s) != 0) { + log_error("Invalid email address for faction %s: %s\n", itoa36(f->no), s); + ADDMSG(&f->msgs, msg_message("changemail_invalid", "value", s)); + } else { + ADDMSG(&f->msgs, msg_message("changemail", "value", f->email)); + } + } + return 0; +} + +int password_cmd(unit * u, struct order *ord) +{ + char pwbuf[32]; + int i; + const char *s; + bool pwok = true; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + if (!s || !*s) { + for (i = 0; i < 6; i++) + pwbuf[i] = (char)(97 + rng_int() % 26); + pwbuf[6] = 0; + } else { + char *c; + + strlcpy(pwbuf, (const char *)s, 31); + pwbuf[31] = 0; + c = pwbuf; + while (*c && pwok) { + if (!isalnum(*(unsigned char *)c)) + pwok = false; + c++; + } + } + free(u->faction->passw); + if (!pwok) { + cmistake(u, ord, 283, MSG_EVENT); + u->faction->passw = strdup(itoa36(rng_int())); + } else { + u->faction->passw = strdup(pwbuf); + } + fset(u->faction, FFL_OVERRIDE); + ADDMSG(&u->faction->msgs, msg_message("changepasswd", + "value", u->faction->passw)); + return 0; +} + +int send_cmd(unit * u, struct order *ord) +{ + const char *s; + int option; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + option = findoption(s, u->faction->locale); + + if (option == -1) { + cmistake(u, ord, 135, MSG_EVENT); + } else { + if (getparam(u->faction->locale) == P_NOT) { + if (option == O_COMPRESS || option == O_BZIP2) { + cmistake(u, ord, 305, MSG_EVENT); + } else { + u->faction->options = u->faction->options & ~(1 << option); + } + } else { + u->faction->options = u->faction->options | (1 << option); + if (option == O_COMPRESS) + u->faction->options &= ~(1 << O_BZIP2); + if (option == O_BZIP2) + u->faction->options &= ~(1 << O_COMPRESS); + } + } + return 0; +} + +static bool display_item(faction * f, unit * u, const item_type * itype) +{ + const char *name; + const char *key; + const char *info; + + if (u != NULL) { + int i = i_get(u->items, itype); + if (i == 0) { + if (u->region->land != NULL) { + i = i_get(u->region->land->items, itype); + } + if (i == 0) { + i = i_get(u->faction->items, itype); + if (i == 0) + return false; + } + } + } + + name = resourcename(itype->rtype, 0); + key = mkname("iteminfo", name); + info = locale_getstring(f->locale, key); + + if (info == NULL) { + info = locale_string(f->locale, mkname("iteminfo", "no_info")); + } + ADDMSG(&f->msgs, msg_message("displayitem", "weight item description", + itype->weight, itype->rtype, info)); + + return true; +} + +static bool display_potion(faction * f, unit * u, const potion_type * ptype) +{ + attrib *a; + + if (ptype == NULL) + return false; + else { + int i = i_get(u->items, ptype->itype); + if (i == 0 && 2 * ptype->level > effskill(u, SK_ALCHEMY)) { + return false; + } + } + + a = a_find(f->attribs, &at_showitem); + while (a && a->data.v != ptype) + a = a->next; + if (!a) { + a = a_add(&f->attribs, a_new(&at_showitem)); + a->data.v = (void *)ptype->itype; + } + + return true; +} + +static bool display_race(faction * f, unit * u, const race * rc) +{ + const char *name, *key; + const char *info; + int a, at_count; + char buf[2048], *bufp = buf; + size_t size = sizeof(buf) - 1; + int bytes; + + if (u && u_race(u) != rc) + return false; + name = rc_name(rc, 0); + + bytes = slprintf(bufp, size, "%s: ", LOC(f->locale, name)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + key = mkname("raceinfo", rc->_name[0]); + info = locale_getstring(f->locale, key); + if (info == NULL) { + info = locale_string(f->locale, mkname("raceinfo", "no_info")); + } + + bytes = (int)strlcpy(bufp, info, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + /* hp_p : Trefferpunkte */ + bytes = + slprintf(bufp, size, " %d %s", rc->hitpoints, LOC(f->locale, + "stat_hitpoints")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + /* b_attacke : Angriff */ + bytes = + slprintf(bufp, size, ", %s: %d", LOC(f->locale, "stat_attack"), + (rc->at_default + rc->at_bonus)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + /* b_defense : Verteidigung */ + bytes = + slprintf(bufp, size, ", %s: %d", LOC(f->locale, "stat_defense"), + (rc->df_default + rc->df_bonus)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + /* b_armor : Rüstung */ + if (rc->armor > 0) { + bytes = + slprintf(bufp, size, ", %s: %d", LOC(f->locale, "stat_armor"), rc->armor); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + if (size > 1) { + *bufp++ = '.'; + --size; + } else + WARN_STATIC_BUFFER(); + + /* b_damage : Schaden */ + at_count = 0; + for (a = 0; a < 6; a++) { + if (rc->attack[a].type != AT_NONE) { + at_count++; + } + } + if (rc->battle_flags & BF_EQUIPMENT) { + bytes = snprintf(bufp, size, " %s", LOC(f->locale, "stat_equipment")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (rc->battle_flags & BF_RES_PIERCE) { + bytes = snprintf(bufp, size, " %s", LOC(f->locale, "stat_pierce")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (rc->battle_flags & BF_RES_CUT) { + bytes = snprintf(bufp, size, " %s", LOC(f->locale, "stat_cut")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (rc->battle_flags & BF_RES_BASH) { + bytes = snprintf(bufp, size, " %s", LOC(f->locale, "stat_bash")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + bytes = + snprintf(bufp, size, " %d %s", at_count, LOC(f->locale, + (at_count == 1) ? "stat_attack" : "stat_attacks")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + for (a = 0; a < 6; a++) { + if (rc->attack[a].type != AT_NONE) { + if (a != 0) + bytes = (int)strlcpy(bufp, ", ", size); + else + bytes = (int)strlcpy(bufp, ": ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + switch (rc->attack[a].type) { + case AT_STANDARD: + bytes = + snprintf(bufp, size, "%s (%s)", + LOC(f->locale, "attack_standard"), rc->def_damage); + break; + case AT_NATURAL: + bytes = + snprintf(bufp, size, "%s (%s)", + LOC(f->locale, "attack_natural"), rc->attack[a].data.dice); + break; + case AT_SPELL: + case AT_COMBATSPELL: + case AT_DRAIN_ST: + case AT_DAZZLE: + bytes = snprintf(bufp, size, "%s", LOC(f->locale, "attack_magical")); + break; + case AT_STRUCTURAL: + bytes = + snprintf(bufp, size, "%s (%s)", + LOC(f->locale, "attack_structural"), rc->attack[a].data.dice); + break; + default: + bytes = 0; + } + + if (bytes && wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + + if (size > 1) { + *bufp++ = '.'; + --size; + } else + WARN_STATIC_BUFFER(); + + *bufp = 0; + addmessage(0, f, buf, MSG_EVENT, ML_IMPORTANT); + + return true; +} + +static void reshow(unit * u, struct order *ord, const char *s, param_t p) +{ + int skill, c; + const potion_type *ptype; + const item_type *itype; + const spell *sp = 0; + const race *rc; + + switch (p) { + case P_ZAUBER: + a_removeall(&u->faction->attribs, &at_seenspell); + break; + case P_POTIONS: + skill = effskill(u, SK_ALCHEMY); + c = 0; + for (ptype = potiontypes; ptype != NULL; ptype = ptype->next) { + if (ptype->level * 2 <= skill) { + c += display_potion(u->faction, u, ptype); + } + } + if (c == 0) + cmistake(u, ord, 285, MSG_EVENT); + break; + case NOPARAM: + if (s) { + /* check if it's an item */ + itype = finditemtype(s, u->faction->locale); + if (itype != NULL) { + ptype = resource2potion(item2resource(itype)); + if (ptype != NULL) { + if (display_potion(u->faction, u, ptype)) + break; + } else { + if (display_item(u->faction, u, itype)) + break; + } + } + /* try for a spell */ + sp = unit_getspell(u, s, u->faction->locale); + if (sp) { + attrib *a = a_find(u->faction->attribs, &at_seenspell); + while (a != NULL && a->type == &at_seenspell && a->data.v != sp) { + a = a->next; + } + if (a != NULL) { + a_remove(&u->faction->attribs, a); + } + break; + } + /* last, check if it's a race. */ + rc = findrace(s, u->faction->locale); + if (rc != NULL && display_race(u->faction, u, rc)) { + break; + } + } + cmistake(u, ord, 21, MSG_EVENT); + break; + default: + cmistake(u, ord, 222, MSG_EVENT); + break; + } +} + +int promotion_cmd(unit * u, struct order *ord) +{ + int money, people; + + if (fval(u, UFL_HERO)) { + /* TODO: message "is already a hero" */ + return 0; + } + + if (maxheroes(u->faction) < countheroes(u->faction) + u->number) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "heroes_maxed", "max count", + maxheroes(u->faction), countheroes(u->faction))); + return 0; + } + if (!valid_race(u->faction, u_race(u))) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "heroes_race", "race", + u_race(u))); + return 0; + } + people = count_all(u->faction) * u->number; + money = get_pooled(u, i_silver->rtype, GET_ALL, people); + + if (people > money) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "heroes_cost", "cost have", people, money)); + return 0; + } + use_pooled(u, i_silver->rtype, GET_ALL, people); + fset(u, UFL_HERO); + ADDMSG(&u->faction->msgs, msg_message("hero_promotion", "unit cost", + u, people)); + return 0; +} + +int group_cmd(unit * u, struct order *ord) +{ + const char *s; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + join_group(u, s); + return 0; +} + +int origin_cmd(unit * u, struct order *ord) +{ + short px, py; + + init_tokens(ord); + skip_token(); + + px = (short)getint(); + py = (short)getint(); + + set_ursprung(u->faction, getplaneid(u->region), px, py); + return 0; +} + +int guard_off_cmd(unit * u, struct order *ord) +{ + assert(get_keyword(ord) == K_GUARD); + init_tokens(ord); + skip_token(); + + if (getparam(u->faction->locale) == P_NOT) { + setguard(u, GUARD_NONE); + } + return 0; +} + +int reshow_cmd(unit * u, struct order *ord) +{ + const char *s; + param_t p = NOPARAM; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + if (isparam(s, u->faction->locale, P_ANY)) { + p = getparam(u->faction->locale); + s = NULL; + } + + reshow(u, ord, s, p); + return 0; +} + +int status_cmd(unit * u, struct order *ord) +{ + const char *param; + + init_tokens(ord); + skip_token(); + + param = getstrtoken(); + switch (findparam(param, u->faction->locale)) { + case P_NOT: + setstatus(u, ST_AVOID); + break; + case P_BEHIND: + setstatus(u, ST_BEHIND); + break; + case P_FLEE: + setstatus(u, ST_FLEE); + break; + case P_CHICKEN: + setstatus(u, ST_CHICKEN); + break; + case P_AGGRO: + setstatus(u, ST_AGGRO); + break; + case P_VORNE: + setstatus(u, ST_FIGHT); + break; + case P_HELP: + if (getparam(u->faction->locale) == P_NOT) { + fset(u, UFL_NOAID); + } else { + freset(u, UFL_NOAID); + } + break; + default: + if (param[0]) { + add_message(&u->faction->msgs, + msg_feedback(u, ord, "unknown_status", "")); + } else { + setstatus(u, ST_FIGHT); + } + } + return 0; +} + +int combatspell_cmd(unit * u, struct order *ord) +{ + const char *s; + int level = 0; + spell *sp = 0; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + /* KAMPFZAUBER [NICHT] löscht alle gesetzten Kampfzauber */ + if (!s || *s == 0 || findparam(s, u->faction->locale) == P_NOT) { + unset_combatspell(u, 0); + return 0; + } + + /* Optional: STUFE n */ + if (findparam(s, u->faction->locale) == P_LEVEL) { + /* Merken, setzen kommt erst später */ + level = getint(); + level = MAX(0, level); + s = getstrtoken(); + } + + sp = unit_getspell(u, s, u->faction->locale); + if (!sp) { + cmistake(u, ord, 173, MSG_MAGIC); + return 0; + } + + s = getstrtoken(); + + if (findparam(s, u->faction->locale) == P_NOT) { + /* KAMPFZAUBER "" NICHT löscht diesen speziellen + * Kampfzauber */ + unset_combatspell(u, sp); + return 0; + } else { + /* KAMPFZAUBER "" setzt diesen Kampfzauber */ + set_combatspell(u, sp, ord, level); + } + + return 0; +} + +/* ------------------------------------------------------------- */ +/* Beachten: einige Monster sollen auch unbewaffent die Region bewachen + * können */ + +enum { E_GUARD_OK, E_GUARD_UNARMED, E_GUARD_NEWBIE, E_GUARD_FLEEING }; + +static int can_start_guarding(const unit * u) +{ + if (u->status >= ST_FLEE) + return E_GUARD_FLEEING; + if (fval(u_race(u), RCF_UNARMEDGUARD)) + return E_GUARD_OK; + if (!armedmen(u, true)) + return E_GUARD_UNARMED; + if (IsImmune(u->faction)) + return E_GUARD_NEWBIE; + return E_GUARD_OK; +} + +void update_guards(void) +{ + const region *r; + + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + if (fval(u, UFL_GUARD)) { + if (can_start_guarding(u) != E_GUARD_OK) { + setguard(u, GUARD_NONE); + } else { + attrib *a = a_find(u->attribs, &at_guard); + if (a && a->data.i == (int)guard_flags(u)) { + /* this is really rather not necessary */ + a_remove(&u->attribs, a); + } + } + } + } + } +} + +int guard_on_cmd(unit * u, struct order *ord) +{ + assert(get_keyword(ord) == K_GUARD); + + init_tokens(ord); + skip_token(); + + /* GUARD NOT is handled in goard_off_cmd earlier in the turn */ + if (getparam(u->faction->locale) == P_NOT) + return 0; + + if (fval(u->region->terrain, SEA_REGION)) { + cmistake(u, ord, 2, MSG_EVENT); + } else { + if (fval(u, UFL_MOVED)) { + cmistake(u, ord, 187, MSG_EVENT); + } else if (fval(u_race(u), RCF_ILLUSIONARY) + || u_race(u) == new_race[RC_SPELL]) { + cmistake(u, ord, 95, MSG_EVENT); + } else { + /* Monster der Monsterpartei dürfen immer bewachen */ + if (is_monsters(u->faction)) { + guard(u, GUARD_ALL); + } else { + int err = can_start_guarding(u); + if (err == E_GUARD_OK) { + guard(u, GUARD_ALL); + } else if (err == E_GUARD_UNARMED) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "unit_unarmed", "")); + } else if (err == E_GUARD_FLEEING) { + cmistake(u, ord, 320, MSG_EVENT); + } else if (err == E_GUARD_NEWBIE) { + cmistake(u, ord, 304, MSG_EVENT); + } + } + } + } + return 0; +} + +void sinkships(struct region * r) +{ + ship **shp = &r->ships; + + while (*shp) { + ship *sh = *shp; + + if (!sh->type->construction || sh->size >= sh->type->construction->maxsize) { + if (fval(r->terrain, SEA_REGION) && (!enoughsailors(sh, r) + || get_captain(sh) == NULL)) { + /* Schiff nicht seetüchtig */ + float dmg = get_param_flt(global.parameters, + "rules.ship.damage.nocrewocean", + 0.30F); + damage_ship(sh, dmg); + } + if (ship_owner(sh) == NULL) { + float dmg = get_param_flt(global.parameters, "rules.ship.damage.nocrew", + 0.05F); + damage_ship(sh, dmg); + } + } + if (sh->damage >= sh->size * DAMAGE_SCALE) { + remove_ship(shp, sh); + } + if (*shp == sh) + shp = &sh->next; + } +} + +/* The following functions do not really belong here: */ +#include +#include + +static attrib_type at_number = { + "faction_renum", + NULL, NULL, NULL, NULL, NULL, + ATF_UNIQUE +}; + +void renumber_factions(void) + /* gibt parteien neue nummern */ +{ + struct renum { + struct renum *next; + int want; + faction *faction; + attrib *attrib; + } *renum = NULL, *rp; + faction *f; + for (f = factions; f; f = f->next) { + attrib *a = a_find(f->attribs, &at_number); + int want; + struct renum **rn; + faction *old; + + if (!a) + continue; + want = a->data.i; + if (fval(f, FFL_NEWID)) { + ADDMSG(&f->msgs, msg_message("renumber_twice", "id", want)); + continue; + } + old = findfaction(want); + if (old) { + a_remove(&f->attribs, a); + ADDMSG(&f->msgs, msg_message("renumber_inuse", "id", want)); + continue; + } + if (!faction_id_is_unused(want)) { + a_remove(&f->attribs, a); + ADDMSG(&f->msgs, msg_message("renumber_inuse", "id", want)); + continue; + } + for (rn = &renum; *rn; rn = &(*rn)->next) { + if ((*rn)->want >= want) + break; + } + if (*rn && (*rn)->want == want) { + ADDMSG(&f->msgs, msg_message("renumber_inuse", "id", want)); + } else { + struct renum *r = calloc(sizeof(struct renum), 1); + r->next = *rn; + r->attrib = a; + r->faction = f; + r->want = want; + *rn = r; + } + } + for (rp = renum; rp; rp = rp->next) { + f = rp->faction; + a_remove(&f->attribs, rp->attrib); + renumber_faction(f, rp->want); + } + while (renum) { + rp = renum->next; + free(renum); + renum = rp; + } +} + +void restack_units(void) +{ + region *r; + for (r = regions; r; r = r->next) { + unit **up = &r->units; + bool sorted = false; + while (*up) { + unit *u = *up; + if (!fval(u, UFL_MARK)) { + struct order *ord; + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == K_SORT) { + const char *s; + param_t p; + int id; + unit *v; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + p = findparam(s, u->faction->locale); + id = getid(); + v = findunit(id); + + if (!v || v->faction != u->faction || v->region != r) { + cmistake(u, ord, 258, MSG_EVENT); + } else if (v->building != u->building || v->ship != u->ship) { + cmistake(u, ord, 259, MSG_EVENT); + } else if (u->building && building_owner(u->building)==u) { + cmistake(u, ord, 260, MSG_EVENT); + } else if (u->ship && ship_owner(u->ship)==u) { + cmistake(u, ord, 260, MSG_EVENT); + } else if (v == u) { + cmistake(u, ord, 10, MSG_EVENT); + } else { + switch (p) { + case P_AFTER: + *up = u->next; + u->next = v->next; + v->next = u; + fset(u, UFL_MARK); + sorted = true; + break; + case P_BEFORE: + if (v->ship && ship_owner(v->ship)==v) { + cmistake(v, ord, 261, MSG_EVENT); + } else if (v->building && building_owner(v->building)==v) { + cmistake(v, ord, 261, MSG_EVENT); + } else { + unit **vp = &r->units; + while (*vp != v) + vp = &(*vp)->next; + *vp = u; + *up = u->next; + u->next = v; + } + fset(u, UFL_MARK); + sorted = true; + break; + default: + /* TODO: syntax error message? */ + break; + } + } + break; + } + } + } + if (u == *up) + up = &u->next; + } + if (sorted) { + unit *u; + for (u = r->units; u; u = u->next) { + freset(u, UFL_MARK); + } + } + } +} + +int renumber_cmd(unit * u, order * ord) +{ + const char *s; + int i; + faction *f = u->faction; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + switch (findparam_ex(s, u->faction->locale)) { + + case P_FACTION: + s = getstrtoken(); + if (s && *s) { + int id = atoi36((const char *)s); + attrib *a = a_find(f->attribs, &at_number); + if (!a) + a = a_add(&f->attribs, a_new(&at_number)); + a->data.i = id; + } + break; + + case P_UNIT: + s = getstrtoken(); + if (s == NULL || *s == 0) { + i = newunitid(); + } else { + i = atoi36((const char *)s); + if (i <= 0 || i > MAX_UNIT_NR) { + cmistake(u, ord, 114, MSG_EVENT); + break; + } + + if (forbiddenid(i)) { + cmistake(u, ord, 116, MSG_EVENT); + break; + } + + if (findunitg(i, u->region)) { + cmistake(u, ord, 115, MSG_EVENT); + break; + } + } + uunhash(u); + if (!ualias(u)) { + attrib *a = a_add(&u->attribs, a_new(&at_alias)); + a->data.i = -u->no; + } + u->no = i; + uhash(u); + break; + + case P_SHIP: + if (!u->ship) { + cmistake(u, ord, 144, MSG_EVENT); + break; + } + if (ship_owner(u->ship)!=u) { + cmistake(u, ord, 146, MSG_EVENT); + break; + } + if (u->ship->coast != NODIRECTION) { + cmistake(u, ord, 116, MSG_EVENT); + break; + } + s = getstrtoken(); + if (s == NULL || *s == 0) { + i = newcontainerid(); + } else { + i = atoi36((const char *)s); + if (i <= 0 || i > MAX_CONTAINER_NR) { + cmistake(u, ord, 114, MSG_EVENT); + break; + } + if (findship(i) || findbuilding(i)) { + cmistake(u, ord, 115, MSG_EVENT); + break; + } + } + sunhash(u->ship); + u->ship->no = i; + shash(u->ship); + break; + case P_BUILDING: + case P_GEBAEUDE: + if (!u->building) { + cmistake(u, ord, 145, MSG_EVENT); + break; + } + if (building_owner(u->building)!=u) { + cmistake(u, ord, 148, MSG_EVENT); + break; + } + s = getstrtoken(); + if (*s == 0) { + i = newcontainerid(); + } else { + i = atoi36((const char *)s); + if (i <= 0 || i > MAX_CONTAINER_NR) { + cmistake(u, ord, 114, MSG_EVENT); + break; + } + if (findship(i) || findbuilding(i)) { + cmistake(u, ord, 115, MSG_EVENT); + break; + } + } + bunhash(u->building); + u->building->no = i; + bhash(u->building); + break; + + default: + cmistake(u, ord, 239, MSG_EVENT); + } + return 0; +} + +static building *age_building(building * b) +{ + static bool init = false; + static const building_type *bt_blessed; + static const curse_type *ct_astralblock; + if (!init) { + init = true; + bt_blessed = bt_find("blessedstonecircle"); + ct_astralblock = ct_find("astralblock"); + } + + /* blesses stone circles create an astral protection in the astral region + * above the shield, which prevents chaos suction and other spells. + * The shield is created when a magician enters the blessed stone circle, + * and lasts for as long as his skill level / 2 is, at no mana cost. + * + * TODO: this would be nicer in a btype->age function, but we don't have it. + */ + if (ct_astralblock && bt_blessed && b->type == bt_blessed) { + region *r = b->region; + region *rt = r_standard_to_astral(r); + unit *u, *mage = NULL; + + if (fval(rt->terrain, FORBIDDEN_REGION)) + rt = NULL; + /* step 1: give unicorns to people in the building, + * find out if there's a magician in there. */ + for (u = r->units; u; u = u->next) { + if (b == u->building && inside_building(u)) { + if (!(u_race(u)->ec_flags & GIVEITEM) == 0) { + int n, unicorns = 0; + for (n = 0; n != u->number; ++n) { + if (chance(0.02)) { + i_change(&u->items, olditemtype[I_ELVENHORSE], 1); + ++unicorns; + } + if (unicorns) { + ADDMSG(&u->faction->msgs, msg_message("scunicorn", + "unit amount rtype", + u, unicorns, olditemtype[I_ELVENHORSE]->rtype)); + } + } + } + if (mage == NULL && is_mage(u)) { + mage = u; + } + } + } + + /* if there's a magician, and a connection to astral space, create the + * curse. */ + if (rt != NULL && mage != NULL) { + curse *c = get_curse(rt->attribs, ct_astralblock); + if (c == NULL) { + if (mage != NULL) { + int sk = effskill(mage, SK_MAGIC); + double effect; + effect = 100; + /* the mage reactivates the circle */ + c = create_curse(mage, &rt->attribs, ct_astralblock, + (float)MAX(1, sk), MAX(1, sk / 2), effect, 0); + ADDMSG(&r->msgs, + msg_message("astralshield_activate", "region unit", r, mage)); + } + } else if (mage != NULL) { + int sk = effskill(mage, SK_MAGIC); + c->duration = MAX(c->duration, sk / 2); + c->vigour = MAX(c->vigour, sk); + } + } + } + + a_age(&b->attribs); + handle_event(b->attribs, "timer", b); + + if (b->type->age) { + b->type->age(b); + } + + return b; +} + +static double rc_popularity(const struct race *rc) +{ + int pop = get_param_int(rc->parameters, "morale", MORALE_AVERAGE); + return 1.0 / (pop - MORALE_COOLDOWN); /* 10 turns average */ +} + +static void age_region(region * r) +{ + a_age(&r->attribs); + handle_event(r->attribs, "timer", r); + + if (!r->land) + return; + + if (r->land->ownership && r->land->ownership->owner) { + int stability = turn - r->land->ownership->morale_turn; + int maxmorale = MORALE_DEFAULT; + building *b = largestbuilding(r, &cmp_taxes, false); + if (b) { + int bsize = buildingeffsize(b, false); + maxmorale = (int)(0.5 + b->type->taxes(b, bsize + 1) / MORALE_TAX_FACTOR); + } + if (r->land->morale < maxmorale) { + if (stability > MORALE_COOLDOWN && r->land->ownership->owner + && r->land->morale < MORALE_MAX) { + double ch = rc_popularity(r->land->ownership->owner->race); + if (is_cursed(r->attribs, C_GENEROUS, 0)) { + ch *= 1.2; /* 20% improvement */ + } + if (stability >= MORALE_AVERAGE * 2 || chance(ch)) { + region_set_morale(r, r->land->morale + 1, turn); + } + } + } else if (r->land->morale > maxmorale) { + region_set_morale(r, r->land->morale - 1, turn); + } + } else if (r->land->morale > MORALE_DEFAULT) { + region_set_morale(r, r->land->morale - 1, turn); + } +} + +static void ageing(void) +{ + faction *f; + region *r; + + /* altern spezieller Attribute, die eine Sonderbehandlung brauchen? */ + for (r = regions; r; r = r->next) { + unit *u; + + for (u = r->units; u; u = u->next) { + /* Goliathwasser */ + int i = get_effect(u, oldpotiontype[P_STRONG]); + if (i > 0) { + change_effect(u, oldpotiontype[P_STRONG], -1 * MIN(u->number, i)); + } + /* Berserkerblut */ + i = get_effect(u, oldpotiontype[P_BERSERK]); + if (i > 0) { + change_effect(u, oldpotiontype[P_BERSERK], -1 * MIN(u->number, i)); + } + + if (is_cursed(u->attribs, C_OLDRACE, 0)) { + curse *c = get_curse(u->attribs, ct_find("oldrace")); + if (c->duration == 1 && !(c_flags(c) & CURSE_NOAGE)) { + u_setrace(u, new_race[curse_geteffect_int(c)]); + u->irace = NULL; + } + } + } + } + + /* Borders */ + age_borders(); + + /* Factions */ + for (f = factions; f; f = f->next) { + a_age(&f->attribs); + handle_event(f->attribs, "timer", f); + } + + /* Regionen */ + for (r = regions; r; r = r->next) { + building **bp; + unit **up; + ship **sp; + + age_region(r); + + /* Einheiten */ + for (up = &r->units; *up;) { + unit *u = *up; + a_age(&u->attribs); + if (u == *up) + handle_event(u->attribs, "timer", u); + if (u == *up) + up = &(*up)->next; + } + + /* Schiffe */ + for (sp = &r->ships; *sp;) { + ship *s = *sp; + a_age(&s->attribs); + if (s == *sp) + handle_event(s->attribs, "timer", s); + if (s == *sp) + sp = &(*sp)->next; + } + + /* Gebäude */ + for (bp = &r->buildings; *bp;) { + building *b = *bp; + age_building(b); + if (b == *bp) + bp = &b->next; + } + + if (rule_region_owners()) { + update_owners(r); + } + } +} + +static int maxunits(const faction * f) +{ + int flimit = rule_faction_limit(); + int alimit = rule_alliance_limit(); + if (alimit == 0) { + return flimit; + } + if (flimit == 0) { + return alimit; + } + return MIN(alimit, flimit); +} + +int checkunitnumber(const faction * f, int add) +{ + int alimit, flimit; + + alimit = rule_alliance_limit(); + if (alimit) { + /* if unitsperalliance is true, maxunits returns the + number of units allowed in an alliance */ + faction *f2; + int unitsinalliance = add; + + for (f2 = factions; f2; f2 = f2->next) { + if (f->alliance == f2->alliance) { + unitsinalliance += f2->no_units; + } + if (unitsinalliance > alimit) { + return 1; + } + } + } + + flimit = rule_faction_limit(); + if (flimit) { + if (f->no_units + add > flimit) { + return 2; + } + } + + return 0; +} + +void new_units(void) +{ + region *r; + unit *u, *u2; + + /* neue einheiten werden gemacht und ihre befehle (bis zum "ende" zu + * ihnen rueberkopiert, damit diese einheiten genauso wie die alten + * einheiten verwendet werden koennen. */ + + for (r = regions; r; r = r->next) { + for (u = r->units; u; u = u->next) { + order **ordp = &u->orders; + + /* this needs to happen very early in the game somewhere. since this is + ** pretty much the first function called per turn, and I am lazy, I + ** decree that it goes here */ + if (u->flags & UFL_GUARD) { + fset(r, RF_GUARDED); + } + + while (*ordp) { + order *makeord = *ordp; + if (get_keyword(makeord) == K_MAKE) { + init_tokens(makeord); + skip_token(); + if (isparam(getstrtoken(), u->faction->locale, P_TEMP)) { + const char *token; + char *name = NULL; + int alias; + ship *sh; + order **newordersp; + int err = checkunitnumber(u->faction, 1); + + if (err) { + if (err == 1) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, makeord, + "too_many_units_in_alliance", + "allowed", maxunits(u->faction))); + } else { + ADDMSG(&u->faction->msgs, + msg_feedback(u, makeord, + "too_many_units_in_faction", + "allowed", maxunits(u->faction))); + } + ordp = &makeord->next; + + while (*ordp) { + order *ord = *ordp; + if (get_keyword(ord) == K_END) + break; + *ordp = ord->next; + ord->next = NULL; + free_order(ord); + } + continue; + } + alias = getid(); + + token = getstrtoken(); + if (token && token[0]) { + name = strdup(token); + } + u2 = create_unit(r, u->faction, 0, u->faction->race, alias, name, u); + if (name != NULL) + free(name); + fset(u2, UFL_ISNEW); + + a_add(&u2->attribs, a_new(&at_alias))->data.i = alias; + sh = leftship(u); + if (sh) { + set_leftship(u2, sh); + } + setstatus(u2, u->status); + + ordp = &makeord->next; + newordersp = &u2->orders; + while (*ordp) { + order *ord = *ordp; + if (get_keyword(ord) == K_END) + break; + *ordp = ord->next; + ord->next = NULL; + *newordersp = ord; + newordersp = &ord->next; + } + } + } + if (*ordp == makeord) + ordp = &makeord->next; + } + } + } +} + +/** Checks for two long orders and issues a warning if necessary. + */ +void check_long_orders(unit * u) +{ + order *ord; + keyword_t otherorder = MAXKEYWORDS; + + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == NOKEYWORD) { + cmistake(u, ord, 22, MSG_EVENT); + } else if (is_long(ord)) { + keyword_t longorder = get_keyword(ord); + if (otherorder != MAXKEYWORDS) { + switch (longorder) { + case K_CAST: + if (otherorder != longorder) { + cmistake(u, ord, 52, MSG_EVENT); + } + break; + case K_BUY: + if (otherorder == K_SELL) { + otherorder = K_BUY; + } else { + cmistake(u, ord, 52, MSG_EVENT); + } + break; + case K_SELL: + if (otherorder != K_SELL && otherorder != K_BUY) { + cmistake(u, ord, 52, MSG_EVENT); + } + break; + default: + cmistake(u, ord, 52, MSG_EVENT); + } + } else { + otherorder = longorder; + } + } + } +} + +void update_long_order(unit * u) +{ + order *ord; + bool trade = false; + bool hunger = LongHunger(u); + + freset(u, UFL_MOVED); + freset(u, UFL_LONGACTION); + if (hunger) { + /* Hungernde Einheiten führen NUR den default-Befehl aus */ + set_order(&u->thisorder, default_order(u->faction->locale)); + } else { + check_long_orders(u); + } + /* check all orders for a potential new long order this round: */ + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == NOKEYWORD) + continue; + + if (u->old_orders && is_repeated(ord)) { + /* this new order will replace the old defaults */ + free_orders(&u->old_orders); + if (hunger) + break; + } + if (hunger) + continue; + + if (is_exclusive(ord)) { + /* Über dieser Zeile nur Befehle, die auch eine idle Faction machen darf */ + if (idle(u->faction)) { + set_order(&u->thisorder, default_order(u->faction->locale)); + } else { + set_order(&u->thisorder, copy_order(ord)); + } + break; + } else { + keyword_t keyword = get_keyword(ord); + switch (keyword) { + /* Wenn gehandelt wird, darf kein langer Befehl ausgeführt + * werden. Da Handel erst nach anderen langen Befehlen kommt, + * muß das vorher abgefangen werden. Wir merken uns also + * hier, ob die Einheit handelt. */ + case K_BUY: + case K_SELL: + /* Wenn die Einheit handelt, muß der Default-Befehl gelöscht + * werden. + * Wird je diese Ausschliesslichkeit aufgehoben, muss man aufpassen + * mit der Reihenfolge von Kaufen, Verkaufen etc., damit es Spielern + * nicht moeglich ist, Schulden zu machen. */ + trade = true; + break; + + case K_CAST: + /* dient dazu, das neben Zaubern kein weiterer Befehl + * ausgeführt werden kann, Zaubern ist ein kurzer Befehl */ + set_order(&u->thisorder, copy_order(ord)); + break; + + default: + break; + } + } + } + + if (hunger) { + return; + } + /* Wenn die Einheit handelt, muß der Default-Befehl gelöscht + * werden. */ + + if (trade) { + /* fset(u, UFL_LONGACTION|UFL_NOTMOVING); */ + set_order(&u->thisorder, NULL); + } +} + +static int +use_item(unit * u, const item_type * itype, int amount, struct order *ord) +{ + int i; + int target = read_unitid(u->faction, u->region); + + i = get_pooled(u, itype->rtype, GET_DEFAULT, amount); + + if (amount > i) { + amount = i; + } + if (amount == 0) { + return ENOITEM; + } + + if (target == -1) { + if (itype->use == NULL) { + return EUNUSABLE; + } + return itype->use(u, itype, amount, ord); + } else { + if (itype->useonother == NULL) { + return EUNUSABLE; + } + return itype->useonother(u, target, itype, amount, ord); + } +} + +static double heal_factor(const unit * u) +{ + static float elf_regen = -1; + switch (old_race(u_race(u))) { + case RC_TROLL: + case RC_DAEMON: + return 1.5; + case RC_GOBLIN: + return 2.0; + case RC_ELF: + if (elf_regen < 0) + elf_regen = get_param_flt(u_race(u)->parameters, "regen.forest", 1.0F); + if (elf_regen != 1.0 && r_isforest(u->region)) { + return elf_regen; + } + return 1.0; + default: + return 1.0; + } +} + +void monthly_healing(void) +{ + region *r; + static const curse_type *heal_ct = NULL; + if (heal_ct == NULL) + heal_ct = ct_find("healingzone"); + + for (r = regions; r; r = r->next) { + unit *u; + double healingcurse = 0; + + if (heal_ct != NULL) { + /* bonus zurücksetzen */ + curse *c = get_curse(r->attribs, heal_ct); + if (c != NULL) { + healingcurse = curse_geteffect(c); + } + } + for (u = r->units; u; u = u->next) { + int umhp = unit_max_hp(u) * u->number; + double p = 1.0; + + /* hp über Maximum bauen sich ab. Wird zb durch Elixier der Macht + * oder verändertes Ausdauertalent verursacht */ + if (u->hp > umhp) { + u->hp -= (int)ceil((u->hp - umhp) / 2.0); + if (u->hp < umhp) + u->hp = umhp; + continue; + } + + if (u_race(u)->flags & RCF_NOHEAL) + continue; + if (fval(u, UFL_HUNGER)) + continue; + + if (fval(r->terrain, SEA_REGION) && u->ship == NULL + && !(canswim(u) || canfly(u))) { + continue; + } + + p *= heal_factor(u); + if (u->hp < umhp) { +#ifdef NEW_DAEMONHUNGER_RULE + double maxheal = MAX(u->number, umhp / 20.0); +#else + double maxheal = MAX(u->number, umhp / 10.0); +#endif + int addhp; + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + if (btype == bt_find("inn")) { + p *= 1.5; + } + /* pro punkt 5% höher */ + p *= (1.0 + healingcurse * 0.05); + + maxheal = p * maxheal; + addhp = (int)maxheal; + maxheal -= addhp; + if (maxheal > 0.0 && chance(maxheal)) + ++addhp; + + /* Aufaddieren der geheilten HP. */ + u->hp = MIN(u->hp + addhp, umhp); + + /* soll man an negativer regeneration sterben können? */ + assert(u->hp > 0); + } + } + } +} + +static void remove_exclusive(order ** ordp) +{ + while (*ordp) { + order *ord = *ordp; + if (is_exclusive(ord)) { + *ordp = ord->next; + ord->next = NULL; + free_order(ord); + } else { + ordp = &ord->next; + } + } +} + +void defaultorders(void) +{ + region *r; + + assert(!global.disabled[K_DEFAULT]); + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + bool neworders = false; + order **ordp = &u->orders; + while (*ordp != NULL) { + order *ord = *ordp; + if (get_keyword(ord) == K_DEFAULT) { + char lbuf[8192]; + order *new_order; + init_tokens(ord); + skip_token(); /* skip the keyword */ + strcpy(lbuf, getstrtoken()); + new_order = parse_order(lbuf, u->faction->locale); + *ordp = ord->next; + ord->next = NULL; + free_order(ord); + if (!neworders) { + /* lange Befehle aus orders und old_orders löschen zu gunsten des neuen */ + remove_exclusive(&u->orders); + remove_exclusive(&u->old_orders); + neworders = true; + ordp = &u->orders; /* we could have broken ordp */ + } + if (new_order) + addlist(&u->old_orders, new_order); + } else + ordp = &ord->next; + } + } + } +} + +/* ************************************************************ */ +/* GANZ WICHTIG! ALLE GEAENDERTEN SPRUECHE NEU ANZEIGEN */ +/* GANZ WICHTIG! FUEGT AUCH NEUE ZAUBER IN DIE LISTE DER BEKANNTEN EIN */ +/* ************************************************************ */ +#define COMMONSPELLS 1 /* number of new common spells per level */ +#define MAXMAGES 128 /* should be enough */ + +static int faction_getmages(faction * f, unit ** results, int numresults) +{ + unit *u; + int maxlevel = 0, n = 0; + + for (u = f->units; u; u = u->nextF) { + if (u->number > 0) { + sc_mage *mage = get_mage(u); + if (mage) { + int level = eff_skill(u, SK_MAGIC, u->region); + if (level > maxlevel) { + maxlevel = level; + } + if (nspells) { + quicklist *ql; + int qi; + for (qi = 0, ql = src->spells; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry * sbe = (spellbook_entry *)ql_get(ql, qi); + if (sbe->level<=maxlevel) { + if (!spellbook_get(dst, sbe->sp)) { + spellbook_add(dst, sbe->sp, sbe->level); + } + } + } + } +} + +static void update_spells(void) +{ + faction *f; + + for (f = factions; f; f = f->next) { + if (f->magiegebiet != M_NONE && !is_monsters(f)) { + unit *mages[MAXMAGES]; + int i; + int maxlevel = faction_getmages(f, mages, MAXMAGES); + + if (maxlevel && FactionSpells()) { + spellbook * book = get_spellbook(magic_school[f->magiegebiet]); + if (!f->spellbook) { + f->spellbook = create_spellbook(0); + } + copy_spells(book, f->spellbook, maxlevel); + if (maxlevel > f->max_spelllevel) { + spellbook * common_spells = get_spellbook(magic_school[M_COMMON]); + pick_random_spells(f, maxlevel, common_spells, COMMONSPELLS); + } + } + show_new_spells(f, maxlevel, faction_get_spellbook(f)); + for (i=0; i!=MAXMAGES && mages[i]; ++i) { + unit * u = mages[i]; + sc_mage *mage = get_mage(u); + if (mage && mage->spellbook) { + int level = effskill(u, SK_MAGIC); + show_new_spells(f, level, mage->spellbook); + } + } + } + } +} + +int use_cmd(unit * u, struct order *ord) +{ + const char *t; + int n, err = ENOITEM; + const item_type *itype; + + init_tokens(ord); + skip_token(); + + t = getstrtoken(); + n = atoi((const char *)t); + if (n == 0) { + if (isparam(t, u->faction->locale, P_ANY)) { + /* BENUTZE ALLES Yanxspirit */ + n = INT_MAX; + t = getstrtoken(); + } else { + /* BENUTZE Yanxspirit */ + n = 1; + } + } else { + /* BENUTZE 42 Yanxspirit */ + t = getstrtoken(); + } + itype = finditemtype(t, u->faction->locale); + + if (itype != NULL) { + err = use_item(u, itype, n, ord); + assert(err <= 0 || !"use_item should not return positive values."); + if (err > 0) { + log_error("use_item returned a value>0 for %s\n", resourcename(itype->rtype, 0)); + } + } + switch (err) { + case ENOITEM: + cmistake(u, ord, 43, MSG_PRODUCE); + break; + case EUNUSABLE: + cmistake(u, ord, 76, MSG_PRODUCE); + break; + case ENOSKILL: + cmistake(u, ord, 50, MSG_PRODUCE); + break; + } + return err; +} + +int pay_cmd(unit * u, struct order *ord) +{ + if (!u->building) { + cmistake(u, ord, 6, MSG_EVENT); + } else { + param_t p; + init_tokens(ord); + skip_token(); + p = getparam(u->faction->locale); + if (p == P_NOT) { + unit *owner = building_owner(u->building); + if (owner->faction != u->faction) { + cmistake(u, ord, 1222, MSG_EVENT); + } else { + u->building->flags |= BLD_DONTPAY; + } + } + } + return 0; +} + + +int reserve_cmd(unit * u, struct order *ord) +{ + if (u->number > 0 && (urace(u)->ec_flags & GETITEM)) { + int use, count; + const resource_type *rtype; + const char *s; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + count = atoip((const char *)s); + + if (count == 0 && findparam(s, u->faction->locale) == P_EACH) { + count = getint() * u->number; + } + + rtype = findresourcetype(getstrtoken(), u->faction->locale); + if (rtype == NULL) + return 0; + + set_resvalue(u, rtype, 0); /* make sure the pool is empty */ + use = use_pooled(u, rtype, GET_DEFAULT, count); + if (use) { + set_resvalue(u, rtype, use); + change_resource(u, rtype, use); + return use; + } + } + return 0; +} + +int claim_cmd(unit * u, struct order *ord) +{ + const char *t; + int n; + const item_type *itype; + + init_tokens(ord); + skip_token(); + + t = getstrtoken(); + n = atoi((const char *)t); + if (n == 0) { + n = 1; + } else { + t = getstrtoken(); + } + itype = finditemtype(t, u->faction->locale); + + if (itype != NULL) { + item **iclaim = i_find(&u->faction->items, itype); + if (iclaim != NULL && *iclaim != NULL) { + n = MIN(n, (*iclaim)->number); + i_change(iclaim, itype, -n); + i_change(&u->items, itype, n); + } + } else { + cmistake(u, ord, 43, MSG_PRODUCE); + } + return 0; +} + +enum { + PROC_THISORDER = 1 << 0, + PROC_LONGORDER = 1 << 1 +}; + +typedef enum { PR_GLOBAL, PR_REGION_PRE, PR_UNIT, PR_ORDER, PR_REGION_POST } processor_t; + +typedef struct processor { + struct processor *next; + int priority; + processor_t type; + unsigned int flags; + union { + struct { + keyword_t kword; + int (*process) (struct unit *, struct order *); + } per_order; + struct { + void (*process) (struct unit *); + } per_unit; + struct { + void (*process) (struct region *); + } per_region; + struct { + void (*process) (void); + } global; + } data; + const char *name; +} processor; + +static processor *processors; + +static processor *add_proc(int priority, const char *name, processor_t type) +{ + processor **pproc = &processors; + processor *proc; + + while (*pproc) { + proc = *pproc; + if (proc->priority > priority) + break; + else if (proc->priority == priority && proc->type >= type) + break; + pproc = &proc->next; + } + + proc = (processor *)malloc(sizeof(processor)); + proc->priority = priority; + proc->type = type; + proc->name = name; + proc->next = *pproc; + *pproc = proc; + return proc; +} + +void +add_proc_order(int priority, keyword_t kword, int (*parser) (struct unit *, + struct order *), unsigned int flags, const char *name) +{ + if (!global.disabled[kword]) { + processor *proc = add_proc(priority, name, PR_ORDER); + if (proc) { + proc->data.per_order.process = parser; + proc->data.per_order.kword = kword; + proc->flags = flags; + } + } +} + +void add_proc_global(int priority, void (*process) (void), const char *name) +{ + processor *proc = add_proc(priority, name, PR_GLOBAL); + if (proc) { + proc->data.global.process = process; + } +} + +void add_proc_region(int priority, void (*process) (region *), const char *name) +{ + processor *proc = add_proc(priority, name, PR_REGION_PRE); + if (proc) { + proc->data.per_region.process = process; + } +} + +void +add_proc_postregion(int priority, void (*process) (region *), const char *name) +{ + processor *proc = add_proc(priority, name, PR_REGION_POST); + if (proc) { + proc->data.per_region.process = process; + } +} + +void add_proc_unit(int priority, void (*process) (unit *), const char *name) +{ + processor *proc = add_proc(priority, name, PR_UNIT); + if (proc) { + proc->data.per_unit.process = process; + } +} + +/* per priority, execute processors in order from PR_GLOBAL down to PR_ORDER */ +void process(void) +{ + processor *proc = processors; + faction *f; + + while (proc) { + int prio = proc->priority; + region *r; + processor *pglobal = proc; + + if (verbosity >= 3) + printf("- Step %u\n", prio); + while (proc && proc->priority == prio) { + if (proc->name && verbosity >= 1) + log_printf(stdout, " - %s\n", proc->name); + proc = proc->next; + } + + while (pglobal && pglobal->priority == prio && pglobal->type == PR_GLOBAL) { + pglobal->data.global.process(); + pglobal = pglobal->next; + } + if (pglobal == NULL || pglobal->priority != prio) + continue; + + for (r = regions; r; r = r->next) { + unit *u; + processor *pregion = pglobal; + + while (pregion && pregion->priority == prio + && pregion->type == PR_REGION_PRE) { + pregion->data.per_region.process(r); + pregion = pregion->next; + } + if (pregion == NULL || pregion->priority != prio) + continue; + + if (r->units) { + for (u = r->units; u; u = u->next) { + processor *porder, *punit = pregion; + + while (punit && punit->priority == prio && punit->type == PR_UNIT) { + punit->data.per_unit.process(u); + punit = punit->next; + } + if (punit == NULL || punit->priority != prio) + continue; + + porder = punit; + while (porder && porder->priority == prio && porder->type == PR_ORDER) { + order **ordp = &u->orders; + if (porder->flags & PROC_THISORDER) + ordp = &u->thisorder; + while (*ordp) { + order *ord = *ordp; + if (get_keyword(ord) == porder->data.per_order.kword) { + if (porder->flags & PROC_LONGORDER) { + if (u->number == 0) { + ord = NULL; + } else if (u_race(u) == new_race[RC_INSECT] + && r_insectstalled(r) + && !is_cursed(u->attribs, C_KAELTESCHUTZ, 0)) { + ord = NULL; + } else if (LongHunger(u)) { + cmistake(u, ord, 224, MSG_MAGIC); + ord = NULL; + } else if (fval(u, UFL_LONGACTION)) { + /* this message was already given in laws.update_long_order + cmistake(u, ord, 52, MSG_PRODUCE); + */ + ord = NULL; + } else if (fval(r->terrain, SEA_REGION) + && u_race(u) != new_race[RC_AQUARIAN] + && !(u_race(u)->flags & RCF_SWIM)) { + /* error message disabled by popular demand */ + ord = NULL; + } + } + if (ord) { + porder->data.per_order.process(u, ord); + } + } + if (!ord || *ordp == ord) + ordp = &(*ordp)->next; + } + porder = porder->next; + } + } + } + + while (pregion && pregion->priority == prio + && pregion->type != PR_REGION_POST) { + pregion = pregion->next; + } + + while (pregion && pregion->priority == prio + && pregion->type == PR_REGION_POST) { + pregion->data.per_region.process(r); + pregion = pregion->next; + } + if (pregion == NULL || pregion->priority != prio) + continue; + + } + } + + if (verbosity >= 3) + printf("\n - Leere Gruppen loeschen...\n"); + for (f = factions; f; f = f->next) { + group **gp = &f->groups; + while (*gp) { + group *g = *gp; + if (g->members == 0) { + *gp = g->next; + free_group(g); + } else + gp = &g->next; + } + } + +} + +int siege_cmd(unit * u, order * ord) +{ + region *r = u->region; + building *b; + int d, pooled; + int bewaffnete, katapultiere = 0; + static bool init = false; + static const curse_type *magicwalls_ct; + static item_type *it_catapultammo = NULL; + static item_type *it_catapult = NULL; + if (!init) { + init = true; + magicwalls_ct = ct_find("magicwalls"); + it_catapultammo = it_find("catapultammo"); + it_catapult = it_find("catapult"); + } + /* gibt es ueberhaupt Burgen? */ + + init_tokens(ord); + skip_token(); + b = getbuilding(r); + + if (!b) { + cmistake(u, ord, 31, MSG_BATTLE); + return 31; + } + + if (!playerrace(u_race(u))) { + /* keine Drachen, Illusionen, Untote etc */ + cmistake(u, ord, 166, MSG_BATTLE); + return 166; + } + /* schaden durch katapulte */ + + d = i_get(u->items, it_catapult); + d = MIN(u->number, d); + pooled = get_pooled(u, it_catapultammo->rtype, GET_DEFAULT, d); + d = MIN(pooled, d); + if (eff_skill(u, SK_CATAPULT, r) >= 1) { + katapultiere = d; + d *= eff_skill(u, SK_CATAPULT, r); + } else { + d = 0; + } + + bewaffnete = armedmen(u, true); + if (d == 0 && bewaffnete == 0) { + /* abbruch, falls unbewaffnet oder unfaehig, katapulte zu benutzen */ + cmistake(u, ord, 80, MSG_EVENT); + return 80; + } + + if (!is_guard(u, GUARD_TRAVELTHRU)) { + /* abbruch, wenn die einheit nicht vorher die region bewacht - als + * warnung fuer alle anderen! */ + cmistake(u, ord, 81, MSG_EVENT); + return 81; + } + /* einheit und burg markieren - spart zeit beim behandeln der einheiten + * in der burg, falls die burg auch markiert ist und nicht alle + * einheiten wieder abgesucht werden muessen! */ + + usetsiege(u, b); + b->besieged += MAX(bewaffnete, katapultiere); + + /* definitiver schaden eingeschraenkt */ + + d = MIN(d, b->size - 1); + + /* meldung, schaden anrichten */ + if (d && !curse_active(get_curse(b->attribs, magicwalls_ct))) { + b->size -= d; + use_pooled(u, it_catapultammo->rtype, + GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, d); + /* send message to the entire region */ + ADDMSG(&r->msgs, msg_message("siege_catapults", + "unit building destruction", u, b, d)); + } else { + /* send message to the entire region */ + ADDMSG(&r->msgs, msg_message("siege", "unit building", u, b)); + } + return 0; +} + +void do_siege(region * r) +{ + if (fval(r->terrain, LAND_REGION)) { + unit *u; + + for (u = r->units; u; u = u->next) { + if (get_keyword(u->thisorder) == K_BESIEGE) { + siege_cmd(u, u->thisorder); + } + } + } +} + +static void enter_1(region * r) +{ + do_enter(r, 0); +} + +static void enter_2(region * r) +{ + do_enter(r, 1); +} + +static void maintain_buildings_1(region * r) +{ + maintain_buildings(r, false); +} + +/** warn about passwords that are not US ASCII. + * even though passwords are technically UTF8 strings, the server receives + * them as part of the Subject of an email when reports are requested. + * This means that we need to limit them to ASCII characters until that + * mechanism has been changed. + */ +static int warn_password(void) +{ + faction *f = factions; + while (f) { + bool pwok = true; + const char *c = f->passw; + while (*c && pwok) { + if (!isalnum((unsigned char)*c)) + pwok = false; + c++; + } + if (!pwok) { + free(f->passw); + f->passw = strdup(itoa36(rng_int())); + ADDMSG(&f->msgs, msg_message("illegal_password", "newpass", f->passw)); + } + f = f->next; + } + return 0; +} + +void init_processor(void) +{ + int p; + + p = 10; + add_proc_global(p, &new_units, "Neue Einheiten erschaffen"); + + p += 10; + add_proc_unit(p, update_long_order, "Langen Befehl aktualisieren"); + add_proc_order(p, K_BANNER, banner_cmd, 0, NULL); + add_proc_order(p, K_EMAIL, &email_cmd, 0, NULL); + add_proc_order(p, K_PASSWORD, &password_cmd, 0, NULL); + add_proc_order(p, K_SEND, &send_cmd, 0, NULL); + add_proc_order(p, K_GROUP, &group_cmd, 0, NULL); + + p += 10; + add_proc_order(p, K_QUIT, &quit_cmd, 0, NULL); + add_proc_order(p, K_URSPRUNG, &origin_cmd, 0, NULL); + add_proc_order(p, K_ALLY, &ally_cmd, 0, NULL); + add_proc_order(p, K_PREFIX, &prefix_cmd, 0, NULL); + add_proc_order(p, K_SETSTEALTH, &setstealth_cmd, 0, NULL); + add_proc_order(p, K_STATUS, &status_cmd, 0, NULL); + add_proc_order(p, K_COMBATSPELL, &combatspell_cmd, 0, NULL); + add_proc_order(p, K_DISPLAY, &display_cmd, 0, NULL); + add_proc_order(p, K_NAME, &name_cmd, 0, NULL); + add_proc_order(p, K_GUARD, &guard_off_cmd, 0, NULL); + add_proc_order(p, K_RESHOW, &reshow_cmd, 0, NULL); + + if (get_param_int(global.parameters, "rules.alliances", 0) == 1) { + p += 10; + add_proc_global(p, &alliance_cmd, NULL); + } + + p += 10; + add_proc_region(p, do_contact, "Kontaktieren"); + add_proc_order(p, K_MAIL, &mail_cmd, 0, "Botschaften"); + + p += 10; /* all claims must be done before we can USE */ + add_proc_region(p, &enter_1, "Betreten (1. Versuch)"); + add_proc_order(p, K_USE, &use_cmd, 0, "Benutzen"); + + if (!global.disabled[K_GM]) { + add_proc_global(p, &gmcommands, "GM Kommandos"); + } + + p += 10; /* in case it has any effects on alliance victories */ + add_proc_order(p, K_GIVE, &give_control_cmd, 0, "GIB KOMMANDO"); + + p += 10; /* in case it has any effects on alliance victories */ + add_proc_order(p, K_LEAVE, &leave_cmd, 0, "Verlassen"); + + add_proc_region(p, &do_battle, "Attackieren"); + + if (!global.disabled[K_BESIEGE]) { + p += 10; + add_proc_region(p, &do_siege, "Belagern"); + } + + p += 10; /* can't allow reserve before siege (weapons) */ + add_proc_region(p, &enter_1, "Betreten (2. Versuch)"); + add_proc_order(p, K_RESERVE, &reserve_cmd, 0, "Reservieren"); + add_proc_order(p, K_CLAIM, &claim_cmd, 0, NULL); + add_proc_unit(p, &follow_unit, "Folge auf Einheiten setzen"); + + p += 10; /* rest rng again before economics */ + add_proc_region(p, &economics, "Zerstoeren, Geben, Rekrutieren, Vergessen"); + + p += 10; + if (!global.disabled[K_PAY]) { + add_proc_order(p, K_PAY, &pay_cmd, 0, "Gebaeudeunterhalt (disable)"); + } + add_proc_postregion(p, &maintain_buildings_1, + "Gebaeudeunterhalt (1. Versuch)"); + + p += 10; /* QUIT fuer sich alleine */ + add_proc_global(p, quit, "Sterben"); + + if (!global.disabled[K_CAST]) { + p += 10; + add_proc_global(p, &magic, "Zaubern"); + } + + p += 10; + add_proc_order(p, K_TEACH, &teach_cmd, PROC_THISORDER | PROC_LONGORDER, + "Lehren"); + p += 10; + add_proc_order(p, K_STUDY, &learn_cmd, PROC_THISORDER | PROC_LONGORDER, + "Lernen"); + + p += 10; + add_proc_order(p, K_MAKE, &make_cmd, PROC_THISORDER | PROC_LONGORDER, + "Produktion"); + add_proc_postregion(p, &produce, "Arbeiten, Handel, Rekruten"); + add_proc_postregion(p, &split_allocations, "Produktion II"); + + p += 10; + add_proc_region(p, &enter_2, "Betreten (3. Versuch)"); + + p += 10; + add_proc_region(p, &sinkships, "Schiffe sinken"); + + p += 10; + add_proc_global(p, &movement, "Bewegungen"); + + if (get_param_int(global.parameters, "work.auto", 0)) { + p += 10; + add_proc_region(p, &auto_work, "Arbeiten (auto)"); + } + + p += 10; + add_proc_order(p, K_GUARD, &guard_on_cmd, 0, "Bewache (an)"); + + if (get_param_int(global.parameters, "rules.encounters", 1)) { + p += 10; + add_proc_global(p, &encounters, "Zufallsbegegnungen"); + } + + p += 10; + add_proc_unit(p, &monster_kills_peasants, + "Monster fressen und vertreiben Bauern"); + + p += 10; + add_proc_global(p, &randomevents, "Zufallsereignisse"); + + p += 10; + + add_proc_global(p, &monthly_healing, "Regeneration (HP)"); + add_proc_global(p, ®enerate_aura, "Regeneration (Aura)"); + if (!global.disabled[K_DEFAULT]) { + add_proc_global(p, &defaultorders, "Defaults setzen"); + } + add_proc_global(p, &demographics, "Nahrung, Seuchen, Wachstum, Wanderung"); + + if (!global.disabled[K_SORT]) { + p += 10; + add_proc_global(p, restack_units, "Einheiten sortieren"); + } + add_proc_order(p, K_PROMOTION, &promotion_cmd, 0, "Heldenbefoerderung"); + if (!global.disabled[K_NUMBER]) { + add_proc_order(p, K_NUMBER, &renumber_cmd, 0, "Neue Nummern (Einheiten)"); + p += 10; + add_proc_global(p, &renumber_factions, "Neue Nummern"); + } +} + +void processorders(void) +{ + static int init = 0; + + if (!init) { + init_processor(); + init = 1; + } + update_spells(); + process(); + /*************************************************/ + + if (get_param_int(global.parameters, "modules.markets", 0)) { + do_markets(); + } + + log_info(" - Attribute altern"); + ageing(); + remove_empty_units(); + + /* must happen AFTER age, because that would destroy them right away */ + if (get_param_int(global.parameters, "modules.wormholes", 0)) { + create_wormholes(); + } + + /* immer ausführen, wenn neue Sprüche dazugekommen sind, oder sich + * Beschreibungen geändert haben */ + update_spells(); + warn_password(); +} + +int writepasswd(void) +{ + FILE *F; + char zText[128]; + + sprintf(zText, "%s/passwd", basepath()); + F = cfopen(zText, "w"); + if (F) { + faction *f; + log_info("writing passwords..."); + + for (f = factions; f; f = f->next) { + fprintf(F, "%s:%s:%s:%s:%u\n", + factionid(f), f->email, f->passw, f->override, f->subscription); + } + fclose(F); + return 0; + } + return 1; +} + +void update_subscriptions(void) +{ + FILE *F; + char zText[MAX_PATH]; + faction *f; + strcat(strcpy(zText, basepath()), "/subscriptions"); + F = fopen(zText, "r"); + if (F == NULL) { + log_warning(0, "could not open %s.\n", zText); + return; + } + for (;;) { + char zFaction[5]; + int subscription, fno; + if (fscanf(F, "%d %s", &subscription, zFaction) <= 0) + break; + fno = atoi36(zFaction); + f = findfaction(fno); + if (f != NULL) { + f->subscription = subscription; + } + } + fclose(F); + + sprintf(zText, "subscriptions.%u", turn); + F = fopen(zText, "w"); + for (f = factions; f != NULL; f = f->next) { + fprintf(F, "%s:%u:%s:%s:%s:%u:\n", + itoa36(f->no), f->subscription, f->email, f->override, + dbrace(f->race), f->lastorders); + } + fclose(F); +} + +int init_data(const char *filename, const char *catalog) +{ + int l; + + l = read_xml(filename, catalog); + if (l) + return l; + + init_locales(); + init_archetypes(); + + if (turn < 0) { + turn = first_turn; + } + return 0; +} diff --git a/core/src/gamecode/laws.h b/core/src/gamecode/laws.h new file mode 100755 index 000000000..09f7ce5e8 --- /dev/null +++ b/core/src/gamecode/laws.h @@ -0,0 +1,94 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_GC_LAWS +#define H_GC_LAWS +#ifdef __cplusplus +extern "C" { +#endif + + extern int writepasswd(void); + int getoption(void); + int wanderoff(struct region *r, int p); + void demographics(void); + void last_orders(void); + void find_address(void); + void update_guards(void); + void update_subscriptions(void); + void deliverMail(struct faction *f, struct region *r, struct unit *u, + const char *s, struct unit *receiver); + int init_data(const char *filename, const char *catalog); + + bool renamed_building(const struct building * b); + int rename_building(struct unit * u, struct order * ord, struct building * b, const char *name); + void get_food(struct region * r); + extern int can_contact(const struct region *r, const struct unit *u, const struct unit *u2); + +/* eressea-specific. put somewhere else, please. */ + void processorders(void); + extern struct attrib_type at_germs; + + extern int dropouts[2]; + extern int *age; + + extern int enter_building(struct unit *u, struct order *ord, int id, int report); + extern int enter_ship(struct unit *u, struct order *ord, int id, int report); + + extern void new_units(void); + extern void defaultorders(void); + extern void quit(void); + extern void monthly_healing(void); + extern void renumber_factions(void); + extern void restack_units(void); + extern void update_long_order(struct unit *u); + extern void sinkships(struct region * r); + extern void do_enter(struct region *r, bool is_final_attempt); + + extern int password_cmd(struct unit *u, struct order *ord); + extern int banner_cmd(struct unit *u, struct order *ord); + extern int email_cmd(struct unit *u, struct order *ord); + extern int send_cmd(struct unit *u, struct order *ord); + extern int ally_cmd(struct unit* u, struct order *ord); + extern int prefix_cmd(struct unit *u, struct order *ord); + extern int setstealth_cmd(struct unit *u, struct order *ord); + extern int status_cmd(struct unit *u, struct order *ord); + extern int display_cmd(struct unit *u, struct order *ord); + extern int group_cmd(struct unit *u, struct order *ord); + extern int origin_cmd(struct unit *u, struct order *ord); + extern int quit_cmd(struct unit *u, struct order *ord); + extern int name_cmd(struct unit *u, struct order *ord); + extern int use_cmd(struct unit *u, struct order *ord); + extern int siege_cmd(struct unit *u, struct order *ord); + extern int leave_cmd(struct unit *u, struct order *ord); + extern int pay_cmd(struct unit *u, struct order *ord); + extern int promotion_cmd(struct unit *u, struct order *ord); + extern int renumber_cmd(struct unit *u, struct order *ord); + extern int combatspell_cmd(struct unit *u, struct order *ord); + extern int contact_cmd(struct unit *u, struct order *ord); + extern int guard_on_cmd(struct unit *u, struct order *ord); + extern int guard_off_cmd(struct unit *u, struct order *ord); + extern int reshow_cmd(struct unit *u, struct order *ord); + extern int mail_cmd(struct unit *u, struct order *ord); + extern int reserve_cmd(struct unit *u, struct order *ord); + extern int claim_cmd(struct unit *u, struct order *ord); + extern int follow_cmd(struct unit *u, struct order *ord); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/laws_test.c b/core/src/gamecode/laws_test.c new file mode 100644 index 000000000..e624d12ce --- /dev/null +++ b/core/src/gamecode/laws_test.c @@ -0,0 +1,221 @@ +#include +#include +#include "laws.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +static void test_new_building_can_be_renamed(CuTest * tc) +{ + region *r; + building *b; + building_type *btype; + + test_cleanup(); + test_create_world(); + + btype = bt_find("castle"); + r = findregion(-1, 0); + + b = new_building(btype, r, default_locale); + CuAssertTrue(tc, !renamed_building(b)); +} + +static void test_rename_building(CuTest * tc) +{ + region *r; + building *b; + unit *u; + faction *f; + building_type *btype; + + test_cleanup(); + test_create_world(); + + btype = bt_find("castle"); + + r = findregion(-1, 0); + b = new_building(btype, r, default_locale); + f = test_create_faction(rc_find("human")); + u = test_create_unit(f, r); + u_set_building(u, b); + + rename_building(u, NULL, b, "Villa Nagel"); + CuAssertStrEquals(tc, "Villa Nagel", b->name); +} + +static void test_rename_building_twice(CuTest * tc) +{ + region *r; + building *b; + unit *u; + faction *f; + building_type *btype; + + test_cleanup(); + test_create_world(); + + btype = bt_find("castle"); + + r = findregion(-1, 0); + b = new_building(btype, r, default_locale); + f = test_create_faction(rc_find("human")); + u = test_create_unit(f, r); + u_set_building(u, b); + + rename_building(u, NULL, b, "Villa Nagel"); + CuAssertStrEquals(tc, "Villa Nagel", b->name); + + rename_building(u, NULL, b, "Villa Kunterbunt"); + CuAssertStrEquals(tc, "Villa Kunterbunt", b->name); +} + +static void test_fishing_feeds_2_people(CuTest * tc) +{ + region *r; + faction *f; + unit *u; + ship *sh; + + test_cleanup(); + test_create_world(); + r = findregion(-1, 0); + CuAssertStrEquals(tc, "ocean", r->terrain->_name); /* test_create_world needs coverage */ + f = test_create_faction(rc_find("human")); + u = test_create_unit(f, r); + sh = new_ship(st_find("boat"), r, 0); + u_set_ship(u, sh); + i_change(&u->items, it_find("money"), 42); + + scale_number(u, 1); + sh->flags |= SF_FISHING; + get_food(r); + CuAssertIntEquals(tc, 42, i_get(u->items, it_find("money"))); + + scale_number(u, 2); + sh->flags |= SF_FISHING; + get_food(r); + CuAssertIntEquals(tc, 42, i_get(u->items, it_find("money"))); + + scale_number(u, 3); + sh->flags |= SF_FISHING; + get_food(r); + CuAssertIntEquals(tc, 32, i_get(u->items, it_find("money"))); + +} + +static int not_so_hungry(const unit * u) +{ + return 6 * u->number; +} + +static void test_fishing_does_not_give_goblins_money(CuTest * tc) +{ + region *r; + faction *f; + unit *u; + ship *sh; + + test_cleanup(); + test_create_world(); + + r = findregion(-1, 0); + CuAssertStrEquals(tc, "ocean", r->terrain->_name); /* test_create_world needs coverage */ + f = test_create_faction(rc_find("human")); + u = test_create_unit(f, r); + sh = new_ship(st_find("boat"), r, 0); + u_set_ship(u, sh); + i_change(&u->items, it_find("money"), 42); + + global.functions.maintenance = not_so_hungry; + scale_number(u, 2); + sh->flags |= SF_FISHING; + get_food(r); + CuAssertIntEquals(tc, 42, i_get(u->items, it_find("money"))); + +} + +static void test_fishing_gets_reset(CuTest * tc) +{ + region *r; + faction *f; + unit *u; + ship *sh; + + test_cleanup(); + test_create_world(); + r = findregion(-1, 0); + CuAssertStrEquals(tc, "ocean", r->terrain->_name); /* test_create_world needs coverage */ + f = test_create_faction(rc_find("human")); + u = test_create_unit(f, r); + sh = new_ship(st_find("boat"), r, 0); + u_set_ship(u, sh); + i_change(&u->items, it_find("money"), 42); + + scale_number(u, 1); + sh->flags |= SF_FISHING; + get_food(r); + CuAssertIntEquals(tc, 42, i_get(u->items, it_find("money"))); + + scale_number(u, 1); + get_food(r); + CuAssertIntEquals(tc, 32, i_get(u->items, it_find("money"))); + +} + +static void test_unit_limit(CuTest * tc) +{ + set_param(&global.parameters, "rules.limit.faction", "250"); + CuAssertIntEquals(tc, 250, rule_faction_limit()); + + set_param(&global.parameters, "rules.limit.faction", "200"); + CuAssertIntEquals(tc, 200, rule_faction_limit()); + + set_param(&global.parameters, "rules.limit.alliance", "250"); + CuAssertIntEquals(tc, 250, rule_alliance_limit()); + +} + +extern int checkunitnumber(const faction * f, int add); +static void test_cannot_create_unit_above_limit(CuTest * tc) +{ + faction *f; + + test_cleanup(); + test_create_world(); + f = test_create_faction(rc_find("human")); + set_param(&global.parameters, "rules.limit.faction", "4"); + + CuAssertIntEquals(tc, 0, checkunitnumber(f, 4)); + CuAssertIntEquals(tc, 2, checkunitnumber(f, 5)); + + set_param(&global.parameters, "rules.limit.alliance", "3"); + CuAssertIntEquals(tc, 0, checkunitnumber(f, 3)); + CuAssertIntEquals(tc, 1, checkunitnumber(f, 4)); +} + +CuSuite *get_laws_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_new_building_can_be_renamed); + SUITE_ADD_TEST(suite, test_rename_building); + SUITE_ADD_TEST(suite, test_rename_building_twice); + SUITE_ADD_TEST(suite, test_fishing_feeds_2_people); + SUITE_ADD_TEST(suite, test_fishing_does_not_give_goblins_money); + SUITE_ADD_TEST(suite, test_fishing_gets_reset); + SUITE_ADD_TEST(suite, test_unit_limit); + SUITE_ADD_TEST(suite, test_cannot_create_unit_above_limit); + return suite; +} diff --git a/core/src/gamecode/market.c b/core/src/gamecode/market.c new file mode 100644 index 000000000..2e812e9c0 --- /dev/null +++ b/core/src/gamecode/market.c @@ -0,0 +1,179 @@ +/* vi: set ts=2: ++-------------------+ Christian Schlittchen +| | Enno Rehling +| Eressea PBEM host | Katja Zedel +| (c) 1998 - 2003 | Henning Peters +| | Ingo Wilken ++-------------------+ Stefan Reich + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. + +*/ +#include +#include +#include "market.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +static unsigned int get_markets(region * r, unit ** results, size_t size) +{ + unsigned int n = 0; + building *b; + static building_type *btype; + if (!btype) + btype = bt_find("market"); + if (!btype) + return 0; + for (b = r->buildings; n < size && b; b = b->next) { + if (b->type == btype && (b->flags & BLD_WORKING) + && b->size >= b->type->maxsize) { + unit *u = building_owner(b); + unsigned int i; + for (i = 0; u && i != n; ++i) { + /* only one market per faction */ + if (results[i]->faction == u->faction) + u = NULL; + } + if (u) { + results[n++] = u; + } + } + } + return n; +} + +static void free_market(attrib * a) +{ + item *items = (item *) a->data.v; + i_freeall(&items); + a->data.v = 0; +} + +attrib_type at_market = { + "script", + NULL, free_market, NULL, + NULL, NULL, ATF_UNIQUE +}; + +static int rc_luxury_trade(const struct race *rc) +{ + if (rc) { + return get_param_int(rc->parameters, "luxury_trade", 1000); + } + return 1000; +} + +static int rc_herb_trade(const struct race *rc) +{ + if (rc) { + return get_param_int(rc->parameters, "herb_trade", 500); + } + return 500; +} + +#define MAX_MARKETS 128 +#define MIN_PEASANTS 50 /* if there are at least this many peasants, you will get 1 good */ + +void do_markets(void) +{ + quicklist *traders = 0; + unit *markets[MAX_MARKETS]; + region *r; + for (r = regions; r; r = r->next) { + if (r->land) { + faction *f = region_get_owner(r); + const struct race *rc = f ? f->race : NULL; + int p = rpeasants(r); + int numlux = rc_luxury_trade(rc), numherbs = rc_herb_trade(rc); + numlux = (p + numlux - MIN_PEASANTS) / numlux; + numherbs = (p + numherbs - MIN_PEASANTS) / numherbs; + if (numlux > 0 || numherbs > 0) { + int d, nmarkets = 0; + const item_type *lux = r_luxury(r); + const item_type *herb = r->land->herbtype; + + nmarkets += get_markets(r, markets + nmarkets, MAX_MARKETS - nmarkets); + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *r2 = rconnect(r, d); + if (r2 && r2->buildings) { + nmarkets += + get_markets(r2, markets + nmarkets, MAX_MARKETS - nmarkets); + } + } + if (nmarkets) { + while (lux && numlux--) { + int n = rng_int() % nmarkets; + unit *u = markets[n]; + item *items; + attrib *a = a_find(u->attribs, &at_market); + if (a == NULL) { + a = a_add(&u->attribs, a_new(&at_market)); + ql_push(&traders, u); + } + items = (item *) a->data.v; + i_change(&items, lux, 1); + a->data.v = items; + /* give 1 luxury */ + } + while (herb && numherbs--) { + int n = rng_int() % nmarkets; + unit *u = markets[n]; + item *items; + attrib *a = a_find(u->attribs, &at_market); + if (a == NULL) { + a = a_add(&u->attribs, a_new(&at_market)); + ql_push(&traders, u); + } + items = (item *) a->data.v; + i_change(&items, herb, 1); + a->data.v = items; + /* give 1 herb */ + } + } + } + } + } + + if (traders) { + quicklist *qliter = traders; + int qli = 0; + for (qli = 0; qliter; ql_advance(&qliter, &qli, 1)) { + unit *u = (unit *) ql_get(qliter, qli); + attrib *a = a_find(u->attribs, &at_market); + item *items = (item *) a->data.v; + + a->data.v = NULL; + while (items) { + item *itm = items; + items = itm->next; + + if (itm->number) { + ADDMSG(&u->faction->msgs, msg_message("buyamount", + "unit amount resource", u, itm->number, itm->type->rtype)); + itm->next = NULL; + i_add(&u->items, itm); + } else { + i_free(itm); + } + } + + a_remove(&u->attribs, a); + } + ql_free(traders); + } +} diff --git a/core/src/gamecode/market.h b/core/src/gamecode/market.h new file mode 100644 index 000000000..e45fbb7a1 --- /dev/null +++ b/core/src/gamecode/market.h @@ -0,0 +1,25 @@ +/* vi: set ts=2: ++-------------------+ Christian Schlittchen +| | Enno Rehling +| Eressea PBEM host | Katja Zedel +| (c) 1998 - 2003 | Henning Peters +| | Ingo Wilken ++-------------------+ Stefan Reich + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. + +*/ +#ifndef H_GC_MARKET +#define H_GC_MARKET +#ifdef __cplusplus +extern "C" { +#endif + struct building; + + extern void do_markets(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/market_test.c b/core/src/gamecode/market_test.c new file mode 100644 index 000000000..71f9bcf24 --- /dev/null +++ b/core/src/gamecode/market_test.c @@ -0,0 +1,88 @@ +#include +#include +#include "market.h" +#include "tests.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +static void test_market_curse(CuTest * tc) +{ + region *r; + building *b; + unit *u; + faction *f; + int x, y; + const char *names[4] = { "herb", "herbs", "balm", "balms" }; + const terrain_type *terrain; + item_type *htype, *ltype; + luxury_type *lux; + building_type *btype; + + free_gamedata(); + test_cleanup(); + test_create_world(); + + htype = test_create_itemtype(names); + htype->flags |= ITF_HERB; + htype->rtype->flags |= (RTF_ITEM | RTF_POOLED); + + ltype = test_create_itemtype(names + 2); + ltype->rtype->flags |= (RTF_ITEM | RTF_POOLED); + lux = new_luxurytype(ltype, 0); + + set_param(&global.parameters, "rules.region_owners", "1"); + + btype = (building_type *)calloc(1, sizeof(building_type)); + btype->_name = "market"; + bt_register(btype); + + terrain = get_terrain("plain"); + + for (x = 0; x != 3; ++x) { + for (y = 0; y != 3; ++y) { + r = findregion(x, y); + if (!r) { + r = test_create_region(x, y, terrain); + } else { + terraform_region(r, terrain); + } + rsetpeasants(r, 5000); + r_setdemand(r, lux, 0); + rsetherbtype(r, htype); + } + } + r = findregion(1, 1); + b = test_create_building(r, btype); + b->flags |= BLD_WORKING; + b->size = b->type->maxsize; + + f = test_create_faction(0); + u = test_create_unit(f, r); + u_set_building(u, b); + + do_markets(); + + CuAssertIntEquals(tc, 70, i_get(u->items, htype)); + CuAssertIntEquals(tc, 35, i_get(u->items, ltype)); +} + +CuSuite *get_market_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_market_curse); + return suite; +} diff --git a/core/src/gamecode/monster.c b/core/src/gamecode/monster.c new file mode 100644 index 000000000..ab016e5a5 --- /dev/null +++ b/core/src/gamecode/monster.c @@ -0,0 +1,222 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "monster.h" + +/* gamecode includes */ +#include "economy.h" +#include "give.h" + +/* triggers includes */ +#include + +/* attributes includes */ +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +#define MOVECHANCE 25 /* chance fuer bewegung */ + +#define MAXILLUSION_TEXTS 3 + +bool monster_is_waiting(const unit * u) +{ + if (fval(u, UFL_ISNEW | UFL_MOVED)) + return true; + return false; +} + +static void eaten_by_monster(unit * u) +{ + /* adjustment for smaller worlds */ + static double multi = 0.0; + int n = 0; + int horse = 0; + + if (multi == 0.0) { + multi = RESOURCE_QUANTITY * newterrain(T_PLAIN)->size / 10000.0; + } + + switch (old_race(u_race(u))) { + case RC_FIREDRAGON: + n = rng_int() % 80 * u->number; + horse = get_item(u, I_HORSE); + break; + case RC_DRAGON: + n = rng_int() % 200 * u->number; + horse = get_item(u, I_HORSE); + break; + case RC_WYRM: + n = rng_int() % 500 * u->number; + horse = get_item(u, I_HORSE); + break; + default: + n = rng_int() % (u->number / 20 + 1); + } + + n = (int)(n * multi); + if (n > 0) { + n = lovar(n); + n = MIN(rpeasants(u->region), n); + + if (n > 0) { + deathcounts(u->region, n); + rsetpeasants(u->region, rpeasants(u->region) - n); + ADDMSG(&u->region->msgs, msg_message("eatpeasants", "unit amount", u, n)); + } + } + if (horse > 0) { + set_item(u, I_HORSE, 0); + ADDMSG(&u->region->msgs, msg_message("eathorse", "unit amount", u, horse)); + } +} + +static void absorbed_by_monster(unit * u) +{ + int n; + + switch (old_race(u_race(u))) { + default: + n = rng_int() % (u->number / 20 + 1); + } + + if (n > 0) { + n = lovar(n); + n = MIN(rpeasants(u->region), n); + if (n > 0) { + rsetpeasants(u->region, rpeasants(u->region) - n); + scale_number(u, u->number + n); + ADDMSG(&u->region->msgs, msg_message("absorbpeasants", + "unit race amount", u, u_race(u), n)); + } + } +} + +static int scareaway(region * r, int anzahl) +{ + int n, p, diff = 0, emigrants[MAXDIRECTIONS]; + direction_t d; + + anzahl = MIN(MAX(1, anzahl), rpeasants(r)); + + /* Wandern am Ende der Woche (normal) oder wegen Monster. Die + * Wanderung wird erst am Ende von demographics () ausgefuehrt. + * emigrants[] ist local, weil r->newpeasants durch die Monster + * vielleicht schon hochgezaehlt worden ist. */ + + for (d = 0; d != MAXDIRECTIONS; d++) + emigrants[d] = 0; + + p = rpeasants(r); + assert(p >= 0 && anzahl >= 0); + for (n = MIN(p, anzahl); n; n--) { + direction_t dir = (direction_t) (rng_int() % MAXDIRECTIONS); + region *rc = rconnect(r, dir); + + if (rc && fval(rc->terrain, LAND_REGION)) { + ++diff; + rc->land->newpeasants++; + emigrants[dir]++; + } + } + rsetpeasants(r, p - diff); + assert(p >= diff); + return diff; +} + +static void scared_by_monster(unit * u) +{ + int n; + + switch (old_race(u_race(u))) { + case RC_FIREDRAGON: + n = rng_int() % 160 * u->number; + break; + case RC_DRAGON: + n = rng_int() % 400 * u->number; + break; + case RC_WYRM: + n = rng_int() % 1000 * u->number; + break; + default: + n = rng_int() % (u->number / 4 + 1); + } + + if (n > 0) { + n = lovar(n); + n = MIN(rpeasants(u->region), n); + if (n > 0) { + n = scareaway(u->region, n); + if (n > 0) { + ADDMSG(&u->region->msgs, msg_message("fleescared", + "amount unit", n, u)); + } + } + } +} + +void monster_kills_peasants(unit * u) +{ + if (!monster_is_waiting(u)) { + if (u_race(u)->flags & RCF_SCAREPEASANTS) { + scared_by_monster(u); + } + if (u_race(u)->flags & RCF_KILLPEASANTS) { + eaten_by_monster(u); + } + if (u_race(u)->flags & RCF_ABSORBPEASANTS) { + absorbed_by_monster(u); + } + } +} diff --git a/core/src/gamecode/monster.h b/core/src/gamecode/monster.h new file mode 100644 index 000000000..27a8f23f7 --- /dev/null +++ b/core/src/gamecode/monster.h @@ -0,0 +1,31 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_GC_MONSTER +#define H_GC_MONSTER +#ifdef __cplusplus +extern "C" { +#endif + + void monster_kills_peasants(struct unit *u); + bool monster_is_waiting(const struct unit *u); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/randenc.c b/core/src/gamecode/randenc.c new file mode 100644 index 000000000..f1e577477 --- /dev/null +++ b/core/src/gamecode/randenc.c @@ -0,0 +1,1327 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "randenc.h" + +#include "economy.h" +#include "monster.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* attributes includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +#include +extern struct attrib_type at_unitdissolve; +extern struct attrib_type at_orcification; + +/* In a->data.ca[1] steht der Prozentsatz mit dem sich die Einheit + * auflöst, in a->data.ca[0] kann angegeben werden, wohin die Personen + * verschwinden. Passiert bereits in der ersten Runde! */ +static void dissolve_units(void) +{ + region *r; + unit *u; + int n; + int i; + + for (r = regions; r; r = r->next) { + for (u = r->units; u; u = u->next) { + attrib *a = a_find(u->attribs, &at_unitdissolve); + if (a) { + message *msg; + + if (u->age == 0 && a->data.ca[1] < 100) + continue; + + /* TODO: Durch einzelne Berechnung ersetzen */ + if (a->data.ca[1] == 100) { + n = u->number; + } else { + n = 0; + for (i = 0; i < u->number; i++) { + if (rng_int() % 100 < a->data.ca[1]) + n++; + } + } + + /* wenn keiner verschwindet, auch keine Meldung */ + if (n == 0) { + continue; + } + + scale_number(u, u->number - n); + + switch (a->data.ca[0]) { + case 1: + rsetpeasants(r, rpeasants(r) + n); + msg = + msg_message("dissolve_units_1", "unit region number race", u, r, + n, u_race(u)); + break; + case 2: + if (r->land && !fval(r, RF_MALLORN)) { + rsettrees(r, 2, rtrees(r, 2) + n); + msg = + msg_message("dissolve_units_2", "unit region number race", u, r, + n, u_race(u)); + } else { + msg = + msg_message("dissolve_units_3", "unit region number race", u, r, + n, u_race(u)); + } + break; + default: + if (u_race(u) == new_race[RC_STONEGOLEM] + || u_race(u) == new_race[RC_IRONGOLEM]) { + msg = + msg_message("dissolve_units_4", "unit region number race", u, r, + n, u_race(u)); + } else { + msg = + msg_message("dissolve_units_5", "unit region number race", u, r, + n, u_race(u)); + } + break; + } + + add_message(&u->faction->msgs, msg); + msg_release(msg); + } + } + } + + remove_empty_units(); +} + +static int improve_all(faction * f, skill_t sk, int by_weeks) +{ + unit *u; + bool ret = by_weeks; + + for (u = f->units; u; u = u->nextF) { + if (has_skill(u, sk)) { + int weeks = 0; + for (; weeks != by_weeks; ++weeks) { + learn_skill(u, sk, 1.0); + ret = 0; + } + } + } + + return ret; +} + +void find_manual(region * r, unit * u) +{ + char zLocation[32]; + char zBook[32]; + skill_t skill = NOSKILL; + message *msg; + + switch (rng_int() % 36) { + case 0: + skill = SK_MAGIC; + break; + case 1: + case 2: + case 3: + case 4: + skill = SK_WEAPONSMITH; + break; + case 5: + case 6: + skill = SK_TACTICS; + break; + case 7: + case 8: + case 9: + case 10: + skill = SK_SHIPBUILDING; + break; + case 11: + case 12: + case 13: + case 14: + skill = SK_SAILING; + break; + case 15: + case 16: + case 17: + skill = SK_HERBALISM; + break; + case 18: + case 19: + skill = SK_ALCHEMY; + break; + case 20: + case 21: + case 22: + case 23: + skill = SK_BUILDING; + break; + case 24: + case 25: + case 26: + case 27: + skill = SK_ARMORER; + break; + case 28: + case 29: + case 30: + case 31: + skill = SK_MINING; + break; + case 32: + case 33: + case 34: + case 35: + skill = SK_ENTERTAINMENT; + break; + } + + slprintf(zLocation, sizeof(zLocation), "manual_location_%d", + (int)(rng_int() % 4)); + slprintf(zBook, sizeof(zLocation), "manual_title_%s", skillnames[skill]); + + msg = msg_message("find_manual", "unit location book", u, zLocation, zBook); + r_addmessage(r, u->faction, msg); + msg_release(msg); + + if (improve_all(u->faction, skill, 3) == 3) { + int i; + for (i = 0; i != 9; ++i) + learn_skill(u, skill, 1.0); + } +} + +static void get_villagers(region * r, unit * u) +{ + unit *newunit; + message *msg = msg_message("encounter_villagers", "unit", u); + const char *name = LOC(u->faction->locale, "villagers"); + + r_addmessage(r, u->faction, msg); + msg_release(msg); + + newunit = + create_unit(r, u->faction, rng_int() % 20 + 3, u->faction->race, 0, name, + u); + leave(newunit, true); + fset(newunit, UFL_ISNEW | UFL_MOVED); + equip_unit(newunit, get_equipment("random_villagers")); +} + +static void get_allies(region * r, unit * u) +{ + unit *newunit = NULL; + const char *name; + const char *equip; + int number; + message *msg; + + assert(u->number); + + switch (rterrain(r)) { + case T_PLAIN: + if (!r_isforest(r)) { + if (get_money(u) / u->number < 100 + rng_int() % 200) + return; + name = "random_plain_men"; + equip = "random_plain"; + number = rng_int() % 8 + 2; + break; + } else { + if (eff_skill(u, SK_LONGBOW, r) < 3 + && eff_skill(u, SK_HERBALISM, r) < 2 + && eff_skill(u, SK_MAGIC, r) < 2) { + return; + } + name = "random_forest_men"; + equip = "random_forest"; + number = rng_int() % 6 + 2; + } + break; + + case T_SWAMP: + if (eff_skill(u, SK_MELEE, r) <= 1) { + return; + } + name = "random_swamp_men"; + equip = "random_swamp"; + number = rng_int() % 6 + 2; + break; + + case T_DESERT: + if (eff_skill(u, SK_RIDING, r) <= 2) { + return; + } + name = "random_desert_men"; + equip = "random_desert"; + number = rng_int() % 12 + 2; + break; + + case T_HIGHLAND: + if (eff_skill(u, SK_MELEE, r) <= 1) { + return; + } + name = "random_highland_men"; + equip = "random_highland"; + number = rng_int() % 8 + 2; + break; + + case T_MOUNTAIN: + if (eff_skill(u, SK_MELEE, r) <= 1 || eff_skill(u, SK_TRADE, r) <= 2) { + return; + } + name = "random_mountain_men"; + equip = "random_mountain"; + number = rng_int() % 6 + 2; + break; + + case T_GLACIER: + if (eff_skill(u, SK_MELEE, r) <= 1 || eff_skill(u, SK_TRADE, r) <= 1) { + return; + } + name = "random_glacier_men"; + equip = "random_glacier"; + number = rng_int() % 4 + 2; + break; + + default: + return; + } + + newunit = + create_unit(r, u->faction, number, u->faction->race, 0, + LOC(u->faction->locale, name), u); + equip_unit(newunit, get_equipment(equip)); + + u_setfaction(newunit, u->faction); + set_racename(&newunit->attribs, get_racename(u->attribs)); + if (u_race(u)->flags & RCF_SHAPESHIFT) { + newunit->irace = u->irace; + } + if (fval(u, UFL_ANON_FACTION)) + fset(newunit, UFL_ANON_FACTION); + fset(newunit, UFL_ISNEW); + + msg = msg_message("encounter_allies", "unit name", u, name); + r_addmessage(r, u->faction, msg); + msg_release(msg); +} + +static void encounter(region * r, unit * u) +{ + if (!fval(r, RF_ENCOUNTER)) + return; + freset(r, RF_ENCOUNTER); + if (rng_int() % 100 >= ENCCHANCE) + return; + switch (rng_int() % 3) { + case 0: + find_manual(r, u); + break; + case 1: + get_villagers(r, u); + break; + case 2: + get_allies(r, u); + break; + } +} + +void encounters(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + if (!fval(r->terrain, SEA_REGION) && fval(r, RF_ENCOUNTER)) { + int c = 0; + unit *u; + for (u = r->units; u; u = u->next) { + c += u->number; + } + + if (c > 0) { + int i = 0; + int n = rng_int() % c; + + for (u = r->units; u; u = u->next) { + if (i + u->number > n) + break; + i += u->number; + } + assert(u && u->number); + encounter(r, u); + } + } + } +} + +static const terrain_type *chaosterrain(void) +{ + static const terrain_type **types; + static int numtypes; + + if (numtypes == 0) { + const terrain_type *terrain; + for (terrain = terrains(); terrain != NULL; terrain = terrain->next) { + if (fval(terrain, LAND_REGION) && terrain->herbs) { + ++numtypes; + } + } + types = malloc(sizeof(terrain_type) * numtypes); + numtypes = 0; + for (terrain = terrains(); terrain != NULL; terrain = terrain->next) { + if (fval(terrain, LAND_REGION) && terrain->herbs) { + types[numtypes++] = terrain; + } + } + } + return types[rng_int() % numtypes]; +} + +static unit *random_unit(const region * r) +{ + int c = 0; + int n; + unit *u; + + for (u = r->units; u; u = u->next) { + if (u_race(u) != new_race[RC_SPELL]) { + c += u->number; + } + } + + if (c == 0) { + return NULL; + } + n = rng_int() % c; + c = 0; + u = r->units; + + while (u && c < n) { + if (u_race(u) != new_race[RC_SPELL]) { + c += u->number; + } + u = u->next; + } + + return u; +} + +void chaos(region * r) +{ + if (rng_int() % 100 < 8) { + switch (rng_int() % 3) { + case 0: /* Untote */ + if (!fval(r->terrain, SEA_REGION)) { + unit *u = random_unit(r); + if (u && playerrace(u_race(u))) { + ADDMSG(&u->faction->msgs, msg_message("chaos_disease", "unit", u)); + u_setfaction(u, get_monsters()); + u_setrace(u, new_race[RC_GHOUL]); + } + } + break; + case 1: /* Drachen */ + if (random_unit(r)) { + int mfac = 0; + unit *u; + switch (rng_int() % 3) { + case 0: + mfac = 100; + u = + createunit(r, get_monsters(), rng_int() % 8 + 1, + new_race[RC_FIREDRAGON]); + break; + case 1: + mfac = 500; + u = + createunit(r, get_monsters(), rng_int() % 4 + 1, + new_race[RC_DRAGON]); + break; + default: + mfac = 1000; + u = + createunit(r, get_monsters(), rng_int() % 2 + 1, + new_race[RC_WYRM]); + break; + } + if (mfac) + set_money(u, u->number * (rng_int() % mfac)); + fset(u, UFL_ISNEW | UFL_MOVED); + } + case 2: /* Terrainveränderung */ + if (!fval(r->terrain, FORBIDDEN_REGION)) { + if (!fval(r->terrain, SEA_REGION)) { + direction_t dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *rn = rconnect(r, dir); + if (rn && fval(rn->terrain, SEA_REGION)) + break; + } + if (dir != MAXDIRECTIONS) { + ship *sh = r->ships; + unit **up; + + while (sh) { + ship *nsh = sh->next; + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.atlantis", + 0.50); + damage_ship(sh, dmg); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + remove_ship(&sh->region->ships, sh); + } + sh = nsh; + } + + for (up = &r->units; *up;) { + unit *u = *up; + if (u_race(u) != new_race[RC_SPELL] && u->ship == 0 && !canfly(u)) { + ADDMSG(&u->faction->msgs, msg_message("tidalwave_kill", + "region unit", r, u)); + remove_unit(up, u); + } + if (*up == u) + up = &u->next; + } + ADDMSG(&r->msgs, msg_message("tidalwave", "region", r)); + + while (r->buildings) { + remove_building(&r->buildings, r->buildings); + } + terraform_region(r, newterrain(T_OCEAN)); + } + } else { + direction_t dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *rn = rconnect(r, dir); + if (rn && fval(rn->terrain, SEA_REGION)) + break; + } + if (dir != MAXDIRECTIONS) { + terraform_region(r, chaosterrain()); + } + } + } + } + } +} + +static int nb_armor(const unit * u, int index) +{ + const item *itm; + int av = 0; + int s = 0, a = 0; + + if (!(u_race(u)->battle_flags & BF_EQUIPMENT)) + return 0; + + /* Normale Rüstung */ + + for (itm = u->items; itm; itm = itm->next) { + const armor_type *atype = itm->type->rtype->atype; + if (atype != NULL) { + int *schutz = &a; + if (atype->flags & ATF_SHIELD) + schutz = &s; + if (*schutz <= index) { + *schutz += itm->number; + if (*schutz > index) { + av += atype->prot; + } + } + } + } + return av; +} + +static int +damage_unit(unit * u, const char *dam, bool physical, bool magic) +{ + int *hp = malloc(u->number * sizeof(int)); + int h; + int i, dead = 0, hp_rem = 0, heiltrank; + double magres = magic_resistance(u); + + assert(u->number); + if (fval(u_race(u), RCF_ILLUSIONARY) || u_race(u) == new_race[RC_SPELL]) { + return 0; + } + + h = u->hp / u->number; + /* HP verteilen */ + for (i = 0; i < u->number; i++) + hp[i] = h; + h = u->hp - (u->number * h); + for (i = 0; i < h; i++) + hp[i]++; + + /* Schaden */ + for (i = 0; i < u->number; i++) { + int damage = dice_rand(dam); + if (magic) + damage = (int)(damage * (1.0 - magres)); + if (physical) + damage -= nb_armor(u, i); + hp[i] -= damage; + } + + /* Auswirkungen */ + for (i = 0; i < u->number; i++) { + if (hp[i] <= 0) { + heiltrank = 0; + + /* Sieben Leben */ + if (old_race(u_race(u)) == RC_CAT && (chance(1.0 / 7))) { + hp[i] = u->hp / u->number; + hp_rem += hp[i]; + continue; + } + + /* Heiltrank */ + if (oldpotiontype[P_HEAL]) { + if (get_effect(u, oldpotiontype[P_HEAL]) > 0) { + change_effect(u, oldpotiontype[P_HEAL], -1); + heiltrank = 1; + } else if (i_get(u->items, oldpotiontype[P_HEAL]->itype) > 0) { + i_change(&u->items, oldpotiontype[P_HEAL]->itype, -1); + change_effect(u, oldpotiontype[P_HEAL], 3); + heiltrank = 1; + } + if (heiltrank && (chance(0.50))) { + hp[i] = u->hp / u->number; + hp_rem += hp[i]; + continue; + } + } + dead++; + } else { + hp_rem += hp[i]; + } + } + + scale_number(u, u->number - dead); + u->hp = hp_rem; + + free(hp); + + return dead; +} + +void drown(region * r) +{ + if (fval(r->terrain, SEA_REGION)) { + unit **up = up = &r->units; + while (*up) { + unit *u = *up; + int amphibian_level = 0; + if (u->ship || u_race(u) == new_race[RC_SPELL] || u->number == 0) { + up = &u->next; + continue; + } + + if (amphibian_level) { + int dead = damage_unit(u, "5d1", false, false); + if (dead) { + ADDMSG(&u->faction->msgs, msg_message("drown_amphibian_dead", + "amount unit region", dead, u, r)); + } else { + ADDMSG(&u->faction->msgs, msg_message("drown_amphibian_nodead", + "unit region", u, r)); + } + } else if (!(canswim(u) || canfly(u))) { + scale_number(u, 0); + ADDMSG(&u->faction->msgs, msg_message("drown", "unit region", u, r)); + } + if (*up == u) + up = &u->next; + } + remove_empty_units_in_region(r); + } +} + +region *rrandneighbour(region * r) +{ + direction_t i; + region *rc = NULL; + int rr, c = 0; + + /* Nachsehen, wieviele Regionen in Frage kommen */ + + for (i = 0; i != MAXDIRECTIONS; i++) { + c++; + } + /* Zufällig eine auswählen */ + + rr = rng_int() % c; + + /* Durchzählen */ + + c = -1; + for (i = 0; i != MAXDIRECTIONS; i++) { + rc = rconnect(r, i); + c++; + if (c == rr) + break; + } + assert(i != MAXDIRECTIONS); + return rc; +} + +static void +volcano_destruction(region * volcano, region * r, region * rn, + const char *damage) +{ + attrib *a; + unit **up; + int percent = 25, time = 6 + rng_int() % 12; + + rsettrees(r, 2, 0); + rsettrees(r, 1, 0); + rsettrees(r, 0, 0); + + a = a_find(r->attribs, &at_reduceproduction); + if (!a) { + a = make_reduceproduction(percent, time); + } else { + /* Produktion vierteln ... */ + a->data.sa[0] = (short)percent; + /* Für 6-17 Runden */ + a->data.sa[1] = (short)(a->data.sa[1] + time); + } + + /* Personen bekommen 4W10 Punkte Schaden. */ + + for (up = &r->units; *up;) { + unit *u = *up; + if (u->number) { + int dead = damage_unit(u, damage, true, false); + if (dead) { + ADDMSG(&u->faction->msgs, msg_message("volcano_dead", + "unit region dead", u, volcano, dead)); + } + if (r == volcano && !fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (rn) { + ADDMSG(&u->faction->msgs, msg_message("volcanooutbreak", + "regionv regionn", r, rn)); + } else { + ADDMSG(&u->faction->msgs, msg_message("volcanooutbreaknn", + "region", r)); + } + } + } + if (u == *up) + up = &u->next; + } + + remove_empty_units_in_region(r); +} + +void volcano_outbreak(region * r) +{ + region *rn; + unit *u; + faction *f; + + for (f = NULL, u = r->units; u; u = u->next) { + if (f != u->faction) { + f = u->faction; + freset(f, FFL_SELECT); + } + } + + /* Zufällige Nachbarregion verwüsten */ + rn = rrandneighbour(r); + + volcano_destruction(r, r, rn, "4d10"); + if (rn) { + volcano_destruction(r, rn, NULL, "3d10"); + } +} + +static void melt_iceberg(region * r) +{ + attrib *a; + unit *u; + + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + ADDMSG(&u->faction->msgs, msg_message("iceberg_melt", "region", r)); + } + + /* driftrichtung löschen */ + a = a_find(r->attribs, &at_iceberg); + if (a) + a_remove(&r->attribs, a); + + /* Gebäude löschen */ + while (r->buildings) { + remove_building(&r->buildings, r->buildings); + } + + /* in Ozean wandeln */ + terraform_region(r, newterrain(T_OCEAN)); + + /* Einheiten, die nicht schwimmen können oder in Schiffen sind, + * ertrinken */ + drown(r); +} + +static void move_iceberg(region * r) +{ + attrib *a; + direction_t dir; + region *rc; + + a = a_find(r->attribs, &at_iceberg); + if (!a) { + dir = (direction_t) (rng_int() % MAXDIRECTIONS); + a = a_add(&r->attribs, make_iceberg(dir)); + } else { + if (rng_int() % 100 < 20) { + dir = (direction_t) (rng_int() % MAXDIRECTIONS); + a->data.i = dir; + } else { + dir = (direction_t) a->data.i; + } + } + + rc = rconnect(r, dir); + + if (rc && !fval(rc->terrain, ARCTIC_REGION)) { + if (fval(rc->terrain, SEA_REGION)) { /* Eisberg treibt */ + ship *sh, *shn; + unit *u; + int x, y; + + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + ADDMSG(&u->faction->msgs, msg_message("iceberg_drift", + "region dir", r, dir)); + } + + x = r->x; + y = r->y; + + runhash(r); + runhash(rc); + r->x = rc->x; + r->y = rc->y; + rc->x = x; + rc->y = y; + rhash(rc); + rhash(r); + + /* rc ist der Ozean (Ex-Eisberg), r der Eisberg (Ex-Ozean) */ + + /* Schiffe aus dem Zielozean werden in den Eisberg transferiert + * und nehmen Schaden. */ + + for (sh = r->ships; sh; sh = sh->next) + freset(sh, SF_SELECT); + + for (sh = r->ships; sh; sh = sh->next) { + /* Meldung an Kapitän */ + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.intoiceberg", + 0.10F); + damage_ship(sh, dmg); + fset(sh, SF_SELECT); + } + + /* Personen, Schiffe und Gebäude verschieben */ + while (rc->buildings) { + rc->buildings->region = r; + translist(&rc->buildings, &r->buildings, rc->buildings); + } + while (rc->ships) { + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.withiceberg", + 0.10F); + fset(rc->ships, SF_SELECT); + damage_ship(rc->ships, dmg); + move_ship(rc->ships, rc, r, NULL); + } + while (rc->units) { + building *b = rc->units->building; + u = rc->units; + u->building = 0; /* prevent leaving in move_unit */ + move_unit(rc->units, r, NULL); + u_set_building(u, b); /* undo leave-prevention */ + } + + /* Beschädigte Schiffe können sinken */ + + for (sh = r->ships; sh;) { + shn = sh->next; + if (fval(sh, SF_SELECT)) { + u = ship_owner(sh); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + if (u != NULL) { + ADDMSG(&u->faction->msgs, msg_message("overrun_by_iceberg_des", + "ship", sh)); + } + remove_ship(&sh->region->ships, sh); + } else if (u != NULL) { + ADDMSG(&u->faction->msgs, msg_message("overrun_by_iceberg", + "ship", sh)); + } + } + sh = shn; + } + + } else if (rng_int() % 100 < 20) { /* Eisberg bleibt als Gletscher liegen */ + unit *u; + + rsetterrain(r, T_GLACIER); + a_remove(&r->attribs, a); + + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + ADDMSG(&u->faction->msgs, msg_message("iceberg_land", "region", r)); + } + } + } +} + +static void move_icebergs(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + if (r->terrain == newterrain(T_ICEBERG) && !fval(r, RF_SELECT)) { + int select = rng_int() % 10; + if (select < 4) { + /* 4% chance */ + fset(r, RF_SELECT); + melt_iceberg(r); + } else if (select < 64) { + /* 60% chance */ + fset(r, RF_SELECT); + move_iceberg(r); + } + } + } +} + +void create_icebergs(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + if (r->terrain == newterrain(T_ICEBERG_SLEEP) && chance(0.05)) { + bool has_ocean_neighbour = false; + direction_t dir; + region *rc; + unit *u; + + freset(r, RF_SELECT); + for (dir = 0; dir < MAXDIRECTIONS; dir++) { + rc = rconnect(r, dir); + if (rc && fval(rc->terrain, SEA_REGION)) { + has_ocean_neighbour = true; + break; + } + } + if (!has_ocean_neighbour) + continue; + + rsetterrain(r, T_ICEBERG); + + fset(r, RF_SELECT); + move_iceberg(r); + + for (u = r->units; u; u = u->next) { + freset(u->faction, FFL_SELECT); + } + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + ADDMSG(&u->faction->msgs, msg_message("iceberg_create", "region", r)); + } + } + } + } +} + +static void godcurse(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + if (is_cursed(r->attribs, C_CURSED_BY_THE_GODS, 0)) { + unit *u; + for (u = r->units; u; u = u->next) { + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + int weeks = 1 + rng_int() % 3; + reduce_skill(u, sv, weeks); + ++sv; + } + } + if (fval(r->terrain, SEA_REGION)) { + ship *sh; + for (sh = r->ships; sh;) { + ship *shn = sh->next; + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.godcurse", + 0.10F); + damage_ship(sh, dmg); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + unit *u = ship_owner(sh); + if (u) + ADDMSG(&u->faction->msgs, + msg_message("godcurse_destroy_ship", "ship", sh)); + remove_ship(&sh->region->ships, sh); + } + sh = shn; + } + } + } + } + +} + +/** handles the "orcish" curse that makes units grow like old orks + * This would probably be better handled in an age-function for the curse, + * but it's now being called by randomevents() + */ +static void orc_growth(void) +{ + region *r; + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + static bool init = false; + static const curse_type *ct_orcish = 0; + curse *c = 0; + if (!init) { + init = true; + ct_orcish = ct_find("orcish"); + } + if (ct_orcish) + c = get_curse(u->attribs, ct_orcish); + + if (c && !has_skill(u, SK_MAGIC) && !has_skill(u, SK_ALCHEMY) + && !fval(u, UFL_HERO)) { + int n; + int increase = 0; + int num = get_cursedmen(u, c); + double prob = curse_geteffect(c); + + for (n = (num - get_item(u, I_CHASTITY_BELT)); n > 0; n--) { + if (chance(prob)) { + ++increase; + } + } + if (increase) { + unit *u2 = create_unit(r, u->faction, increase, u_race(u), 0, NULL, u); + transfermen(u2, u, u2->number); + + ADDMSG(&u->faction->msgs, msg_message("orcgrowth", + "unit amount race", u, increase, u_race(u))); + } + } + } + } +} + +/** Talente von Dämonen verschieben sich. + */ +static void demon_skillchanges(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + if (u_race(u) == new_race[RC_DAEMON]) { + skill *sv = u->skills; + int upchance = 15; + int downchance = 10; + + if (fval(u, UFL_HUNGER)) { + /* hungry demons only go down, never up in skill */ + static int rule_hunger = -1; + if (rule_hunger < 0) { + rule_hunger = + get_param_int(global.parameters, "hunger.demon.skill", 0); + } + if (rule_hunger) { + upchance = 0; + downchance = 15; + } + } + + while (sv != u->skills + u->skill_size) { + int roll = rng_int() % 100; + if (sv->level > 0 && roll < upchance + downchance) { + int weeks = 1 + rng_int() % 3; + if (roll < downchance) { + reduce_skill(u, sv, weeks); + if (sv->level < 1) { + /* demons should never forget below 1 */ + set_level(u, sv->id, 1); + } + } else { + while (weeks--) + learn_skill(u, sv->id, 1.0); + } + if (sv->old > sv->level) { + if (verbosity >= 3) { + log_printf(stdout, "%s dropped from %u to %u:%u in %s\n", + unitname(u), sv->old, sv->level, sv->weeks, skillname(sv->id, + NULL)); + } + } + } + ++sv; + } + } + } + } +} + +/** Eisberge entstehen und bewegen sich. + * Einheiten die im Wasser landen, ertrinken. + */ +static void icebergs(void) +{ + region *r; + create_icebergs(); + move_icebergs(); + for (r = regions; r; r = r->next) { + drown(r); + } +} + +#ifdef HERBS_ROT +static void rotting_herbs(void) +{ + static int rule_rot = -1; + region *r; + + if (rule_rot < 0) { + rule_rot = + get_param_int(global.parameters, "rules.economy.herbrot", HERBROTCHANCE); + } + if (rule_rot == 0) + return; + + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + item **itmp = &u->items; + item *hbag = *i_find(itmp, olditemtype[I_SACK_OF_CONSERVATION]); + int rot_chance = rule_rot; + + if (hbag) + rot_chance = (rot_chance * 2) / 5; + while (*itmp) { + item *itm = *itmp; + int n = itm->number; + double k = n * rot_chance / 100.0; + if (fval(itm->type, ITF_HERB)) { + double nv = normalvariate(k, k / 4); + int inv = (int)nv; + int delta = MIN(n, inv); + if (i_change(itmp, itm->type, -delta) == NULL) { + continue; + } + } + itmp = &itm->next; + } + } + } +} +#endif + +void randomevents(void) +{ + region *r; + + icebergs(); + godcurse(); + orc_growth(); + demon_skillchanges(); + + /* Orkifizierte Regionen mutieren und mutieren zurück */ + + for (r = regions; r; r = r->next) { + if (fval(r, RF_ORCIFIED)) { + direction_t dir; + double probability = 0.0; + for (dir = 0; dir < MAXDIRECTIONS; dir++) { + region *rc = rconnect(r, dir); + if (rc && rpeasants(rc) > 0 && !fval(rc, RF_ORCIFIED)) + probability += 0.02; + } + if (chance(probability)) { + ADDMSG(&r->msgs, msg_message("deorcified", "region", r)); + freset(r, RF_ORCIFIED); + } + } else { + attrib *a = a_find(r->attribs, &at_orcification); + if (a != NULL) { + double probability = 0.0; + if (rpeasants(r) <= 0) + continue; + probability = a->data.i / (double)rpeasants(r); + if (chance(probability)) { + fset(r, RF_ORCIFIED); + a_remove(&r->attribs, a); + ADDMSG(&r->msgs, msg_message("orcified", "region", r)); + } else { + a->data.i -= MAX(10, a->data.i / 10); + if (a->data.i <= 0) + a_remove(&r->attribs, a); + } + } + } + } + + /* Vulkane qualmen, brechen aus ... */ + for (r = regions; r; r = r->next) { + if (r->terrain == newterrain(T_VOLCANO_SMOKING)) { + if (a_find(r->attribs, &at_reduceproduction)) { + ADDMSG(&r->msgs, msg_message("volcanostopsmoke", "region", r)); + rsetterrain(r, T_VOLCANO); + } else { + if (rng_int() % 100 < 12) { + ADDMSG(&r->msgs, msg_message("volcanostopsmoke", "region", r)); + rsetterrain(r, T_VOLCANO); + } else if (r->age > 20 && rng_int() % 100 < 8) { + volcano_outbreak(r); + } + } + } else if (r->terrain == newterrain(T_VOLCANO)) { + if (rng_int() % 100 < 4) { + ADDMSG(&r->msgs, msg_message("volcanostartsmoke", "region", r)); + rsetterrain(r, T_VOLCANO_SMOKING); + } + } + } + + /* Monumente zerfallen, Schiffe verfaulen */ + + for (r = regions; r; r = r->next) { + building **blist = &r->buildings; + while (*blist) { + building *b = *blist; + if (fval(b->type, BTF_DECAY) && !building_owner(b)) { + b->size -= MAX(1, (b->size * 20) / 100); + if (b->size == 0) { + remove_building(blist, r->buildings); + } + } + if (*blist == b) + blist = &b->next; + } + } + + /* monster-einheiten desertieren */ + for (r = regions; r; r = r->next) { + unit *u; + + for (u = r->units; u; u = u->next) { + if (u->faction && !is_monsters(u->faction) + && (u_race(u)->flags & RCF_DESERT)) { + if (fval(u, UFL_ISNEW)) + continue; + if (rng_int() % 100 < 5) { + ADDMSG(&u->faction->msgs, msg_message("desertion", + "unit region", u, r)); + u_setfaction(u, get_monsters()); + } + } + } + } + + /* Chaos */ + for (r = regions; r; r = r->next) { + int i; + + if (fval(r, RF_CHAOTIC)) { + chaos(r); + } + i = chaoscount(r); + if (i) { + chaoscounts(r, -(int)(i * ((double)(rng_int() % 10)) / 100.0)); + } + } +#ifdef HERBS_ROT + rotting_herbs(); +#endif + + dissolve_units(); +} diff --git a/core/src/gamecode/randenc.h b/core/src/gamecode/randenc.h new file mode 100644 index 000000000..0f39d9c61 --- /dev/null +++ b/core/src/gamecode/randenc.h @@ -0,0 +1,31 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_GC_RANDENC +#define H_GC_RANDENC +#ifdef __cplusplus +extern "C" { +#endif + + extern void encounters(void); + extern void randomevents(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/report.c b/core/src/gamecode/report.c new file mode 100644 index 000000000..a9d4625e6 --- /dev/null +++ b/core/src/gamecode/report.c @@ -0,0 +1,2621 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#define ECHECK_VERSION "4.01" + +#include +#include + +/* modules includes */ +#include + +/* attributes includes */ +#include +#include +#include +#include + +/* gamecode includes */ +#include "creport.h" +#include "economy.h" +#include "monster.h" +#include "laws.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_STAT +#include +#include +#endif + +extern int verbosity; +extern int *storms; +extern int weeks_per_month; +extern int months_per_year; + +static char *gamedate_season(const struct locale *lang) +{ + static char buf[256]; + gamedate gd; + + get_gamedate(turn, &gd); + + sprintf(buf, (const char *)LOC(lang, "nr_calendar_season"), + LOC(lang, weeknames[gd.week]), + LOC(lang, monthnames[gd.month]), + gd.year, + agename ? LOC(lang, agename) : "", LOC(lang, seasonnames[gd.season])); + + return buf; +} + +void rpc(FILE * F, char c, size_t num) +{ + while (num > 0) { + putc(c, F); + num--; + } +} + +void rnl(FILE * F) +{ + fputc('\n', F); +} + +static void centre(FILE * F, const char *s, bool breaking) +{ + /* Bei Namen die genau 80 Zeichen lang sind, kann es hier Probleme + * geben. Seltsamerweise wird i dann auf MAXINT oder aehnlich + * initialisiert. Deswegen keine Strings die laenger als REPORTWIDTH + * sind! */ + + if (breaking && REPORTWIDTH < strlen(s)) { + strlist *T, *SP = 0; + sparagraph(&SP, s, 0, 0); + T = SP; + while (SP) { + centre(F, SP->s, false); + SP = SP->next; + } + freestrlist(T); + } else { + rpc(F, ' ', (REPORTWIDTH - strlen(s) + 1) / 2); + fputs(s, F); + putc('\n', F); + } +} + +static void +rparagraph(FILE * F, const char *str, ptrdiff_t indent, int hanging_indent, + char mark) +{ + static const char *spaces = " "; + size_t length = REPORTWIDTH; + const char *end, *begin; + + if (!str) return; + /* find out if there's a mark + indent already encoded in the string. */ + if (!mark) { + const char *x = str; + while (*x == ' ') + ++x; + indent += x - str; + if (x[0] && indent && x[1] == ' ') { + indent += 2; + mark = x[0]; + str = x + 2; + hanging_indent -= 2; + } + } + begin = end = str; + + do { + const char *last_space = begin; + + if (mark && indent >= 2) { + fwrite(spaces, sizeof(char), indent - 2, F); + fputc(mark, F); + fputc(' ', F); + mark = 0; + } else if (begin == str) { + fwrite(spaces, sizeof(char), indent, F); + } else { + fwrite(spaces, sizeof(char), indent + hanging_indent, F); + } + while (*end && end <= begin + length - indent) { + if (*end == ' ') { + last_space = end; + } + ++end; + } + if (*end == 0) + last_space = end; + if (last_space == begin) { + /* there was no space in this line. clip it */ + last_space = end; + } + fwrite(begin, sizeof(char), last_space - begin, F); + begin = last_space; + while (*begin == ' ') { + ++begin; + } + if (begin > end) + begin = end; + fputc('\n', F); + } while (*begin); +} + +static size_t write_spell_modifier(spell * sp, int flag, const char * str, bool cont, char * bufp, size_t size) { + if (sp->sptyp & flag) { + size_t bytes = 0; + if (cont) { + bytes = strlcpy(bufp, ", ", size); + } else { + bytes = strlcpy(bufp, " ", size); + } + bytes += strlcpy(bufp+bytes, str, size-bytes); + return bytes; + } + return 0; +} + +static void nr_spell(FILE * F, spellbook_entry * sbe, const struct locale *lang) +{ + int bytes, k, itemanz, costtyp; + char buf[4096]; + char *startp, *bufp = buf; + size_t size = sizeof(buf) - 1; + spell * sp = sbe->sp; + const char *params = sp->parameter; + + rnl(F); + centre(F, spell_name(sp, lang), true); + rnl(F); + rparagraph(F, LOC(lang, "nr_spell_description"), 0, 0, 0); + rparagraph(F, spell_info(sp, lang), 2, 0, 0); + + bytes = (int)strlcpy(bufp, LOC(lang, "nr_spell_type"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (size) { + *bufp++ = ' '; + --size; + } + if (sp->sptyp & PRECOMBATSPELL) { + bytes = (int)strlcpy(bufp, LOC(lang, "sptype_precombat"), size); + } else if (sp->sptyp & COMBATSPELL) { + bytes = (int)strlcpy(bufp, LOC(lang, "sptype_combat"), size); + } else if (sp->sptyp & POSTCOMBATSPELL) { + bytes = (int)strlcpy(bufp, LOC(lang, "sptype_postcombat"), size); + } else { + bytes = (int)strlcpy(bufp, LOC(lang, "sptype_normal"), size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + + sprintf(buf, "%s %d", LOC(lang, "nr_spell_level"), sbe->level); + rparagraph(F, buf, 0, 0, 0); + + sprintf(buf, "%s %d", LOC(lang, "nr_spell_rank"), sp->rank); + rparagraph(F, buf, 0, 0, 0); + + rparagraph(F, LOC(lang, "nr_spell_components"), 0, 0, 0); + for (k = 0; sp->components[k].type; ++k) { + const resource_type *rtype = sp->components[k].type; + itemanz = sp->components[k].amount; + costtyp = sp->components[k].cost; + if (itemanz > 0) { + size = sizeof(buf) - 1; + bufp = buf; + if (sp->sptyp & SPELLLEVEL) { + bytes = + snprintf(bufp, size, " %d %s", itemanz, LOC(lang, resourcename(rtype, + itemanz != 1))); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (costtyp == SPC_LEVEL || costtyp == SPC_LINEAR) { + bytes = snprintf(bufp, size, " * %s", LOC(lang, "nr_level")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } else { + bytes = snprintf(bufp, size, "%d %s", itemanz, LOC(lang, resourcename(rtype, itemanz != 1))); + if (wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + } + *bufp = 0; + rparagraph(F, buf, 2, 2, '-'); + } + } + + size = sizeof(buf) - 1; + bufp = buf; + bytes = (int)strlcpy(buf, LOC(lang, "nr_spell_modifiers"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + startp = bufp; + bytes = (int)write_spell_modifier(sp, FARCASTING, LOC(lang, "smod_far"), startp!=bufp, bufp, size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + bytes = (int)write_spell_modifier(sp, OCEANCASTABLE, LOC(lang, "smod_sea"), startp!=bufp, bufp, size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + bytes = (int)write_spell_modifier(sp, ONSHIPCAST, LOC(lang, "smod_ship"), startp!=bufp, bufp, size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + bytes = (int)write_spell_modifier(sp, NOTFAMILIARCAST, LOC(lang, "smod_nofamiliar"), startp!=bufp, bufp, size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + if (startp==bufp) { + bytes = (int)write_spell_modifier(sp, NOTFAMILIARCAST, LOC(lang, "smod_none"), startp!=bufp, bufp, size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + } + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + + rparagraph(F, LOC(lang, "nr_spell_syntax"), 0, 0, 0); + + bufp = buf; + size = sizeof(buf) - 1; + + if (sp->sptyp & ISCOMBATSPELL) { + bytes = (int)strlcpy(bufp, LOC(lang, keywords[K_COMBATSPELL]), size); + } else { + bytes = (int)strlcpy(bufp, LOC(lang, keywords[K_CAST]), size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + /* Reihenfolge beachten: Erst REGION, dann STUFE! */ + if (sp->sptyp & FARCASTING) { + bytes = snprintf(bufp, size, " [%s x y]", LOC(lang, parameters[P_REGION])); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (sp->sptyp & SPELLLEVEL) { + bytes = snprintf(bufp, size, " [%s n]", LOC(lang, parameters[P_LEVEL])); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + bytes = (int)snprintf(bufp, size, " \"%s\"", spell_name(sp, lang)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + while (params && *params) { + typedef struct starget { + param_t param; + int flag; + const char *vars; + } starget; + starget targets[] = { + {P_REGION, REGIONSPELL, NULL}, + {P_UNIT, UNITSPELL, "par_unit"}, + {P_SHIP, SHIPSPELL, "par_ship"}, + {P_BUILDING, BUILDINGSPELL, "par_building"}, + {0, 0, NULL} + }; + starget *targetp; + char cp = *params++; + int i, maxparam = 0; + const char *locp; + const char *syntaxp = sp->syntax; + + if (cp == 'u') { + targetp = targets + 1; + locp = LOC(lang, targetp->vars); + bytes = (int)snprintf(bufp, size, " <%s>", locp); + if (*params == '+') { + ++params; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)snprintf(bufp, size, " [<%s> ...]", locp); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else if (cp == 's') { + targetp = targets + 2; + locp = LOC(lang, targetp->vars); + bytes = (int)snprintf(bufp, size, " <%s>", locp); + if (*params == '+') { + ++params; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)snprintf(bufp, size, " [<%s> ...]", locp); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else if (cp == 'r') { + bytes = (int)strlcpy(bufp, " ", size); + if (*params == '+') { + ++params; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " [ ...]", size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else if (cp == 'b') { + targetp = targets + 3; + locp = LOC(lang, targetp->vars); + bytes = (int)snprintf(bufp, size, " <%s>", locp); + if (*params == '+') { + ++params; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)snprintf(bufp, size, " [<%s> ...]", locp); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else if (cp == 'k') { + if (*params == 'c') { + /* skip over a potential id */ + ++params; + } + for (targetp = targets; targetp->flag; ++targetp) { + if (sp->sptyp & targetp->flag) + ++maxparam; + } + if (maxparam > 1) { + bytes = (int)strlcpy(bufp, " (", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + i = 0; + for (targetp = targets; targetp->flag; ++targetp) { + if (sp->sptyp & targetp->flag) { + if (i++ != 0) { + bytes = (int)strlcpy(bufp, " |", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (targetp->param) { + locp = LOC(lang, targetp->vars); + bytes = + (int)snprintf(bufp, size, " %s <%s>", parameters[targetp->param], + locp); + if (*params == '+') { + ++params; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)snprintf(bufp, size, " [<%s> ...]", locp); + } + } else { + bytes = + (int)snprintf(bufp, size, " %s", parameters[targetp->param]); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + if (maxparam > 1) { + bytes = (int)strlcpy(bufp, " )", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } else if (cp == 'i' || cp == 'c') { + const char *cstr; + assert(syntaxp); + cstr = strchr(syntaxp, ':'); + if (!cstr) { + locp = LOC(lang, mkname("spellpar", syntaxp)); + } else { + char substr[32]; + strncpy(substr, syntaxp, cstr - syntaxp); + substr[cstr - syntaxp] = 0; + locp = LOC(lang, mkname("spellpar", substr)); + syntaxp = substr + 1; + } + bytes = (int)snprintf(bufp, size, " <%s>", locp); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + *bufp = 0; + rparagraph(F, buf, 2, 0, 0); + rnl(F); +} + +void sparagraph(strlist ** SP, const char *s, int indent, char mark) +{ + + /* Die Liste SP wird mit dem String s aufgefuellt, mit indent und einer + * mark, falls angegeben. SP wurde also auf 0 gesetzt vor dem Aufruf. + * Vgl. spunit (). */ + + int i, j, width; + int firstline; + static char buf[REPORTWIDTH + 1]; + + width = REPORTWIDTH - indent; + firstline = 1; + + for (;;) { + i = 0; + + do { + j = i; + while (s[j] && s[j] != ' ') + j++; + if (j > width) { + + /* j zeigt auf das ende der aktuellen zeile, i zeigt auf den anfang der + * nächsten zeile. existiert ein wort am anfang der zeile, welches + * länger als eine zeile ist, muss dieses hier abgetrennt werden. */ + + if (i == 0) + i = width - 1; + break; + } + i = j + 1; + } + while (s[j]); + + for (j = 0; j != indent; j++) + buf[j] = ' '; + + if (firstline && mark) + buf[indent - 2] = mark; + + for (j = 0; j != i - 1; j++) + buf[indent + j] = s[j]; + buf[indent + j] = 0; + + addstrlist(SP, buf); + + if (s[i - 1] == 0) + break; + + s += i; + firstline = 0; + } +} + +int hat_in_region(item_t it, region * r, faction * f) +{ + unit *u; + + for (u = r->units; u; u = u->next) { + if (u->faction == f && get_item(u, it) > 0) { + return 1; + } + } + return 0; +} + +static void +nr_curses(FILE * F, const faction * viewer, const void *obj, objtype_t typ, + int indent) +{ + attrib *a = NULL; + int self = 0; + region *r; + + /* Die Sichtbarkeit eines Zaubers und die Zaubermeldung sind bei + * Gebäuden und Schiffen je nach, ob man Besitzer ist, verschieden. + * Bei Einheiten sieht man Wirkungen auf eigene Einheiten immer. + * Spezialfälle (besonderes Talent, verursachender Magier usw. werde + * bei jedem curse gesondert behandelt. */ + if (typ == TYP_SHIP) { + ship *sh = (ship *) obj; + unit *owner = ship_owner(sh); + a = sh->attribs; + r = sh->region; + if (owner) { + if (owner->faction == viewer) { + self = 2; + } else { /* steht eine person der Partei auf dem Schiff? */ + unit *u = NULL; + for (u = r->units; u; u = u->next) { + if (u->ship == sh) { + self = 1; + break; + } + } + } + } + } else if (typ == TYP_BUILDING) { + building *b = (building *) obj; + unit *owner; + a = b->attribs; + r = b->region; + if ((owner = building_owner(b)) != NULL) { + if (owner->faction == viewer) { + self = 2; + } else { /* steht eine Person der Partei in der Burg? */ + unit *u = NULL; + for (u = r->units; u; u = u->next) { + if (u->building == b) { + self = 1; + break; + } + } + } + } + } else if (typ == TYP_UNIT) { + unit *u = (unit *) obj; + a = u->attribs; + r = u->region; + if (u->faction == viewer) { + self = 2; + } + } else if (typ == TYP_REGION) { + r = (region *) obj; + a = r->attribs; + } else { + /* fehler */ + } + + for (; a; a = a->next) { + char buf[4096]; + + if (fval(a->type, ATF_CURSE)) { + curse *c = (curse *) a->data.v; + message *msg; + + if (c->type->cansee) { + self = c->type->cansee(viewer, obj, typ, c, self); + } + msg = msg_curse(c, obj, typ, self); + + if (msg) { + rnl(F); + nr_render(msg, viewer->locale, buf, sizeof(buf), viewer); + rparagraph(F, buf, indent, 2, 0); + msg_release(msg); + } + } else if (a->type == &at_effect && self) { + effect_data *data = (effect_data *) a->data.v; + if (data->value > 0) { + sprintf(buf, "Auf der Einheit lieg%s %d Wirkung%s %s.", + (data->value == 1 ? "t" : "en"), + data->value, + (data->value == 1 ? "" : "en"), + LOC(default_locale, resourcename(data->type->itype->rtype, 0))); + rnl(F); + rparagraph(F, buf, indent, 2, 0); + } + } + } +} + +static void rps_nowrap(FILE * F, const char *s) +{ + const char *x = s; + size_t indent = 0; + + while (*x++ == ' ') ; + indent = x - s - 1; + if (*(x - 1) && indent && *x == ' ') + indent += 2; + x = s; + while (*s) { + if (s == x) { + x = strchr(x + 1, ' '); + if (!x) + x = s + strlen(s); + } + rpc(F, *s++, 1); + } +} + +static void +nr_unit(FILE * F, const faction * f, const unit * u, int indent, int mode) +{ + attrib *a_otherfaction; + char marker; + int dh; + bool isbattle = (bool) (mode == see_battle); + char buf[8192]; + + if (fval(u_race(u), RCF_INVISIBLE)) + return; + + { + rnl(F); + dh = bufunit(f, u, indent, mode, buf, sizeof(buf)); + } + + a_otherfaction = a_find(u->attribs, &at_otherfaction); + + if (u->faction == f) { + marker = '*'; + } else if ALLIED + (u->faction, f) { + marker = 'o'; + } else if (a_otherfaction && f != u->faction + && get_otherfaction(a_otherfaction) == f && !fval(u, UFL_ANON_FACTION)) { + marker = '!'; + } else { + if (dh && !fval(u, UFL_ANON_FACTION)) { + marker = '+'; + } else { + marker = '-'; + } + } + rparagraph(F, buf, indent, 0, marker); + + if (!isbattle) { + nr_curses(F, f, u, TYP_UNIT, indent); + } +} + +static void +rp_messages(FILE * F, message_list * msgs, faction * viewer, int indent, + bool categorized) +{ + nrsection *section; + if (!msgs) + return; + for (section = sections; section; section = section->next) { + int k = 0; + struct mlist *m = msgs->begin; + while (m) { + /* messagetype * mt = m->type; */ + if (!categorized || strcmp(nr_section(m->msg), section->name) == 0) { + char lbuf[8192]; + + if (!k && categorized) { + const char *section_title; + char cat_identifier[24]; + + rnl(F); + sprintf(cat_identifier, "section_%s", section->name); + section_title = LOC(viewer->locale, cat_identifier); + centre(F, section_title, true); + rnl(F); + k = 1; + } + nr_render(m->msg, viewer->locale, lbuf, sizeof(lbuf), viewer); + rparagraph(F, lbuf, indent, 2, 0); + } + m = m->next; + } + if (!categorized) + break; + } +} + +static void rp_battles(FILE * F, faction * f) +{ + if (f->battles != NULL) { + struct bmsg *bm = f->battles; + rnl(F); + centre(F, LOC(f->locale, "section_battle"), false); + rnl(F); + + while (bm) { + char buf[256]; + RENDER(f, buf, sizeof(buf), ("battle::header", "region", bm->r)); + rnl(F); + centre(F, buf, true); + rnl(F); + rp_messages(F, bm->msgs, f, 0, false); + bm = bm->next; + } + } +} + +static void prices(FILE * F, const region * r, const faction * f) +{ + const luxury_type *sale = NULL; + struct demand *dmd; + message *m; + int bytes, n = 0; + char buf[4096], *bufp = buf; + size_t size = sizeof(buf) - 1; + + if (r->land == NULL || r->land->demands == NULL) + return; + for (dmd = r->land->demands; dmd; dmd = dmd->next) { + if (dmd->value == 0) + sale = dmd->type; + else if (dmd->value > 0) + n++; + } + assert(sale != NULL); + + m = msg_message("nr_market_sale", "product price", + sale->itype->rtype, sale->price); + + bytes = (int)nr_render(m, f->locale, bufp, size, f); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + msg_release(m); + + if (n > 0) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_trade_intro"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + for (dmd = r->land->demands; dmd; dmd = dmd->next) + if (dmd->value > 0) { + m = msg_message("nr_market_price", "product price", + dmd->type->itype->rtype, dmd->value * dmd->type->price); + bytes = (int)nr_render(m, f->locale, bufp, size, f); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + msg_release(m); + n--; + if (n == 0) { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_trade_end"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else if (n == 1) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_trade_final"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_trade_next"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + } + /* Schreibe Paragraphen */ + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + +} + +bool see_border(const connection * b, const faction * f, const region * r) +{ + bool cs = b->type->fvisible(b, f, r); + if (!cs) { + cs = b->type->rvisible(b, r); + if (!cs) { + const unit *us = r->units; + while (us && !cs) { + if (us->faction == f) { + cs = b->type->uvisible(b, us); + if (cs) + break; + } + us = us->next; + } + } + } + return cs; +} + +static void describe(FILE * F, const seen_region * sr, faction * f) +{ + const region *r = sr->r; + int n; + bool dh; + direction_t d; + int trees; + int saplings; + attrib *a; + const char *tname; + struct edge { + struct edge *next; + char *name; + bool transparent; + bool block; + bool exist[MAXDIRECTIONS]; + direction_t lastd; + } *edges = NULL, *e; + bool see[MAXDIRECTIONS]; + char buf[8192]; + char *bufp = buf; + size_t size = sizeof(buf); + int bytes; + + for (d = 0; d != MAXDIRECTIONS; d++) { + /* Nachbarregionen, die gesehen werden, ermitteln */ + region *r2 = rconnect(r, d); + connection *b; + see[d] = true; + if (!r2) + continue; + for (b = get_borders(r, r2); b;) { + struct edge *e = edges; + bool transparent = b->type->transparent(b, f); + const char *name = b->type->name(b, r, f, GF_DETAILED | GF_ARTICLE); + + if (!transparent) + see[d] = false; + if (!see_border(b, f, r)) { + b = b->next; + continue; + } + while (e && (e->transparent != transparent || strcmp(name, e->name))) + e = e->next; + if (!e) { + e = calloc(sizeof(struct edge), 1); + e->name = strdup(name); + e->transparent = transparent; + e->next = edges; + edges = e; + } + e->lastd = d; + e->exist[d] = true; + b = b->next; + } + } + + bytes = (int)f_regionid(r, f, bufp, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (sr->mode == see_travel) { + bytes = snprintf(bufp, size, " (%s)", LOC(f->locale, "see_travel")); + } else if (sr->mode == see_neighbour) { + bytes = snprintf(bufp, size, " (%s)", LOC(f->locale, "see_neighbour")); + } else if (sr->mode == see_lighthouse) { + bytes = snprintf(bufp, size, " (%s)", LOC(f->locale, "see_lighthouse")); + } else { + bytes = 0; + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + /* Terrain */ + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + tname = terrain_name(r); + bytes = (int)strlcpy(bufp, LOC(f->locale, tname), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + /* Trees */ + trees = rtrees(r, 2); + saplings = rtrees(r, 1); + if (production(r)) { + if (trees > 0 || saplings > 0) { + bytes = snprintf(bufp, size, ", %d/%d ", trees, saplings); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (fval(r, RF_MALLORN)) { + if (trees == 1) { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_mallorntree"), size); + } else { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_mallorntree_p"), size); + } + } else if (trees == 1) { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_tree"), size); + } else { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_tree_p"), size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + + /* iron & stone */ + if (sr->mode == see_unit && f != (faction *) NULL) { + resource_report result[MAX_RAWMATERIALS]; + int n, numresults = report_resources(sr, result, MAX_RAWMATERIALS, f); + + for (n = 0; n < numresults; ++n) { + if (result[n].number >= 0 && result[n].level >= 0) { + bytes = snprintf(bufp, size, ", %d %s/%d", result[n].number, + LOC(f->locale, result[n].name), result[n].level); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + } + + /* peasants & silver */ + if (rpeasants(r)) { + int n = rpeasants(r); + bytes = snprintf(bufp, size, ", %d", n); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (r->land->ownership) { + const char *str = + locale_string(f->locale, mkname("morale", itoa10(r->land->morale))); + bytes = snprintf(bufp, size, " %s", str); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (fval(r, RF_ORCIFIED)) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + bytes = + (int)strlcpy(bufp, LOC(f->locale, n == 1 ? "rc_orc" : "rc_orc_p"), + size); + } else { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = + (int)strlcpy(bufp, LOC(f->locale, n == 1 ? "peasant" : "peasant_p"), + size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (is_mourning(r, turn + 1)) { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_mourning"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + if (rmoney(r) && sr->mode >= see_travel) { + bytes = snprintf(bufp, size, ", %d ", rmoney(r)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = + (int)strlcpy(bufp, LOC(f->locale, resourcename(oldresourcetype[R_SILVER], + rmoney(r) != 1)), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + /* Pferde */ + + if (rhorses(r)) { + bytes = snprintf(bufp, size, ", %d ", rhorses(r)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = + (int)strlcpy(bufp, LOC(f->locale, resourcename(oldresourcetype[R_HORSE], + (rhorses(r) > 1) ? GR_PLURAL : 0)), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, ".", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (r->display && r->display[0]) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, r->display, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + n = r->display[strlen(r->display) - 1]; + if (n != '!' && n != '?' && n != '.') { + bytes = (int)strlcpy(bufp, ".", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + + if (rule_region_owners()) { + const faction *owner = region_get_owner(r); + if (owner != NULL) { + bytes = snprintf(bufp, size, " Die Region ist im Besitz von %s.", + factionname(owner)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + a = a_find(r->attribs, &at_overrideroads); + + if (a) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, (char *)a->data.v, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else { + int nrd = 0; + + /* Nachbarregionen, die gesehen werden, ermitteln */ + for (d = 0; d != MAXDIRECTIONS; d++) { + if (see[d] && rconnect(r, d)) + nrd++; + } + /* list directions */ + + dh = false; + for (d = 0; d != MAXDIRECTIONS; d++) + if (see[d]) { + region *r2 = rconnect(r, d); + if (!r2) + continue; + nrd--; + if (dh) { + char regname[4096]; + if (nrd == 0) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_nb_final"), size); + } else { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_nb_next"), size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, directions[d]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + f_regionid(r2, f, regname, sizeof(regname)); + bytes = snprintf(bufp, size, trailinto(r2, f->locale), regname); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + MSG(("nr_vicinitystart", "dir region", d, r2), bufp, size, f->locale, + f); + bufp += strlen(bufp); + dh = true; + } + } + /* Spezielle Richtungen */ + for (a = a_find(r->attribs, &at_direction); a && a->type == &at_direction; + a = a->next) { + spec_direction *d = (spec_direction *) (a->data.v); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, d->desc), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " (\"", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, d->keyword), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, "\")", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, ".", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + dh = 1; + } + } + rnl(F); + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + + if (sr->mode == see_unit && is_astral(r) && + !is_cursed(r->attribs, C_ASTRALBLOCK, 0)) { + /* Sonderbehandlung Teleport-Ebene */ + region_list *rl = astralregions(r, inhabitable); + region_list *rl2; + + if (rl) { + bufp = buf; + size = sizeof(buf) - 1; + bytes = (int)strlcpy(bufp, "Schemen der Regionen ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + rl2 = rl; + while (rl2) { + bytes = (int)f_regionid(rl2->data, f, bufp, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + rl2 = rl2->next; + if (rl2) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + bytes = (int)strlcpy(bufp, " sind erkennbar.", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + free_regionlist(rl); + /* Schreibe Paragraphen */ + rnl(F); + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + } + } + + n = 0; + + /* Wirkungen permanenter Sprüche */ + nr_curses(F, f, r, TYP_REGION, 0); + + /* Produktionsreduktion */ + a = a_find(r->attribs, &at_reduceproduction); + if (a) { + const char *str = LOC(f->locale, "nr_reduced_production"); + rparagraph(F, str, 0, 0, 0); + } + + if (edges) + rnl(F); + for (e = edges; e; e = e->next) { + bool first = true; + bufp = buf; + size = sizeof(buf) - 1; + for (d = 0; d != MAXDIRECTIONS; ++d) { + if (!e->exist[d]) + continue; + if (first) + bytes = (int)strlcpy(bufp, "Im ", size); + else if (e->lastd == d) + bytes = (int)strlcpy(bufp, " und im ", size); + else + bytes = (int)strlcpy(bufp, ", im ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, directions[d]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + first = false; + } + if (!e->transparent) + bytes = (int)strlcpy(bufp, " versperrt ", size); + else + bytes = (int)strlcpy(bufp, " befindet sich ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, e->name, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (!e->transparent) + bytes = (int)strlcpy(bufp, " die Sicht.", size); + else + bytes = (int)strlcpy(bufp, ".", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + } + if (edges) { + while (edges) { + e = edges->next; + free(edges->name); + free(edges); + edges = e; + } + } +} + +static void statistics(FILE * F, const region * r, const faction * f) +{ + const unit *u; + int number = 0, p = rpeasants(r); + message *m; + item *itm, *items = NULL; + char buf[4096]; + + /* count */ + for (u = r->units; u; u = u->next) { + if (u->faction == f && !fval(u_race(u), RCF_INVISIBLE)) { + for (itm = u->items; itm; itm = itm->next) { + i_change(&items, itm->type, itm->number); + } + number += u->number; + } + } + /* print */ + rnl(F); + m = msg_message("nr_stat_header", "region", r); + nr_render(m, f->locale, buf, sizeof(buf), f); + msg_release(m); + rparagraph(F, buf, 0, 0, 0); + rnl(F); + + /* Region */ + if (skill_enabled[SK_ENTERTAINMENT] && fval(r->terrain, LAND_REGION) + && rmoney(r)) { + m = msg_message("nr_stat_maxentertainment", "max", entertainmoney(r)); + nr_render(m, f->locale, buf, sizeof(buf), f); + rparagraph(F, buf, 2, 2, 0); + msg_release(m); + } + if (production(r) && (!fval(r->terrain, SEA_REGION) + || f->race == new_race[RC_AQUARIAN])) { + if (markets_module()) { /* hack */ + m = + msg_message("nr_stat_salary_new", "max", wage(r, NULL, NULL, turn + 1)); + } else { + m = msg_message("nr_stat_salary", "max", wage(r, f, f->race, turn + 1)); + } + nr_render(m, f->locale, buf, sizeof(buf), f); + rparagraph(F, buf, 2, 2, 0); + msg_release(m); + } + + if (p) { + m = msg_message("nr_stat_recruits", "max", p / RECRUITFRACTION); + nr_render(m, f->locale, buf, sizeof(buf), f); + rparagraph(F, buf, 2, 2, 0); + msg_release(m); + + if (!markets_module()) { + if (buildingtype_exists(r, bt_find("caravan"), true)) { + m = msg_message("nr_stat_luxuries", "max", (p * 2) / TRADE_FRACTION); + } else { + m = msg_message("nr_stat_luxuries", "max", p / TRADE_FRACTION); + } + nr_render(m, f->locale, buf, sizeof(buf), f); + rparagraph(F, buf, 2, 2, 0); + msg_release(m); + } + + if (r->land->ownership) { + m = msg_message("nr_stat_morale", "morale", r->land->morale); + nr_render(m, f->locale, buf, sizeof(buf), f); + rparagraph(F, buf, 2, 2, 0); + msg_release(m); + } + + } + /* info about units */ + + m = msg_message("nr_stat_people", "max", number); + nr_render(m, f->locale, buf, sizeof(buf), f); + rparagraph(F, buf, 2, 2, 0); + msg_release(m); + + for (itm = items; itm; itm = itm->next) { + sprintf(buf, "%s: %d", + LOC(f->locale, resourcename(itm->type->rtype, GR_PLURAL)), itm->number); + rparagraph(F, buf, 2, 2, 0); + } + while (items) + i_free(i_remove(&items, items)); +} + +static void durchreisende(FILE * F, const region * r, const faction * f) +{ + if (fval(r, RF_TRAVELUNIT)) { + attrib *abegin = a_find(r->attribs, &at_travelunit), *a; + int counter = 0, maxtravel = 0; + char buf[8192]; + char *bufp = buf; + int bytes; + size_t size = sizeof(buf) - 1; + + /* How many are we listing? For grammar. */ + for (a = abegin; a && a->type == &at_travelunit; a = a->next) { + unit *u = (unit *) a->data.v; + + if (r != u->region && (!u->ship || ship_owner(u->ship)==u)) { + if (cansee_durchgezogen(f, r, u, 0)) { + ++maxtravel; + } + } + } + + if (maxtravel == 0) { + return; + } + + /* Auflisten. */ + rnl(F); + + for (a = abegin; a && a->type == &at_travelunit; a = a->next) { + unit *u = (unit *) a->data.v; + + if (r != u->region && (!u->ship || ship_owner(u->ship)==u)) { + if (cansee_durchgezogen(f, r, u, 0)) { + ++counter; + if (u->ship != NULL) { + if (counter == 1) { + bytes = (int)strlcpy(bufp, "Die ", size); + } else { + bytes = (int)strlcpy(bufp, "die ", size); + } + if (wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + break; + } + bytes = (int)strlcpy(bufp, shipname(u->ship), size); + } else { + bytes = (int)strlcpy(bufp, unitname(u), size); + } + if (wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + break; + } + + if (counter + 1 < maxtravel) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + break; + } + } else if (counter + 1 == maxtravel) { + bytes = (int)strlcpy(bufp, LOC(f->locale, "list_and"), size); + if (wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + break; + } + } + } + } + } + /* TODO: finish localization */ + if (maxtravel == 1) { + bytes = snprintf(bufp, size, " %s", LOC(f->locale, "has_moved_one")); + } else { + bytes = snprintf(bufp, size, " %s", LOC(f->locale, "has_moved_many")); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + } +} + +static int buildingmaintenance(const building * b, const resource_type * rtype) +{ + const building_type *bt = b->type; + int c, cost = 0; + static bool init = false; + static const curse_type *nocost_ct; + if (!init) { + init = true; + nocost_ct = ct_find("nocostbuilding"); + } + if (curse_active(get_curse(b->attribs, nocost_ct))) { + return 0; + } + for (c = 0; bt->maintenance && bt->maintenance[c].number; ++c) { + const maintenance *m = bt->maintenance + c; + if (m->rtype == rtype) { + if (fval(m, MTF_VARIABLE)) + cost += (b->size * m->number); + else + cost += m->number; + } + } + return cost; +} + +static int +report_template(const char *filename, report_context * ctx, const char *charset) +{ + faction *f = ctx->f; + region *r; + FILE *F = fopen(filename, "wt"); + seen_region *sr = NULL; + char buf[8192], *bufp; + size_t size; + int bytes; + + int enc = xmlParseCharEncoding(charset); + + if (F == NULL) { + perror(filename); + return -1; + } + + if (enc == XML_CHAR_ENCODING_UTF8) { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + fwrite(utf8_bom, 1, 3, F); + } + + rps_nowrap(F, ""); + rnl(F); + rps_nowrap(F, LOC(f->locale, "nr_template")); + rnl(F); + rps_nowrap(F, ""); + rnl(F); + + sprintf(buf, "%s %s \"%s\"", LOC(f->locale, "ERESSEA"), factionid(f), + LOC(f->locale, "enterpasswd")); + rps_nowrap(F, buf); + rnl(F); + + rps_nowrap(F, ""); + rnl(F); + sprintf(buf, "; ECHECK -l -w4 -r%d -v%s", f->race->recruitcost, + ECHECK_VERSION); + /* -v3.4: ECheck Version 3.4.x */ + rps_nowrap(F, buf); + rnl(F); + + for (r = ctx->first; sr == NULL && r != ctx->last; r = r->next) { + sr = find_seen(ctx->seen, r); + } + + for (; sr != NULL; sr = sr->next) { + region *r = sr->r; + unit *u; + int dh = 0; + + if (sr->mode < see_unit) + continue; + + for (u = r->units; u; u = u->next) { + if (u->faction == f && !fval(u_race(u), RCF_INVISIBLE)) { + order *ord; + if (!dh) { + plane *pl = getplane(r); + int nx = r->x, ny = r->y; + + pnormalize(&nx, &ny, pl); + adjust_coordinates(f, &nx, &ny, pl, r); + rps_nowrap(F, ""); + rnl(F); + if (pl && pl->id != 0) { + sprintf(buf, "%s %d,%d,%d ; %s", LOC(f->locale, + parameters[P_REGION]), nx, ny, pl->id, rname(r, f->locale)); + } else { + sprintf(buf, "%s %d,%d ; %s", LOC(f->locale, parameters[P_REGION]), + nx, ny, rname(r, f->locale)); + } + rps_nowrap(F, buf); + rnl(F); + sprintf(buf, "; ECheck Lohn %d", wage(r, f, f->race, turn + 1)); + rps_nowrap(F, buf); + rnl(F); + rps_nowrap(F, ""); + rnl(F); + } + dh = 1; + + bufp = buf; + size = sizeof(buf) - 1; + bytes = snprintf(bufp, size, "%s %s; %s [%d,%d$", + LOC(u->faction->locale, parameters[P_UNIT]), + unitid(u), u->name, u->number, get_money(u)); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (u->building && building_owner(u->building)==u) { + building *b = u->building; + int cost = buildingmaintenance(b, r_silver); + + if (cost > 0) { + bytes = (int)strlcpy(bufp, ",U", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, itoa10(cost), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } else if (u->ship) { + if (ship_owner(u->ship)==u) { + bytes = (int)strlcpy(bufp, ",S", size); + } else { + bytes = (int)strlcpy(bufp, ",s", size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, shipid(u->ship), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (lifestyle(u) == 0) { + bytes = (int)strlcpy(bufp, ",I", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, "]", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + *bufp = 0; + rps_nowrap(F, buf); + rnl(F); + + for (ord = u->old_orders; ord; ord = ord->next) { + /* this new order will replace the old defaults */ + strcpy(buf, " "); + write_order(ord, buf + 2, sizeof(buf) - 2); + rps_nowrap(F, buf); + rnl(F); + } + for (ord = u->orders; ord; ord = ord->next) { + if (u->old_orders && is_repeated(ord)) + continue; /* unit has defaults */ + if (is_persistent(ord)) { + strcpy(buf, " "); + write_order(ord, buf + 2, sizeof(buf) - 2); + rps_nowrap(F, buf); + rnl(F); + } + } + + /* If the lastorder begins with an @ it should have + * been printed in the loop before. */ + } + } + } + rps_nowrap(F, ""); + rnl(F); + strcpy(buf, LOC(f->locale, parameters[P_NEXT])); + rps_nowrap(F, buf); + rnl(F); + fclose(F); + return 0; +} + +static void +show_allies(const faction * f, const ally * allies, char *buf, size_t size) +{ + int allierte = 0; + int i = 0, h, hh = 0; + int bytes, dh = 0; + const ally *sf; + char *bufp = buf; /* buf already contains data */ + + --size; /* leave room for a null-terminator */ + + for (sf = allies; sf; sf = sf->next) { + int mode = alliedgroup(NULL, f, sf->faction, sf, HELP_ALL); + if (mode > 0) + ++allierte; + } + + for (sf = allies; sf; sf = sf->next) { + int mode = alliedgroup(NULL, f, sf->faction, sf, HELP_ALL); + if (mode <= 0) + continue; + i++; + if (dh) { + if (i == allierte) { + bytes = (int)strlcpy(bufp, LOC(f->locale, "list_and"), size); + } else { + bytes = (int)strlcpy(bufp, ", ", size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + dh = 1; + hh = 0; + bytes = (int)strlcpy(bufp, factionname(sf->faction), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " (", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if ((mode & HELP_ALL) == HELP_ALL) { + bytes = (int)strlcpy(bufp, "Alles", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else { + for (h = 1; h < HELP_ALL; h *= 2) { + int p = MAXPARAMS; + if ((mode & h) == h) { + switch (h) { + case HELP_TRAVEL: + p = P_TRAVEL; + break; + case HELP_MONEY: + p = P_MONEY; + break; + case HELP_FIGHT: + p = P_FIGHT; + break; + case HELP_GIVE: + p = P_GIVE; + break; + case HELP_GUARD: + p = P_GUARD; + break; + case HELP_FSTEALTH: + p = P_FACTIONSTEALTH; + break; + } + } + if (p != MAXPARAMS) { + if (hh) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, parameters[p], size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + hh = 1; + } + } + } + bytes = (int)strlcpy(bufp, ")", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, ".", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + *bufp = 0; +} + +static void allies(FILE * F, const faction * f) +{ + const group *g = f->groups; + char buf[16384]; + + if (f->allies) { + int bytes; + size_t size = sizeof(buf); + if (!f->allies->next) { + bytes = snprintf(buf, size, "%s ", LOC(f->locale, "faction_help_one")); + } else { + bytes = snprintf(buf, size, "%s ", LOC(f->locale, "faction_help_many")); + } + size -= bytes; + show_allies(f, f->allies, buf + bytes, size); + rparagraph(F, buf, 0, 0, 0); + rnl(F); + } + + while (g) { + if (g->allies) { + int bytes; + size_t size = sizeof(buf); + if (!g->allies->next) { + bytes = snprintf(buf, size, "%s %s ", g->name, LOC(f->locale, "group_help_one")); + } else { + bytes = snprintf(buf, size, "%s %s ", g->name, LOC(f->locale, "group_help_many")); + } + size -= bytes; + show_allies(f, g->allies, buf + bytes, size); + rparagraph(F, buf, 0, 0, 0); + rnl(F); + } + g = g->next; + } +} + +static void guards(FILE * F, const region * r, const faction * see) +{ + /* die Partei see sieht dies; wegen + * "unbekannte Partei", wenn man es selbst ist... */ + + faction *guardians[512]; + + int nextguard = 0; + + unit *u; + int i; + + bool tarned = false; + /* Bewachung */ + + for (u = r->units; u; u = u->next) { + if (is_guard(u, GUARD_ALL) != 0) { + faction *f = u->faction; + faction *fv = visible_faction(see, u); + + if (fv != f && see != fv) { + f = fv; + } + + if (f != see && fval(u, UFL_ANON_FACTION)) { + tarned = true; + } else { + for (i = 0; i != nextguard; ++i) + if (guardians[i] == f) + break; + if (i == nextguard) { + guardians[nextguard++] = f; + } + } + } + } + + if (nextguard || tarned) { + char buf[8192]; + char *bufp = buf; + size_t size = sizeof(buf) - 1; + int bytes; + + bytes = (int)strlcpy(bufp, "Die Region wird von ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + for (i = 0; i != nextguard + (tarned ? 1 : 0); ++i) { + if (i != 0) { + if (i == nextguard - (tarned ? 0 : 1)) { + bytes = (int)strlcpy(bufp, LOC(see->locale, "list_and"), size); + } else { + bytes = (int)strlcpy(bufp, ", ", size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (i < nextguard) { + bytes = (int)strlcpy(bufp, factionname(guardians[i]), size); + } else { + bytes = (int)strlcpy(bufp, "unbekannten Einheiten", size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, " bewacht.", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + rnl(F); + *bufp = 0; + rparagraph(F, buf, 0, 0, 0); + } +} + +static void rpline(FILE * F) +{ + static char line[REPORTWIDTH + 1]; + if (line[0] != '-') { + memset(line, '-', sizeof(line)); + line[REPORTWIDTH] = '\n'; + } + fwrite(line, sizeof(char), sizeof(line), F); +} + +static void list_address(FILE * F, const faction * uf, quicklist * seenfactions) +{ + int qi = 0; + quicklist *flist = seenfactions; + + centre(F, LOC(uf->locale, "nr_addresses"), false); + rnl(F); + + while (flist != NULL) { + const faction *f = (const faction *)ql_get(flist, qi); + if (!is_monsters(f)) { + char buf[8192]; + char label = '-'; + + sprintf(buf, "%s: %s; %s", factionname(f), f->email, + f->banner ? f->banner : ""); + if (uf == f) + label = '*'; + else if (ALLIED(uf, f)) + label = 'o'; + else if (alliedfaction(NULL, uf, f, HELP_ALL)) + label = '+'; + rparagraph(F, buf, 4, 0, label); + + } + ql_advance(&flist, &qi, 1); + } + rnl(F); + rpline(F); +} + +static void +nr_ship(FILE * F, const seen_region * sr, const ship * sh, const faction * f, + const unit * captain) +{ + const region *r = sr->r; + char buffer[8192], *bufp = buffer; + size_t size = sizeof(buffer) - 1; + int bytes; + char ch; + + rnl(F); + + if (captain && captain->faction == f) { + int n = 0, p = 0; + getshipweight(sh, &n, &p); + n = (n + 99) / 100; /* 1 Silber = 1 GE */ + + bytes = snprintf(bufp, size, "%s, %s, (%d/%d)", shipname(sh), + LOC(f->locale, sh->type->name[0]), n, shipcapacity(sh) / 100); + } else { + bytes = + snprintf(bufp, size, "%s, %s", shipname(sh), LOC(f->locale, + sh->type->name[0])); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ + if (sh->size != sh->type->construction->maxsize) { + bytes = snprintf(bufp, size, ", %s (%d/%d)", + LOC(f->locale, "nr_undercons"), sh->size, + sh->type->construction->maxsize); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (sh->damage) { + int percent = + (sh->damage * 100 + DAMAGE_SCALE - 1) / (sh->size * DAMAGE_SCALE); + bytes = + snprintf(bufp, size, ", %d%% %s", percent, LOC(f->locale, "nr_damaged")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (!fval(r->terrain, SEA_REGION)) { + if (sh->coast != NODIRECTION) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, coasts[sh->coast]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + ch = 0; + if (sh->display && sh->display[0]) { + bytes = (int)strlcpy(bufp, "; ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, sh->display, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + ch = sh->display[strlen(sh->display) - 1]; + } + if (ch != '!' && ch != '?' && ch != '.') { + bytes = (int)strlcpy(bufp, ".", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + *bufp = 0; + rparagraph(F, buffer, 2, 0, 0); + + nr_curses(F, f, sh, TYP_SHIP, 4); +} + +static void +nr_building(FILE * F, const seen_region * sr, const building * b, + const faction * f) +{ + int i, bytes; + const char *name, *bname, *billusion = NULL; + const struct locale *lang = NULL; + char buffer[8192], *bufp = buffer; + size_t size = sizeof(buffer) - 1; + + rnl(F); + + if (f) + lang = f->locale; + + bytes = + snprintf(bufp, size, "%s, %s %d, ", buildingname(b), LOC(f->locale, + "nr_size"), b->size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + report_building(b, &bname, &billusion); + name = LOC(lang, billusion ? billusion : bname); + bytes = (int)strlcpy(bufp, name, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (billusion) { + unit *owner = building_owner(b); + if (owner && owner->faction == f) { + /* illusion. report real type */ + name = LOC(lang, bname); + bytes = snprintf(bufp, size, " (%s)", name); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + + if (b->size < b->type->maxsize) { + bytes = (int)strlcpy(bufp, " (im Bau)", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + if (b->besieged > 0 && sr->mode >= see_lighthouse) { + bytes = (int)strlcpy(bufp, ", belagert von ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, itoa10(b->besieged), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " Personen ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (b->besieged >= b->size * SIEGEFACTOR) { + bytes = (int)strlcpy(bufp, "(abgeschnitten)", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + i = 0; + if (b->display && b->display[0]) { + bytes = (int)strlcpy(bufp, "; ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, b->display, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + i = b->display[strlen(b->display) - 1]; + } +#ifdef WDW_PYRAMID + + if (i != '!' && i != '?' && i != '.') { + scat(", "); + } + + if (b->type == bt_find("pyramid")) { + unit *owner = building_owner(b); + scat("Größenstufe "); + icat(wdw_pyramid_level(b)); + scat("."); + + if (owner && owner->faction == f) { + const construction *ctype = b->type->construction; + int completed = b->size; + int c; + + scat(" Baukosten pro Größenpunkt: "); + + while (ctype->improvement != NULL && + ctype->improvement != ctype && + ctype->maxsize > 0 && ctype->maxsize <= completed) { + completed -= ctype->maxsize; + ctype = ctype->improvement; + } + + assert(ctype->materials != NULL); + + for (c = 0; ctype->materials[c].number; c++) { + const resource_type *rtype = ctype->materials[c].rtype; + int number = ctype->materials[c].number; + + if (c > 0) { + scat(", "); + } + icat(number); + scat(" "); + scat(locale_string(lang, resourcename(rtype, + number != 1 ? GR_PLURAL : 0))); + } + + scat("."); + + scat(" Erforderlicher Talentwert: "); + icat(b->type->construction->minskill); + scat("."); + } + } +#else + + if (i != '!' && i != '?' && i != '.') { + bytes = (int)strlcpy(bufp, ".", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } +#endif + *bufp = 0; + rparagraph(F, buffer, 2, 0, 0); + + if (sr->mode < see_lighthouse) + return; + + nr_curses(F, f, b, TYP_BUILDING, 4); +} + +static void nr_paragraph(FILE * F, message * m, faction * f) +{ + int bytes; + char buf[4096], *bufp = buf; + size_t size = sizeof(buf) - 1; + + bytes = (int)nr_render(m, f->locale, bufp, size, f); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + msg_release(m); + + rparagraph(F, buf, 0, 0, 0); +} + +int +report_plaintext(const char *filename, report_context * ctx, + const char *charset) +{ + int flag = 0; + char ch; + int anyunits, no_units, no_people; + const struct region *r; + faction *f = ctx->f; + unit *u; + char pzTime[64]; + attrib *a; + message *m; + unsigned char op; + int bytes, ix = want(O_STATISTICS); + int wants_stats = (f->options & ix); + FILE *F = fopen(filename, "wt"); + seen_region *sr = NULL; + char buf[8192]; + char *bufp; + int enc = xmlParseCharEncoding(charset); + size_t size; + + /* static variables can cope with writing for different turns */ + static int thisseason = -1; + static int nextseason = -1; + static int gamecookie = -1; + if (gamecookie != global.cookie) { + gamedate date; + get_gamedate(turn + 1, &date); + thisseason = date.season; + get_gamedate(turn + 2, &date); + nextseason = date.season; + gamecookie = global.cookie; + } + + if (F == NULL) { + perror(filename); + return -1; + } + if (enc == XML_CHAR_ENCODING_UTF8) { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + fwrite(utf8_bom, 1, 3, F); + } + + strftime(pzTime, 64, "%A, %d. %B %Y, %H:%M", localtime(&ctx->report_time)); + m = msg_message("nr_header_date", "game date", global.gamename, pzTime); + nr_render(m, f->locale, buf, sizeof(buf), f); + msg_release(m); + centre(F, buf, true); + + centre(F, gamedate_season(f->locale), true); + rnl(F); + sprintf(buf, "%s, %s/%s (%s)", factionname(f), + LOC(f->locale, rc_name(f->race, 1)), + LOC(f->locale, mkname("school", magic_school[f->magiegebiet])), f->email); + centre(F, buf, true); + if (f_get_alliance(f)) { + centre(F, alliancename(f->alliance), true); + } + + if (f->age <= 2) { + const char *s; + if (f->age <= 1) { + ADDMSG(&f->msgs, msg_message("changepasswd", "value", f->passw)); + } + RENDER(f, buf, sizeof(buf), ("newbie_password", "password", f->passw)); + rnl(F); + centre(F, buf, true); + s = locale_getstring(f->locale, "newbie_info_1"); + if (s) { + rnl(F); + centre(F, s, true); + } + s = locale_getstring(f->locale, "newbie_info_2"); + if (s) { + rnl(F); + centre(F, s, true); + } + if ((f->options & want(O_COMPUTER)) == 0) { + f->options |= want(O_COMPUTER); + s = locale_getstring(f->locale, "newbie_info_3"); + if (s) { + rnl(F); + centre(F, s, true); + } + } + } + rnl(F); +#if SCORE_MODULE + if (f->options & want(O_SCORE) && f->age > DISPLAYSCORE) { + RENDER(f, buf, sizeof(buf), ("nr_score", "score average", f->score, + average_score_of_age(f->age, f->age / 24 + 1))); + centre(F, buf, true); + } +#endif +#ifdef COUNT_AGAIN + no_units = 0; + no_people = 0; + for (u = f->units; u; u = u->nextF) { + if (playerrace(u_race(u))) { + ++no_people; + no_units += u->number; + assert(f == u->faction); + } + } + if (no_units != f->no_units) { + f->no_units = no_units; + } + if (no_people != f->num_people) { + f->num_people = no_people; + } +#else + no_units = f->no_units; + no_people = f->num_people; +#endif + m = msg_message("nr_population", "population units", no_people, no_units); + nr_render(m, f->locale, buf, sizeof(buf), f); + msg_release(m); + centre(F, buf, true); + if (f->race == new_race[RC_HUMAN]) { + int maxmig = count_maxmigrants(f); + if (maxmig > 0) { + m = + msg_message("nr_migrants", "units maxunits", count_migrants(f), maxmig); + nr_render(m, f->locale, buf, sizeof(buf), f); + msg_release(m); + centre(F, buf, true); + } + } + if (f_get_alliance(f)) { + m = + msg_message("nr_alliance", "leader name id age", + alliance_get_leader(f->alliance), f->alliance->name, f->alliance->id, + turn - f->alliance_joindate); + nr_render(m, f->locale, buf, sizeof(buf), f); + msg_release(m); + centre(F, buf, true); + } + { + int maxh = maxheroes(f); + if (maxh) { + message *msg = + msg_message("nr_heroes", "units maxunits", countheroes(f), maxh); + nr_render(msg, f->locale, buf, sizeof(buf), f); + msg_release(msg); + centre(F, buf, true); + } + } + + if (f->items != NULL) { + message *msg = msg_message("nr_claims", "items", f->items); + nr_render(msg, f->locale, buf, sizeof(buf), f); + msg_release(msg); + rnl(F); + centre(F, buf, true); + } + + /* Insekten-Winter-Warnung */ + if (f->race == new_race[RC_INSECT]) { + if (thisseason == 0) { + centre(F, LOC(f->locale, "nr_insectwinter"), true); + rnl(F); + } else { + if (nextseason == 0) { + centre(F, LOC(f->locale, "nr_insectfall"), true); + rnl(F); + } + } + } + + bufp = buf; + size = sizeof(buf) - 1; + bytes = snprintf(buf, size, "%s:", LOC(f->locale, "nr_options")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + for (op = 0; op != MAXOPTIONS; op++) { + if (f->options & want(op) && options[op]) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, options[op]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + flag++; + } + } + if (flag > 0) { + rnl(F); + *bufp = 0; + centre(F, buf, true); + } + + rp_messages(F, f->msgs, f, 0, true); + rp_battles(F, f); + a = a_find(f->attribs, &at_reportspell); + if (a) { + rnl(F); + centre(F, LOC(f->locale, "section_newspells"), true); + while (a && a->type == &at_reportspell) { + spellbook_entry *sbe = (spellbook_entry *) a->data.v; + nr_spell(F, sbe, f->locale); + a = a->next; + } + } + + ch = 0; + for (a = a_find(f->attribs, &at_showitem); a && a->type == &at_showitem; + a = a->next) { + const potion_type *ptype = + resource2potion(((const item_type *)a->data.v)->rtype); + const char *description = NULL; + if (ptype != NULL) { + const char *pname = resourcename(ptype->itype->rtype, 0); + + if (ch == 0) { + rnl(F); + centre(F, LOC(f->locale, "section_newpotions"), true); + ch = 1; + } + + rnl(F); + centre(F, LOC(f->locale, pname), true); + snprintf(buf, sizeof(buf), "%s %d", LOC(f->locale, "nr_level"), + ptype->level); + centre(F, buf, true); + rnl(F); + + bufp = buf; + size = sizeof(buf) - 1; + bytes = snprintf(bufp, size, "%s: ", LOC(f->locale, "nr_herbsrequired")); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (ptype->itype->construction) { + requirement *m = ptype->itype->construction->materials; + while (m->number) { + bytes = + (int)strlcpy(bufp, LOC(f->locale, resourcename(m->rtype, 0)), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + ++m; + if (m->number) + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + *bufp = 0; + centre(F, buf, true); + rnl(F); + if (description == NULL) { + const char *potiontext = mkname("potion", pname); + description = LOC(f->locale, potiontext); + } + centre(F, description, true); + } + } + rnl(F); + centre(F, LOC(f->locale, "nr_alliances"), false); + rnl(F); + + allies(F, f); + + rpline(F); + + anyunits = 0; + + for (r = ctx->first; sr == NULL && r != ctx->last; r = r->next) { + sr = find_seen(ctx->seen, r); + } + for (; sr != NULL; sr = sr->next) { + region *r = sr->r; + int stealthmod = stealth_modifier(sr->mode); + building *b = r->buildings; + ship *sh = r->ships; + + if (sr->mode < see_lighthouse) + continue; + /* Beschreibung */ + + if (sr->mode == see_unit) { + anyunits = 1; + describe(F, sr, f); + if (markets_module() && r->land) { + const item_type *lux = r_luxury(r); + const item_type *herb = r->land->herbtype; + message *m = 0; + if (herb && lux) { + m = msg_message("nr_market_info_p", "p1 p2", + lux ? lux->rtype : 0, herb ? herb->rtype : 0); + } else if (lux || herb) { + m = msg_message("nr_market_info_s", "p1", + lux ? lux->rtype : herb->rtype); + } + if (m) { + rnl(F); + nr_paragraph(F, m, f); + } + /* */ + } else { + if (!fval(r->terrain, SEA_REGION) && rpeasants(r) / TRADE_FRACTION > 0) { + rnl(F); + prices(F, r, f); + } + } + guards(F, r, f); + durchreisende(F, r, f); + } else { + if (sr->mode == see_far) { + describe(F, sr, f); + guards(F, r, f); + durchreisende(F, r, f); + } else if (sr->mode == see_lighthouse) { + describe(F, sr, f); + durchreisende(F, r, f); + } else { + describe(F, sr, f); + durchreisende(F, r, f); + } + } + /* Statistik */ + + if (wants_stats && sr->mode == see_unit) + statistics(F, r, f); + + /* Nachrichten an REGION in der Region */ + + if (sr->mode == see_unit || sr->mode == see_travel) { + message_list *mlist = r_getmessages(r, f); + rp_messages(F, r->msgs, f, 0, true); + if (mlist) + rp_messages(F, mlist, f, 0, true); + } + + /* report all units. they are pre-sorted in an efficient manner */ + u = r->units; + while (b) { + while (b && (!u || u->building != b)) { + nr_building(F, sr, b, f); + b = b->next; + } + if (b) { + nr_building(F, sr, b, f); + while (u && u->building == b) { + nr_unit(F, f, u, 6, sr->mode); + u = u->next; + } + b = b->next; + } + } + while (u && !u->ship) { + if (stealthmod > INT_MIN) { + if (u->faction == f || cansee(f, r, u, stealthmod)) { + nr_unit(F, f, u, 4, sr->mode); + } + } + assert(!u->building); + u = u->next; + } + while (sh) { + while (sh && (!u || u->ship != sh)) { + nr_ship(F, sr, sh, f, NULL); + sh = sh->next; + } + if (sh) { + nr_ship(F, sr, sh, f, u); + while (u && u->ship == sh) { + nr_unit(F, f, u, 6, sr->mode); + u = u->next; + } + sh = sh->next; + } + } + + assert(!u); + + rnl(F); + rpline(F); + } + if (!is_monsters(f)) { + if (!anyunits) { + rnl(F); + rparagraph(F, LOC(f->locale, "nr_youaredead"), 0, 2, 0); + } else { + list_address(F, f, ctx->addresses); + } + } + fclose(F); + return 0; +} + +void base36conversion(void) +{ + region *r; + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + if (forbiddenid(u->no)) { + uunhash(u); + u->no = newunitid(); + uhash(u); + } + } + } +} + +#define FMAXHASH 1021 + +struct fsee { + struct fsee *nexthash; + faction *f; + struct see { + struct see *next; + faction *seen; + unit *proof; + } *see; +} *fsee[FMAXHASH]; + +#define REPORT_NR (1 << O_REPORT) +#define REPORT_CR (1 << O_COMPUTER) +#define REPORT_ZV (1 << O_ZUGVORLAGE) +#define REPORT_ZIP (1 << O_COMPRESS) +#define REPORT_BZIP2 (1 << O_BZIP2) + +unit *can_find(faction * f, faction * f2) +{ + int key = f->no % FMAXHASH; + struct fsee *fs = fsee[key]; + struct see *ss; + if (f == f2) + return f->units; + while (fs && fs->f != f) + fs = fs->nexthash; + if (!fs) + return NULL; + ss = fs->see; + while (ss && ss->seen != f2) + ss = ss->next; + if (ss) { + /* bei TARNE PARTEI yxz muss die Partei von unit proof nicht + * wirklich Partei f2 sein! */ + /* assert(ss->proof->faction==f2); */ + return ss->proof; + } + return NULL; +} + +static void add_find(faction * f, unit * u, faction * f2) +{ + /* faction f sees f2 through u */ + int key = f->no % FMAXHASH; + struct fsee **fp = &fsee[key]; + struct fsee *fs; + struct see **sp; + struct see *ss; + while (*fp && (*fp)->f != f) + fp = &(*fp)->nexthash; + if (!*fp) { + fs = *fp = calloc(sizeof(struct fsee), 1); + fs->f = f; + } else + fs = *fp; + sp = &fs->see; + while (*sp && (*sp)->seen != f2) + sp = &(*sp)->next; + if (!*sp) { + ss = *sp = calloc(sizeof(struct see), 1); + ss->proof = u; + ss->seen = f2; + } else + ss = *sp; + ss->proof = u; +} + +static void update_find(void) +{ + region *r; + static bool initial = true; + + if (initial) + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + faction *lastf = u->faction; + unit *u2; + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction == lastf || u2->faction == u->faction) + continue; + if (seefaction(u->faction, r, u2, 0)) { + faction *fv = visible_faction(u->faction, u2); + lastf = fv; + add_find(u->faction, u2, fv); + } + } + } + } + initial = false; +} + +bool kann_finden(faction * f1, faction * f2) +{ + update_find(); + return (bool) (can_find(f1, f2) != NULL); +} + +/******* end summary ******/ + +void register_nr(void) +{ + if (!nocr) + register_reporttype("nr", &report_plaintext, 1 << O_REPORT); + if (!nonr) + register_reporttype("txt", &report_template, 1 << O_ZUGVORLAGE); +} + +void report_cleanup(void) +{ + int i; + for (i = 0; i != FMAXHASH; ++i) { + while (fsee[i]) { + struct fsee *fs = fsee[i]->nexthash; + free(fsee[i]); + fsee[i] = fs; + } + } +} diff --git a/core/src/gamecode/report.h b/core/src/gamecode/report.h new file mode 100644 index 000000000..8a338115f --- /dev/null +++ b/core/src/gamecode/report.h @@ -0,0 +1,24 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#ifndef H_GC_REPORT +#define H_GC_REPORT +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_nr(void); + extern void report_cleanup(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/spy.c b/core/src/gamecode/spy.c new file mode 100644 index 000000000..fa5116060 --- /dev/null +++ b/core/src/gamecode/spy.c @@ -0,0 +1,514 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "spy.h" +#include "laws.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* attributes includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +/* in spy steht der Unterschied zwischen Wahrnehmung des Opfers und +* Spionage des Spions */ +void spy_message(int spy, const unit * u, const unit * target) +{ + const char *str = report_kampfstatus(target, u->faction->locale); + + ADDMSG(&u->faction->msgs, msg_message("spyreport", "spy target status", u, + target, str)); + if (spy > 20) { + sc_mage *mage = get_mage(target); + /* bei Magiern Zaubersprüche und Magiegebiet */ + if (mage) { + ADDMSG(&u->faction->msgs, msg_message("spyreport_mage", "target type", + target, magic_school[mage->magietyp])); + } + } + if (spy > 6) { + faction *fv = visible_faction(u->faction, target); + if (fv && fv != target->faction) { + /* wahre Partei */ + ADDMSG(&u->faction->msgs, msg_message("spyreport_faction", + "target faction", target, target->faction)); + ql_set_insert(&u->faction->seen_factions, target->faction); + } + } + if (spy > 0) { + int first = 1; + int found = 0; + skill *sv; + char buf[4096]; + + buf[0] = 0; + for (sv = target->skills; sv != target->skills + target->skill_size; ++sv) { + if (sv->level > 0) { + found++; + if (first == 1) { + first = 0; + } else { + strncat(buf, ", ", sizeof(buf)); + } + strncat(buf, (const char *)skillname((skill_t)sv->id, u->faction->locale), + sizeof(buf)); + strncat(buf, " ", sizeof(buf)); + strncat(buf, itoa10(eff_skill(target, (skill_t)sv->id, target->region)), + sizeof(buf)); + } + } + if (found) { + ADDMSG(&u->faction->msgs, msg_message("spyreport_skills", "target skills", + target, buf)); + } + + if (target->items) { + ADDMSG(&u->faction->msgs, msg_message("spyreport_items", "target items", + target, target->items)); + } + } +} + +int spy_cmd(unit * u, struct order *ord) +{ + unit *target; + int spy, observe; + double spychance, observechance; + region *r = u->region; + + init_tokens(ord); + skip_token(); + target = getunit(r, u->faction); + + if (!target) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "feedback_unit_not_found", "")); + return 0; + } + if (!can_contact(r, u, target)) { + cmistake(u, u->thisorder, 24, MSG_EVENT); + return 0; + } + if (eff_skill(u, SK_SPY, r) < 1) { + cmistake(u, u->thisorder, 39, MSG_EVENT); + return 0; + } + /* Die Grundchance für einen erfolgreichen Spionage-Versuch ist 10%. + * Für jeden Talentpunkt, den das Spionagetalent das Tarnungstalent + * des Opfers übersteigt, erhöht sich dieses um 5%*/ + spy = eff_skill(u, SK_SPY, r) - eff_skill(target, SK_STEALTH, r); + spychance = 0.1 + MAX(spy * 0.05, 0.0); + + if (chance(spychance)) { + produceexp(u, SK_SPY, u->number); + spy_message(spy, u, target); + } else { + ADDMSG(&u->faction->msgs, msg_message("spyfail", "spy target", u, target)); + } + + /* der Spion kann identifiziert werden, wenn das Opfer bessere + * Wahrnehmung als das Ziel Tarnung + Spionage/2 hat */ + observe = eff_skill(target, SK_PERCEPTION, r) + - (effskill(u, SK_STEALTH) + eff_skill(u, SK_SPY, r) / 2); + + if (invisible(u, target) >= u->number) { + observe = MIN(observe, 0); + } + + /* Anschließend wird - unabhängig vom Erfolg - gewürfelt, ob der + * Spionageversuch bemerkt wurde. Die Wahrscheinlich dafür ist (100 - + * SpionageSpion*5 + WahrnehmungOpfer*2)%. */ + observechance = 1.0 - (eff_skill(u, SK_SPY, r) * 0.05) + + (eff_skill(target, SK_PERCEPTION, r) * 0.02); + + if (chance(observechance)) { + ADDMSG(&target->faction->msgs, msg_message("spydetect", + "spy target", observe > 0 ? u : NULL, target)); + } + return 0; +} + +void set_factionstealth(unit * u, faction * f) +{ + region *lastr = NULL; + /* for all units mu of our faction, check all the units in the region + * they are in, if their visible faction is f, it's ok. use lastr to + * avoid testing the same region twice in a row. */ + unit *mu = u->faction->units; + while (mu != NULL) { + if (mu->number && mu->region != lastr) { + unit *ru = mu->region->units; + lastr = mu->region; + while (ru != NULL) { + if (ru->number) { + faction *fv = visible_faction(f, ru); + if (fv == f) { + if (cansee(f, lastr, ru, 0)) + break; + } + } + ru = ru->next; + } + if (ru != NULL) + break; + } + mu = mu->nextF; + } + if (mu != NULL) { + attrib *a = a_find(u->attribs, &at_otherfaction); + if (!a) + a = a_add(&u->attribs, make_otherfaction(f)); + else + a->data.v = f; + } +} + +int setstealth_cmd(unit * u, struct order *ord) +{ + const char *s; + int level, rule; + const race *trace; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + /* Tarne ohne Parameter: Setzt maximale Tarnung */ + + if (s == NULL || *s == 0) { + u_seteffstealth(u, -1); + return 0; + } + + if (isdigit(s[0])) { + /* Tarnungslevel setzen */ + level = atoi((const char *)s); + if (level > effskill(u, SK_STEALTH)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_lowstealth", "")); + return 0; + } + u_seteffstealth(u, level); + return 0; + } + + trace = findrace(s, u->faction->locale); + if (trace) { + /* Dämonen können sich nur als andere Spielerrassen tarnen */ + if (u_race(u) == new_race[RC_DAEMON]) { + race_t allowed[] = { RC_DWARF, RC_ELF, RC_ORC, RC_GOBLIN, RC_HUMAN, + RC_TROLL, RC_DAEMON, RC_INSECT, RC_HALFLING, RC_CAT, RC_AQUARIAN, + NORACE + }; + int i; + for (i = 0; allowed[i] != NORACE; ++i) + if (new_race[allowed[i]] == trace) + break; + if (new_race[allowed[i]] == trace) { + u->irace = trace; + if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs)) + set_racename(&u->attribs, NULL); + } + return 0; + } + + /* Singdrachen können sich nur als Drachen tarnen */ + if (u_race(u) == new_race[RC_SONGDRAGON] + || u_race(u) == new_race[RC_BIRTHDAYDRAGON]) { + if (trace == new_race[RC_SONGDRAGON] || trace == new_race[RC_FIREDRAGON] + || trace == new_race[RC_DRAGON] || trace == new_race[RC_WYRM]) { + u->irace = trace; + if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs)) + set_racename(&u->attribs, NULL); + } + return 0; + } + + /* Dämomen und Illusionsparteien können sich als andere race tarnen */ + if (u_race(u)->flags & RCF_SHAPESHIFT) { + if (playerrace(trace)) { + u->irace = trace; + if ((u_race(u)->flags & RCF_SHAPESHIFTANY) && get_racename(u->attribs)) + set_racename(&u->attribs, NULL); + } + } + return 0; + } + + switch (findparam(s, u->faction->locale)) { + case P_FACTION: + /* TARNE PARTEI [NICHT|NUMMER abcd] */ + rule = rule_stealth_faction(); + if (!rule) { + /* TARNE PARTEI is disabled */ + break; + } + s = getstrtoken(); + if (rule&1) { + if (!s || *s == 0) { + fset(u, UFL_ANON_FACTION); + break; + } else if (findparam(s, u->faction->locale) == P_NOT) { + freset(u, UFL_ANON_FACTION); + break; + } + } + if (rule&2) { + if (findkeyword(s, u->faction->locale) == K_NUMBER) { + const char *s2 = (const char *)getstrtoken(); + int nr = -1; + + if (s2) { + nr = atoi36(s2); + } + if (!s2 || *s2 == 0 || nr == u->faction->no) { + a_removeall(&u->attribs, &at_otherfaction); + break; + } else { + struct faction *f = findfaction(nr); + if (f == NULL) { + cmistake(u, ord, 66, MSG_EVENT); + break; + } else { + set_factionstealth(u, f); + break; + } + } + } + } + cmistake(u, ord, 289, MSG_EVENT); + break; + case P_ANY: + case P_NOT: + /* TARNE ALLES (was nicht so alles geht?) */ + u_seteffstealth(u, -1); + break; + default: + if (u_race(u)->flags & RCF_SHAPESHIFTANY) { + set_racename(&u->attribs, s); + } else { + cmistake(u, ord, 289, MSG_EVENT); + } + } + return 0; +} + +static int crew_skill(region * r, faction * f, ship * sh, skill_t sk) +{ + int value = 0; + unit *u; + + for (u = r->units; u; u = u->next) { + if (u->ship == sh && u->faction == f) { + int s = eff_skill(u, sk, r); + value = MAX(s, value); + } + } + return value; +} + +static int try_destruction(unit * u, unit * u2, const ship * sh, int skilldiff) +{ + const char *destruction_success_msg = "destroy_ship_0"; + const char *destruction_failed_msg = "destroy_ship_1"; + const char *destruction_detected_msg = "destroy_ship_2"; + const char *detect_failure_msg = "destroy_ship_3"; + const char *object_destroyed_msg = "destroy_ship_4"; + + if (skilldiff == 0) { + /* tell the unit that the attempt failed: */ + ADDMSG(&u->faction->msgs, msg_message(destruction_failed_msg, "ship unit", + sh, u)); + /* tell the enemy about the attempt: */ + if (u2) { + ADDMSG(&u2->faction->msgs, msg_message(detect_failure_msg, "ship", sh)); + } + return 0; + } else if (skilldiff < 0) { + /* tell the unit that the attempt was detected: */ + ADDMSG(&u2->faction->msgs, msg_message(destruction_detected_msg, + "ship unit", sh, u)); + /* tell the enemy whodunit: */ + if (u2) { + ADDMSG(&u2->faction->msgs, msg_message(detect_failure_msg, "ship", sh)); + } + return 0; + } else { + /* tell the unit that the attempt succeeded */ + ADDMSG(&u->faction->msgs, msg_message(destruction_success_msg, "ship unit", + sh, u)); + if (u2) { + ADDMSG(&u2->faction->msgs, msg_message(object_destroyed_msg, "ship", sh)); + } + } + return 1; /* success */ +} + +static void sink_ship(region * r, ship * sh, const char *name, unit * saboteur) +{ + unit **ui, *u; + region *safety = r; + int i; + direction_t d; + double probability = 0.0; + message *sink_msg = NULL; + faction *f; + + for (f = NULL, u = r->units; u; u = u->next) { + /* slight optimization to avoid dereferencing u->faction each time */ + if (f != u->faction) { + f = u->faction; + freset(f, FFL_SELECT); + } + } + + /* figure out what a unit's chances of survival are: */ + if (!fval(r->terrain, SEA_REGION)) { + probability = CANAL_SWIMMER_CHANCE; + } else { + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (!fval(rn->terrain, SEA_REGION) && !move_blocked(NULL, r, rn)) { + safety = rn; + probability = OCEAN_SWIMMER_CHANCE; + break; + } + } + } + for (ui = &r->units; *ui; ui = &(*ui)->next) { + unit *u = *ui; + + /* inform this faction about the sinking ship: */ + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (sink_msg == NULL) { + sink_msg = msg_message("sink_msg", "ship region", sh, r); + } + add_message(&f->msgs, sink_msg); + } + + if (u->ship == sh) { + int dead = 0; + message *msg; + + /* if this fails, I misunderstood something: */ + for (i = 0; i != u->number; ++i) + if (chance(probability)) + ++dead; + + if (dead != u->number) { + /* she will live. but her items get stripped */ + if (dead > 0) { + msg = + msg_message("sink_lost_msg", "dead region unit", dead, safety, u); + } else { + msg = msg_message("sink_saved_msg", "region unit", safety, u); + } + leave_ship(u); + if (r != safety) { + setguard(u, GUARD_NONE); + } + while (u->items) { + i_remove(&u->items, u->items); + } + move_unit(u, safety, NULL); + } else { + msg = msg_message("sink_lost_msg", "dead region unit", dead, NULL, u); + } + add_message(&u->faction->msgs, msg); + msg_release(msg); + if (dead == u->number) { + /* the poor creature, she dies */ + if (remove_unit(ui, u) != 0) { + ui = &u->next; + } + } + } + } + if (sink_msg) + msg_release(sink_msg); + /* finally, get rid of the ship */ + remove_ship(&sh->region->ships, sh); +} + +int sabotage_cmd(unit * u, struct order *ord) +{ + const char *s; + int i; + ship *sh; + unit *u2; + char buffer[DISPLAYSIZE]; + region *r = u->region; + int skdiff; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + i = findparam(s, u->faction->locale); + + switch (i) { + case P_SHIP: + sh = u->ship; + if (!sh) { + cmistake(u, u->thisorder, 144, MSG_EVENT); + return 0; + } + u2 = ship_owner(sh); + skdiff = + eff_skill(u, SK_SPY, r) - crew_skill(r, u2->faction, sh, SK_PERCEPTION); + if (try_destruction(u, u2, sh, skdiff)) { + sink_ship(r, sh, buffer, u); + } + break; + default: + cmistake(u, u->thisorder, 9, MSG_EVENT); + return 0; + } + + return 0; +} diff --git a/core/src/gamecode/spy.h b/core/src/gamecode/spy.h new file mode 100644 index 000000000..7f6eded75 --- /dev/null +++ b/core/src/gamecode/spy.h @@ -0,0 +1,41 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_SPY +#define H_KRNL_SPY +#ifdef __cplusplus +extern "C" { +#endif + + struct unit; + struct region; + struct strlist; + + extern int setstealth_cmd(struct unit *u, struct order *ord); + extern int spy_cmd(struct unit *u, struct order *ord); + extern int sabotage_cmd(struct unit *u, struct order *ord); + extern void spy_message(int spy, const struct unit *u, + const struct unit *target); + +#define OCEAN_SWIMMER_CHANCE 0.1 +#define CANAL_SWIMMER_CHANCE 0.9 + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/study.c b/core/src/gamecode/study.c new file mode 100644 index 000000000..4c20dd090 --- /dev/null +++ b/core/src/gamecode/study.c @@ -0,0 +1,810 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#define TEACH_ALL 1 +#define TEACH_FRIENDS + +#include +#include +#include "study.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +#define TEACHNUMBER 10 + +static skill_t getskill(const struct locale *lang) +{ + return findskill(getstrtoken(), lang); +} + +magic_t getmagicskill(const struct locale * lang) +{ + void **tokens = get_translations(lang, UT_MAGIC); + variant token; + const char *s = getstrtoken(); + + if (tokens && s && s[0]) { + if (findtoken(*tokens, s, &token) == E_TOK_SUCCESS) { + return (magic_t) token.i; + } else { + char buffer[3]; + buffer[0] = s[0]; + buffer[1] = s[1]; + buffer[2] = '\0'; + if (findtoken(*tokens, buffer, &token) == E_TOK_SUCCESS) { + return (magic_t) token.i; + } + } + } + return M_NONE; +} + +/* ------------------------------------------------------------- */ +/* Vertraute und Kröten sind keine Migranten */ +bool is_migrant(unit * u) +{ + if (u_race(u) == u->faction->race) + return false; + + if (fval(u_race(u), RCF_UNDEAD | RCF_ILLUSIONARY)) + return false; + if (is_familiar(u)) + return false; + if (u_race(u) == new_race[RC_TOAD]) + return false; + + return true; +} + +/* ------------------------------------------------------------- */ +bool magic_lowskill(unit * u) +{ + return (u_race(u) == new_race[RC_TOAD]) ? true : false; +} + +/* ------------------------------------------------------------- */ + +int study_cost(unit * u, skill_t sk) +{ + static int cost[MAXSKILLS]; + int stufe, k = 50; + + if (cost[sk] == 0) { + char buffer[256]; + sprintf(buffer, "skills.cost.%s", skillnames[sk]); + cost[sk] = get_param_int(global.parameters, buffer, -1); + } + if (cost[sk] >= 0) { + return cost[sk]; + } + switch (sk) { + case SK_SPY: + return 100; + case SK_TACTICS: + case SK_HERBALISM: + case SK_ALCHEMY: + return 200; + case SK_MAGIC: /* Die Magiekosten betragen 50+Summe(50*Stufe) */ + /* 'Stufe' ist dabei die nächste zu erreichende Stufe */ + stufe = 1 + get_level(u, SK_MAGIC); + return k * (1 + ((stufe + 1) * stufe / 2)); + default: + return 0; + } +} + +/* ------------------------------------------------------------- */ + +static void init_learning(struct attrib *a) +{ + a->data.v = calloc(sizeof(teaching_info), 1); +} + +static void done_learning(struct attrib *a) +{ + free(a->data.v); +} + +const attrib_type at_learning = { + "learning", + init_learning, done_learning, NULL, NULL, NULL, + ATF_UNIQUE +}; + +static int study_days(unit * student, skill_t sk) +{ + int speed = 30; + if (u_race(student)->study_speed) { + speed += u_race(student)->study_speed[sk]; + if (speed < 30) { + skill *sv = get_skill(student, sk); + if (sv == 0) { + speed = 30; + } + } + } + return student->number * speed; +} + +static int +teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk, + bool report, int *academy) +{ + teaching_info *teach = NULL; + attrib *a; + int n; + + /* learning sind die Tage, die sie schon durch andere Lehrer zugute + * geschrieben bekommen haben. Total darf dies nicht über 30 Tage pro Mann + * steigen. + * + * n ist die Anzahl zusätzlich gelernter Tage. n darf max. die Differenz + * von schon gelernten Tagen zum MAX(30 Tage pro Mann) betragen. */ + + if (magic_lowskill(student)) { + cmistake(teacher, teacher->thisorder, 292, MSG_EVENT); + return 0; + } + + n = 30 * student->number; + a = a_find(student->attribs, &at_learning); + if (a != NULL) { + teach = (teaching_info *) a->data.v; + n -= teach->value; + } + + n = MIN(n, nteaching); + + if (n != 0) { + struct building *b = inside_building(teacher); + const struct building_type *btype = b ? b->type : NULL; + int index = 0; + + if (teach == NULL) { + a = a_add(&student->attribs, a_new(&at_learning)); + teach = (teaching_info *) a->data.v; + } else { + while (teach->teachers[index] && index != MAXTEACHERS) + ++index; + } + if (index < MAXTEACHERS) + teach->teachers[index++] = teacher; + if (index < MAXTEACHERS) + teach->teachers[index] = NULL; + teach->value += n; + + /* Solange Akademien größenbeschränkt sind, sollte Lehrer und + * Student auch in unterschiedlichen Gebäuden stehen dürfen */ + if (btype == bt_find("academy") + && student->building && student->building->type == bt_find("academy")) { + int j = study_cost(student, sk); + j = MAX(50, j * 2); + /* kann Einheit das zahlen? */ + if (get_pooled(student, oldresourcetype[R_SILVER], GET_DEFAULT, j) >= j) { + /* Jeder Schüler zusätzlich +10 Tage wenn in Uni. */ + teach->value += (n / 30) * 10; /* learning erhöhen */ + /* Lehrer zusätzlich +1 Tag pro Schüler. */ + if (academy) + *academy += n; + } /* sonst nehmen sie nicht am Unterricht teil */ + } + + /* Teaching ist die Anzahl Leute, denen man noch was beibringen kann. Da + * hier nicht n verwendet wird, werden die Leute gezählt und nicht die + * effektiv gelernten Tage. -> FALSCH ? (ENNO) + * + * Eine Einheit A von 11 Mann mit Talent 0 profitiert vom ersten Lehrer B + * also 10x30=300 tage, und der zweite Lehrer C lehrt für nur noch 1x30=30 + * Tage (damit das Maximum von 11x30=330 nicht überschritten wird). + * + * Damit es aber in der Ausführung nicht auf die Reihenfolge drauf ankommt, + * darf der zweite Lehrer C keine weiteren Einheiten D mehr lehren. Also + * wird student 30 Tage gutgeschrieben, aber teaching sinkt auf 0 (300-11x30 <= + * 0). + * + * Sonst träte dies auf: + * + * A: lernt B: lehrt A C: lehrt A D D: lernt + * + * Wenn B vor C dran ist, lehrt C nur 30 Tage an A (wie oben) und + * 270 Tage an D. + * + * Ist C aber vor B dran, lehrt C 300 tage an A, und 0 tage an D, + * und B lehrt auch 0 tage an A. + * + * Deswegen darf C D nie lehren dürfen. + * + * -> Das ist wirr. wer hat das entworfen? + * Besser wäre, man macht erst vorab alle zuordnungen, und dann + * die Talentänderung (enno). + */ + + nteaching = MAX(0, nteaching - student->number * 30); + + } + return n; +} + +int teach_cmd(unit * u, struct order *ord) +{ + static const curse_type *gbdream_ct = NULL; + plane *pl; + region *r = u->region; + int teaching, i, j, count, academy = 0; + skill_t sk = NOSKILL; + + if (gbdream_ct == 0) + gbdream_ct = ct_find("gbdream"); + if (gbdream_ct) { + if (get_curse(u->region->attribs, gbdream_ct)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "gbdream_noteach", "")); + return 0; + } + } + + if ((u_race(u)->flags & RCF_NOTEACH) || fval(u, UFL_WERE)) { + cmistake(u, ord, 274, MSG_EVENT); + return 0; + } + + pl = rplane(r); + if (pl && fval(pl, PFL_NOTEACH)) { + cmistake(u, ord, 273, MSG_EVENT); + return 0; + } + + teaching = u->number * 30 * TEACHNUMBER; + + if ((i = get_effect(u, oldpotiontype[P_FOOL])) > 0) { /* Trank "Dumpfbackenbrot" */ + i = MIN(i, u->number * TEACHNUMBER); + /* Trank wirkt pro Schüler, nicht pro Lehrer */ + teaching -= i * 30; + change_effect(u, oldpotiontype[P_FOOL], -i); + j = teaching / 30; + ADDMSG(&u->faction->msgs, msg_message("teachdumb", "teacher amount", u, j)); + } + if (teaching == 0) + return 0; + + count = 0; + + init_tokens(ord); + skip_token(); + +#if TEACH_ALL + if (getparam(u->faction->locale) == P_ANY) { + unit *student = r->units; + skill_t teachskill[MAXSKILLS]; + int i = 0; + do { + sk = getskill(u->faction->locale); + teachskill[i++] = sk; + } while (sk != NOSKILL); + while (teaching && student) { + if (student->faction == u->faction) { +#ifdef NEW_DAEMONHUNGER_RULE + if (LongHunger(student)) + continue; +#else + if (fval(student, UFL_HUNGER)) + continue; +#endif + if (get_keyword(student->thisorder) == K_STUDY) { + /* Input ist nun von student->thisorder !! */ + init_tokens(student->thisorder); + skip_token(); + sk = getskill(student->faction->locale); + if (sk != NOSKILL && teachskill[0] != NOSKILL) { + for (i = 0; teachskill[i] != NOSKILL; ++i) + if (sk == teachskill[i]) + break; + sk = teachskill[i]; + } + if (sk != NOSKILL + && eff_skill_study(u, sk, + r) - TEACHDIFFERENCE > eff_skill_study(student, sk, r)) { + teaching -= teach_unit(u, student, teaching, sk, true, &academy); + } + } + } + student = student->next; + } +#ifdef TEACH_FRIENDS + while (teaching && student) { + if (student->faction != u->faction + && alliedunit(u, student->faction, HELP_GUARD)) { +#ifdef NEW_DAEMONHUNGER_RULE + if (LongHunger(student)) + continue; +#else + if (fval(student, UFL_HUNGER)) + continue; +#endif + if (get_keyword(student->thisorder) == K_STUDY) { + /* Input ist nun von student->thisorder !! */ + init_tokens(student->thisorder); + skip_token(); + sk = getskill(student->faction->locale); + if (sk != NOSKILL + && eff_skill_study(u, sk, r) - TEACHDIFFERENCE >= eff_skill(student, + sk, r)) { + teaching -= teach_unit(u, student, teaching, sk, true, &academy); + } + } + } + student = student->next; + } +#endif + } else +#endif + { + char zOrder[4096]; + order *new_order; + + zOrder[0] = '\0'; + init_tokens(ord); + skip_token(); + + while (!parser_end()) { + unit *u2 = getunit(r, u->faction); + bool feedback; + ++count; + + /* Falls die Unit nicht gefunden wird, Fehler melden */ + + if (!u2) { + char tbuf[20]; + const char *uid; + const char *token; + /* Finde den string, der den Fehler verursacht hat */ + parser_pushstate(); + init_tokens(ord); + skip_token(); + + for (j = 0; j != count - 1; ++j) { + /* skip over the first 'count' units */ + getunit(r, u->faction); + } + + token = getstrtoken(); + + /* Beginne die Fehlermeldung */ + if (isparam(token, u->faction->locale, P_TEMP)) { + token = getstrtoken(); + sprintf(tbuf, "%s %s", LOC(u->faction->locale, + parameters[P_TEMP]), token); + uid = tbuf; + } else { + uid = token; + } + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "unitnotfound_id", + "id", uid)); + + parser_popstate(); + continue; + } + + feedback = u->faction == u2->faction + || alliedunit(u2, u->faction, HELP_GUARD); + + /* Neuen Befehl zusammenbauen. TEMP-Einheiten werden automatisch in + * ihre neuen Nummern übersetzt. */ + if (zOrder[0]) + strcat(zOrder, " "); + strcat(zOrder, unitid(u2)); + + if (get_keyword(u2->thisorder) != K_STUDY) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "teach_nolearn", "student", u2)); + continue; + } + + /* Input ist nun von u2->thisorder !! */ + parser_pushstate(); + init_tokens(u2->thisorder); + skip_token(); + sk = getskill(u2->faction->locale); + parser_popstate(); + + if (sk == NOSKILL) { + ADDMSG(&u->faction->msgs, + msg_feedback(u, ord, "teach_nolearn", "student", u2)); + continue; + } + + /* u is teacher, u2 is student */ + if (eff_skill_study(u2, sk, r) > eff_skill_study(u, sk, + r) - TEACHDIFFERENCE) { + if (feedback) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "teach_asgood", + "student", u2)); + } + continue; + } + if (sk == SK_MAGIC) { + /* ist der Magier schon spezialisiert, so versteht er nur noch + * Lehrer seines Gebietes */ + sc_mage *mage1 = get_mage(u); + sc_mage *mage2 = get_mage(u2); + if (!mage2 || !mage1 || (mage2->magietyp != M_GRAY + && mage1->magietyp != mage2->magietyp)) { + if (feedback) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "error_different_magic", "target", u2)); + } + continue; + } + } + + teaching -= teach_unit(u, u2, teaching, sk, false, &academy); + } + new_order = create_order(K_TEACH, u->faction->locale, "%s", zOrder); + replace_order(&u->orders, ord, new_order); + free_order(new_order); /* parse_order & set_order have each increased the refcount */ + } + if (academy && sk != NOSKILL) { + academy = academy / 30; /* anzahl gelehrter wochen, max. 10 */ + learn_skill(u, sk, academy / 30.0 / TEACHNUMBER); + } + return 0; +} + +static double study_speedup(unit * u) +{ +#define MINTURN 5 /* 5 */ +#define OFSTURN 2 /* 2 */ + if (turn > MINTURN) { + static int speed_rule = -1; + if (speed_rule < 0) { + speed_rule = get_param_int(global.parameters, "study.speedup", 0); + } + if (speed_rule == 1) { + double learn_age = OFSTURN; + int i; + for (i = 0; i != u->skill_size; ++i) { + skill *sv = u->skills + i; + double learn_time = sv->level * (sv->level + 1) / 2.0; + learn_age += learn_time; + } + if (learn_age < turn) { + return 2.0 - learn_age / turn; + } + } + } + return 1.0; +} + +int learn_cmd(unit * u, order * ord) +{ + region *r = u->region; + int p; + magic_t mtyp; + int l; + int studycost, days; + double multi = 1.0; + attrib *a = NULL; + teaching_info *teach = NULL; + int money = 0; + skill_t sk; + int maxalchemy = 0; + static int learn_newskills = -1; + if (learn_newskills < 0) { + const char *str = get_param(global.parameters, "study.newskills"); + if (str && strcmp(str, "false") == 0) + learn_newskills = 0; + else + learn_newskills = 1; + } + + if ((u_race(u)->flags & RCF_NOLEARN) || fval(u, UFL_WERE)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_race_nolearn", "race", + u_race(u))); + return 0; + } + + init_tokens(ord); + skip_token(); + sk = getskill(u->faction->locale); + + if (sk < 0) { + cmistake(u, ord, 77, MSG_EVENT); + return 0; + } + if (SkillCap(sk) && SkillCap(sk) <= effskill(u, sk)) { + cmistake(u, ord, 771, MSG_EVENT); + return 0; + } + /* Hack: Talente mit Malus -99 können nicht gelernt werden */ + if (u_race(u)->bonus[sk] == -99) { + cmistake(u, ord, 771, MSG_EVENT); + return 0; + } + if (learn_newskills == 0) { + skill *sv = get_skill(u, sk); + if (sv == NULL) { + /* we can only learn skills we already have */ + cmistake(u, ord, 771, MSG_EVENT); + return 0; + } + } + + /* snotlings können Talente nur bis T8 lernen */ + if (u_race(u) == new_race[RC_SNOTLING]) { + if (get_level(u, sk) >= 8) { + cmistake(u, ord, 308, MSG_EVENT); + return 0; + } + } + + p = studycost = study_cost(u, sk); + a = a_find(u->attribs, &at_learning); + if (a != NULL) { + teach = (teaching_info *) a->data.v; + } + + /* keine kostenpflichtigen Talente für Migranten. Vertraute sind + * keine Migranten, wird in is_migrant abgefangen. Vorsicht, + * studycost darf hier noch nicht durch Akademie erhöht sein */ + if (studycost > 0 && !ExpensiveMigrants() && is_migrant(u)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_migrants_nolearn", + "")); + return 0; + } + /* Akademie: */ + { + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + + if (btype && btype == bt_find("academy")) { + studycost = MAX(50, studycost * 2); + } + } + + if (sk == SK_MAGIC) { + if (u->number > 1) { + cmistake(u, ord, 106, MSG_MAGIC); + return 0; + } + if (is_familiar(u)) { + /* Vertraute zählen nicht zu den Magiern einer Partei, + * können aber nur Graue Magie lernen */ + mtyp = M_GRAY; + if (!is_mage(u)) + create_mage(u, mtyp); + } else if (!has_skill(u, SK_MAGIC)) { + int mmax = skill_limit(u->faction, SK_MAGIC); + /* Die Einheit ist noch kein Magier */ + if (count_skill(u->faction, SK_MAGIC) + u->number > mmax) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_max_magicians", + "amount", mmax)); + return 0; + } + mtyp = getmagicskill(u->faction->locale); + if (mtyp == M_NONE || mtyp == M_GRAY) { + /* wurde kein Magiegebiet angegeben, wird davon + * ausgegangen, daß das normal gelernt werden soll */ + if (u->faction->magiegebiet != 0) { + mtyp = u->faction->magiegebiet; + } else { + /* Es wurde kein Magiegebiet angegeben und die Partei + * hat noch keins gewählt. */ + mtyp = getmagicskill(u->faction->locale); + if (mtyp == M_NONE) { + cmistake(u, ord, 178, MSG_MAGIC); + return 0; + } + } + } + if (mtyp != u->faction->magiegebiet) { + /* Es wurde versucht, ein anderes Magiegebiet zu lernen + * als das der Partei */ + if (u->faction->magiegebiet != 0) { + cmistake(u, ord, 179, MSG_MAGIC); + return 0; + } else { + /* Lernt zum ersten mal Magie und legt damit das + * Magiegebiet der Partei fest */ + u->faction->magiegebiet = mtyp; + } + } + if (!is_mage(u)) + create_mage(u, mtyp); + } else { + /* ist schon ein Magier und kein Vertrauter */ + if (u->faction->magiegebiet == 0) { + /* die Partei hat noch kein Magiegebiet gewählt. */ + mtyp = getmagicskill(u->faction->locale); + if (mtyp == M_NONE) { + mtyp = getmagicskill(u->faction->locale); + if (mtyp == M_NONE) { + cmistake(u, ord, 178, MSG_MAGIC); + return 0; + } + } + /* Legt damit das Magiegebiet der Partei fest */ + u->faction->magiegebiet = mtyp; + } + } + } + if (sk == SK_ALCHEMY) { + maxalchemy = eff_skill(u, SK_ALCHEMY, r); + if (!has_skill(u, SK_ALCHEMY)) { + int amax = skill_limit(u->faction, SK_ALCHEMY); + if (count_skill(u->faction, SK_ALCHEMY) + u->number > amax) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_max_alchemists", + "amount", amax)); + return 0; + } + } + } + if (studycost) { + int cost = studycost * u->number; + money = get_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, cost); + money = MIN(money, cost); + } + if (money < studycost * u->number) { + studycost = p; /* Ohne Univertreurung */ + money = MIN(money, studycost); + if (p > 0 && money < studycost * u->number) { + cmistake(u, ord, 65, MSG_EVENT); + multi = money / (double)(studycost * u->number); + } + } + + if (teach == NULL) { + a = a_add(&u->attribs, a_new(&at_learning)); + teach = (teaching_info *) a->data.v; + teach->teachers[0] = 0; + } + if (money > 0) { + use_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, money); + ADDMSG(&u->faction->msgs, msg_message("studycost", + "unit region cost skill", u, u->region, money, sk)); + } + + if (get_effect(u, oldpotiontype[P_WISE])) { + l = MIN(u->number, get_effect(u, oldpotiontype[P_WISE])); + teach->value += l * 10; + change_effect(u, oldpotiontype[P_WISE], -l); + } + if (get_effect(u, oldpotiontype[P_FOOL])) { + l = MIN(u->number, get_effect(u, oldpotiontype[P_FOOL])); + teach->value -= l * 30; + change_effect(u, oldpotiontype[P_FOOL], -l); + } + + if (p != studycost) { + /* ist_in_gebaeude(r, u, BT_UNIVERSITAET) == 1) { */ + /* p ist Kosten ohne Uni, studycost mit; wenn + * p!=studycost, ist die Einheit zwangsweise + * in einer Uni */ + teach->value += u->number * 10; + } + + if (is_cursed(r->attribs, C_BADLEARN, 0)) { + teach->value -= u->number * 10; + } + + multi *= study_speedup(u); + days = study_days(u, sk); + days = (int)((days + teach->value) * multi); + + /* the artacademy currently improves the learning of entertainment + of all units in the region, to be able to make it cumulative with + with an academy */ + + if (sk == SK_ENTERTAINMENT + && buildingtype_exists(r, bt_find("artacademy"), false)) { + days *= 2; + } + + if (fval(u, UFL_HUNGER)) + days /= 2; + + while (days) { + if (days >= u->number * 30) { + learn_skill(u, sk, 1.0); + days -= u->number * 30; + } else { + double chance = (double)days / u->number / 30; + learn_skill(u, sk, chance); + days = 0; + } + } + if (a != NULL) { + if (teach != NULL) { + int index = 0; + while (teach->teachers[index] && index != MAXTEACHERS) { + unit *teacher = teach->teachers[index++]; + if (teacher->faction != u->faction) { + bool feedback = alliedunit(u, teacher->faction, HELP_GUARD); + if (feedback) { + ADDMSG(&teacher->faction->msgs, msg_message("teach_teacher", + "teacher student skill level", teacher, u, sk, + effskill(u, sk))); + } + ADDMSG(&u->faction->msgs, msg_message("teach_student", + "teacher student skill", teacher, u, sk)); + } + } + } + a_remove(&u->attribs, a); + a = NULL; + } + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + + /* Anzeigen neuer Tränke */ + /* Spruchlistenaktualiesierung ist in Regeneration */ + + if (sk == SK_ALCHEMY) { + const potion_type *ptype; + faction *f = u->faction; + int skill = eff_skill(u, SK_ALCHEMY, r); + if (skill > maxalchemy) { + for (ptype = potiontypes; ptype; ptype = ptype->next) { + if (skill == ptype->level * 2) { + attrib *a = a_find(f->attribs, &at_showitem); + while (a && a->type == &at_showitem && a->data.v != ptype) + a = a->next; + if (a == NULL || a->type != &at_showitem) { + a = a_add(&f->attribs, a_new(&at_showitem)); + a->data.v = (void *)ptype->itype; + } + } + } + } + } else if (sk == SK_MAGIC) { + sc_mage *mage = get_mage(u); + if (!mage) { + mage = create_mage(u, u->faction->magiegebiet); + } + } + + return 0; +} diff --git a/core/src/gamecode/study.h b/core/src/gamecode/study.h new file mode 100644 index 000000000..182e364ee --- /dev/null +++ b/core/src/gamecode/study.h @@ -0,0 +1,44 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_STUDY +#define H_KRNL_STUDY + +#ifdef __cplusplus +extern "C" { +#endif + + extern int teach_cmd(struct unit *u, struct order *ord); + extern int learn_cmd(struct unit *u, struct order *ord); + + extern magic_t getmagicskill(const struct locale *lang); + extern bool is_migrant(struct unit *u); + extern int study_cost(struct unit *u, skill_t talent); + +#define MAXTEACHERS 4 + typedef struct teaching_info { + struct unit *teachers[MAXTEACHERS]; + int value; + } teaching_info; + + extern const struct attrib_type at_learning; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/summary.c b/core/src/gamecode/summary.c new file mode 100644 index 000000000..ff19477f1 --- /dev/null +++ b/core/src/gamecode/summary.c @@ -0,0 +1,413 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2007 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ + +/* wenn platform.h nicht vor curses included wird, kompiliert es unter windows nicht */ +#include +#include + +#include "summary.h" + +#include "laws.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#undef SUMMARY_BOM /* write a BOM in the summary file */ + +typedef struct summary { + int waffen; + int factions; + int ruestungen; + int schiffe; + int gebaeude; + int maxskill; + int heroes; + int inhabitedregions; + int peasants; + int nunits; + int playerpop; + double playermoney; + double peasantmoney; + int armed_men; + int poprace[MAXRACES]; + int factionrace[MAXRACES]; + int landregionen; + int regionen_mit_spielern; + int landregionen_mit_spielern; + int orkifizierte_regionen; + int inactive_volcanos; + int active_volcanos; + int spielerpferde; + int pferde; + struct language { + struct language *next; + int number; + const struct locale *locale; + } *languages; +} summary; + +static char *pcomp(double i, double j) +{ + static char buf[32]; + sprintf(buf, "%.0f (%s%.0f)", i, (i >= j) ? "+" : "", i - j); + return buf; +} + +static char *rcomp(int i, int j) +{ + static char buf[32]; + sprintf(buf, "%d (%s%d,%s%d%%)", + i, (i >= j) ? "+" : "", i - j, (i >= j) ? "+" : "", + j ? ((i - j) * 100) / j : 0); + return buf; +} + +static void out_faction(FILE * file, const struct faction *f) +{ + if (alliances != NULL) { + fprintf(file, "%s (%s/%d) (%.3s/%.3s), %d Einh., %d Pers., $%d, %d NMR\n", + f->name, itoa36(f->no), f_get_alliance(f) ? f->alliance->id : 0, + LOC(default_locale, rc_name(f->race, 0)), magic_school[f->magiegebiet], + f->no_units, f->num_total, f->money, turn - f->lastorders); + } else { + fprintf(file, "%s (%.3s/%.3s), %d Einh., %d Pers., $%d, %d NMR\n", + factionname(f), LOC(default_locale, rc_name(f->race, 0)), + magic_school[f->magiegebiet], f->no_units, f->num_total, f->money, + turn - f->lastorders); + } +} + +static char *gamedate2(const struct locale *lang) +{ + static char buf[256]; + gamedate gd; + + get_gamedate(turn, &gd); + sprintf(buf, "in %s des Monats %s im Jahre %d %s.", + LOC(lang, weeknames2[gd.week]), + LOC(lang, monthnames[gd.month]), + gd.year, agename ? LOC(lang, agename) : ""); + return buf; +} + +static void writeturn(void) +{ + char zText[MAX_PATH]; + FILE *f; + + sprintf(zText, "%s/datum", basepath()); + f = cfopen(zText, "w"); + if (!f) + return; + fputs(gamedate2(default_locale), f); + fclose(f); + sprintf(zText, "%s/turn", basepath()); + f = cfopen(zText, "w"); + if (!f) + return; + fprintf(f, "%d\n", turn); + fclose(f); +} + +void report_summary(summary * s, summary * o, bool full) +{ + FILE *F = NULL; + int i, newplayers = 0; + faction *f; + char zText[MAX_PATH]; + + if (full) { + sprintf(zText, "%s/parteien.full", basepath()); + } else { + sprintf(zText, "%s/parteien", basepath()); + } + F = cfopen(zText, "w"); + if (!F) + return; +#ifdef SUMMARY_BOM + else { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + fwrite(utf8_bom, 1, 3, F); + } +#endif + printf("Schreibe Zusammenfassung (parteien)...\n"); + fprintf(F, "%s\n%s\n\n", global.gamename, gamedate2(default_locale)); + fprintf(F, "Auswertung Nr: %d\n\n", turn); + fprintf(F, "Parteien: %s\n", pcomp(s->factions, o->factions)); + fprintf(F, "Einheiten: %s\n", pcomp(s->nunits, o->nunits)); + fprintf(F, "Spielerpopulation: %s\n", pcomp(s->playerpop, o->playerpop)); + fprintf(F, " davon bewaffnet: %s\n", pcomp(s->armed_men, o->armed_men)); + fprintf(F, " Helden: %s\n", pcomp(s->heroes, o->heroes)); + + if (full) { + fprintf(F, "Regionen: %d\n", listlen(regions)); + fprintf(F, "Bewohnte Regionen: %d\n", s->inhabitedregions); + fprintf(F, "Landregionen: %d\n", s->landregionen); + fprintf(F, "Spielerregionen: %d\n", s->regionen_mit_spielern); + fprintf(F, "Landspielerregionen: %d\n", s->landregionen_mit_spielern); + fprintf(F, "Orkifizierte Regionen: %d\n", s->orkifizierte_regionen); + fprintf(F, "Inaktive Vulkane: %d\n", s->inactive_volcanos); + fprintf(F, "Aktive Vulkane: %d\n\n", s->active_volcanos); + } + + for (i = 0; i < MAXRACES; i++) { + const race *rc = new_race[i]; + if (s->factionrace[i] && rc && playerrace(rc) + && i != RC_TEMPLATE && i != RC_CLONE) { + fprintf(F, "%13s%s: %s\n", LOC(default_locale, rc_name(rc, 3)), + LOC(default_locale, "stat_tribe_p"), pcomp(s->factionrace[i], + o->factionrace[i])); + } + } + + if (full) { + fprintf(F, "\n"); + { + struct language *plang = s->languages; + while (plang != NULL) { + struct language *olang = o->languages; + int nold = 0; + while (olang && olang->locale != plang->locale) + olang = olang->next; + if (olang) + nold = olang->number; + fprintf(F, "Sprache %12s: %s\n", locale_name(plang->locale), + rcomp(plang->number, nold)); + plang = plang->next; + } + } + } + + fprintf(F, "\n"); + if (full) { + for (i = 0; i < MAXRACES; i++) { + const race *rc = new_race[i]; + if (s->poprace[i]) { + fprintf(F, "%20s: %s\n", LOC(default_locale, rc_name(rc, 1)), + rcomp(s->poprace[i], o->poprace[i])); + } + } + } else { + for (i = 0; i < MAXRACES; i++) { + const race *rc = new_race[i]; + if (s->poprace[i] && playerrace(rc) + && i != RC_TEMPLATE && i != RC_CLONE) { + fprintf(F, "%20s: %s\n", LOC(default_locale, rc_name(rc, 1)), + rcomp(s->poprace[i], o->poprace[i])); + } + } + } + + if (full) { + fprintf(F, "\nWaffen: %s\n", pcomp(s->waffen, o->waffen)); + fprintf(F, "Ruestungen: %s\n", + pcomp(s->ruestungen, o->ruestungen)); + fprintf(F, "ungezaehmte Pferde: %s\n", pcomp(s->pferde, o->pferde)); + fprintf(F, "gezaehmte Pferde: %s\n", + pcomp(s->spielerpferde, o->spielerpferde)); + fprintf(F, "Schiffe: %s\n", pcomp(s->schiffe, o->schiffe)); + fprintf(F, "Gebaeude: %s\n", pcomp(s->gebaeude, o->gebaeude)); + + fprintf(F, "\nBauernpopulation: %s\n", pcomp(s->peasants, o->peasants)); + + fprintf(F, "Population gesamt: %d\n\n", s->playerpop + s->peasants); + + fprintf(F, "Reichtum Spieler: %s Silber\n", + pcomp(s->playermoney, o->playermoney)); + fprintf(F, "Reichtum Bauern: %s Silber\n", + pcomp(s->peasantmoney, o->peasantmoney)); + fprintf(F, "Reichtum gesamt: %s Silber\n\n", + pcomp(s->playermoney + s->peasantmoney, + o->playermoney + o->peasantmoney)); + } + + fprintf(F, "\n\n"); + + newplayers = update_nmrs(); + + for (i = 0; i <= NMRTimeout(); ++i) { + if (i == NMRTimeout()) { + fprintf(F, "+ NMR:\t\t %d\n", nmrs[i]); + } else { + fprintf(F, "%d NMR:\t\t %d\n", i, nmrs[i]); + } + } + if (age) { + if (age[2] != 0) { + fprintf(F, "Erstabgaben:\t %d%%\n", 100 - (dropouts[0] * 100 / age[2])); + } + if (age[3] != 0) { + fprintf(F, "Zweitabgaben:\t %d%%\n", 100 - (dropouts[1] * 100 / age[3])); + } + } + fprintf(F, "Neue Spieler:\t %d\n", newplayers); + + if (full) { + if (factions) + fprintf(F, "\nParteien:\n\n"); + + for (f = factions; f; f = f->next) { + out_faction(F, f); + } + + if (NMRTimeout() && full) { + fprintf(F, "\n\nFactions with NMRs:\n"); + for (i = NMRTimeout(); i > 0; --i) { + for (f = factions; f; f = f->next) { + if (i == NMRTimeout()) { + if (turn - f->lastorders >= i) { + out_faction(F, f); + } + } else { + if (turn - f->lastorders == i) { + out_faction(F, f); + } + } + } + } + } + } + + fclose(F); + + if (full) { + printf("writing date & turn\n"); + writeturn(); + } + free(nmrs); + nmrs = NULL; +} + +summary *make_summary(void) +{ + faction *f; + region *r; + unit *u; + summary *s = calloc(1, sizeof(summary)); + + for (f = factions; f; f = f->next) { + const struct locale *lang = f->locale; + struct language *plang = s->languages; + while (plang && plang->locale != lang) + plang = plang->next; + if (!plang) { + plang = calloc(sizeof(struct language), 1); + plang->next = s->languages; + s->languages = plang; + plang->locale = lang; + } + ++plang->number; + f->nregions = 0; + f->num_total = 0; + f->money = 0; + if (f->alive && f->units) { + s->factions++; + /* Problem mit Monsterpartei ... */ + if (!is_monsters(f)) { + s->factionrace[old_race(f->race)]++; + } + } + } + + /* Alles zählen */ + + for (r = regions; r; r = r->next) { + s->pferde += rhorses(r); + s->schiffe += listlen(r->ships); + s->gebaeude += listlen(r->buildings); + if (!fval(r->terrain, SEA_REGION)) { + s->landregionen++; + if (r->units) { + s->landregionen_mit_spielern++; + } + if (fval(r, RF_ORCIFIED)) { + s->orkifizierte_regionen++; + } + if (r->terrain == newterrain(T_VOLCANO)) { + s->inactive_volcanos++; + } else if (r->terrain == newterrain(T_VOLCANO_SMOKING)) { + s->active_volcanos++; + } + } + if (r->units) { + s->regionen_mit_spielern++; + } + if (rpeasants(r) || r->units) { + s->inhabitedregions++; + s->peasants += rpeasants(r); + s->peasantmoney += rmoney(r); + + /* Einheiten Info. nregions darf nur einmal pro Partei + * incrementiert werden. */ + + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + f = u->faction; + if (!is_monsters(u->faction)) { + skill *sv; + item *itm; + + s->nunits++; + s->playerpop += u->number; + if (u->flags & UFL_HERO) { + s->heroes += u->number; + } + s->spielerpferde += get_item(u, I_HORSE); + s->playermoney += get_money(u); + s->armed_men += armedmen(u, true); + for (itm = u->items; itm; itm = itm->next) { + if (itm->type->rtype->wtype) { + s->waffen += itm->number; + } + if (itm->type->rtype->atype) { + s->ruestungen += itm->number; + } + } + + s->spielerpferde += get_item(u, I_HORSE); + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + skill_t sk = sv->id; + int aktskill = eff_skill(u, sk, r); + if (aktskill > s->maxskill) + s->maxskill = aktskill; + } + if (!fval(f, FFL_SELECT)) { + f->nregions++; + fset(f, FFL_SELECT); + } + } + + f->num_total += u->number; + f->money += get_money(u); + s->poprace[old_race(u_race(u))] += u->number; + } + } + } + + return s; +} diff --git a/core/src/gamecode/summary.h b/core/src/gamecode/summary.h new file mode 100644 index 000000000..bb2ae04b7 --- /dev/null +++ b/core/src/gamecode/summary.h @@ -0,0 +1,26 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2007 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ + +#ifndef H_GC_SUMMARY +#define H_GC_SUMMARY + +#ifdef __cplusplus +extern "C" { +#endif + + struct summary; + extern void report_summary(struct summary *n, struct summary *o, + bool full); + extern struct summary *make_summary(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gamecode/xmlreport.c b/core/src/gamecode/xmlreport.c new file mode 100644 index 000000000..f4e4f9b6d --- /dev/null +++ b/core/src/gamecode/xmlreport.c @@ -0,0 +1,799 @@ +/* vi: set ts=2: ++-------------------+ Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel ++-------------------+ +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "xmlreport.h" + +#define XML_ATL_NAMESPACE (const xmlChar *) "http://www.eressea.de/XML/2008/atlantis" +#define XML_XML_LANG (const xmlChar *) "lang" + +/* modules include */ +#include + +/* attributes include */ +#include +#include +#include +#include +#include + +/* gamecode includes */ +#include "laws.h" +#include "economy.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* libxml2 includes */ +#include +#include +#ifdef USE_ICONV +#include +#endif + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +#define L10N(x) x + +typedef struct xml_context { + xmlDocPtr doc; + xmlNsPtr ns_atl; + xmlNsPtr ns_xml; +} xml_context; + +static xmlNodePtr +xml_link(report_context * ctx, const xmlChar * rel, const xmlChar * ref) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr node = xmlNewNode(xct->ns_atl, BAD_CAST "link"); + + xmlNewNsProp(node, xct->ns_atl, BAD_CAST "rel", rel); + xmlNewNsProp(node, xct->ns_atl, BAD_CAST "ref", ref); + + return node; +} + +static const xmlChar *xml_ref_unit(const unit * u) +{ + static char idbuf[20]; + snprintf(idbuf, sizeof(idbuf), "unit_%d", u->no); + return (const xmlChar *)idbuf; +} + +static const xmlChar *xml_ref_faction(const faction * f) +{ + static char idbuf[20]; + snprintf(idbuf, sizeof(idbuf), "fctn_%d", f->no); + return (const xmlChar *)idbuf; +} + +static const xmlChar *xml_ref_group(const group * g) +{ + static char idbuf[20]; + snprintf(idbuf, sizeof(idbuf), "grp_%d", g->gid); + return (const xmlChar *)idbuf; +} + +static const xmlChar *xml_ref_prefix(const char *str) +{ + static char idbuf[20]; + snprintf(idbuf, sizeof(idbuf), "pref_%s", str); + return (const xmlChar *)idbuf; +} + +static const xmlChar *xml_ref_building(const building * b) +{ + static char idbuf[20]; + snprintf(idbuf, sizeof(idbuf), "bldg_%d", b->no); + return (const xmlChar *)idbuf; +} + +static const xmlChar *xml_ref_ship(const ship * sh) +{ + static char idbuf[20]; + snprintf(idbuf, sizeof(idbuf), "shp_%d", sh->no); + return (const xmlChar *)idbuf; +} + +static const xmlChar *xml_ref_region(const region * r) +{ + static char idbuf[20]; + snprintf(idbuf, sizeof(idbuf), "rgn_%d", r->uid); + return (const xmlChar *)idbuf; +} + +static xmlNodePtr xml_inventory(report_context * ctx, item * items, unit * u) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr node = xmlNewNode(xct->ns_atl, BAD_CAST "items"); + item *itm; + + for (itm = items; itm; itm = itm->next) { + xmlNodePtr child; + const char *name; + int n; + + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "item")); + report_item(u, itm, ctx->f, NULL, &name, &n, true); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", (xmlChar *) name); + xmlNodeAddContent(child, (xmlChar *) itoab(n, 10)); + } + return node; +} + +#ifdef TODO /*spellbooks */ +static xmlNodePtr +xml_spells(report_context * ctx, quicklist * slist, int maxlevel) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr child, node = xmlNewNode(xct->ns_atl, BAD_CAST "spells"); + quicklist *ql; + int qi; + + for (ql = slist, qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spell *sp = (spell *) ql_get(ql, qi); + + if (sp->level <= maxlevel) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "spell")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "name", BAD_CAST sp->sname); + } + } + return node; +} +#endif + +static xmlNodePtr xml_skills(report_context * ctx, unit * u) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr child, node = xmlNewNode(xct->ns_atl, BAD_CAST "skills"); + skill *sv; + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level > 0) { + skill_t sk = sv->id; + int esk = eff_skill(u, sk, u->region); + + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "skill", BAD_CAST itoab(esk, + 10)); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", BAD_CAST skillnames[sk]); + } + } + + return node; +} + +static xmlNodePtr xml_unit(report_context * ctx, unit * u, int mode) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr node = xmlNewNode(xct->ns_atl, BAD_CAST "unit"); + static const curse_type *itemcloak_ct = 0; + static bool init = false; + xmlNodePtr child; + const char *str, *rcname, *rcillusion; + bool disclosure = (ctx->f == u->faction || omniscient(ctx->f)); + + /* TODO: hitpoints, aura, combatspells, curses */ + + xmlNewNsProp(node, xct->ns_xml, XML_XML_ID, xml_ref_unit(u)); + xmlNewNsProp(node, xct->ns_atl, BAD_CAST "key", BAD_CAST itoa36(u->no)); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "name", (const xmlChar *)u->name); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "number", + (const xmlChar *)itoab(u->number, 10)); + + /* optional description */ + str = u_description(u, ctx->f->locale); + if (str) { + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "text", (const xmlChar *)str); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "public"); + if (str != u->display) { + xmlNewNsProp(child, xct->ns_atl, XML_XML_LANG, + BAD_CAST locale_name(ctx->f->locale)); + } + } + + /* possible info */ + if (is_guard(u, GUARD_ALL) != 0) { + xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "guard")); + } + + /* siege */ + if (fval(u, UFL_SIEGE)) { + building *b = usiege(u); + if (b) { + xmlAddChild(node, xml_link(ctx, BAD_CAST "siege", xml_ref_building(b))); + } + } + + /* TODO: temp/alias */ + + /* race information */ + report_race(u, &rcname, &rcillusion); + if (disclosure) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "race")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "true"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", (const xmlChar *)rcname); + if (rcillusion) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "race")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "stealth"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + (const xmlChar *)rcillusion); + } + } else { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "race")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + (const xmlChar *)(rcillusion ? rcillusion : rcname)); + } + + /* group and prefix information. we only write the prefix if we really must */ + if (fval(u, UFL_GROUP)) { + attrib *a = a_find(u->attribs, &at_group); + if (a != NULL) { + const group *g = (const group *)a->data.v; + if (disclosure) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "group")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", xml_ref_group(g)); + } else { + const char *prefix = get_prefix(g->attribs); + if (prefix) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "prefix")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + xml_ref_prefix(prefix)); + } + } + } + } + + if (disclosure) { + unit *mage; + + str = uprivate(u); + if (str) { + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "text", + (const xmlChar *)str); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "private"); + } + + /* familiar info */ + mage = get_familiar_mage(u); + if (mage) + xmlAddChild(node, xml_link(ctx, BAD_CAST "familiar_of", + xml_ref_unit(mage))); + + /* combat status */ + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "status")); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "combat"); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "value", + BAD_CAST combatstatus[u->status]); + + if (fval(u, UFL_NOAID)) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "status")); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "aid"); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "value", BAD_CAST "false"); + } + + if (fval(u, UFL_STEALTH)) { + int i = u_geteffstealth(u); + if (i >= 0) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "status")); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "stealth"); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "value", BAD_CAST itoab(i, + 10)); + } + } + if (fval(u, UFL_HERO)) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "status")); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "hero"); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "value", BAD_CAST "true"); + } + + if (fval(u, UFL_HUNGER)) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "status")); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "hunger"); + xmlSetNsProp(child, xct->ns_atl, BAD_CAST "value", BAD_CAST "true"); + } + + /* skills */ + if (u->skill_size) { + xmlAddChild(node, xml_skills(ctx, u)); + } + +#ifdef TODO /*spellbooks */ + /* spells */ + if (is_mage(u)) { + sc_mage *mage = get_mage(u); + quicklist *slist = mage->spells; + if (slist) { + xmlAddChild(node, xml_spells(ctx, slist, effskill(u, SK_MAGIC))); + } + } +#endif + } + + /* faction information w/ visibiility */ + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "faction")); + if (disclosure) { + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "true"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + xml_ref_faction(u->faction)); + + if (fval(u, UFL_ANON_FACTION)) { + const faction *sf = visible_faction(NULL, u); + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "faction")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "stealth"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", xml_ref_faction(sf)); + } + } else { + const faction *sf = visible_faction(ctx->f, u); + if (sf == ctx->f) { + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "stealth"); + } + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", xml_ref_faction(sf)); + } + + /* the inventory */ + if (u->items) { + item result[MAX_INVENTORY]; + item *show = NULL; + + if (!init) { + init = true; + itemcloak_ct = ct_find("itemcloak"); + } + + if (disclosure) { + show = u->items; + } else { + bool see_items = (mode >= see_unit); + if (see_items) { + if (itemcloak_ct && curse_active(get_curse(u->attribs, itemcloak_ct))) { + see_items = false; + } else { + see_items = effskill(u, SK_STEALTH) < 3; + } + } + if (see_items) { + int n = report_items(u->items, result, MAX_INVENTORY, u, ctx->f); + assert(n >= 0); + if (n > 0) + show = result; + else + show = NULL; + } else { + show = NULL; + } + } + + if (show) { + xmlAddChild(node, xml_inventory(ctx, show, u)); + } + } + + return node; +} + +static xmlNodePtr xml_resources(report_context * ctx, const seen_region * sr) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr node = NULL; + resource_report result[MAX_RAWMATERIALS]; + int n, size = report_resources(sr, result, MAX_RAWMATERIALS, ctx->f); + + if (size) { + node = xmlNewNode(xct->ns_atl, BAD_CAST "resources"); + for (n = 0; n < size; ++n) { + if (result[n].number >= 0) { + xmlNodePtr child; + + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "resource")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + (xmlChar *) result[n].name); + if (result[n].level >= 0) { + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "level", + (xmlChar *) itoab(result[n].level, 10)); + } + xmlNodeAddContent(child, (xmlChar *) itoab(result[n].number, 10)); + } + } + } + return node; +} + +static xmlNodePtr xml_diplomacy(report_context * ctx, const struct ally *allies) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr child, node = xmlNewNode(xct->ns_atl, BAD_CAST "diplomacy"); + const struct ally *sf; + + for (sf = allies; sf; sf = sf->next) { + int i, status = sf->status; + for (i = 0; helpmodes[i].name; ++i) { + if (sf->faction && (status & helpmodes[i].status) == helpmodes[i].status) { + status -= helpmodes[i].status; + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "status")); + xmlNewNsProp(child, xct->ns_xml, BAD_CAST "faction", + xml_ref_faction(sf->faction)); + xmlNewNsProp(child, xct->ns_xml, BAD_CAST "status", + (xmlChar *) helpmodes[i].name); + } + } + } + return node; +} + +static xmlNodePtr xml_groups(report_context * ctx, const group * groups) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr child, node = xmlNewNode(xct->ns_atl, BAD_CAST "faction"); + const group *g; + + for (g = groups; g; g = g->next) { + const char *prefix = get_prefix(g->attribs); + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "group")); + xmlNewNsProp(child, xct->ns_xml, XML_XML_ID, xml_ref_group(g)); + xmlNewTextChild(child, xct->ns_atl, BAD_CAST "name", + (const xmlChar *)g->name); + + if (g->allies) + xmlAddChild(child, xml_diplomacy(ctx, g->allies)); + + if (prefix) { + child = xmlAddChild(child, xmlNewNode(xct->ns_atl, BAD_CAST "prefix")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", xml_ref_prefix(prefix)); + } + } + + return node; +} + +static xmlNodePtr xml_faction(report_context * ctx, faction * f) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr child, node = xmlNewNode(xct->ns_atl, BAD_CAST "faction"); + + /* TODO: alliance, locale */ + + xmlNewNsProp(node, xct->ns_xml, XML_XML_ID, xml_ref_faction(f)); + xmlNewNsProp(node, xct->ns_atl, BAD_CAST "key", BAD_CAST itoa36(f->no)); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "name", (const xmlChar *)f->name); + if (f->email) + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "email", + (const xmlChar *)f->email); + if (f->banner) { + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "text", + (const xmlChar *)f->banner); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "public"); + } + + if (ctx->f == f) { + xmlAddChild(node, xml_link(ctx, BAD_CAST "race", + BAD_CAST f->race->_name[0])); + + if (f->items) + xmlAddChild(node, xml_inventory(ctx, f->items, NULL)); + if (f->allies) + xmlAddChild(node, xml_diplomacy(ctx, f->allies)); + if (f->groups) + xmlAddChild(node, xml_groups(ctx, f->groups)); + + /* TODO: age, options, score, prefix, magic, immigrants, heroes, nmr, groups */ + } + return node; +} + +static xmlNodePtr +xml_building(report_context * ctx, seen_region * sr, const building * b, + const unit * owner) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr node = xmlNewNode(xct->ns_atl, BAD_CAST "building"); + xmlNodePtr child; + const char *bname, *billusion; + + xmlNewNsProp(node, xct->ns_xml, XML_XML_ID, xml_ref_building(b)); + xmlNewNsProp(node, xct->ns_atl, BAD_CAST "key", BAD_CAST itoa36(b->no)); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "name", (const xmlChar *)b->name); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "size", + (const xmlChar *)itoab(b->size, 10)); + if (b->display && b->display[0]) { + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "text", + (const xmlChar *)b->display); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "public"); + } + if (b->besieged) { + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "siege", + (const xmlChar *)itoab(b->besieged, 10)); + } + if (owner) + xmlAddChild(node, xml_link(ctx, BAD_CAST "owner", xml_ref_unit(owner))); + + report_building(b, &bname, &billusion); + if (owner && owner->faction == ctx->f) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "type")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "true"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", (const xmlChar *)bname); + if (billusion) { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "type")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "illusion"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + (const xmlChar *)billusion); + } + } else { + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "type")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + (const xmlChar *)(billusion ? billusion : bname)); + } + + return node; +} + +static xmlNodePtr +xml_ship(report_context * ctx, const seen_region * sr, const ship * sh, + const unit * owner) +{ + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr child, node = xmlNewNode(xct->ns_atl, BAD_CAST "ship"); + + xmlNewNsProp(node, xct->ns_xml, XML_XML_ID, xml_ref_ship(sh)); + xmlNewNsProp(node, xct->ns_atl, BAD_CAST "key", BAD_CAST itoa36(sh->no)); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "name", + (const xmlChar *)sh->name); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "size", + (const xmlChar *)itoab(sh->size, 10)); + + if (sh->damage) { + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "damage", + (const xmlChar *)itoab(sh->damage, 10)); + } + + if (fval(sr->r->terrain, SEA_REGION) && sh->coast != NODIRECTION) { + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "coast", + BAD_CAST directions[sh->coast]); + } + + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "type")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + (const xmlChar *)sh->type->name[0]); + + if (sh->display && sh->display[0]) { + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "text", + (const xmlChar *)sh->display); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "public"); + } + + if (owner) + xmlAddChild(node, xml_link(ctx, BAD_CAST "owner", xml_ref_unit(owner))); + + if ((owner && owner->faction == ctx->f) || omniscient(ctx->f)) { + int n = 0, p = 0; + getshipweight(sh, &n, &p); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "cargo", + (const xmlChar *)itoab(n, 10)); + } + return node; +} + +static xmlNodePtr xml_region(report_context * ctx, seen_region * sr) +{ + xml_context *xct = (xml_context *) ctx->userdata; + const region *r = sr->r; + xmlNodePtr node = xmlNewNode(xct->ns_atl, BAD_CAST "region"); + xmlNodePtr child; + int stealthmod = stealth_modifier(sr->mode); + unit *u; + ship *sh = r->ships; + building *b = r->buildings; + plane *pl = rplane(r); + int nx = r->x, ny = r->y; + + pnormalize(&nx, &ny, pl); + adjust_coordinates(ctx->f, &nx, &ny, pl, r); + + /* TODO: entertain-quota, recruits, salary, prices, curses, borders, apparitions (Schemen), spells, travelthru, messages */ + xmlNewNsProp(node, xct->ns_xml, XML_XML_ID, xml_ref_region(r)); + + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "coordinate")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "x", xml_i(nx)); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "y", xml_i(ny)); + if (pl && pl->name) { + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "plane", (xmlChar *) pl->name); + } + + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "terrain")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", (xmlChar *) terrain_name(r)); + + if (r->land != NULL) { + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "name", + (const xmlChar *)r->land->name); + if (r->land->items) { + xmlAddChild(node, xml_inventory(ctx, r->land->items, NULL)); + } + } + if (r->display && r->display[0]) { + child = + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "text", + (const xmlChar *)r->display); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "rel", BAD_CAST "public"); + } + child = xml_resources(ctx, sr); + if (child) + xmlAddChild(node, child); + + child = xmlNewNode(xct->ns_atl, BAD_CAST "terrain"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "ref", + (const xmlChar *)terrain_name(r)); + + if (sr->mode > see_neighbour) { + /* report all units. they are pre-sorted in an efficient manner */ + u = r->units; + while (b) { + while (b && (!u || u->building != b)) { + xmlAddChild(node, xml_building(ctx, sr, b, NULL)); + b = b->next; + } + if (b) { + child = xmlAddChild(node, xml_building(ctx, sr, b, u)); + while (u && u->building == b) { + xmlAddChild(child, xml_unit(ctx, u, sr->mode)); + u = u->next; + } + b = b->next; + } + } + while (u && !u->ship) { + if (stealthmod > INT_MIN) { + if (u->faction == ctx->f || cansee(ctx->f, r, u, stealthmod)) { + xmlAddChild(node, xml_unit(ctx, u, sr->mode)); + } + } + u = u->next; + } + while (sh) { + while (sh && (!u || u->ship != sh)) { + xmlAddChild(node, xml_ship(ctx, sr, sh, NULL)); + sh = sh->next; + } + if (sh) { + child = xmlAddChild(node, xml_ship(ctx, sr, sh, u)); + while (u && u->ship == sh) { + xmlAddChild(child, xml_unit(ctx, u, sr->mode)); + u = u->next; + } + sh = sh->next; + } + } + } + return node; +} + +static xmlNodePtr report_root(report_context * ctx) +{ + int qi; + quicklist *address; + region *r = ctx->first, *rend = ctx->last; + xml_context *xct = (xml_context *) ctx->userdata; + xmlNodePtr node, child, xmlReport = xmlNewNode(NULL, BAD_CAST "atlantis"); + const char *mailto = locale_string(ctx->f->locale, "mailto"); + const char *mailcmd = locale_string(ctx->f->locale, "mailcmd"); + char zText[128]; + /* TODO: locale, age, options, messages */ + + xct->ns_xml = xmlNewNs(xmlReport, XML_XML_NAMESPACE, BAD_CAST "xml"); + xct->ns_atl = xmlNewNs(xmlReport, XML_ATL_NAMESPACE, NULL); + xmlSetNs(xmlReport, xct->ns_atl); + + node = xmlAddChild(xmlReport, xmlNewNode(xct->ns_atl, BAD_CAST "server")); + if (mailto) { + snprintf(zText, sizeof(zText), "mailto:%s?subject=%s", mailto, mailcmd); + child = xmlAddChild(node, xmlNewNode(xct->ns_atl, BAD_CAST "delivery")); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "method", BAD_CAST "mail"); + xmlNewNsProp(child, xct->ns_atl, BAD_CAST "href", BAD_CAST zText); + } + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "game", + (xmlChar *) global.gamename); + strftime(zText, sizeof(zText), "%Y-%m-%dT%H:%M:%SZ", + gmtime(&ctx->report_time)); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "time", (xmlChar *) zText); + xmlNewTextChild(node, xct->ns_atl, BAD_CAST "turn", (xmlChar *) itoab(turn, + 10)); + + for (qi = 0, address = ctx->addresses; address; ql_advance(&address, &qi, 1)) { + faction *f = (faction *) ql_get(address, qi); + xmlAddChild(xmlReport, xml_faction(ctx, f)); + } + + for (; r != rend; r = r->next) { + seen_region *sr = find_seen(ctx->seen, r); + if (sr != NULL) + xmlAddChild(xmlReport, xml_region(ctx, sr)); + } + return xmlReport; +} + +/* main function of the xmlreport. creates the header and traverses all regions */ +static int +report_xml(const char *filename, report_context * ctx, const char *encoding) +{ + xml_context xct; + xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); + + xct.doc = doc; + assert(ctx->userdata == NULL); + ctx->userdata = &xct; + + xmlDocSetRootElement(doc, report_root(ctx)); + xmlKeepBlanksDefault(0); + xmlSaveFormatFileEnc(filename, doc, "utf-8", 1); + xmlFreeDoc(doc); + + ctx->userdata = NULL; + + return 0; +} + +void register_xr(void) +{ + register_reporttype("xml", &report_xml, 1 << O_XML); +#ifdef USE_ICONV + utf8 = iconv_open("UTF-8", ""); +#endif +} + +void xmlreport_cleanup(void) +{ +#ifdef USE_ICONV + iconv_close(utf8); +#endif +} diff --git a/core/src/gamecode/xmlreport.h b/core/src/gamecode/xmlreport.h new file mode 100644 index 000000000..bb84fb8dd --- /dev/null +++ b/core/src/gamecode/xmlreport.h @@ -0,0 +1,26 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2005 | Enno Rehling + +-------------------+ + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#ifndef H_GC_XMLREPORT +#define H_GC_XMLREPORT +#ifdef __cplusplus +extern "C" { +#endif + +#include + + extern void xmlreport_cleanup(void); + extern void register_xr(void); + + extern int crwritemap(const char *filename); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gmtool.c b/core/src/gmtool.c new file mode 100644 index 000000000..83d637d04 --- /dev/null +++ b/core/src/gmtool.c @@ -0,0 +1,1321 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2006 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ + +/* wenn platform.h nicht vor curses included wird, kompiliert es unter windows nicht */ +#include +#include +#include + +#include "gmtool.h" +#include "gmtool_structs.h" + +#include +#include +#if MUSEUM_MODULE +#include +#endif +#if ARENA_MODULE +#include +#endif +#include +#include +#if DUNGEON_MODULE +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +static int g_quit; +int force_color = 0; + +state *current_state = NULL; + +#define IFL_SHIPS (1<<0) +#define IFL_UNITS (1<<1) +#define IFL_FACTIONS (1<<2) +#define IFL_BUILDINGS (1<<3) + +static WINDOW *hstatus; + +static void init_curses(void) +{ + short fg, bg; + initscr(); + + if (has_colors() || force_color) { + short bcol = COLOR_BLACK; + short hcol = COLOR_MAGENTA; + start_color(); +#ifdef WIN32 + /* looks crap on putty with TERM=linux */ + if (can_change_color()) { + init_color(COLOR_YELLOW, 1000, 1000, 0); + } +#endif + + for (fg = 0; fg != 8; ++fg) { + for (bg = 0; bg != 2; ++bg) { + init_pair(fg + 8 * bg, fg, bg ? hcol : bcol); + } + } + + attrset(COLOR_PAIR(COLOR_BLACK)); + bkgd(' ' | COLOR_PAIR(COLOR_BLACK)); + bkgdset(' ' | COLOR_PAIR(COLOR_BLACK)); + } + + keypad(stdscr, TRUE); /* enable keyboard mapping */ + meta(stdscr, TRUE); + nonl(); /* tell curses not to do NL->CR/NL on output */ + cbreak(); /* take input chars one at a time, no wait for \n */ + noecho(); /* don't echo input */ + scrollok(stdscr, FALSE); + refresh(); +} + +void cnormalize(const coordinate * c, int *x, int *y) +{ + *x = c->x; + *y = c->y; + pnormalize(x, y, c->pl); +} + +map_region *mr_get(const view * vi, int xofs, int yofs) +{ + return vi->regions + xofs + yofs * vi->size.width; +} + +static point *coor2point(const coordinate * c, point * p) +{ + assert(c && p); + p->x = c->x * TWIDTH + c->y * TWIDTH / 2; + p->y = c->y * THEIGHT; + return p; +} + +static window *wnd_first, *wnd_last; + +static window *win_create(WINDOW * hwin) +{ + window *wnd = calloc(1, sizeof(window)); + wnd->handle = hwin; + if (wnd_first != NULL) { + wnd->next = wnd_first; + wnd_first->prev = wnd; + wnd_first = wnd; + } else { + wnd_first = wnd; + wnd_last = wnd; + } + return wnd; +} + +static void untag_region(selection * s, int nx, int ny) +{ + unsigned int key = ((nx << 12) ^ ny); + tag **tp = &s->tags[key & (MAXTHASH - 1)]; + tag *t = NULL; + while (*tp) { + t = *tp; + if (t->coord.x == nx && t->coord.y == ny) + break; + tp = &t->nexthash; + } + if (!*tp) + return; + *tp = t->nexthash; + free(t); + return; +} + +static void tag_region(selection * s, int nx, int ny) +{ + unsigned int key = ((nx << 12) ^ ny); + tag **tp = &s->tags[key & (MAXTHASH - 1)]; + while (*tp) { + tag *t = *tp; + if (t->coord.x == nx && t->coord.y == ny) + return; + tp = &t->nexthash; + } + *tp = calloc(1, sizeof(tag)); + (*tp)->coord.x = nx; + (*tp)->coord.y = ny; + (*tp)->coord.pl = findplane(nx, ny); + return; +} + +static int tagged_region(selection * s, int nx, int ny) +{ + unsigned int key = ((nx << 12) ^ ny); + tag **tp = &s->tags[key & (MAXTHASH - 1)]; + while (*tp) { + tag *t = *tp; + if (t->coord.x == nx && t->coord.y == ny) + return 1; + tp = &t->nexthash; + } + return 0; +} + +static int mr_tile(const map_region * mr, int highlight) +{ + int hl = 8 * highlight; + if (mr != NULL && mr->r != NULL) { + const region *r = mr->r; + switch (r->terrain->_name[0]) { + case 'o': + return '.' | COLOR_PAIR(hl + COLOR_CYAN); + case 'd': + return 'D' | COLOR_PAIR(hl + COLOR_YELLOW) | A_BOLD; + case 't': + return '%' | COLOR_PAIR(hl + COLOR_YELLOW) | A_BOLD; + case 'f': + if (r->terrain->_name[1] == 'o') { /* fog */ + return '.' | COLOR_PAIR(hl + COLOR_YELLOW) | A_NORMAL; + } else if (r->terrain->_name[1] == 'i') { /* firewall */ + return '%' | COLOR_PAIR(hl + COLOR_RED) | A_BOLD; + } + break; + case 'h': + return 'H' | COLOR_PAIR(hl + COLOR_YELLOW) | A_NORMAL; + case 'm': + return '^' | COLOR_PAIR(hl + COLOR_WHITE) | A_NORMAL; + case 'p': + if (r->terrain->_name[1] == 'l') { /* plain */ + if (r_isforest(r)) + return '#' | COLOR_PAIR(hl + COLOR_GREEN) | A_NORMAL; + return '+' | COLOR_PAIR(hl + COLOR_GREEN) | A_BOLD; + } else if (r->terrain->_name[1] == 'a') { /* packice */ + return ':' | COLOR_PAIR(hl + COLOR_WHITE) | A_BOLD; + } + break; + case 'g': + return '*' | COLOR_PAIR(hl + COLOR_WHITE) | A_BOLD; + case 's': + return 'S' | COLOR_PAIR(hl + COLOR_MAGENTA) | A_NORMAL; + } + return r->terrain->_name[0] | COLOR_PAIR(hl + COLOR_RED); + } + return ' ' | COLOR_PAIR(hl + COLOR_WHITE); +} + +static void paint_map(window * wnd, const state * st) +{ + WINDOW *win = wnd->handle; + int lines = getmaxy(win); + int cols = getmaxx(win); + int vx, vy; + + lines = lines / THEIGHT; + cols = cols / TWIDTH; + for (vy = 0; vy != lines; ++vy) { + int yp = (lines - vy - 1) * THEIGHT; + for (vx = 0; vx != cols; ++vx) { + map_region *mr = mr_get(&st->display, vx, vy); + int attr = 0; + int hl = 0; + int xp = vx * TWIDTH + (vy & 1) * TWIDTH / 2; + int nx, ny; + if (mr) { + if (st) { + cnormalize(&mr->coord, &nx, &ny); + if (tagged_region(st->selected, nx, ny)) { + attr |= A_REVERSE; + } + } + if (mr->r && (mr->r->flags & RF_MAPPER_HIGHLIGHT)) + hl = 1; + mvwaddch(win, yp, xp, mr_tile(mr, hl) | attr); + } + } + } +} + +map_region *cursor_region(const view * v, const coordinate * c) +{ + coordinate relpos; + int cx, cy; + + if (c) { + relpos.x = c->x - v->topleft.x; + relpos.y = c->y - v->topleft.y; + cy = relpos.y; + cx = relpos.x + cy / 2; + return mr_get(v, cx, cy); + } + return NULL; +} + +static void +draw_cursor(WINDOW * win, selection * s, const view * v, const coordinate * c, + int show) +{ + int lines = getmaxy(win) / THEIGHT; + int xp, yp, nx, ny; + int attr = 0; + map_region *mr = cursor_region(v, c); + coordinate relpos; + int cx, cy; + + if (!mr) + return; + + relpos.x = c->x - v->topleft.x; + relpos.y = c->y - v->topleft.y; + cy = relpos.y; + cx = relpos.x + cy / 2; + + yp = (lines - cy - 1) * THEIGHT; + xp = cx * TWIDTH + (cy & 1) * TWIDTH / 2; + cnormalize(&mr->coord, &nx, &ny); + if (s && tagged_region(s, nx, ny)) + attr = A_REVERSE; + if (mr->r) { + int hl = 0; + if (mr->r->flags & RF_MAPPER_HIGHLIGHT) + hl = 1; + mvwaddch(win, yp, xp, mr_tile(mr, hl) | attr); + } else + mvwaddch(win, yp, xp, ' ' | attr | COLOR_PAIR(COLOR_YELLOW)); + if (show) { + attr = A_BOLD; + mvwaddch(win, yp, xp - 1, '<' | attr | COLOR_PAIR(COLOR_YELLOW)); + mvwaddch(win, yp, xp + 1, '>' | attr | COLOR_PAIR(COLOR_YELLOW)); + } else { + attr = A_NORMAL; + mvwaddch(win, yp, xp - 1, ' ' | attr | COLOR_PAIR(COLOR_WHITE)); + mvwaddch(win, yp, xp + 1, ' ' | attr | COLOR_PAIR(COLOR_WHITE)); + } + wmove(win, yp, xp); + wnoutrefresh(win); +} + +static void paint_status(window * wnd, const state * st) +{ + WINDOW *win = wnd->handle; + const char *name = ""; + int nx, ny, uid = 0; + const char *terrain = "----"; + map_region *mr = cursor_region(&st->display, &st->cursor); + if (mr && mr->r) { + uid = mr->r->uid; + if (mr->r->land) { + name = (const char *)mr->r->land->name; + } else { + name = mr->r->terrain->_name; + } + terrain = mr->r->terrain->_name; + } + cnormalize(&st->cursor, &nx, &ny); + mvwprintw(win, 0, 0, "%4d %4d | %.4s | %.20s (%d)", nx, ny, terrain, name, + uid); + wclrtoeol(win); +} + +static bool handle_info_region(window * wnd, state * st, int c) +{ + return false; +} + +static void paint_info_region(window * wnd, const state * st) +{ + WINDOW *win = wnd->handle; + int size = getmaxx(win) - 2; + int line = 0, maxline = getmaxy(win) - 2; + map_region *mr = cursor_region(&st->display, &st->cursor); + + unused(st); + werase(win); + wxborder(win); + if (mr && mr->r) { + const region *r = mr->r; + if (r->land) { + mvwaddnstr(win, line++, 1, (char *)r->land->name, size); + } else { + mvwaddnstr(win, line++, 1, r->terrain->_name, size); + } + line++; + mvwprintw(win, line++, 1, "%s, age %d", r->terrain->_name, r->age); + if (r->land) { + mvwprintw(win, line++, 1, "$:%6d P:%5d", r->land->money, + r->land->peasants); + mvwprintw(win, line++, 1, "H:%6d %s:%5d", r->land->horses, + (r->flags & RF_MALLORN) ? "M" : "T", + r->land->trees[1] + r->land->trees[2]); + } + line++; + if (r->ships && (st->info_flags & IFL_SHIPS)) { + ship *sh; + wattron(win, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + mvwaddnstr(win, line++, 1, "* ships:", size - 5); + wattroff(win, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + for (sh = r->ships; sh && line < maxline; sh = sh->next) { + mvwprintw(win, line, 1, "%.4s ", itoa36(sh->no)); + mvwaddnstr(win, line++, 6, (char *)sh->type->name[0], size - 5); + } + } + if (r->units && (st->info_flags & IFL_FACTIONS)) { + unit *u; + wattron(win, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + mvwaddnstr(win, line++, 1, "* factions:", size - 5); + wattroff(win, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + for (u = r->units; u && line < maxline; u = u->next) { + if (!fval(u->faction, FFL_MARK)) { + mvwprintw(win, line, 1, "%.4s ", itoa36(u->faction->no)); + mvwaddnstr(win, line++, 6, (char *)u->faction->name, size - 5); + fset(u->faction, FFL_MARK); + } + } + for (u = r->units; u && line < maxline; u = u->next) { + freset(u->faction, FFL_MARK); + } + } + if (r->units && (st->info_flags & IFL_UNITS)) { + unit *u; + wattron(win, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + mvwaddnstr(win, line++, 1, "* units:", size - 5); + wattroff(win, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + for (u = r->units; u && line < maxline; u = u->next) { + mvwprintw(win, line, 1, "%.4s ", itoa36(u->no)); + mvwaddnstr(win, line++, 6, (char *)u->name, size - 5); + } + } + } +} + +static void (*paint_info) (struct window * wnd, const struct state * st); + +static void paint_info_default(window * wnd, const state * st) +{ + if (paint_info) + paint_info(wnd, st); + else + paint_info_region(wnd, st); +} + +void set_info_function(void (*callback) (struct window *, const struct state *)) +{ + paint_info = callback; +} + +static char *askstring(WINDOW * win, const char *q, char *buffer, size_t size) +{ + werase(win); + mvwaddstr(win, 0, 0, (char *)q); + wmove(win, 0, (int)(strlen(q) + 1)); + echo(); + wgetnstr(win, buffer, (int)size); + noecho(); + return buffer; +} + +static void statusline(WINDOW * win, const char *str) +{ + mvwaddstr(win, 0, 0, (char *)str); + wclrtoeol(win); + wnoutrefresh(win); +} + +static void terraform_at(coordinate * c, const terrain_type * terrain) +{ + if (terrain != NULL) { + region *r; + int nx = c->x, ny = c->y; + pnormalize(&nx, &ny, c->pl); + r = findregion(nx, ny); + if (r == NULL) { + r = new_region(nx, ny, c->pl, 0); + } + terraform_region(r, terrain); + } +} + +static void +terraform_selection(selection * selected, const terrain_type * terrain) +{ + int i; + + if (terrain == NULL) + return; + for (i = 0; i != MAXTHASH; ++i) { + tag **tp = &selected->tags[i]; + while (*tp) { + region *r; + tag *t = *tp; + int nx = t->coord.x, ny = t->coord.y; + plane *pl = t->coord.pl; + + pnormalize(&nx, &ny, pl); + r = findregion(nx, ny); + if (r == NULL) { + r = new_region(nx, ny, pl, 0); + } + terraform_region(r, terrain); + tp = &t->nexthash; + } + } +} + +static faction *select_faction(state * st) +{ + list_selection *ilist = NULL, **iinsert; + list_selection *selected = NULL; + faction *f = factions; + + if (!f) + return NULL; + iinsert = &ilist; + + while (f) { + char buffer[32]; + sprintf(buffer, "%.4s %.26s", itoa36(f->no), f->name); + insert_selection(iinsert, NULL, buffer, (void *)f); + f = f->next; + } + selected = do_selection(ilist, "Select Faction", NULL, NULL); + st->wnd_info->update |= 1; + st->wnd_map->update |= 1; + st->wnd_status->update |= 1; + + if (selected == NULL) + return NULL; + return (faction *) selected->data; +} + +static const terrain_type *select_terrain(state * st, + const terrain_type * default_terrain) +{ + list_selection *ilist = NULL, **iinsert; + list_selection *selected = NULL; + const terrain_type *terrain = terrains(); + + if (!terrain) + return NULL; + iinsert = &ilist; + + while (terrain) { + insert_selection(iinsert, NULL, terrain->_name, (void *)terrain); + terrain = terrain->next; + } + selected = do_selection(ilist, "Terrain", NULL, NULL); + st->wnd_info->update |= 1; + st->wnd_map->update |= 1; + st->wnd_status->update |= 1; + + if (selected == NULL) + return NULL; + return (const terrain_type *)selected->data; +} + +static coordinate *region2coord(const region * r, coordinate * c) +{ + c->x = r->x; + c->y = r->y; + c->pl = rplane(r); + return c; +} + +#ifdef __PDCURSES__ +#define FAST_UP CTL_UP +#define FAST_DOWN CTL_DOWN +#define FAST_LEFT CTL_LEFT +#define FAST_RIGHT CTL_RIGHT +#else +#define FAST_UP KEY_PPAGE +#define FAST_DOWN KEY_NPAGE +#define FAST_LEFT KEY_SLEFT +#define FAST_RIGHT KEY_SRIGHT +#endif + +void highlight_region(region * r, int toggle) +{ + if (r != NULL) { + if (toggle) + r->flags |= RF_MAPPER_HIGHLIGHT; + else + r->flags &= ~RF_MAPPER_HIGHLIGHT; + } +} + +void select_coordinate(struct selection *selected, int nx, int ny, int toggle) +{ + if (toggle) + tag_region(selected, nx, ny); + else + untag_region(selected, nx, ny); +} + +enum { MODE_MARK, MODE_SELECT, MODE_UNMARK, MODE_UNSELECT }; + +static void select_regions(state * st, int selectmode) +{ + char sbuffer[80]; + int findmode; + const char *statustext[] = { + "mark-", "select-", "unmark-", "deselect-" + }; + const char *status = statustext[selectmode]; + statusline(st->wnd_status->handle, status); + doupdate(); + findmode = getch(); + if (findmode == 'n') { /* none */ + int i; + sprintf(sbuffer, "%snone", status); + statusline(st->wnd_status->handle, sbuffer); + if (selectmode & MODE_SELECT) { + for (i = 0; i != MAXTHASH; ++i) { + tag **tp = &st->selected->tags[i]; + while (*tp) { + tag *t = *tp; + *tp = t->nexthash; + free(t); + } + } + } else { + region *r; + for (r = regions; r; r = r->next) { + r->flags &= ~RF_MAPPER_HIGHLIGHT; + } + } + } else if (findmode == 'm') { + region *r; + sprintf(sbuffer, "%smonsters", status); + statusline(st->wnd_status->handle, sbuffer); + for (r = regions; r; r = r->next) { + unit *u = r->units; + for (; u; u = u->next) { + if (fval(u->faction, FFL_NPC) != 0) + break; + } + if (u) { + if (selectmode & MODE_SELECT) { + select_coordinate(st->selected, r->x, r->y, + selectmode == MODE_SELECT); + } else { + highlight_region(r, selectmode == MODE_MARK); + } + } + } + } else if (findmode == 'p') { + region *r; + sprintf(sbuffer, "%splayers", status); + statusline(st->wnd_status->handle, sbuffer); + for (r = regions; r; r = r->next) { + unit *u = r->units; + for (; u; u = u->next) { + if (fval(u->faction, FFL_NPC) == 0) + break; + } + if (u) { + if (selectmode & MODE_SELECT) { + select_coordinate(st->selected, r->x, r->y, + selectmode == MODE_SELECT); + } else { + highlight_region(r, selectmode == MODE_MARK); + } + } + } + } else if (findmode == 'u') { + region *r; + sprintf(sbuffer, "%sunits", status); + statusline(st->wnd_status->handle, sbuffer); + for (r = regions; r; r = r->next) { + if (r->units) { + if (selectmode & MODE_SELECT) { + select_coordinate(st->selected, r->x, r->y, + selectmode == MODE_SELECT); + } else { + highlight_region(r, selectmode == MODE_MARK); + } + } + } + } else if (findmode == 's') { + region *r; + sprintf(sbuffer, "%sships", status); + statusline(st->wnd_status->handle, sbuffer); + for (r = regions; r; r = r->next) { + if (r->ships) { + if (selectmode & MODE_SELECT) { + select_coordinate(st->selected, r->x, r->y, + selectmode == MODE_SELECT); + } else { + highlight_region(r, selectmode == MODE_MARK); + } + } + } + } else if (findmode == 'f') { + char fbuffer[12]; + sprintf(sbuffer, "%sfaction:", status); + askstring(st->wnd_status->handle, sbuffer, fbuffer, 12); + if (fbuffer[0]) { + faction *f = findfaction(atoi36(fbuffer)); + + if (f != NULL) { + unit *u; + + sprintf(sbuffer, "%sfaction: %s", status, itoa36(f->no)); + statusline(st->wnd_status->handle, sbuffer); + for (u = f->units; u; u = u->nextF) { + region *r = u->region; + if (selectmode & MODE_SELECT) { + select_coordinate(st->selected, r->x, r->y, + selectmode == MODE_SELECT); + } else { + highlight_region(r, selectmode == MODE_MARK); + } + } + } else { + statusline(st->wnd_status->handle, "faction not found."); + beep(); + return; + } + } + } else if (findmode == 't') { + const struct terrain_type *terrain; + sprintf(sbuffer, "%sterrain: ", status); + statusline(st->wnd_status->handle, sbuffer); + terrain = select_terrain(st, NULL); + if (terrain != NULL) { + region *r; + sprintf(sbuffer, "%sterrain: %s", status, terrain->_name); + statusline(st->wnd_status->handle, sbuffer); + for (r = regions; r; r = r->next) { + if (r->terrain == terrain) { + if (selectmode & MODE_SELECT) { + select_coordinate(st->selected, r->x, r->y, + selectmode == MODE_SELECT); + } else { + highlight_region(r, selectmode == MODE_MARK); + } + } + } + } + } else { + statusline(st->wnd_status->handle, "unknown command."); + beep(); + return; + } + st->wnd_info->update |= 3; + st->wnd_status->update |= 3; + st->wnd_map->update |= 3; +} + +static void handlekey(state * st, int c) +{ + window *wnd; + coordinate *cursor = &st->cursor; + static char locate[80]; + static int findmode = 0; + region *r; + char sbuffer[80]; + static char kbuffer[80]; + int n, nx, ny; + + switch (c) { + case FAST_RIGHT: + cursor->x += 10; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case FAST_LEFT: + cursor->x -= 10; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case FAST_UP: + cursor->y += 10; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case FAST_DOWN: + cursor->y -= 10; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case KEY_UP: + cursor->y++; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case KEY_DOWN: + cursor->y--; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case KEY_RIGHT: + cursor->x++; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case KEY_LEFT: + cursor->x--; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + case 'S': + case KEY_SAVE: + case KEY_F(2): + /* if (st->modified) */ { + char datafile[MAX_PATH]; + + askstring(st->wnd_status->handle, "save as:", datafile, sizeof(datafile)); + if (strlen(datafile) > 0) { + create_backup(datafile); + remove_empty_units(); + writegame(datafile, IO_DEFAULT); + st->modified = 0; + } + } + break; + case 'B': + /* + make_block(st->cursor.x, st->cursor.y, 6, select_terrain(st, NULL)); + */ + cnormalize(&st->cursor, &nx, &ny); + n = rng_int() % 8 + 8; + build_island_e3(nx, ny, n, n * 3); + st->modified = 1; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 1; + break; + case 0x02: /* CTRL+b */ + cnormalize(&st->cursor, &nx, &ny); + make_block(nx, ny, 6, newterrain(T_OCEAN)); + st->modified = 1; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 1; + break; + case 0x09: /* tab = next selected */ + if (regions != NULL) { + map_region *mr = cursor_region(&st->display, cursor); + if (mr) { + region *first = mr->r; + region *cur = (first && first->next) ? first->next : regions; + + while (cur != first) { + coordinate coord; + region2coord(cur, &coord); + cnormalize(&coord, &nx, &ny); + if (tagged_region(st->selected, nx, ny)) { + st->cursor = coord; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + break; + } + cur = cur->next; + if (!cur && first) + cur = regions; + } + } + } + break; + + case 'p': + if (planes) { + plane *pl = planes; + if (cursor->pl) { + while (pl && pl != cursor->pl) { + pl = pl->next; + } + if (pl && pl->next) { + cursor->pl = pl->next; + } else { + cursor->pl = get_homeplane(); + } + } else { + cursor->pl = planes; + } + } + break; + + case 'a': + if (regions != NULL) { + map_region *mr = cursor_region(&st->display, cursor); + if (mr && mr->r) { + region *cur = mr->r; + plane *pl = rplane(cur); + if (pl == NULL) { + cur = r_standard_to_astral(cur); + } else if (is_astral(cur)) { + cur = r_astral_to_standard(cur); + } else { + cur = NULL; + } + if (cur != NULL) { + region2coord(cur, &st->cursor); + } else { + beep(); + } + } + } + break; + case 'g': + askstring(st->wnd_status->handle, "goto-x:", sbuffer, 12); + if (sbuffer[0]) { + askstring(st->wnd_status->handle, "goto-y:", sbuffer + 16, 12); + if (sbuffer[16]) { + st->cursor.x = atoi(sbuffer); + st->cursor.y = atoi(sbuffer + 16); + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + } + } + break; + case 0x14: /* C-t */ + terraform_at(&st->cursor, select_terrain(st, NULL)); + st->modified = 1; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 1; + break; + case 'I': + statusline(st->wnd_status->handle, "info-"); + doupdate(); + do { + c = getch(); + switch (c) { + case 's': + st->info_flags ^= IFL_SHIPS; + if (st->info_flags & IFL_SHIPS) + statusline(st->wnd_status->handle, "info-ships true"); + else + statusline(st->wnd_status->handle, "info-ships false"); + break; + case 'b': + st->info_flags ^= IFL_BUILDINGS; + if (st->info_flags & IFL_BUILDINGS) + statusline(st->wnd_status->handle, "info-buildings true"); + else + statusline(st->wnd_status->handle, "info-buildings false"); + case 'f': + st->info_flags ^= IFL_FACTIONS; + if (st->info_flags & IFL_FACTIONS) + statusline(st->wnd_status->handle, "info-factions true"); + else + statusline(st->wnd_status->handle, "info-factions false"); + break; + case 'u': + st->info_flags ^= IFL_UNITS; + if (st->info_flags & IFL_UNITS) + statusline(st->wnd_status->handle, "info-units true"); + else + statusline(st->wnd_status->handle, "info-units false"); + break; + case 27: /* esc */ + break; + default: + beep(); + c = 0; + } + } while (c == 0); + break; + case 'L': + if (global.vm_state) { + move(0, 0); + refresh(); + lua_do((struct lua_State *)global.vm_state); + /* todo: do this from inside the script */ + clear(); + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 1; + } + break; + case 12: /* Ctrl-L */ + clear(); + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 1; + break; + case 'h': + select_regions(st, MODE_MARK); + break; + case 'H': + select_regions(st, MODE_UNMARK); + break; + case 't': + select_regions(st, MODE_SELECT); + break; + case 'T': + select_regions(st, MODE_UNSELECT); + break; + case ';': + statusline(st->wnd_status->handle, "tag-"); + doupdate(); + switch (getch()) { + case 't': + terraform_selection(st->selected, select_terrain(st, NULL)); + st->modified = 1; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 1; + break; + case 'm': + break; + default: + statusline(st->wnd_status->handle, "unknown command."); + beep(); + } + break; + case ' ': + cnormalize(cursor, &nx, &ny); + if (tagged_region(st->selected, nx, ny)) + untag_region(st->selected, nx, ny); + else + tag_region(st->selected, nx, ny); + break; + case 'A': + sprintf(sbuffer, "%s/newfactions", basepath()); + seed_players(sbuffer, false); + st->wnd_map->update |= 1; + break; + case '/': + statusline(st->wnd_status->handle, "find-"); + doupdate(); + findmode = getch(); + if (findmode == 'r') { + askstring(st->wnd_status->handle, "find-region:", locate, + sizeof(locate)); + } else if (findmode == 'u') { + askstring(st->wnd_status->handle, "find-unit:", locate, sizeof(locate)); + } else if (findmode == 'f') { + askstring(st->wnd_status->handle, "find-faction:", locate, + sizeof(locate)); + } else if (findmode == 'F') { + faction *f = select_faction(st); + if (f != NULL) { + strcpy(locate, itoa36(f->no)); + findmode = 'f'; + } else { + break; + } + } else { + statusline(st->wnd_status->handle, "unknown command."); + beep(); + break; + } + /* achtung: fall-through ist absicht: */ + if (!strlen(locate)) + break; + case 'n': + if (findmode == 'u') { + unit *u = findunit(atoi36(locate)); + r = u ? u->region : NULL; + } else if (findmode && regions != NULL) { + struct faction *f = NULL; + map_region *mr = cursor_region(&st->display, cursor); + region *first = (mr && mr->r && mr->r->next) ? mr->r->next : regions; + + if (findmode == 'f') { + sprintf(sbuffer, "find-faction: %s", locate); + statusline(st->wnd_status->handle, sbuffer); + f = findfaction(atoi36(locate)); + if (f == NULL) { + statusline(st->wnd_status->handle, "faction not found."); + beep(); + break; + } + } + for (r = first;;) { + if (findmode == 'r' && r->land && r->land->name + && strstr((const char *)r->land->name, locate)) { + break; + } else if (findmode == 'f') { + unit *u; + for (u = r->units; u; u = u->next) { + if (u->faction == f) { + break; + } + } + if (u) + break; + } + r = r->next; + if (r == NULL) + r = regions; + if (r == first) { + r = NULL; + statusline(st->wnd_status->handle, "not found."); + beep(); + break; + } + } + } else { + r = NULL; + } + if (r != NULL) { + region2coord(r, &st->cursor); + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + } + break; + case 'Q': + g_quit = 1; + break; + default: + for (wnd = wnd_first; wnd != NULL; wnd = wnd->next) { + if (wnd->handlekey) { + if (wnd->handlekey(wnd, st, c)) + break; + } + } + if (wnd == NULL) { + if (kbuffer[0] == 0) { + strcpy(kbuffer, "getch:"); + } + sprintf(sbuffer, " 0x%x", c); + strncat(kbuffer, sbuffer, sizeof(kbuffer)); + statusline(st->wnd_status->handle, kbuffer); + if (strlen(kbuffer) > 70) + kbuffer[0] = 0; + } + break; + } +} + +static void init_view(view * display, WINDOW * win) +{ + display->topleft.x = 1; + display->topleft.y = 1; + display->topleft.pl = get_homeplane(); + display->pl = get_homeplane(); + display->size.width = getmaxx(win) / TWIDTH; + display->size.height = getmaxy(win) / THEIGHT; + display->regions = + calloc(display->size.height * display->size.width, sizeof(map_region)); +} + +static void update_view(view * vi) +{ + int i, j; + for (i = 0; i != vi->size.width; ++i) { + for (j = 0; j != vi->size.height; ++j) { + map_region *mr = mr_get(vi, i, j); + mr->coord.x = vi->topleft.x + i - j / 2; + mr->coord.y = vi->topleft.y + j; + mr->coord.pl = vi->pl; + pnormalize(&mr->coord.x, &mr->coord.y, mr->coord.pl); + mr->r = findregion(mr->coord.x, mr->coord.y); + } + } +} + +state *state_open(void) +{ + state *st = calloc(sizeof(state), 1); + st->display.pl = get_homeplane(); + st->cursor.pl = get_homeplane(); + st->cursor.x = 0; + st->cursor.y = 0; + st->selected = calloc(1, sizeof(struct selection)); + st->modified = 0; + st->info_flags = 0xFFFFFFFF; + st->prev = current_state; + current_state = st; + return st; +} + +void state_close(state * st) +{ + assert(st == current_state); + current_state = st->prev; + free(st); +} + +void run_mapper(void) +{ + WINDOW *hwinstatus; + WINDOW *hwininfo; + WINDOW *hwinmap; + int width, height, x, y; + int split = 20, old_flags = log_flags; + state *st; + point tl; + + log_flags &= ~(LOG_CPERROR | LOG_CPWARNING); + init_curses(); + curs_set(1); + + set_readline(curses_readline); + + getbegyx(stdscr, x, y); + width = getmaxx(stdscr); + height = getmaxy(stdscr); + + hwinmap = subwin(stdscr, getmaxy(stdscr) - 1, getmaxx(stdscr) - split, y, x); + hwininfo = + subwin(stdscr, getmaxy(stdscr) - 1, split, y, x + getmaxx(stdscr) - split); + hwinstatus = subwin(stdscr, 1, width, height - 1, x); + + st = state_open(); + st->wnd_map = win_create(hwinmap); + st->wnd_map->paint = &paint_map; + st->wnd_map->update = 1; + st->wnd_info = win_create(hwininfo); + st->wnd_info->paint = &paint_info_default; + st->wnd_info->handlekey = &handle_info_region; + st->wnd_info->update = 1; + st->wnd_status = win_create(hwinstatus); + st->wnd_status->paint = &paint_status; + st->wnd_status->update = 1; + + init_view(&st->display, hwinmap); + coor2point(&st->display.topleft, &tl); + + hstatus = st->wnd_status->handle; /* the lua console needs this */ + + while (!g_quit) { + int c; + point p; + window *wnd; + view *vi = &st->display; + + getbegyx(hwinmap, x, y); + width = getmaxx(hwinmap) - x; + height = getmaxy(hwinmap) - y; + coor2point(&st->cursor, &p); + + if (st->cursor.pl != vi->pl) { + vi->pl = st->cursor.pl; + st->wnd_map->update |= 1; + } + if (p.y < tl.y) { + vi->topleft.y = st->cursor.y - vi->size.height / 2; + st->wnd_map->update |= 1; + } else if (p.y >= tl.y + vi->size.height * THEIGHT) { + vi->topleft.y = st->cursor.y - vi->size.height / 2; + st->wnd_map->update |= 1; + } + if (p.x <= tl.x) { + vi->topleft.x = + st->cursor.x + (st->cursor.y - vi->topleft.y) / 2 - vi->size.width / 2; + st->wnd_map->update |= 1; + } else if (p.x >= tl.x + vi->size.width * TWIDTH - 1) { + vi->topleft.x = + st->cursor.x + (st->cursor.y - vi->topleft.y) / 2 - vi->size.width / 2; + st->wnd_map->update |= 1; + } + + if (st->wnd_map->update) { + update_view(vi); + coor2point(&vi->topleft, &tl); + } + for (wnd = wnd_last; wnd != NULL; wnd = wnd->prev) { + if (wnd->update && wnd->paint) { + if (wnd->update & 1) { + wnd->paint(wnd, st); + wnoutrefresh(wnd->handle); + } + if (wnd->update & 2) { + touchwin(wnd->handle); + } + wnd->update = 0; + } + } + draw_cursor(st->wnd_map->handle, st->selected, vi, &st->cursor, 1); + doupdate(); + c = getch(); + draw_cursor(st->wnd_map->handle, st->selected, vi, &st->cursor, 0); + handlekey(st, c); + } + g_quit = 0; + set_readline(NULL); + curs_set(1); + endwin(); + log_flags = old_flags; + state_close(st); +} + +int +curses_readline(struct lua_State *L, char *buffer, size_t size, + const char *prompt) +{ + unused(L); + askstring(hstatus, prompt, buffer, size); + return buffer[0] != 0; +} + +void seed_players(const char *filename, bool new_island) +{ + newfaction *players = read_newfactions(filename); + if (players != NULL) { + while (players) { + int n = listlen(players); + int k = (n + ISLANDSIZE - 1) / ISLANDSIZE; + k = n / k; + n = autoseed(&players, k, new_island ? 0 : TURNS_PER_ISLAND); + if (n == 0) { + break; + } + } + } +} + +void make_block(int x, int y, int radius, const struct terrain_type *terrain) +{ + int cx, cy; + region *r; + plane *pl = findplane(x, y); + + if (terrain == NULL) + return; + + for (cx = x - radius; cx != x + radius; ++cx) { + for (cy = y - radius; cy != y + radius; ++cy) { + int nx = cx, ny = cy; + pnormalize(&nx, &ny, pl); + if (koor_distance(nx, ny, x, y) < radius) { + if (!findregion(nx, ny)) { + r = new_region(nx, ny, pl, 0); + terraform_region(r, terrain); + } + } + } + } +} diff --git a/core/src/gmtool.h b/core/src/gmtool.h new file mode 100644 index 000000000..0359973b5 --- /dev/null +++ b/core/src/gmtool.h @@ -0,0 +1,41 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2006 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ + +#ifndef H_GMTOOL +#define H_GMTOOL + +#ifdef __cplusplus +extern "C" { +#endif + struct lua_State; + struct selection; + struct state; + struct region; + + int gmmain(int argc, char *argv[]); + int curses_readline(struct lua_State *L, char *buffer, size_t size, + const char *prompt); + + void highlight_region(struct region *r, int on); + void select_coordinate(struct selection *selected, int x, int y, int on); + void run_mapper(void); + + extern int force_color; + + struct state *state_open(void); + void state_close(struct state *); + + void make_block(int x, int y, int radius, const struct terrain_type *terrain); + void seed_players(const char *filename, bool new_island); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/gmtool_structs.h b/core/src/gmtool_structs.h new file mode 100644 index 000000000..1f3e4e9a7 --- /dev/null +++ b/core/src/gmtool_structs.h @@ -0,0 +1,102 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2006 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ + +#ifndef H_GMTOOL_STRUCTS +#define H_GMTOOL_STRUCTS + +#ifdef __cplusplus +extern "C" { +#endif + +/* types imported from eressea: */ + struct region; + + typedef struct extent { + /* Ein Vektor */ + int width, height; + } extent; + + typedef struct point { + /* Eine Koordinate in einer Ascii-Karte */ + int x, y; + } point; + + typedef struct coordinate { + /* Eine Koordinate im Editor, nicht normalisiert */ + int x, y; + struct plane *pl; + } coordinate; + + typedef struct map_region { + struct region *r; + coordinate coord; + } map_region; + + typedef struct view { + struct map_region *regions; + struct plane *pl; + coordinate topleft; /* upper left corner in map. */ + extent size; /* dimensions. */ + } view; + + typedef struct tag { + coordinate coord; + struct tag *nexthash; + } tag; + +#define MAXTHASH 512 + + typedef struct selection { + tag *tags[MAXTHASH]; + } selection; + + typedef struct state { + coordinate cursor; + selection *selected; + struct state *prev; + view display; + int modified; + unsigned int info_flags; + struct window *wnd_info; + struct window *wnd_map; + struct window *wnd_status; + } state; + + typedef struct window { + bool(*handlekey) (struct window * win, struct state * st, int key); + void (*paint) (struct window * win, const struct state * st); + + WINDOW *handle; + struct window *next; + struct window *prev; + bool initialized; + int update; + } window; + + extern map_region *cursor_region(const view * v, const coordinate * c); + extern void cnormalize(const coordinate * c, int *x, int *y); + extern state *current_state; + + extern void set_info_function(void (*callback) (struct window *, + const struct state *)); + +#define TWIDTH 2 /* width of tile */ +#define THEIGHT 1 /* height of tile */ + +#ifdef WIN32 +#define wxborder(win) wborder(win, 0, 0, 0, 0, 0, 0, 0, 0) +#else +#define wxborder(win) wborder(win, '|', '|', '-', '-', '+', '+', '+', '+') +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/artrewards.c b/core/src/items/artrewards.c new file mode 100644 index 000000000..a5b67bc58 --- /dev/null +++ b/core/src/items/artrewards.c @@ -0,0 +1,156 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "artrewards.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +#define HORNRANGE 10 +#define HORNDURATION 3 +#define HORNIMMUNITY 30 + +static int age_peaceimmune(attrib * a) +{ + return (--a->data.i > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +static attrib_type at_peaceimmune = { + "peaceimmune", + NULL, NULL, + age_peaceimmune, + a_writeint, + a_readint +}; + +static int +use_hornofdancing(struct unit *u, const struct item_type *itype, + int amount, struct order *ord) +{ + region *r; + int regionsPacified = 0; + + for (r = regions; r; r = r->next) { + if (distance(u->region, r) < HORNRANGE) { + if (a_find(r->attribs, &at_peaceimmune) == NULL) { + attrib *a; + + create_curse(u, &r->attribs, ct_find("peacezone"), + 20, HORNDURATION, 1.0, 0); + + a = a_add(&r->attribs, a_new(&at_peaceimmune)); + a->data.i = HORNIMMUNITY; + + ADDMSG(&r->msgs, msg_message("hornofpeace_r_success", + "unit region", u, u->region)); + + regionsPacified++; + } else { + ADDMSG(&r->msgs, msg_message("hornofpeace_r_nosuccess", + "unit region", u, u->region)); + } + } + } + + if (regionsPacified > 0) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "hornofpeace_u_success", + "pacified", regionsPacified)); + } else { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "hornofpeace_u_nosuccess", + "")); + } + + return 0; +} + +#define SPEEDUP 2 + +static int +useonother_trappedairelemental(struct unit *u, int shipId, + const struct item_type *itype, int amount, struct order *ord) +{ + curse *c; + ship *sh; + + if (shipId <= 0) { + cmistake(u, ord, 20, MSG_MOVE); + return -1; + } + + sh = findshipr(u->region, shipId); + if (!sh) { + cmistake(u, ord, 20, MSG_MOVE); + return -1; + } + + c = + create_curse(u, &sh->attribs, ct_find("shipspeedup"), 20, INT_MAX, SPEEDUP, + 0); + c_setflag(c, CURSE_NOAGE); + + ADDMSG(&u->faction->msgs, msg_message("trappedairelemental_success", + "unit region command ship", u, u->region, ord, sh)); + + use_pooled(u, itype->rtype, GET_DEFAULT, 1); + + return 0; +} + +static int +use_trappedairelemental(struct unit *u, + const struct item_type *itype, int amount, struct order *ord) +{ + ship *sh = u->ship; + + if (sh == NULL) { + cmistake(u, ord, 20, MSG_MOVE); + return -1; + } + return useonother_trappedairelemental(u, sh->no, itype, amount, ord); +} + +void register_artrewards(void) +{ + at_register(&at_peaceimmune); + register_item_use(use_hornofdancing, "use_hornofdancing"); + register_item_use(use_trappedairelemental, "use_trappedairelemental"); + register_item_useonother(useonother_trappedairelemental, + "useonother_trappedairelemental"); +} diff --git a/core/src/items/artrewards.h b/core/src/items/artrewards.h new file mode 100644 index 000000000..4141c9331 --- /dev/null +++ b/core/src/items/artrewards.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ITM_ARTREWARDS +#define H_ITM_ARTREWARDS +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_artrewards(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/demonseye.c b/core/src/items/demonseye.c new file mode 100644 index 000000000..53a06bd59 --- /dev/null +++ b/core/src/items/demonseye.c @@ -0,0 +1,65 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "demonseye.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include + +/* libc includes */ +#include + +static int +summon_igjarjuk(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + struct plane *p = rplane(u->region); + unused(amount); + unused(itype); + if (p != NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "use_realworld_only", "")); + return EUNUSABLE; + } else { + assert(!"not implemented"); + return EUNUSABLE; + } +} + +static int +give_igjarjuk(struct unit *src, struct unit *d, const struct item_type *itype, + int n, struct order *ord) +{ + ADDMSG(&src->faction->msgs, msg_feedback(src, ord, "error_giveeye", "")); + return 0; +} + +void register_demonseye(void) +{ + register_item_use(summon_igjarjuk, "useigjarjuk"); + register_item_give(give_igjarjuk, "giveigjarjuk"); +} diff --git a/core/src/items/demonseye.h b/core/src/items/demonseye.h new file mode 100644 index 000000000..155eac8c6 --- /dev/null +++ b/core/src/items/demonseye.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ITM_DEMONSEYE +#define H_ITM_DEMONSEYE +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_demonseye(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/itemtypes.c b/core/src/items/itemtypes.c new file mode 100644 index 000000000..eb72449ea --- /dev/null +++ b/core/src/items/itemtypes.c @@ -0,0 +1,36 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "itemtypes.h" + +#include "xerewards.h" +#include "artrewards.h" +#include "phoenixcompass.h" +#include "weapons.h" +#include "seed.h" + +void register_itemtypes(void) +{ + /* registering misc. functions */ + register_weapons(); + register_xerewards(); + register_artrewards(); + register_phoenixcompass(); +} + +void init_itemtypes(void) +{ + init_seed(); + init_mallornseed(); +} diff --git a/core/src/items/itemtypes.h b/core/src/items/itemtypes.h new file mode 100644 index 000000000..f1a70a705 --- /dev/null +++ b/core/src/items/itemtypes.h @@ -0,0 +1,25 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_ITM_ITEMS +#define H_ITM_ITEMS +#ifdef __cplusplus +extern "C" { +#endif + + extern void init_itemtypes(void); + extern void register_itemtypes(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/phoenixcompass.c b/core/src/items/phoenixcompass.c new file mode 100644 index 000000000..1aa6b3d2e --- /dev/null +++ b/core/src/items/phoenixcompass.c @@ -0,0 +1,128 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "phoenixcompass.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +static int +use_phoenixcompass(struct unit *u, const struct item_type *itype, + int amount, struct order *ord) +{ + region *r; + unit *closest_phoenix = NULL; + int closest_phoenix_distance = INT_MAX; + bool confusion = false; + direction_t direction; + unit *u2; + direction_t closest_neighbour_direction = 0; + static race *rc_phoenix = NULL; + + if (rc_phoenix == NULL) { + rc_phoenix = rc_find("phoenix"); + if (rc_phoenix == NULL) + return 0; + } + + /* find the closest phoenix. */ + + for (r = regions; r; r = r->next) { + for (u2 = r->units; u2; u2 = u2->next) { + if (u_race(u2) == rc_phoenix) { + if (closest_phoenix == NULL) { + closest_phoenix = u2; + closest_phoenix_distance = + distance(u->region, closest_phoenix->region); + } else { + int dist = distance(u->region, r); + if (dist < closest_phoenix_distance) { + closest_phoenix = u2; + closest_phoenix_distance = dist; + confusion = false; + } else if (dist == closest_phoenix_distance) { + confusion = true; + } + } + } + } + } + + /* no phoenix found at all.* if confusion == true more than one phoenix + * at the same distance was found and the device is confused */ + + if (closest_phoenix == NULL + || closest_phoenix->region == u->region || confusion) { + add_message(&u->faction->msgs, msg_message("phoenixcompass_confusion", + "unit region command", u, u->region, ord)); + return 0; + } + + /* else calculate the direction. this is tricky. we calculate the + * neighbouring region which is closest to the phoenix found. hardcoded + * for readability. */ + + for (direction = 0; direction < MAXDIRECTIONS; ++direction) { + region *neighbour; + int closest_neighbour_distance = INT_MAX; + + neighbour = r_connect(u->region, direction); + if (neighbour != NULL) { + int dist = distance(neighbour, closest_phoenix->region); + if (dist < closest_neighbour_distance) { + closest_neighbour_direction = direction; + closest_neighbour_distance = dist; + } else if (dist == closest_neighbour_distance && rng_int() % 100 < 50) { + /* there can never be more than two neighbours with the same + * distance (except when you are standing in the same region + * as the phoenix, but that case has already been handled). + * therefore this simple solution is correct */ + closest_neighbour_direction = direction; + closest_neighbour_distance = dist; + } + } + } + + add_message(&u->faction->msgs, msg_message("phoenixcompass_success", + "unit region command dir", + u, u->region, ord, closest_neighbour_direction)); + + return 0; +} + +void register_phoenixcompass(void) +{ + register_item_use(use_phoenixcompass, "use_phoenixcompass"); +} diff --git a/core/src/items/phoenixcompass.h b/core/src/items/phoenixcompass.h new file mode 100644 index 000000000..7bb03ba62 --- /dev/null +++ b/core/src/items/phoenixcompass.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ITM_PHOENIXCOMPASS +#define H_ITM_PHOENIXCOMPASS +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_phoenixcompass(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/seed.c b/core/src/items/seed.c new file mode 100644 index 000000000..683e4714b --- /dev/null +++ b/core/src/items/seed.c @@ -0,0 +1,96 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include + +#include "seed.h" + +/* kernel includes */ +#include +#include +#include +#include + +/* util includes */ +#include +#include + +/* libc includes */ +#include + +resource_type *rt_seed = 0; +resource_type *rt_mallornseed = 0; + +static void produce_seeds(region * r, const resource_type * rtype, int norders) +{ + assert(rtype == rt_seed && r->land && r->land->trees[0] >= norders); + r->land->trees[0] -= norders; +} + +static int limit_seeds(const region * r, const resource_type * rtype) +{ + assert(rtype == rt_seed); + if (fval(r, RF_MALLORN)) + return 0; + return r->land ? r->land->trees[0] : 0; +} + +void init_seed(void) +{ + attrib *a; + resource_limit *rdata; + + rt_seed = rt_find("seed"); + if (rt_seed != NULL) { + a = a_add(&rt_seed->attribs, a_new(&at_resourcelimit)); + rdata = (resource_limit *) a->data.v; + rdata->limit = limit_seeds; + rdata->produce = produce_seeds; + } +} + +/* mallorn */ + +static void +produce_mallornseeds(region * r, const resource_type * rtype, int norders) +{ + assert(rtype == rt_mallornseed && r->land && r->land->trees[0] >= norders); + assert(fval(r, RF_MALLORN)); + r->land->trees[0] -= norders; +} + +static int limit_mallornseeds(const region * r, const resource_type * rtype) +{ + assert(rtype == rt_mallornseed); + if (!fval(r, RF_MALLORN)) { + return 0; + } + return r->land ? r->land->trees[0] : 0; +} + +void init_mallornseed(void) +{ + attrib *a; + resource_limit *rdata; + + rt_mallornseed = rt_find("mallornseed"); + if (rt_mallornseed != NULL) { + rt_mallornseed->flags |= RTF_LIMITED; + rt_mallornseed->flags |= RTF_POOLED; + + a = a_add(&rt_mallornseed->attribs, a_new(&at_resourcelimit)); + rdata = (resource_limit *) a->data.v; + rdata->limit = limit_mallornseeds; + rdata->produce = produce_mallornseeds; + } +} diff --git a/core/src/items/seed.h b/core/src/items/seed.h new file mode 100644 index 000000000..810d8bd08 --- /dev/null +++ b/core/src/items/seed.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ITM_SEED +#define H_ITM_SEED +#ifdef __cplusplus +extern "C" { +#endif + + extern struct resource_type *rt_seed; + extern void init_seed(void); + + extern struct resource_type *rt_mallornseed; + extern void init_mallornseed(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/speedsail.c b/core/src/items/speedsail.c new file mode 100644 index 000000000..cc7cff3e3 --- /dev/null +++ b/core/src/items/speedsail.c @@ -0,0 +1,73 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "speedsail.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include + +/* libc includes */ +#include + +static int +use_speedsail(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + struct plane *p = rplane(u->region); + unused(amount); + unused(itype); + if (p != NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "use_realworld_only", "")); + } else { + if (u->ship) { + attrib *a = a_find(u->ship->attribs, &at_speedup); + if (a == NULL) { + a = a_add(&u->ship->attribs, a_new(&at_speedup)); + a->data.sa[0] = 50; /* speed */ + a->data.sa[1] = 50; /* decay */ + ADDMSG(&u->faction->msgs, msg_message("use_speedsail", "unit", u)); + /* Ticket abziehen */ + i_change(&u->items, itype, -1); + return 0; + } else { + cmistake(u, ord, 211, MSG_EVENT); + } + } else { + cmistake(u, ord, 144, MSG_EVENT); + } + } + return EUNUSABLE; +} + +void register_speedsail(void) +{ + register_item_use(use_speedsail, "use_speedsail"); +} diff --git a/core/src/items/speedsail.h b/core/src/items/speedsail.h new file mode 100644 index 000000000..45440f358 --- /dev/null +++ b/core/src/items/speedsail.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ITM_SPEEDVIAL +#define H_ITM_SPEEDVIAL +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_speedsail(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/weapons.c b/core/src/items/weapons.c new file mode 100644 index 000000000..9eb3d8d72 --- /dev/null +++ b/core/src/items/weapons.c @@ -0,0 +1,170 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "weapons.h" + +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include + +/* libc includes */ +#include +#include + +/* damage types */ + +static bool +attack_firesword(const troop * at, const struct weapon_type *wtype, + int *casualties) +{ + fighter *fi = at->fighter; + troop dt; + int killed = 0; + const char *damage = "2d8"; + int force = 1 + rng_int() % 10; + int enemies = + count_enemies(fi->side->battle, fi, 0, 1, SELECT_ADVANCE | SELECT_DISTANCE); + + if (!enemies) { + if (casualties) + *casualties = 0; + return true; /* if no enemy found, no use doing standarad attack */ + } + + if (fi->catmsg == -1) { + int i, k = 0; + message *msg; + for (i = 0; i <= at->index; ++i) { + struct weapon *wp = fi->person[i].melee; + if (wp != NULL && wp->type == wtype) + ++k; + } + msg = msg_message("battle::useflamingsword", "amount unit", k, fi->unit); + message_all(fi->side->battle, msg); + msg_release(msg); + fi->catmsg = 0; + } + + do { + dt = select_enemy(fi, 0, 1, SELECT_ADVANCE | SELECT_DISTANCE); + --force; + if (dt.fighter) { + killed += terminate(dt, *at, AT_SPELL, damage, 1); + } + } while (force && killed < enemies); + if (casualties) + *casualties = killed; + return true; +} + +#define CATAPULT_ATTACKS 6 + +static bool +attack_catapult(const troop * at, const struct weapon_type *wtype, + int *casualties) +{ + fighter *af = at->fighter; + unit *au = af->unit; + battle *b = af->side->battle; + troop dt; + int d = 0, enemies; + weapon *wp = af->person[at->index].missile; + static item_type *it_catapultammo = NULL; + if (it_catapultammo == NULL) { + it_catapultammo = it_find("catapultammo"); + } + + assert(wp->type == wtype); + assert(af->person[at->index].reload == 0); + + if (it_catapultammo != NULL) { + if (get_pooled(au, it_catapultammo->rtype, + GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, 1) <= 0) { + /* No ammo. Use other weapon if available. */ + return true; + } + } + + enemies = count_enemies(b, af, FIGHT_ROW, FIGHT_ROW, SELECT_ADVANCE); + enemies = MIN(enemies, CATAPULT_ATTACKS); + if (enemies == 0) { + return true; /* allow further attacks */ + } + + if (af->catmsg == -1) { + int i, k = 0; + message *msg; + + for (i = 0; i <= at->index; ++i) { + if (af->person[i].reload == 0 && af->person[i].missile == wp) + ++k; + } + msg = msg_message("battle::usecatapult", "amount unit", k, au); + message_all(b, msg); + msg_release(msg); + af->catmsg = 0; + } + + if (it_catapultammo != NULL) { + use_pooled(au, it_catapultammo->rtype, + GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, 1); + } + + while (--enemies >= 0) { + /* Select defender */ + dt = select_enemy(af, FIGHT_ROW, FIGHT_ROW, SELECT_ADVANCE); + if (!dt.fighter) + break; + + /* If battle succeeds */ + if (hits(*at, dt, wp)) { + d += terminate(dt, *at, AT_STANDARD, wp->type->damage[0], true); +#ifdef CATAPULT_STRUCTURAL_DAMAGE + if (dt.fighter->unit->building && rng_int() % 100 < 5) { + float dmg = + get_param_flt(global.parameters, "rules.building.damage.catapult", 1); + damage_building(b, dt.fighter->unit->building, dmg); + } else if (dt.fighter->unit->ship && rng_int() % 100 < 5) { + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.catapult", 0.01); + damage_ship(dt.fighter->unit->ship, dmg) + } +#endif + } + } + + if (casualties) + *casualties = d; + return false; /* keine weitren attacken */ +} + +void register_weapons(void) +{ + register_function((pf_generic) attack_catapult, "attack_catapult"); + register_function((pf_generic) attack_firesword, "attack_firesword"); +} diff --git a/core/src/items/weapons.h b/core/src/items/weapons.h new file mode 100644 index 000000000..d63bbd6ee --- /dev/null +++ b/core/src/items/weapons.h @@ -0,0 +1,24 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_ITM_WEAPONS +#define H_ITM_WEAPONS +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_weapons(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/items/xerewards.c b/core/src/items/xerewards.c new file mode 100644 index 000000000..26cd621bb --- /dev/null +++ b/core/src/items/xerewards.c @@ -0,0 +1,93 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "xerewards.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include + +/* libc includes */ +#include +#include +#include + +static int +use_skillpotion(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + /* the problem with making this a lua function is that there's no way + * to get the list of skills for a unit. and with the way skills are + * currently saved, it doesn't look likely (can't make eressea::list + * from them) + */ + int n; + for (n = 0; n != amount; ++n) { + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + int i; + for (i = 0; i != 3; ++i) + learn_skill(u, (skill_t)sv->id, 1.0); + ++sv; + } + } + ADDMSG(&u->faction->msgs, msg_message("skillpotion_use", "unit", u)); + + change_resource(u, itype->rtype, -amount); + return 0; +} + +static int +use_manacrystal(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + int i, sp = 0; + + if (!is_mage(u)) { + cmistake(u, u->thisorder, 295, MSG_EVENT); + return -1; + } + + for (i = 0; i != amount; ++i) { + sp += MAX(25, max_spellpoints(u->region, u) / 2); + change_spellpoints(u, sp); + } + + ADDMSG(&u->faction->msgs, msg_message("manacrystal_use", "unit aura", u, sp)); + + change_resource(u, itype->rtype, -amount); + return 0; +} + +void register_xerewards(void) +{ + register_item_use(use_skillpotion, "use_skillpotion"); + register_item_use(use_manacrystal, "use_manacrystal"); +} diff --git a/core/src/items/xerewards.h b/core/src/items/xerewards.h new file mode 100644 index 000000000..8fa8b3859 --- /dev/null +++ b/core/src/items/xerewards.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_ITM_XEREWARDS +#define H_ITM_XEREWARDS +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_xerewards(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/alchemy.c b/core/src/kernel/alchemy.c new file mode 100644 index 000000000..4a6f17052 --- /dev/null +++ b/core/src/kernel/alchemy.c @@ -0,0 +1,346 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "alchemy.h" + +#include "item.h" +#include "faction.h" +#include "message.h" +#include "build.h" +#include "magic.h" +#include "region.h" +#include "pool.h" +#include "race.h" +#include "unit.h" +#include "skill.h" +#include "move.h" + +/* util includes */ +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +/* ------------------------------------------------------------- */ + +void herbsearch(region * r, unit * u, int max) +{ + int herbsfound; + const item_type *whichherb; + + if (eff_skill(u, SK_HERBALISM, r) == 0) { + cmistake(u, u->thisorder, 59, MSG_PRODUCE); + return; + } + + if (is_guarded(r, u, GUARD_PRODUCE)) { + cmistake(u, u->thisorder, 70, MSG_EVENT); + return; + } + + whichherb = rherbtype(r); + if (whichherb == NULL) { + cmistake(u, u->thisorder, 108, MSG_PRODUCE); + return; + } + + if (max) + max = MIN(max, rherbs(r)); + else + max = rherbs(r); + herbsfound = ntimespprob(eff_skill(u, SK_HERBALISM, r) * u->number, + (double)rherbs(r) / 100.0F, -0.01F); + herbsfound = MIN(herbsfound, max); + rsetherbs(r, rherbs(r) - herbsfound); + + if (herbsfound) { + produceexp(u, SK_HERBALISM, u->number); + i_change(&u->items, whichherb, herbsfound); + ADDMSG(&u->faction->msgs, msg_message("herbfound", + "unit region amount herb", u, r, herbsfound, whichherb->rtype)); + } else { + ADDMSG(&u->faction->msgs, msg_message("researchherb_none", + "unit region", u, u->region)); + } +} + +static int begin_potion(unit * u, const potion_type * ptype, struct order *ord) +{ + static int rule_multipotion = -1; + assert(ptype != NULL); + + if (rule_multipotion < 0) { + /* should we allow multiple different potions to be used the same turn? */ + rule_multipotion = + get_param_int(global.parameters, "rules.magic.multipotion", 0); + } + if (!rule_multipotion) { + const potion_type *use = ugetpotionuse(u); + if (use != NULL && use != ptype) { + ADDMSG(&u->faction->msgs, + msg_message("errusingpotion", "unit using command", + u, use->itype->rtype, ord)); + return ECUSTOM; + } + } + return 0; +} + +static void end_potion(unit * u, const potion_type * ptype, int amount) +{ + use_pooled(u, ptype->itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + amount); + usetpotionuse(u, ptype); + + ADDMSG(&u->faction->msgs, msg_message("usepotion", + "unit potion", u, ptype->itype->rtype)); +} + +static int do_potion(unit * u, region *r, const potion_type * ptype, int amount) +{ + if (ptype == oldpotiontype[P_LIFE]) { + int holz = 0; + static int tree_type = -1; + static int tree_count = -1; + if (tree_type < 0) { + tree_type = get_param_int(global.parameters, "rules.magic.wol_type", 1); + tree_count = + get_param_int(global.parameters, "rules.magic.wol_effect", 10); + } + /* mallorn is required to make mallorn forests, wood for regular ones */ + if (fval(r, RF_MALLORN)) { + holz = use_pooled(u, rt_find("mallorn"), + GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, tree_count * amount); + } else { + holz = use_pooled(u, rt_find("log"), + GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, tree_count * amount); + } + if (r->land == 0) + holz = 0; + if (holz < tree_count * amount) { + int x = holz / tree_count; + if (holz % tree_count) + ++x; + if (x < amount) + amount = x; + } + rsettrees(r, tree_type, rtrees(r, tree_type) + holz); + ADDMSG(&u->faction->msgs, msg_message("growtree_effect", + "mage amount", u, holz)); + } else if (ptype == oldpotiontype[P_HEILWASSER]) { + u->hp = MIN(unit_max_hp(u) * u->number, u->hp + 400 * amount); + } else if (ptype == oldpotiontype[P_PEOPLE]) { + attrib *a = (attrib *) a_find(r->attribs, &at_peasantluck); + if (!a) + a = a_add(&r->attribs, a_new(&at_peasantluck)); + a->data.i += amount; + } else if (ptype == oldpotiontype[P_HORSE]) { + attrib *a = (attrib *) a_find(r->attribs, &at_horseluck); + if (!a) + a = a_add(&r->attribs, a_new(&at_horseluck)); + a->data.i += amount; + } else if (ptype == oldpotiontype[P_WAHRHEIT]) { + fset(u, UFL_DISBELIEVES); + amount = 1; + } else if (ptype == oldpotiontype[P_MACHT]) { + /* Verfünffacht die HP von max. 10 Personen in der Einheit */ + u->hp += MIN(u->number, 10 * amount) * unit_max_hp(u) * 4; + } else { + change_effect(u, ptype, 10 * amount); + } + return amount; +} + +int use_potion(unit * u, const item_type * itype, int amount, struct order *ord) +{ + const potion_type *ptype = resource2potion(itype->rtype); + + if (oldpotiontype[P_HEAL] && ptype == oldpotiontype[P_HEAL]) { + return EUNUSABLE; + } else { + int result = begin_potion(u, ptype, ord); + if (result) + return result; + amount = do_potion(u, u->region, ptype, amount); + end_potion(u, ptype, amount); + } + return 0; +} + +typedef struct potiondelay { + unit *u; + region *r; + const potion_type *ptype; + int amount; +} potiondelay; + +static void init_potiondelay(attrib * a) +{ + a->data.v = malloc(sizeof(potiondelay)); +} + +static void free_potiondelay(attrib * a) +{ + free(a->data.v); +} + +static int age_potiondelay(attrib * a) +{ + potiondelay *pd = (potiondelay *) a->data.v; + pd->amount = do_potion(pd->u, pd->r, pd->ptype, pd->amount); + return AT_AGE_REMOVE; +} + +/* TODO: + * - this should be a more general item_delay + * - it should not just happen in age(), but be done with eventhandling + */ +attrib_type at_potiondelay = { + "potiondelay", + init_potiondelay, + free_potiondelay, + age_potiondelay, 0, 0 +}; + +static attrib *make_potiondelay(unit * u, const potion_type * ptype, int amount) +{ + attrib *a = a_new(&at_potiondelay); + potiondelay *pd = (potiondelay *) a->data.v; + pd->u = u; + pd->r = u->region; + pd->ptype = ptype; + pd->amount = amount; + return a; +} + +int +use_potion_delayed(unit * u, const item_type * itype, int amount, + struct order *ord) +{ + const potion_type *ptype = resource2potion(itype->rtype); + int result = begin_potion(u, ptype, ord); + if (result) + return result; + + a_add(&u->attribs, make_potiondelay(u, ptype, amount)); + + end_potion(u, ptype, amount); + return 0; +} + +/*****************/ +/* at_effect */ +/*****************/ + +static void a_initeffect(attrib * a) +{ + a->data.v = calloc(sizeof(effect_data), 1); +} + +static void a_finalizeeffect(attrib * a) +{ + free(a->data.v); +} + +static void +a_writeeffect(const attrib * a, const void *owner, struct storage *store) +{ + effect_data *edata = (effect_data *) a->data.v; + store->w_tok(store, resourcename(edata->type->itype->rtype, 0)); + store->w_int(store, edata->value); +} + +static int a_readeffect(attrib * a, void *owner, struct storage *store) +{ + int power; + const item_type *itype; + effect_data *edata = (effect_data *) a->data.v; + char zText[32]; + + store->r_tok_buf(store, zText, sizeof(zText)); + itype = it_find(zText); + + power = store->r_int(store); + if (itype == NULL || itype->rtype == NULL || itype->rtype->ptype == NULL + || power <= 0) { + return AT_READ_FAIL; + } + edata->type = itype->rtype->ptype; + edata->value = power; + return AT_READ_OK; +} + +attrib_type at_effect = { + "effect", + a_initeffect, + a_finalizeeffect, + DEFAULT_AGE, + a_writeeffect, + a_readeffect, +}; + +int get_effect(const unit * u, const potion_type * effect) +{ + const attrib *a; + for (a = a_find(u->attribs, &at_effect); a != NULL && a->type == &at_effect; + a = a->next) { + const effect_data *data = (const effect_data *)a->data.v; + if (data->type == effect) + return data->value; + } + return 0; +} + +int change_effect(unit * u, const potion_type * effect, int delta) +{ + if (delta != 0) { + attrib *a = a_find(u->attribs, &at_effect); + effect_data *data = NULL; + + while (a && a->type == &at_effect) { + data = (effect_data *) a->data.v; + if (data->type == effect) { + if (data->value + delta == 0) { + a_remove(&u->attribs, a); + return 0; + } else { + data->value += delta; + return data->value; + } + } + a = a->next; + } + + a = a_add(&u->attribs, a_new(&at_effect)); + data = (effect_data *) a->data.v; + data->type = effect; + data->value = delta; + return data->value; + } + log_error("change effect with delta==0 for unit %s\n", itoa36(u->no)); + return 0; +} diff --git a/core/src/kernel/alchemy.h b/core/src/kernel/alchemy.h new file mode 100644 index 000000000..af7adf459 --- /dev/null +++ b/core/src/kernel/alchemy.h @@ -0,0 +1,75 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_ALCHEMY_H +#define H_KRNL_ALCHEMY_H + +#ifdef __cplusplus +extern "C" { +#endif + + enum { + /* Stufe 1 */ + P_FAST, + P_STRONG, + P_LIFE, + /* Stufe 2 */ + P_DOMORE, + P_HEILWASSER, + P_BAUERNBLUT, + /* Stufe 3 */ + P_WISE, /* 6 */ + P_FOOL, +#ifdef INSECT_POTION + P_WARMTH, +#else + P_STEEL, +#endif + P_HORSE, + P_BERSERK, /* 10 */ + /* Stufe 4 */ + P_PEOPLE, + P_WAHRHEIT, + P_MACHT, + P_HEAL, + MAX_POTIONS + }; + + extern void herbsearch(struct region *r, struct unit *u, int max); + extern int use_potion(struct unit *u, const struct item_type *itype, + int amount, struct order *); + extern int use_potion_delayed(struct unit *u, const struct item_type *itype, + int amount, struct order *); + extern void init_potions(void); + + + extern int get_effect(const struct unit *u, const struct potion_type *effect); + extern int change_effect(struct unit *u, const struct potion_type *effect, + int value); + extern struct attrib_type at_effect; + +/* rausnehmen, sobald man attribute splitten kann: */ + typedef struct effect_data { + const struct potion_type *type; + int value; + } effect_data; + +#ifdef __cplusplus +} +#endif +#endif /* ALCHEMY_H */ diff --git a/core/src/kernel/alliance.c b/core/src/kernel/alliance.c new file mode 100644 index 000000000..afd31343d --- /dev/null +++ b/core/src/kernel/alliance.c @@ -0,0 +1,524 @@ +/* vi: set ts=2: ++-------------------+ Christian Schlittchen +| | Enno Rehling +| Eressea PBEM host | Katja Zedel +| (c) 1998 - 2003 | Henning Peters +| | Ingo Wilken ++-------------------+ Stefan Reich + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#pragma region includes + +#include +#include +#include "alliance.h" + +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +#pragma endregion + +alliance *alliances = NULL; + +void free_alliance(alliance * al) +{ + free(al->name); + if (al->members) + ql_free(al->members); + free(al); +} + +alliance *makealliance(int id, const char *name) +{ + alliance *al;; + + for (;;) { + if (id > 0) { + for (al = alliances; al; al = al->next) { + if (al->id == id) { + id = 0; + break; + } + } + if (id > 0) + break; + } + id = id ? id : (1 + (rng_int() % MAX_UNIT_NR)); + } + al = calloc(1, sizeof(alliance)); + al->id = id; + if (name) { + al->name = strdup(name); + } else { + al->flags |= ALF_NON_ALLIED; + } + al->next = alliances; + return alliances = al; +} + +alliance *findalliance(int id) +{ + alliance *al; + for (al = alliances; al; al = al->next) { + if (al->id == id) + return al; + } + return NULL; +} + +typedef struct alliance_transaction { + struct alliance_transaction *next; + unit *u; + order *ord; +/* alliance * al; */ +/* variant userdata; */ +} alliance_transaction; + +static struct alliance_transaction *transactions[ALLIANCE_MAX]; + +faction *alliance_get_leader(alliance * al) +{ + if (!al->_leader) { + if (al->members) { + al->_leader = (faction *) ql_get(al->members, 0); + } + } + return al->_leader; +} + +static void create_transaction(int type, unit * u, order * ord) +{ + alliance_transaction *tr = + (alliance_transaction *) calloc(1, sizeof(alliance_transaction)); + tr->ord = ord; + tr->u = u; + tr->next = transactions[type]; + transactions[type] = tr; +} + +static void cmd_kick(const void *tnext, struct unit *u, struct order *ord) +{ + create_transaction(ALLIANCE_KICK, u, ord); +} + +static void cmd_leave(const void *tnext, struct unit *u, struct order *ord) +{ + create_transaction(ALLIANCE_LEAVE, u, ord); +} + +static void cmd_transfer(const void *tnext, struct unit *u, struct order *ord) +{ + create_transaction(ALLIANCE_TRANSFER, u, ord); +} + +static void cmd_new(const void *tnext, struct unit *u, struct order *ord) +{ + create_transaction(ALLIANCE_NEW, u, ord); +} + +static void cmd_invite(const void *tnext, struct unit *u, struct order *ord) +{ + create_transaction(ALLIANCE_INVITE, u, ord); +} + +static void cmd_join(const void *tnext, struct unit *u, struct order *ord) +{ + create_transaction(ALLIANCE_JOIN, u, ord); +} + +static void perform_kick(void) +{ + alliance_transaction **tap = transactions + ALLIANCE_KICK; + while (*tap) { + alliance_transaction *ta = *tap; + alliance *al = f_get_alliance(ta->u->faction); + + if (al && alliance_get_leader(al) == ta->u->faction) { + faction *f; + init_tokens(ta->ord); + skip_token(); + skip_token(); + f = getfaction(); + if (f && f_get_alliance(f) == al) { + setalliance(f, NULL); + } + } + *tap = ta->next; + free(ta); + } +} + +static void perform_new(void) +{ + alliance_transaction **tap = transactions + ALLIANCE_NEW; + while (*tap) { + alliance_transaction *ta = *tap; + alliance *al; + int id; + faction *f = ta->u->faction; + + init_tokens(ta->ord); + skip_token(); + skip_token(); + id = getid(); + + al = makealliance(id, itoa36(id)); + setalliance(f, al); + + *tap = ta->next; + free(ta); + } +} + +static void perform_leave(void) +{ + alliance_transaction **tap = transactions + ALLIANCE_LEAVE; + while (*tap) { + alliance_transaction *ta = *tap; + faction *f = ta->u->faction; + + setalliance(f, NULL); + + *tap = ta->next; + free(ta); + } +} + +static void perform_transfer(void) +{ + alliance_transaction **tap = transactions + ALLIANCE_TRANSFER; + while (*tap) { + alliance_transaction *ta = *tap; + alliance *al = f_get_alliance(ta->u->faction); + + if (al && alliance_get_leader(al) == ta->u->faction) { + faction *f; + init_tokens(ta->ord); + skip_token(); + skip_token(); + f = getfaction(); + if (f && f_get_alliance(f) == al) { + al->_leader = f; + } + } + *tap = ta->next; + free(ta); + } +} + +static void perform_join(void) +{ + alliance_transaction **tap = transactions + ALLIANCE_JOIN; + while (*tap) { + alliance_transaction *ta = *tap; + faction *fj = ta->u->faction; + int aid; + + init_tokens(ta->ord); + skip_token(); + skip_token(); + aid = getid(); + if (aid) { + alliance *al = findalliance(aid); + if (al && f_get_alliance(fj) != al) { + alliance_transaction **tip = transactions + ALLIANCE_INVITE; + alliance_transaction *ti = *tip; + while (ti) { + faction *fi = ti->u->faction; + if (fi && f_get_alliance(fi) == al) { + int fid; + init_tokens(ti->ord); + skip_token(); + skip_token(); + fid = getid(); + if (fid == fj->no) { + break; + } + } + tip = &ti->next; + ti = *tip; + } + if (ti) { + setalliance(fj, al); + *tip = ti->next; + free(ti); + } else { + /* TODO: error message */ + } + } + } + *tap = ta->next; + free(ta); + } +} + +static void execute(const struct syntaxtree *syntax, keyword_t kwd) +{ + int run = 0; + + region **rp = ®ions; + while (*rp) { + region *r = *rp; + unit **up = &r->units; + while (*up) { + unit *u = *up; + if (u->number) { + const struct locale *lang = u->faction->locale; + void *root = stree_find(syntax, lang); + order *ord; + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == kwd) { + do_command(root, u, ord); + run = 1; + } + } + } + if (u == *up) + up = &u->next; + } + if (*rp == r) + rp = &r->next; + } + + if (run) { + perform_kick(); + perform_leave(); + perform_transfer(); + perform_new(); + perform_join(); + } +} + +void alliance_cmd(void) +{ + static syntaxtree *stree = NULL; + if (stree == NULL) { + syntaxtree *slang = stree = stree_create(); + while (slang) { + void *leaf = 0; + add_command(&leaf, NULL, LOC(slang->lang, "new"), &cmd_new); + add_command(&leaf, NULL, LOC(slang->lang, "invite"), &cmd_invite); + add_command(&leaf, NULL, LOC(slang->lang, "join"), &cmd_join); + add_command(&leaf, NULL, LOC(slang->lang, "kick"), &cmd_kick); + add_command(&leaf, NULL, LOC(slang->lang, "leave"), &cmd_leave); + add_command(&leaf, NULL, LOC(slang->lang, "command"), &cmd_transfer); + slang->root = leaf; + slang = slang->next; + } + } + execute(stree, K_ALLIANCE); + /* some may have been kicked, must remove f->alliance==NULL */ +} + +void alliancejoin(void) +{ + static syntaxtree *stree = NULL; + if (stree == NULL) { + syntaxtree *slang = stree = stree_create(); + while (slang) { + void *leaf = 0; + add_command(&leaf, NULL, LOC(slang->lang, "join"), &cmd_join); + add_command(&slang->root, leaf, LOC(slang->lang, "alliance"), NULL); + slang = slang->next; + } + } + execute(stree, K_ALLIANCE); +} + +void setalliance(faction * f, alliance * al) +{ + if (f->alliance == al) + return; + if (f->alliance != NULL) { + int qi; + quicklist **flistp = &f->alliance->members; + + for (qi = 0; *flistp; ql_advance(flistp, &qi, 1)) { + faction *data = (faction *) ql_get(*flistp, qi); + if (data == f) { + ql_delete(flistp, qi); + break; + } + } + + if (f->alliance->_leader == f) { + if (f->alliance->members) { + f->alliance->_leader = (faction *) ql_get(f->alliance->members, 0); + } else { + f->alliance->_leader = NULL; + } + } + } + f->alliance = al; + f->alliance_joindate = turn; + if (al != NULL) { + ql_push(&al->members, f); + if (al->_leader == NULL) { + al->_leader = f; + } + } +} + +const char *alliancename(const alliance * al) +{ + typedef char name[OBJECTIDSIZE + 1]; + static name idbuf[8]; + static int nextbuf = 0; + + char *ibuf = idbuf[(++nextbuf) % 8]; + + if (al && al->name) { + slprintf(ibuf, sizeof(name), "%s (%s)", al->name, itoa36(al->id)); + } else { + return NULL; + } + return ibuf; +} + +void alliancevictory(void) +{ + const struct building_type *btype = bt_find("stronghold"); + region *r = regions; + alliance *al = alliances; + if (btype == NULL) + return; + while (r != NULL) { + building *b = r->buildings; + while (b != NULL) { + if (b->type == btype) { + unit *u = building_owner(b); + if (u) { + fset(u->faction->alliance, FFL_MARK); + } + } + b = b->next; + } + r = r->next; + } + while (al != NULL) { + if (!fval(al, FFL_MARK)) { + int qi; + quicklist *flist = al->members; + for (qi = 0; flist; ql_advance(&flist, &qi, 1)) { + faction *f = (faction *) ql_get(flist, qi); + if (f->alliance == al) { + ADDMSG(&f->msgs, msg_message("alliance::lost", "alliance", al)); + destroyfaction(f); + } + } + } else { + freset(al, FFL_MARK); + } + al = al->next; + } +} + +int victorycondition(const alliance * al, const char *name) +{ + const char *gems[] = + { "opal", "diamond", "zaphire", "topaz", "beryl", "agate", "garnet", + "emerald", NULL }; + if (strcmp(name, "gems") == 0) { + const char **igem; + + for (igem = gems; *igem; ++igem) { + const struct item_type *itype = it_find(*igem); + quicklist *flist = al->members; + int qi; + bool found = false; + + assert(itype != NULL); + for (qi = 0; flist && !found; ql_advance(&flist, &qi, 1)) { + faction *f = (faction *) ql_get(flist, 0); + unit *u; + + for (u = f->units; u; u = u->nextF) { + if (i_get(u->items, itype) > 0) { + found = true; + break; + } + } + } + if (!found) + return 0; + } + return 1; + + } else if (strcmp(name, "phoenix") == 0) { + quicklist *flist = al->members; + int qi; + + for (qi = 0; flist; ql_advance(&flist, &qi, 1)) { + faction *f = (faction *) ql_get(flist, qi); + if (find_key(f->attribs, atoi36("phnx"))) { + return 1; + } + } + return 0; + + } else if (strcmp(name, "pyramid") == 0) { + + /* Logik: + * - if (pyr > last_passed_size && pyr > all_others) { + * pyr->passed->counter++; + * for(all_other_pyrs) { + * pyr->passed->counter=0; + * } + * + * if(pyr->passed->counter >= 3) { + * set(pyr, passed); + * pyr->owner->set_attrib(pyra); + * } + * last_passed_size = pyr->size; + * } + */ + + quicklist *flist = al->members; + int qi; + + for (qi = 0; flist; ql_advance(&flist, &qi, 1)) { + faction *f = (faction *) ql_get(flist, qi); + if (find_key(f->attribs, atoi36("pyra"))) { + return 1; + } + } + return 0; + } + return -1; +} + +void alliance_setname(alliance * self, const char *name) +{ + free(self->name); + if (name) + self->name = strdup(name); + else + self->name = NULL; +} diff --git a/core/src/kernel/alliance.h b/core/src/kernel/alliance.h new file mode 100644 index 000000000..04871c50a --- /dev/null +++ b/core/src/kernel/alliance.h @@ -0,0 +1,70 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_ALLIANCE +#define H_KRNL_ALLIANCE +#ifdef __cplusplus +extern "C" { +#endif + + struct plane; + struct attrib; + struct unit; + struct faction; + struct region; + + enum { + ALLIANCE_KICK, + ALLIANCE_LEAVE, + ALLIANCE_TRANSFER, + ALLIANCE_NEW, + ALLIANCE_INVITE, + ALLIANCE_JOIN, + ALLIANCE_MAX + }; + +#define ALF_NON_ALLIED (1<<0) /* this alliance is just a default for a non-allied faction */ + +#define ALLY_ENEMY (1<<0) + + typedef struct alliance { + struct alliance *next; + struct faction *_leader; + struct quicklist *members; + unsigned int flags; + int id; + char *name; + struct ally *allies; + } alliance; + + extern alliance *alliances; + extern alliance *findalliance(int id); + extern alliance *makealliance(int id, const char *name); + extern const char *alliancename(const struct alliance *al); + extern void setalliance(struct faction *f, alliance * al); + void free_alliance(struct alliance *al); + extern struct faction *alliance_get_leader(struct alliance *al); + extern void alliance_cmd(void); + + void alliance_setname(alliance * self, const char *name); +/* execute commands */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/ally.c b/core/src/kernel/ally.c new file mode 100644 index 000000000..cf5772fd6 --- /dev/null +++ b/core/src/kernel/ally.c @@ -0,0 +1,39 @@ +#include "types.h" +#include "ally.h" + +#include + +ally * ally_find(ally *al, const struct faction *f) { + for (;al;al=al->next) { + if (al->faction==f) return al; + } + return 0; +} + +ally * ally_add(ally **al_p, struct faction *f) { + ally * al; + while (*al_p) { + al = *al_p; + if (al->faction==f) return al; + al_p = &al->next; + } + al = (ally *)malloc(sizeof(ally)); + al->faction = f; + al->status = 0; + al->next = 0; + *al_p = al; + return al; +} + +void ally_remove(ally **al_p, struct faction *f) { + ally * al; + while (*al_p) { + al = *al_p; + if (al->faction==f) { + *al_p = al->next; + free(al); + break; + } + al_p = &al->next; + } +} diff --git a/core/src/kernel/ally.h b/core/src/kernel/ally.h new file mode 100644 index 000000000..5e09b721c --- /dev/null +++ b/core/src/kernel/ally.h @@ -0,0 +1,40 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef ALLY_H +#define ALLY_H + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct ally { + struct ally *next; + struct faction *faction; + int status; + } ally; + + ally * ally_find(ally *al, const struct faction *f); + ally * ally_add(ally **al_p, struct faction *f); + void ally_remove(ally **al_p, struct faction *f); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/src/kernel/ally_test.c b/core/src/kernel/ally_test.c new file mode 100644 index 000000000..c7b8500ad --- /dev/null +++ b/core/src/kernel/ally_test.c @@ -0,0 +1,27 @@ +#include +#include "types.h" +#include "ally.h" + +#include +#include + +static void test_ally(CuTest * tc) +{ + ally * al = 0; + struct faction * f1 = test_create_faction(0); + + ally_add(&al, f1); + CuAssertPtrNotNull(tc, al); + CuAssertPtrEquals(tc, f1, ally_find(al, f1)->faction); + + ally_remove(&al, f1); + CuAssertPtrEquals(tc, 0, al); + CuAssertPtrEquals(tc, 0, ally_find(al, f1)); +} + +CuSuite *get_ally_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_ally); + return suite; +} diff --git a/core/src/kernel/battle.c b/core/src/kernel/battle.c new file mode 100644 index 000000000..9b249609d --- /dev/null +++ b/core/src/kernel/battle.c @@ -0,0 +1,4369 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#pragma region includes + +#include +#include +#include "battle.h" + +#include "alchemy.h" +#include "alliance.h" +#include "build.h" +#include "building.h" +#include "curse.h" +#include "equipment.h" +#include "faction.h" +#include "group.h" +#include "item.h" +#include "magic.h" +#include "message.h" +#include "move.h" +#include "names.h" +#include "order.h" +#include "plane.h" +#include "race.h" +#include "region.h" +#include "reports.h" +#include "ship.h" +#include "skill.h" +#include "spell.h" +#include "terrain.h" +#include "unit.h" + +/* attributes includes */ +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +#pragma endregion + +static FILE *bdebug; + +#define TACTICS_BONUS 1 /* when undefined, we have a tactics round. else this is the bonus tactics give */ +#define TACTICS_MODIFIER 1 /* modifier for generals in the fromt/rear */ + +#define CATAPULT_INITIAL_RELOAD 4 /* erster schuss in runde 1 + rng_int() % INITIAL */ +#define CATAPULT_STRUCTURAL_DAMAGE + +#define BASE_CHANCE 70 /* 70% Basis-Überlebenschance */ +#ifdef NEW_COMBATSKILLS_RULE +#define TDIFF_CHANGE 5 /* 5% höher pro Stufe */ +#define DAMAGE_QUOTIENT 2 /* damage += skilldiff/DAMAGE_QUOTIENT */ +#else +#define TDIFF_CHANGE 10 +# define DAMAGE_QUOTIENT 1 /* damage += skilldiff/DAMAGE_QUOTIENT */ +#endif + +#undef DEBUG_FAST /* should be disabled when b->fast and b->rowcache works */ +#define DEBUG_SELECT /* should be disabled if select_enemy works */ + +typedef enum combatmagic { + DO_PRECOMBATSPELL, + DO_POSTCOMBATSPELL +} combatmagic_t; + +/* globals */ +static int obs_count = 0; + +#define MINSPELLRANGE 1 +#define MAXSPELLRANGE 7 + +#ifndef ROW_FACTOR +# define ROW_FACTOR 10 +#endif +static const double EFFECT_PANIC_SPELL = 0.25; +static const double TROLL_REGENERATION = 0.10; + +/* Nach dem alten System: */ +static int missile_range[2] = { FIGHT_ROW, BEHIND_ROW }; +static int melee_range[2] = { FIGHT_ROW, FIGHT_ROW }; + +static message *msg_separator; + +const troop no_troop = { 0, 0 }; + +static int max_turns = 0; +static int damage_rules = 0; +static int loot_rules = 0; +static int skill_formula = 0; + +#define FORMULA_ORIG 0 +#define FORMULA_NEW 1 + +#define LOOT_MONSTERS (1<<0) +#define LOOT_SELF (1<<1) /* code is mutually exclusive with LOOT_OTHERS */ +#define LOOT_OTHERS (1<<2) +#define LOOT_KEEPLOOT (1<<4) + +#define DAMAGE_CRITICAL (1<<0) +#define DAMAGE_MELEE_BONUS (1<<1) +#define DAMAGE_MISSILE_BONUS (1<<2) +#define DAMAGE_UNARMED_BONUS (1<<3) +#define DAMAGE_SKILL_BONUS (1<<4) +/** initialize rules from configuration. + */ +static void static_rules(void) +{ + loot_rules = + get_param_int(global.parameters, "rules.combat.loot", + LOOT_MONSTERS | LOOT_OTHERS | LOOT_KEEPLOOT); + /* new formula to calculate to-hit-chance */ + skill_formula = + get_param_int(global.parameters, "rules.combat.skill_formula", + FORMULA_ORIG); + /* maximum number of combat turns */ + max_turns = + get_param_int(global.parameters, "rules.combat.turns", COMBAT_TURNS); + /* damage calculation */ + if (get_param_int(global.parameters, "rules.combat.critical", 1)) { + damage_rules |= DAMAGE_CRITICAL; + } + if (get_param_int(global.parameters, "rules.combat.melee_bonus", 1)) { + damage_rules |= DAMAGE_MELEE_BONUS; + } + if (get_param_int(global.parameters, "rules.combat.missile_bonus", 1)) { + damage_rules |= DAMAGE_MISSILE_BONUS; + } + if (get_param_int(global.parameters, "rules.combat.unarmed_bonus", 1)) { + damage_rules |= DAMAGE_UNARMED_BONUS; + } + if (get_param_int(global.parameters, "rules.combat.skill_bonus", 1)) { + damage_rules |= DAMAGE_SKILL_BONUS; + } +} + +static int army_index(side * s) +{ + return s->index; +} + +static char *sidename(side * s) +{ +#define SIDENAMEBUFLEN 256 + static int bufno; /* STATIC_XCALL: used across calls */ + static char sidename_buf[4][SIDENAMEBUFLEN]; /* STATIC_RESULT: used for return, not across calls */ + + bufno = bufno % 4; + strlcpy(sidename_buf[bufno], factionname(s->stealthfaction?s->stealthfaction:s->faction), SIDENAMEBUFLEN); + return sidename_buf[bufno++]; +} + +static const char *sideabkz(side * s, bool truename) +{ + static char sideabkz_buf[8]; /* STATIC_RESULT: used for return, not across calls */ + const faction *f = (s->stealthfaction + && !truename) ? s->stealthfaction : s->faction; + +#undef SIDE_ABKZ +#ifdef SIDE_ABKZ + abkz(f->name, sideabkz_buf, sizeof(sideabkz_buf), 3); +#else + strcpy(sideabkz_buf, itoa36(f->no)); +#endif + return sideabkz_buf; +} + +static void message_faction(battle * b, faction * f, struct message *m) +{ + region *r = b->region; + + if (f->battles == NULL || f->battles->r != r) { + struct bmsg *bm = (struct bmsg *)calloc(1, sizeof(struct bmsg)); + bm->next = f->battles; + f->battles = bm; + bm->r = r; + } + add_message(&f->battles->msgs, m); +} + +int armedmen(const unit * u, bool siege_weapons) +{ + item *itm; + int n = 0; + if (!(urace(u)->flags & RCF_NOWEAPONS)) { + if (effskill(u, SK_WEAPONLESS) >= 1) { + /* kann ohne waffen bewachen: fuer drachen */ + n = u->number; + } else { + /* alle Waffen werden gezaehlt, und dann wird auf die Anzahl + * Personen minimiert */ + for (itm = u->items; itm; itm = itm->next) { + const weapon_type *wtype = resource2weapon(itm->type->rtype); + if (wtype == NULL || (!siege_weapons && (wtype->flags & WTF_SIEGE))) + continue; + if (effskill(u, wtype->skill) >= 1) + n += itm->number; + /* if (effskill(u, wtype->skill) >= wtype->minskill) n += itm->number; */ + if (n > u->number) + break; + } + n = MIN(n, u->number); + } + } + return n; +} + +void message_all(battle * b, message * m) +{ + bfaction *bf; + plane *p = rplane(b->region); + watcher *w; + + for (bf = b->factions; bf; bf = bf->next) { + message_faction(b, bf->faction, m); + } + if (p) + for (w = p->watchers; w; w = w->next) { + for (bf = b->factions; bf; bf = bf->next) + if (bf->faction == w->faction) + break; + if (bf == NULL) + message_faction(b, w->faction, m); + } +} + +static void fbattlerecord(battle * b, faction * f, const char *s) +{ + message *m = msg_message("battle_msg", "string", s); + message_faction(b, f, m); + msg_release(m); +} + +/* being an enemy or a friend is (and must always be!) symmetrical */ +#define enemy_i(as, di) (as->relations[di]&E_ENEMY) +#define friendly_i(as, di) (as->relations[di]&E_FRIEND) +#define enemy(as, ds) (as->relations[ds->index]&E_ENEMY) +#define friendly(as, ds) (as->relations[ds->index]&E_FRIEND) + +static bool set_enemy(side * as, side * ds, bool attacking) +{ + int i; + for (i = 0; i != MAXSIDES; ++i) { + if (ds->enemies[i] == NULL) + ds->enemies[i] = as; + if (ds->enemies[i] == as) + break; + } + for (i = 0; i != MAXSIDES; ++i) { + if (as->enemies[i] == NULL) + as->enemies[i] = ds; + if (as->enemies[i] == ds) + break; + } + assert(i != MAXSIDES); + if (attacking) + as->relations[ds->index] |= E_ATTACKING; + if ((ds->relations[as->index] & E_ENEMY) == 0) { + /* enemy-relation are always symmetrical */ + assert((as->relations[ds->index] & (E_ENEMY | E_FRIEND)) == 0); + ds->relations[as->index] |= E_ENEMY; + as->relations[ds->index] |= E_ENEMY; + return true; + } + return false; +} + +static void set_friendly(side * as, side * ds) +{ + assert((as->relations[ds->index] & E_ENEMY) == 0); + ds->relations[as->index] |= E_FRIEND; + as->relations[ds->index] |= E_FRIEND; +} + +static int allysfm(const side * s, const faction * f, int mode) +{ + if (s->faction == f) + return mode; + if (s->group) { + return alliedgroup(s->battle->plane, s->faction, f, s->group->allies, mode); + } + return alliedfaction(s->battle->plane, s->faction, f, mode); +} + +static int allysf(const side * s, const faction * f) +{ + return allysfm(s, f, HELP_FIGHT); +} + +static int dead_fighters(const fighter * df) +{ + return df->unit->number - df->alive - df->run.number; +} + +fighter *select_corpse(battle * b, fighter * af) +/* Wählt eine Leiche aus, der af hilft. casualties ist die Anzahl der + * Toten auf allen Seiten (im Array). Wenn af == NULL, wird die + * Parteizugehörigkeit ignoriert, und irgendeine Leiche genommen. + * + * Untote werden nicht ausgewählt (casualties, not dead) */ +{ + int si, di, maxcasualties = 0; + fighter *df; + side *s; + + for (si = 0; si != b->nsides; ++si) { + side *s = b->sides + si; + if (af == NULL || (!enemy_i(af->side, si) && allysf(af->side, s->faction))) { + maxcasualties += s->casualties; + } + } + di = rng_int() % maxcasualties; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + for (df = s->fighters; df; df = df->next) { + /* Geflohene haben auch 0 hp, dürfen hier aber nicht ausgewählt + * werden! */ + int dead = dead_fighters(df); + if (!playerrace(u_race(df->unit))) + continue; + + if (af && !helping(af->side, df->side)) + continue; + if (di < dead) { + return df; + } + di -= dead; + } + } + + return NULL; +} + +bool helping(const side * as, const side * ds) +{ + if (as->faction == ds->faction) + return true; + return (bool) (!enemy(as, ds) && allysf(as, ds->faction)); +} + +int statusrow(int status) +{ + switch (status) { + case ST_AGGRO: + case ST_FIGHT: + return FIGHT_ROW; + case ST_BEHIND: + case ST_CHICKEN: + return BEHIND_ROW; + case ST_AVOID: + return AVOID_ROW; + case ST_FLEE: + return FLEE_ROW; + default: + assert(!"unknown combatrow"); + } + return FIGHT_ROW; +} + +static double hpflee(int status) + /* if hp drop below this percentage, run away */ +{ + switch (status) { + case ST_AGGRO: + return 0.0; + case ST_FIGHT: + case ST_BEHIND: + return 0.2; + case ST_CHICKEN: + case ST_AVOID: + return 0.9; + case ST_FLEE: + return 1.0; + default: + assert(!"unknown combatrow"); + } + return 0.0; +} + +static int get_row(const side * s, int row, const side * vs) +{ + bool counted[MAXSIDES]; + int enemyfront = 0; + int line, result; + int retreat = 0; + int size[NUMROWS]; + int front = 0; + battle *b = s->battle; + + memset(counted, 0, sizeof(counted)); + memset(size, 0, sizeof(size)); + for (line = FIRST_ROW; line != NUMROWS; ++line) { + int si, sa_i; + /* how many enemies are there in the first row? */ + for (si = 0; s->enemies[si]; ++si) { + side *se = s->enemies[si]; + if (se->size[line] > 0) { + enemyfront += se->size[line]; + /* - s->nonblockers[line] (nicht, weil angreifer) */ + } + } + for (sa_i = 0; sa_i != b->nsides; ++sa_i) { + side *sa = b->sides + sa_i; + /* count people that like me, but don't like my enemy */ + if (friendly_i(s, sa_i) && enemy_i(vs, sa_i)) { + if (!counted[sa_i]) { + int i; + + for (i = 0; i != NUMROWS; ++i) { + size[i] += sa->size[i] - sa->nonblockers[i]; + } + counted[sa_i] = true; + } + } + } + if (enemyfront) + break; + } + if (enemyfront) { + for (line = FIRST_ROW; line != NUMROWS; ++line) { + front += size[line]; + if (!front || front < enemyfront / ROW_FACTOR) + ++retreat; + else if (front) + break; + } + } + + /* every entry in the size[] array means someone trying to defend us. + * 'retreat' is the number of rows falling. + */ + result = MAX(FIRST_ROW, row - retreat); + + return result; +} + +int get_unitrow(const fighter * af, const side * vs) +{ + int row = statusrow(af->status); + if (vs == NULL) { + int i; + for (i = FIGHT_ROW; i != row; ++i) + if (af->side->size[i]) + break; + return FIGHT_ROW + (row - i); + } else { + battle *b = vs->battle; + if (row != b->rowcache.row || b->alive != b->rowcache.alive + || af->side != b->rowcache.as || vs != b->rowcache.vs) { + b->rowcache.alive = b->alive; + b->rowcache.as = af->side; + b->rowcache.vs = vs; + b->rowcache.row = row; + b->rowcache.result = get_row(af->side, row, vs); + return b->rowcache.result; + } +#ifdef DEBUG_FAST /* validation code */ + { + int i = get_row(af->side, row, vs); + assert(i == b->rowcache.result); + } +#endif + return b->rowcache.result; + } +} + +static void reportcasualties(battle * b, fighter * fig, int dead) +{ + struct message *m; + region *r = NULL; + if (fig->alive == fig->unit->number) + return; + m = msg_message("casualties", "unit runto run alive fallen", + fig->unit, r, fig->run.number, fig->alive, dead); + message_all(b, m); + msg_release(m); +} + +static int +contest_classic(int skilldiff, const armor_type * ar, const armor_type * sh) +{ + int p, vw = BASE_CHANCE - TDIFF_CHANGE * skilldiff; + double mod = 1.0; + + if (ar != NULL) + mod *= (1 + ar->penalty); + if (sh != NULL) + mod *= (1 + sh->penalty); + vw = (int)(100 - ((100 - vw) * mod)); + + do { + p = rng_int() % 100; + vw -= p; + } + while (vw >= 0 && p >= 90); + return (vw <= 0); +} + +/** new rule for Eressea 1.5 + * \param skilldiff - the attack skill with every modifier applied + */ +static int +contest_new(int skilldiff, const troop dt, const armor_type * ar, + const armor_type * sh) +{ + double tohit = 0.5 + skilldiff * 0.1; + if (tohit < 0.5) + tohit = 0.5; + if (chance(tohit)) { + int defense = effskill(dt.fighter->unit, SK_STAMINA); + double tosave = defense * 0.05; + return !chance(tosave); + } + return 0; +} + +static int +contest(int skdiff, const troop dt, const armor_type * ar, + const armor_type * sh) +{ + if (skill_formula == FORMULA_ORIG) { + return contest_classic(skdiff, ar, sh); + } else { + return contest_new(skdiff, dt, ar, sh); + } +} + +static bool is_riding(const troop t) +{ + if (t.fighter->building != NULL) + return false; + if (t.fighter->horses + t.fighter->elvenhorses > t.index) + return true; + return false; +} + +static weapon *preferred_weapon(const troop t, bool attacking) +{ + weapon *missile = t.fighter->person[t.index].missile; + weapon *melee = t.fighter->person[t.index].melee; + if (attacking) { + if (melee == NULL || (missile && missile->attackskill > melee->attackskill)) { + return missile; + } + } else { + if (melee == NULL || (missile + && missile->defenseskill > melee->defenseskill)) { + return missile; + } + } + return melee; +} + +static weapon *select_weapon(const troop t, bool attacking, + bool ismissile) + /* select the primary weapon for this trooper */ +{ + if (attacking) { + if (ismissile) { + /* from the back rows, have to use your missile weapon */ + return t.fighter->person[t.index].missile; + } + } else { + if (!ismissile) { + /* have to use your melee weapon if it's melee */ + return t.fighter->person[t.index].melee; + } + } + return preferred_weapon(t, attacking); +} + +static bool i_canuse(const unit * u, const item_type * itype) +{ + if (itype->canuse) { + return itype->canuse(u, itype); + } + return true; +} + +static int +weapon_skill(const weapon_type * wtype, const unit * u, bool attacking) + /* the 'pure' skill when using this weapon to attack or defend. + * only undiscriminate modifiers (not affected by troops or enemies) + * are taken into account, e.g. no horses, magic, etc. */ +{ + int skill; + + if (wtype == NULL) { + skill = effskill(u, SK_WEAPONLESS); + if (skill <= 0) { + /* wenn kein waffenloser kampf, dann den rassen-defaultwert */ + if (u_race(u) == new_race[RC_ORC]) { + int sword = effskill(u, SK_MELEE); + int spear = effskill(u, SK_SPEAR); + skill = MAX(sword, spear) - 3; + if (attacking) { + skill = MAX(skill, u_race(u)->at_default); + } else { + skill = MAX(skill, u_race(u)->df_default); + } + } else { + if (attacking) { + skill = u_race(u)->at_default; + } else { + skill = u_race(u)->df_default; + } + } + } else { + /* der rassen-defaultwert kann höher sein als der Talentwert von + * waffenloser kampf */ + if (attacking) { + if (skill < u_race(u)->at_default) + skill = u_race(u)->at_default; + } else { + if (skill < u_race(u)->df_default) + skill = u_race(u)->df_default; + } + } + if (attacking) { + skill += u_race(u)->at_bonus; + if (fval(u->region->terrain, SEA_REGION) && u->ship) + skill += u->ship->type->at_bonus; + } else { + skill += u_race(u)->df_bonus; + if (fval(u->region->terrain, SEA_REGION) && u->ship) + skill += u->ship->type->df_bonus; + } + } else { + /* changed: if we own a weapon, we have at least a skill of 0 */ + if (!i_canuse(u, wtype->itype)) + return -1; + skill = effskill(u, wtype->skill); + if (skill < wtype->minskill) + skill = 0; + if (skill > 0) { + if (attacking) { + skill += u_race(u)->at_bonus; + } else { + skill += u_race(u)->df_bonus; + } + } + if (attacking) { + skill += wtype->offmod; + } else { + skill += wtype->defmod; + } + } + + return skill; +} + +static int CavalrySkill(void) +{ + static int skill = -1; + + if (skill < 0) { + skill = get_param_int(global.parameters, "rules.cavalry.skill", 2); + } + return skill; +} + +#define BONUS_SKILL 1 +#define BONUS_DAMAGE 2 +static int CavalryBonus(const unit * u, troop enemy, int type) +{ + static int mode = -1; + + if (mode < 0) { + mode = get_param_int(global.parameters, "rules.cavalry.mode", 1); + } + if (mode == 0) { + /* old rule, Eressea 1.0 compat */ + return (type == BONUS_SKILL) ? 2 : 0; + } else { + /* new rule, chargers in Eressea 1.1 */ + int skl = effskill(u, SK_RIDING); + /* only half against trolls */ + if (skl > 0) { + if (type == BONUS_DAMAGE) { + int dmg = MIN(skl, 8); + if (u_race(enemy.fighter->unit) == new_race[RC_TROLL]) { + dmg = dmg / 4; + } else { + dmg = dmg / 2; + } + return dmg; + } else { + skl = skl / 2; + return MIN(skl, 4); + } + } + } + return 0; +} + +static int +weapon_effskill(troop t, troop enemy, const weapon * w, bool attacking, + bool missile) + /* effektiver Waffenskill während des Kampfes */ +{ + /* In dieser Runde alle die Modifier berechnen, die fig durch die + * Waffen bekommt. */ + fighter *tf = t.fighter; + unit *tu = t.fighter->unit; + int skill; + const weapon_type *wtype = w ? w->type : NULL; + + if (wtype == NULL) { + /* Ohne Waffe: Waffenlose Angriffe */ + skill = weapon_skill(NULL, tu, attacking); + } else { + if (attacking) { + skill = w->attackskill; + } else { + skill = w->defenseskill; + } + if (wtype->modifiers != NULL) { + /* Pferdebonus, Lanzenbonus, usw. */ + int m; + unsigned int flags = + WMF_SKILL | (attacking ? WMF_OFFENSIVE : WMF_DEFENSIVE); + + if (is_riding(t)) + flags |= WMF_RIDING; + else + flags |= WMF_WALKING; + if (is_riding(enemy)) + flags |= WMF_AGAINST_RIDING; + else + flags |= WMF_AGAINST_WALKING; + + for (m = 0; wtype->modifiers[m].value; ++m) { + if ((wtype->modifiers[m].flags & flags) == flags) { + race_list *rlist = wtype->modifiers[m].races; + if (rlist != NULL) { + while (rlist) { + if (rlist->data == u_race(tu)) + break; + rlist = rlist->next; + } + if (rlist == NULL) + continue; + } + skill += wtype->modifiers[m].value; + } + } + } + } + + /* Burgenbonus, Pferdebonus */ + if (is_riding(t) && (wtype == NULL || (fval(wtype, WTF_HORSEBONUS) + && !fval(wtype, WTF_MISSILE)))) { + skill += CavalryBonus(tu, enemy, BONUS_SKILL); + if (wtype) + skill = + skillmod(urace(tu)->attribs, tu, tu->region, wtype->skill, skill, + SMF_RIDING); + } + + if (t.index < tf->elvenhorses) { + /* Elfenpferde: Helfen dem Reiter, egal ob und welche Waffe. Das ist + * eleganter, und vor allem einfacher, sonst muß man noch ein + * WMF_ELVENHORSE einbauen. */ + skill += 2; + } + + if (skill > 0 && !attacking && missile) { + /* + * Wenn ich verteidige, und nicht direkt meinem Feind gegenüberstehe, + * halbiert sich mein Skill: (z.B. gegen Fernkämpfer. Nahkämpfer + * können mich eh nicht treffen) + */ + skill /= 2; + } + return skill; +} + +static const armor_type *select_armor(troop t, bool shield) +{ + unsigned int type = shield ? ATF_SHIELD : 0; + unit *u = t.fighter->unit; + const armor *a = t.fighter->armors; + int geschuetzt = 0; + + /* some monsters should not use armor (dragons in chainmail? ha!) */ + if (!(u_race(u)->battle_flags & BF_EQUIPMENT)) + return NULL; + + /* ... neither do werewolves */ + if (fval(u, UFL_WERE)) { + return NULL; + } + + for (; a; a = a->next) { + if ((a->atype->flags & ATF_SHIELD) == type) { + geschuetzt += a->count; + if (geschuetzt > t.index) { + /* unser Kandidat wird geschuetzt */ + return a->atype; + } + } + } + return NULL; +} + +/* Hier ist zu beachten, ob und wie sich Zauber und Artefakte, die + * Rüstungschutz geben, addieren. + * - Artefakt I_TROLLBELT gibt Rüstung +1 + * - Zauber Rindenhaut gibt Rüstung +3 + */ +int select_magicarmor(troop t) +{ + unit *u = t.fighter->unit; + int geschuetzt = 0; + int ma = 0; + + geschuetzt = MIN(get_item(u, I_TROLLBELT), u->number); + + if (geschuetzt > t.index) /* unser Kandidat wird geschuetzt */ + ma += 1; + + return ma; +} + +/* Sind side ds und Magier des meffect verbündet, dann return 1*/ +bool meffect_protection(battle * b, meffect * s, side * ds) +{ + if (!s->magician->alive) + return false; + if (s->duration <= 0) + return false; + if (enemy(s->magician->side, ds)) + return false; + if (allysf(s->magician->side, ds->faction)) + return true; + return false; +} + +/* Sind side as und Magier des meffect verfeindet, dann return 1*/ +bool meffect_blocked(battle * b, meffect * s, side * as) +{ + if (!s->magician->alive) + return false; + if (s->duration <= 0) + return false; + if (enemy(s->magician->side, as)) + return true; + return false; +} + +/* rmfighter wird schon im PRAECOMBAT gebraucht, da gibt es noch keine + * troops */ +void rmfighter(fighter * df, int i) +{ + side *ds = df->side; + + /* nicht mehr personen abziehen, als in der Einheit am Leben sind */ + assert(df->alive >= i); + assert(df->alive <= df->unit->number); + + /* erst ziehen wir die Anzahl der Personen von den Kämpfern in der + * Schlacht, dann von denen auf dieser Seite ab*/ + df->side->alive -= i; + df->side->battle->alive -= i; + + /* Dann die Kampfreihen aktualisieren */ + ds->size[SUM_ROW] -= i; + ds->size[statusrow(df->status)] -= i; + + /* Spezialwirkungen, z.B. Schattenritter */ + if (u_race(df->unit)->battle_flags & BF_NOBLOCK) { + ds->nonblockers[SUM_ROW] -= i; + ds->nonblockers[statusrow(df->status)] -= i; + } + + /* und die Einheit selbst aktualisieren */ + df->alive -= i; +} + +static void rmtroop(troop dt) +{ + fighter *df = dt.fighter; + + /* troop ist immer eine einzele Person */ + rmfighter(df, 1); + + assert(dt.index >= 0 && dt.index < df->unit->number); + df->person[dt.index] = df->person[df->alive - df->removed]; + if (df->removed) { + df->person[df->alive - df->removed] = df->person[df->alive]; + } + df->person[df->alive].hp = 0; +} + +void remove_troop(troop dt) +{ + fighter *df = dt.fighter; + struct person p = df->person[dt.index]; + battle *b = df->side->battle; + b->fast.alive = -1; /* invalidate cached value */ + b->rowcache.alive = -1; /* invalidate cached value */ + ++df->removed; + ++df->side->removed; + df->person[dt.index] = df->person[df->alive - df->removed]; + df->person[df->alive - df->removed] = p; +} + +void kill_troop(troop dt) +{ + fighter *df = dt.fighter; + unit *du = df->unit; + + rmtroop(dt); + if (!df->alive) { + char eqname[64]; + const struct equipment *eq; + if (u_race(du)->itemdrop) { + item *drops = u_race(du)->itemdrop(u_race(du), du->number - df->run.number); + + if (drops != NULL) { + i_merge(&du->items, &drops); + } + } + sprintf(eqname, "%s_spoils", u_race(du)->_name[0]); + eq = get_equipment(eqname); + if (eq != NULL) { + equip_items(&du->items, eq); + } + } +} + +/** reduces the target's exp by an equivalent of n points learning + * 30 points = 1 week + */ +void drain_exp(struct unit *u, int n) +{ + skill_t sk = (skill_t) (rng_int() % MAXSKILLS); + skill_t ssk; + + ssk = sk; + + while (get_level(u, sk) == 0) { + sk++; + if (sk == MAXSKILLS) + sk = 0; + if (sk == ssk) { + sk = NOSKILL; + break; + } + } + if (sk != NOSKILL) { + skill *sv = get_skill(u, sk); + while (n > 0) { + if (n >= 30 * u->number) { + reduce_skill(u, sv, 1); + n -= 30; + } else { + if (rng_int() % (30 * u->number) < n) + reduce_skill(u, sv, 1); + n = 0; + } + } + } +} + +const char *rel_dam(int dam, int hp) +{ + double q = (double)dam / (double)hp; + + if (q > 0.75) { + return "eine klaffende Wunde"; + } else if (q > 0.5) { + return "eine schwere Wunde"; + } else if (q > 0.25) { + return "eine Wunde"; + } + return "eine kleine Wunde"; +} + +static void vampirism(troop at, int damage) +{ + static int vampire = -1; + if (vampire < 0) + vampire = get_param_int(global.parameters, "rules.combat.demon_vampire", 0); + if (vampire > 0) { + int gain = damage / vampire; + int chance = damage - vampire * gain; + if (chance > 0 && (rng_int() % vampire < chance)) + ++gain; + if (gain > 0) { + int maxhp = unit_max_hp(at.fighter->unit); + at.fighter->person[at.index].hp = + MIN(gain + at.fighter->person[at.index].hp, maxhp); + } + } +} + +static int natural_armor(unit * du) +{ + static int *bonus = 0; + int an = u_race(du)->armor; + if (bonus == 0) { + bonus = calloc(sizeof(int), num_races); + } + if (bonus[u_race(du)->index] == 0) { + bonus[u_race(du)->index] = + get_param_int(u_race(du)->parameters, "armor.stamina", -1); + if (bonus[u_race(du)->index] == 0) + bonus[u_race(du)->index] = -1; + } + if (bonus[u_race(du)->index] > 0) { + int sk = effskill(du, SK_STAMINA); + sk /= bonus[u_race(du)->index]; + an += sk; + } + return an; +} + +bool +terminate(troop dt, troop at, int type, const char *damage, bool missile) +{ + item **pitm; + fighter *df = dt.fighter; + fighter *af = at.fighter; + unit *au = af->unit; + unit *du = df->unit; + battle *b = df->side->battle; + int heiltrank = 0; + static int rule_armor = -1; + + /* Schild */ + side *ds = df->side; + int hp; + + int ar = 0, an, am; + const armor_type *armor = select_armor(dt, true); + const armor_type *shield = select_armor(dt, false); + + const weapon_type *dwtype = NULL; + const weapon_type *awtype = NULL; + const weapon *weapon; + + int rda, sk = 0, sd; + bool magic = false; + int da = dice_rand(damage); + + assert(du->number > 0); + ++at.fighter->hits; + + switch (type) { + case AT_STANDARD: + weapon = select_weapon(at, true, missile); + sk = weapon_effskill(at, dt, weapon, true, missile); + if (weapon) + awtype = weapon->type; + if (awtype && fval(awtype, WTF_MAGICAL)) + magic = true; + break; + case AT_NATURAL: + sk = weapon_effskill(at, dt, NULL, true, missile); + break; + case AT_SPELL: + case AT_COMBATSPELL: + magic = true; + break; + default: + break; + } + weapon = select_weapon(dt, false, true); /* missile=true to get the unmodified best weapon she has */ + sd = weapon_effskill(dt, at, weapon, false, false); + if (weapon != NULL) + dwtype = weapon->type; + + if (is_riding(at) && (awtype == NULL || (fval(awtype, WTF_HORSEBONUS) + && !fval(awtype, WTF_MISSILE)))) { + da += CavalryBonus(au, dt, BONUS_DAMAGE); + } + + if (armor) { + ar += armor->prot; + if (armor->projectile > 0 && chance(armor->projectile)) { + return false; + } + } + if (shield) { + ar += shield->prot; + if (shield->projectile > 0 && chance(shield->projectile)) { + return false; + } + } + + /* natürliche Rüstung */ + an = natural_armor(du); + + /* magische Rüstung durch Artefakte oder Sprüche */ + /* Momentan nur Trollgürtel und Werwolf-Eigenschaft */ + am = select_magicarmor(dt); + +#if CHANGED_CROSSBOWS + if (awtype && fval(awtype, WTF_ARMORPIERCING)) { + /* crossbows */ + ar /= 2; + an /= 2; + } +#endif + + if (rule_armor < 0) { + rule_armor = get_param_int(global.parameters, "rules.combat.nat_armor", 0); + } + if (rule_armor == 0) { + /* natürliche Rüstung ist halbkumulativ */ + if (ar > 0) { + ar += an / 2; + } else { + ar = an; + } + } else { + /* use the higher value, add half the other value */ + ar = (ar > an) ? (ar + an / 2) : (an + ar / 2); + } + ar += am; + + if (type != AT_COMBATSPELL && type != AT_SPELL) { + if (damage_rules & DAMAGE_CRITICAL) { + double kritchance = (sk * 3 - sd) / 200.0; + + kritchance = MAX(kritchance, 0.005); + kritchance = MIN(0.9, kritchance); + + while (chance(kritchance)) { + if (bdebug) { + fprintf(bdebug, "%s/%d lands a critical hit\n", unitid(au), at.index); + } + da += dice_rand(damage); + } + } + + da += rc_specialdamage(u_race(au), u_race(du), awtype); + + if (awtype != NULL && fval(awtype, WTF_MISSILE)) { + /* missile weapon bonus */ + if (damage_rules & DAMAGE_MISSILE_BONUS) { + da += af->person[at.index].damage_rear; + } + } else if (awtype == NULL) { + /* skill bonus for unarmed combat */ + if (damage_rules & DAMAGE_UNARMED_BONUS) { + da += effskill(au, SK_WEAPONLESS); + } + } else { + /* melee bonus */ + if (damage_rules & DAMAGE_MELEE_BONUS) { + da += af->person[at.index].damage; + } + } + + /* Skilldifferenzbonus */ + if (damage_rules & DAMAGE_SKILL_BONUS) { + da += MAX(0, (sk - sd) / DAMAGE_QUOTIENT); + } + } + + if (magic) { + /* Magischer Schaden durch Spruch oder magische Waffe */ + double res = 1.0; + + /* magic_resistance gib x% Resistenzbonus zurück */ + res -= magic_resistance(du) * 3.0; + + if (u_race(du)->battle_flags & BF_EQUIPMENT) { +#ifdef TODO_RUNESWORD + /* Runenschwert gibt im Kampf 80% Resistenzbonus */ + if (dwp == WP_RUNESWORD) + res -= 0.80; +#endif + /* der Effekt von Laen steigt nicht linear */ + if (armor && fval(armor, ATF_LAEN)) + res *= (1 - armor->magres); + if (shield && fval(shield, ATF_LAEN)) + res *= (1 - shield->magres); + if (dwtype) + res *= (1 - dwtype->magres); + } + + if (res > 0) { + da = (int)(MAX(da * res, 0)); + } + /* gegen Magie wirkt nur natürliche und magische Rüstung */ + ar = an + am; + } + + rda = MAX(da - ar, 0); + + if ((u_race(du)->battle_flags & BF_INV_NONMAGIC) && !magic) + rda = 0; + else { + int qi; + quicklist *ql; + unsigned int i = 0; + + if (u_race(du)->battle_flags & BF_RES_PIERCE) + i |= WTF_PIERCE; + if (u_race(du)->battle_flags & BF_RES_CUT) + i |= WTF_CUT; + if (u_race(du)->battle_flags & BF_RES_BASH) + i |= WTF_BLUNT; + + if (i && awtype && fval(awtype, i)) + rda /= 2; + + /* Schilde */ + for (qi = 0, ql = b->meffects; ql; ql_advance(&ql, &qi, 1)) { + meffect *me = (meffect *) ql_get(ql, qi); + if (meffect_protection(b, me, ds) != 0) { + assert(0 <= rda); /* rda sollte hier immer mindestens 0 sein */ + /* jeder Schaden wird um effect% reduziert bis der Schild duration + * Trefferpunkte aufgefangen hat */ + if (me->typ == SHIELD_REDUCE) { + hp = rda * (me->effect / 100); + rda -= hp; + me->duration -= hp; + } + /* gibt Rüstung +effect für duration Treffer */ + if (me->typ == SHIELD_ARMOR) { + rda = MAX(rda - me->effect, 0); + me->duration--; + } + } + } + } + + assert(dt.index < du->number); + df->person[dt.index].hp -= rda; + if (u_race(au) == new_race[RC_DAEMON]) { + vampirism(at, rda); + } + + if (df->person[dt.index].hp > 0) { /* Hat überlebt */ + if (bdebug) { + fprintf(bdebug, "Damage %d, armor %d: %d -> %d HP\n", + da, ar, df->person[dt.index].hp + rda, df->person[dt.index].hp); + } + if (u_race(au) == new_race[RC_DAEMON]) { +#ifdef TODO_RUNESWORD + if (select_weapon(dt, 0, -1) == WP_RUNESWORD) + continue; +#endif + if (!(df->person[dt.index].flags & (FL_COURAGE | FL_DAZZLED))) { + df->person[dt.index].flags |= FL_DAZZLED; + df->person[dt.index].defence--; + } + } + df->person[dt.index].flags = (df->person[dt.index].flags & ~FL_SLEEPING); + return false; + } + + /* Sieben Leben */ + if (u_race(du) == new_race[RC_CAT] && (chance(1.0 / 7))) { + assert(dt.index >= 0 && dt.index < du->number); + df->person[dt.index].hp = unit_max_hp(du); + return false; + } + + /* Heiltrank schluerfen und hoffen */ + if (oldpotiontype[P_HEAL]) { + if (get_effect(du, oldpotiontype[P_HEAL]) > 0) { + change_effect(du, oldpotiontype[P_HEAL], -1); + heiltrank = 1; + } else if (i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0) { + i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); + change_effect(du, oldpotiontype[P_HEAL], 3); + heiltrank = 1; + } + if (heiltrank && (chance(0.50))) { + { + message *m = msg_message("battle::potionsave", "unit", du); + message_faction(b, du->faction, m); + msg_release(m); + } + assert(dt.index >= 0 && dt.index < du->number); + df->person[dt.index].hp = u_race(du)->hitpoints; + return false; + } + } + ++at.fighter->kills; + + if (bdebug) { + fprintf(bdebug, "Damage %d, armor %d, type %d: %d -> %d HP, tot.\n", + da, ar, type, df->person[dt.index].hp + rda, df->person[dt.index].hp); + } + for (pitm = &du->items; *pitm;) { + item *itm = *pitm; + const item_type *itype = itm->type; + if (!(itype->flags & ITF_CURSED) && dt.index < itm->number) { + /* 25% Grundchance, das ein Item kaputtgeht. */ + if (rng_int() % 4 < 1) { + i_change(pitm, itype, -1); + } + } + if (*pitm == itm) { + pitm = &itm->next; + } + } + kill_troop(dt); + + return true; +} + +static int +count_side(const side * s, const side * vs, int minrow, int maxrow, int select) +{ + fighter *fig; + int people = 0; + int unitrow[NUMROWS]; + + if (maxrow < FIGHT_ROW) + return 0; + if (select & SELECT_ADVANCE) { + memset(unitrow, -1, sizeof(unitrow)); + } + + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->alive - fig->removed > 0) { + int row = statusrow(fig->status); + if (select & SELECT_ADVANCE) { + if (unitrow[row] == -1) { + unitrow[row] = get_unitrow(fig, vs); + } + row = unitrow[row]; + } + if (row >= minrow && row <= maxrow) { + people += fig->alive - fig->removed; + if (people > 0 && (select & SELECT_FIND)) + break; + } + } + } + return people; +} + +/* return the number of live allies warning: this function only considers +* troops that are still alive, not those that are still fighting although +* dead. */ +int +count_allies(const side * as, int minrow, int maxrow, int select, int allytype) +{ + battle *b = as->battle; + side *ds; + int count = 0; + + for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { + if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF + && as->faction == ds->faction)) { + count += count_side(ds, NULL, minrow, maxrow, select); + if (count > 0 && (select & SELECT_FIND)) + break; + } + } + return count; +} + +static int +count_enemies_i(battle * b, const fighter * af, int minrow, int maxrow, + int select) +{ + side *es, *as = af->side; + int i = 0; + + for (es = b->sides; es != b->sides + b->nsides; ++es) { + if (as == NULL || enemy(es, as)) { + int offset = 0; + if (select & SELECT_DISTANCE) { + offset = get_unitrow(af, es) - FIGHT_ROW; + } + i += count_side(es, as, minrow - offset, maxrow - offset, select); + if (i > 0 && (select & SELECT_FIND)) + break; + } + } + return i; +} + +int +count_enemies(battle * b, const fighter * af, int minrow, int maxrow, + int select) +{ + int sr = statusrow(af->status); + side *as = af->side; + + if (b->alive == b->fast.alive && as == b->fast.side && sr == b->fast.status + && minrow == b->fast.minrow && maxrow == b->fast.maxrow) { + if (b->fast.enemies[select] >= 0) { +#ifdef DEBUG_FAST + int i = count_enemies_i(b, af, minrow, maxrow, select); + assert(i == b->fast.enemies[select]); +#endif + return b->fast.enemies[select]; + } else if (select & SELECT_FIND) { + if (b->fast.enemies[select - SELECT_FIND] >= 0) { +#ifdef DEBUG_FAST + int i = count_enemies_i(b, af, minrow, maxrow, select); + assert((i > 0) == (b->fast.enemies[select - SELECT_FIND] > 0)); +#endif + return b->fast.enemies[select - SELECT_FIND]; + } + } + } else if (select != SELECT_FIND || b->alive != b->fast.alive) { + b->fast.side = as; + b->fast.status = sr; + b->fast.minrow = minrow; + b->fast.alive = b->alive; + b->fast.maxrow = maxrow; + memset(b->fast.enemies, -1, sizeof(b->fast.enemies)); + } + if (maxrow >= FIRST_ROW) { + int i = count_enemies_i(b, af, minrow, maxrow, select); + b->fast.enemies[select] = i; + return i; + } + return 0; +} + +troop select_enemy(fighter * af, int minrow, int maxrow, int select) +{ + side *as = af->side; + battle *b = as->battle; + int si, selected; + int enemies; +#ifdef DEBUG_SELECT + troop result = no_troop; +#endif + if (u_race(af->unit)->flags & RCF_FLY) { + /* flying races ignore min- and maxrow and can attack anyone fighting + * them */ + minrow = FIGHT_ROW; + maxrow = BEHIND_ROW; + } + minrow = MAX(minrow, FIGHT_ROW); + + enemies = count_enemies(b, af, minrow, maxrow, select); + + /* Niemand ist in der angegebenen Entfernung? */ + if (enemies <= 0) + return no_troop; + + selected = rng_int() % enemies; + for (si = 0; as->enemies[si]; ++si) { + side *ds = as->enemies[si]; + fighter *df; + int unitrow[NUMROWS]; + int offset = 0; + + if (select & SELECT_DISTANCE) + offset = get_unitrow(af, ds) - FIGHT_ROW; + + if (select & SELECT_ADVANCE) { + int ui; + for (ui = 0; ui != NUMROWS; ++ui) + unitrow[ui] = -1; + } + + for (df = ds->fighters; df; df = df->next) { + int dr; + + dr = statusrow(df->status); + if (select & SELECT_ADVANCE) { + if (unitrow[dr] < 0) { + unitrow[dr] = get_unitrow(df, as); + } + dr = unitrow[dr]; + } + + if (select & SELECT_DISTANCE) + dr += offset; + if (dr < minrow || dr > maxrow) + continue; + if (df->alive - df->removed > selected) { +#ifdef DEBUG_SELECT + if (result.fighter == NULL) { + result.index = selected; + result.fighter = df; + } +#else + troop dt; + dt.index = selected; + dt.fighter = df; + return dt; +#endif + } + selected -= (df->alive - df->removed); + enemies -= (df->alive - df->removed); + } + } + if (enemies != 0) { + log_error("select_enemies has a bug.\n"); + } +#ifdef DEBUG_SELECT + return result; +#else + assert(!selected); + return no_troop; +#endif +} + +static int get_tactics(const side * as, const side * ds) +{ + battle *b = as->battle; + side *stac; + int result = 0; + int defense = 0; + + if (b->max_tactics > 0) { + for (stac = b->sides; stac != b->sides + b->nsides; ++stac) { + if (stac->leader.value > result && helping(stac, as)) { + assert(ds == NULL || !helping(stac, ds)); + result = stac->leader.value; + } + if (ds && stac->leader.value > defense && helping(stac, ds)) { + assert(!helping(stac, as)); + defense = stac->leader.value; + } + } + } + return result - defense; +} + +static troop select_opponent(battle * b, troop at, int mindist, int maxdist) +{ + fighter *af = at.fighter; + troop dt; + + if (u_race(af->unit)->flags & RCF_FLY) { + /* flying races ignore min- and maxrow and can attack anyone fighting + * them */ + dt = select_enemy(at.fighter, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + } else { + mindist = MAX(mindist, FIGHT_ROW); + dt = select_enemy(at.fighter, mindist, maxdist, SELECT_ADVANCE); + } + + if (b->turn == 0 && dt.fighter) { + int tactics_formula = -1; + + if (tactics_formula < 0) { + tactics_formula = + get_param_int(global.parameters, "rules.tactics.formula", 0); + } + if (tactics_formula == 1) { + int tactics = get_tactics(at.fighter->side, dt.fighter->side); + + /* percentage chance to get this attack */ + if (tactics > 0) { + double tacch = 0.1 * tactics; + if (fval(b->region->terrain, SEA_REGION)) { + ship *sh = at.fighter->unit->ship; + if (sh) + tacch *= sh->type->tac_bonus; + } + if (!chance(tacch)) { + dt.fighter = NULL; + } + } else { + dt.fighter = NULL; + } + } + } + + return dt; +} + +quicklist *fighters(battle * b, const side * vs, int minrow, int maxrow, + int mask) +{ + side *s; + quicklist *fightervp = 0; + + assert(vs != NULL); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + + if (mask == FS_ENEMY) { + if (!enemy(s, vs)) + continue; + } else if (mask == FS_HELP) { + if (enemy(s, vs) || !allysf(s, vs->faction)) { + continue; + } + } else { + assert(mask == (FS_HELP | FS_ENEMY) || !"invalid alliance state"); + } + for (fig = s->fighters; fig; fig = fig->next) { + int row = get_unitrow(fig, vs); + if (row >= minrow && row <= maxrow) { + ql_push(&fightervp, fig); + } + } + } + + return fightervp; +} + +static void report_failed_spell(battle * b, unit * mage, const spell * sp) +{ + message *m = msg_message("battle::spell_failed", "unit spell", mage, sp); + message_all(b, m); + msg_release(m); +} + +void do_combatmagic(battle * b, combatmagic_t was) +{ + side *s; + region *r = b->region; + castorder *co; + int level, rank, sl; + spellrank spellranks[MAX_SPELLRANK]; + + memset(spellranks, 0, sizeof(spellranks)); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *mage = fig->unit; + + if (fig->alive <= 0) + continue; /* fighter kann im Kampf getötet worden sein */ + + level = eff_skill(mage, SK_MAGIC, r); + if (level > 0) { + double power; + const spell *sp; + const struct locale *lang = mage->faction->locale; + order *ord; + + switch (was) { + case DO_PRECOMBATSPELL: + sp = get_combatspell(mage, 0); + sl = get_combatspelllevel(mage, 0); + break; + case DO_POSTCOMBATSPELL: + sp = get_combatspell(mage, 2); + sl = get_combatspelllevel(mage, 2); + break; + default: + /* Fehler! */ + return; + } + if (sp == NULL) + continue; + + ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); + if (!cancast(mage, sp, 1, 1, ord)) { + free_order(ord); + continue; + } + + level = eff_spelllevel(mage, sp, level, 1); + if (sl > 0) + level = MIN(sl, level); + if (level < 0) { + report_failed_spell(b, mage, sp); + free_order(ord); + continue; + } + + power = spellpower(r, mage, sp, level, ord); + free_order(ord); + if (power <= 0) { /* Effekt von Antimagie */ + report_failed_spell(b, mage, sp); + pay_spell(mage, sp, level, 1); + } else if (fumble(r, mage, sp, level)) { + report_failed_spell(b, mage, sp); + pay_spell(mage, sp, level, 1); + } else { + co = create_castorder(0, fig->unit, 0, sp, r, level, power, 0, 0, 0); + co->magician.fig = fig; + add_castorder(&spellranks[sp->rank], co); + } + } + } + } + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + for (co = spellranks[rank].begin; co; co = co->next) { + fighter *fig = co->magician.fig; + const spell *sp = co->sp; + int level = co->level; + + if (!sp->cast) { + log_error("spell '%s' has no function.\n", sp->sname); + } else { + level = sp->cast(co); + if (level > 0) { + pay_spell(fig->unit, sp, level, 1); + } + } + } + } + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + free_castorders(spellranks[rank].begin); + } +} + +static int cast_combatspell(troop at, const spell * sp, int level, double force) +{ + castorder co; + + create_castorder(&co, at.fighter->unit, 0, sp, at.fighter->unit->region, level, force, 0, 0, 0); + co.magician.fig = at.fighter; + level = sp->cast(&co); + free_castorder(&co); + if (level > 0) { + pay_spell(at.fighter->unit, sp, level, 1); + } + return level; +} + +static void do_combatspell(troop at) +{ + const spell *sp; + fighter *fi = at.fighter; + unit *caster = fi->unit; + battle *b = fi->side->battle; + region *r = b->region; + quicklist *ql; + int level, qi; + double power; + int fumblechance = 0; + order *ord; + int sl; + const struct locale *lang = caster->faction->locale; + + sp = get_combatspell(caster, 1); + if (sp == NULL) { + fi->magic = 0; /* Hat keinen Kampfzauber, kämpft nichtmagisch weiter */ + return; + } + ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); + if (!cancast(caster, sp, 1, 1, ord)) { + fi->magic = 0; /* Kann nicht mehr Zaubern, kämpft nichtmagisch weiter */ + return; + } + + level = eff_spelllevel(caster, sp, fi->magic, 1); + if ((sl = get_combatspelllevel(caster, 1)) > 0) + level = MIN(level, sl); + + if (fumble(r, caster, sp, level)) { + report_failed_spell(b, caster, sp); + pay_spell(caster, sp, level, 1); + return; + } + + for (qi = 0, ql = b->meffects; ql; ql_advance(&ql, &qi, 1)) { + meffect *mblock = (meffect *) ql_get(ql, qi); + if (mblock->typ == SHIELD_BLOCK) { + if (meffect_blocked(b, mblock, fi->side) != 0) { + fumblechance += mblock->duration; + mblock->duration -= mblock->effect; + } + } + } + + /* Antimagie die Fehlschlag erhöht */ + if (rng_int() % 100 < fumblechance) { + report_failed_spell(b, caster, sp); + pay_spell(caster, sp, level, 1); + free_order(ord); + return; + } + power = spellpower(r, caster, sp, level, ord); + free_order(ord); + if (power <= 0) { /* Effekt von Antimagie */ + report_failed_spell(b, caster, sp); + pay_spell(caster, sp, level, 1); + return; + } + + if (!sp->cast) { + log_error("spell '%s' has no function.\n", sp->sname); + } else { + level = cast_combatspell(at, sp, level, power); + } +} + +/* Sonderattacken: Monster patzern nicht und zahlen auch keine + * Spruchkosten. Da die Spruchstärke direkt durch den Level bestimmt + * wird, wirkt auch keine Antimagie (wird sonst in spellpower + * gemacht) */ + +static void do_extra_spell(troop at, const att * a) +{ + const spell *sp = a->data.sp; + + if (sp->cast == NULL) { + log_error("spell '%s' has no function.\n", sp->sname); + } else { + int level = a->level; + assert(a->level>0); + cast_combatspell(at, sp, level, level * MagicPower()); + } +} + +int skilldiff(troop at, troop dt, int dist) +{ + fighter *af = at.fighter, *df = dt.fighter; + unit *au = af->unit, *du = df->unit; + int is_protected = 0, skdiff = 0; + weapon *awp = select_weapon(at, true, dist > 1); + + skdiff += af->person[at.index].attack; + skdiff -= df->person[dt.index].defence; + + if (df->person[dt.index].flags & FL_SLEEPING) + skdiff += 2; + + /* Effekte durch Rassen */ + if (awp != NULL && u_race(au) == new_race[RC_HALFLING] && dragonrace(u_race(du))) { + skdiff += 5; + } + + if (u_race(au) == new_race[RC_GOBLIN]) { + static int goblin_bonus = -1; + if (goblin_bonus < 0) + goblin_bonus = + get_param_int(global.parameters, "rules.combat.goblinbonus", 10); + if (af->side->size[SUM_ROW] >= df->side->size[SUM_ROW] * goblin_bonus) { + skdiff += 1; + } + } + + if (df->building) { + bool init = false; + static const curse_type *strongwall_ct, *magicwalls_ct; + if (!init) { + strongwall_ct = ct_find("strongwall"); + magicwalls_ct = ct_find("magicwalls"); + init = true; + } + if (df->building->type->protection) { + int beff = df->building->type->protection(df->building, du); + if (beff) { + skdiff -= beff; + is_protected = 2; + } + } + if (strongwall_ct) { + curse *c = get_curse(df->building->attribs, strongwall_ct); + if (curse_active(c)) { + /* wirkt auf alle Gebäude */ + skdiff -= curse_geteffect_int(c); + is_protected = 2; + } + } + if (magicwalls_ct + && curse_active(get_curse(df->building->attribs, magicwalls_ct))) { + /* Verdoppelt Burgenbonus */ + skdiff -= buildingeffsize(df->building, false); + } + } + /* Goblin-Verteidigung + * ist direkt in der Rassentabelle als df_default + */ + + /* Effekte der Waffen */ + skdiff += weapon_effskill(at, dt, awp, true, dist > 1); + if (awp && fval(awp->type, WTF_MISSILE)) { + skdiff -= is_protected; + if (awp->type->modifiers) { + int w; + for (w = 0; awp->type->modifiers[w].value != 0; ++w) { + if (awp->type->modifiers[w].flags & WMF_MISSILE_TARGET) { + /* skill decreases by targeting difficulty (bow -2, catapult -4) */ + skdiff -= awp->type->modifiers[w].value; + break; + } + } + } + } + if (skill_formula == FORMULA_ORIG) { + weapon *dwp = select_weapon(dt, false, dist > 1); + skdiff -= weapon_effskill(dt, at, dwp, false, dist > 1); + } + return skdiff; +} + +static int setreload(troop at) +{ + fighter *af = at.fighter; + const weapon_type *wtype = af->person[at.index].missile->type; + if (wtype->reload == 0) + return 0; + return af->person[at.index].reload = wtype->reload; +} + +int getreload(troop at) +{ + return at.fighter->person[at.index].reload; +} + +static void +debug_hit(troop at, const weapon * awp, troop dt, const weapon * dwp, + int skdiff, int dist, bool success) +{ + fprintf(bdebug, "%.4s/%d [%6s/%d] %s %.4s/%d [%6s/%d] with %d, distance %d\n", + unitid(at.fighter->unit), at.index, + LOC(default_locale, awp ? resourcename(awp->type->itype->rtype, + 0) : "unarmed"), weapon_effskill(at, dt, awp, true, dist > 1), + success ? "hits" : "misses", unitid(dt.fighter->unit), dt.index, + LOC(default_locale, dwp ? resourcename(dwp->type->itype->rtype, + 0) : "unarmed"), weapon_effskill(dt, at, dwp, false, dist > 1), skdiff, + dist); +} + +int hits(troop at, troop dt, weapon * awp) +{ + fighter *af = at.fighter, *df = dt.fighter; + const armor_type *armor, *shield = 0; + int skdiff = 0; + int dist = get_unitrow(af, df->side) + get_unitrow(df, af->side) - 1; + weapon *dwp = select_weapon(dt, false, dist > 1); + + if (!df->alive) + return 0; + if (getreload(at)) + return 0; + if (dist > 1 && (awp == NULL || !fval(awp->type, WTF_MISSILE))) + return 0; + + /* mark this person as hit. */ + df->person[dt.index].flags |= FL_HIT; + + if (af->person[at.index].flags & FL_STUNNED) { + af->person[at.index].flags &= ~FL_STUNNED; + return 0; + } + if ((af->person[at.index].flags & FL_TIRED && rng_int() % 100 < 50) + || (af->person[at.index].flags & FL_SLEEPING)) + return 0; + + /* effect of sp_reeling_arrows combatspell */ + if (af->side->battle->reelarrow && awp && fval(awp->type, WTF_MISSILE) + && rng_double() < 0.5) { + return 0; + } + + skdiff = skilldiff(at, dt, dist); + /* Verteidiger bekommt eine Rüstung */ + armor = select_armor(dt, true); + if (dwp == NULL || (dwp->type->flags & WTF_USESHIELD)) { + shield = select_armor(dt, false); + } + if (contest(skdiff, dt, armor, shield)) { + if (bdebug) { + debug_hit(at, awp, dt, dwp, skdiff, dist, true); + } + return 1; + } + if (bdebug) { + debug_hit(at, awp, dt, dwp, skdiff, dist, false); + } + return 0; +} + +void dazzle(battle * b, troop * td) +{ + /* Nicht kumulativ ! */ + if (td->fighter->person[td->index].flags & FL_DAZZLED) + return; + +#ifdef TODO_RUNESWORD + if (td->fighter->weapon[WP_RUNESWORD].count > td->index) { + return; + } +#endif + if (td->fighter->person[td->index].flags & FL_COURAGE) { + return; + } + + if (td->fighter->person[td->index].flags & FL_DAZZLED) { + return; + } + + td->fighter->person[td->index].flags |= FL_DAZZLED; + td->fighter->person[td->index].defence--; +} + +/* TODO: Gebäude/Schiffe sollten auch zerstörbar sein. Schwierig im Kampf, + * besonders bei Schiffen. */ + +void damage_building(battle * b, building * bldg, int damage_abs) +{ + bldg->size = MAX(1, bldg->size - damage_abs); + + /* Wenn Burg, dann gucken, ob die Leute alle noch in das Gebäude passen. */ + + if (bldg->type->protection) { + side *s; + + bldg->sizeleft = bldg->size; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->building == bldg) { + if (bldg->sizeleft >= fig->unit->number) { + fig->building = bldg; + bldg->sizeleft -= fig->unit->number; + } else { + fig->building = NULL; + } + } + } + } + } +} + +static int attacks_per_round(troop t) +{ + return t.fighter->person[t.index].speed; +} + +static void make_heroes(battle * b) +{ + side *s; + static int hero_speed = 0; + if (hero_speed == 0) { + hero_speed = get_param_int(global.parameters, "rules.combat.herospeed", 10); + } + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *u = fig->unit; + if (fval(u, UFL_HERO)) { + int i; + if (!playerrace(u_race(u))) { + log_error("Hero %s is a %s.\n", unitname(u), u_race(u)->_name[0]); + } + for (i = 0; i != u->number; ++i) { + fig->person[i].speed += (hero_speed - 1); + } + } + } + } +} + +static void attack(battle * b, troop ta, const att * a, int numattack) +{ + fighter *af = ta.fighter; + troop td; + unit *au = af->unit; + + switch (a->type) { + case AT_COMBATSPELL: + /* Magier versuchen immer erstmal zu zaubern, erst wenn das + * fehlschlägt, wird af->magic == 0 und der Magier kämpft + * konventionell weiter */ + if (numattack == 0 && af->magic > 0) { + /* wenn der magier in die potenzielle Reichweite von Attacken des + * Feindes kommt, beginnt er auch bei einem Status von KAEMPFE NICHT, + * Kampfzauber zu schleudern: */ + if (count_enemies(b, af, melee_range[0], missile_range[1], + SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND)) { + do_combatspell(ta); + } + } + break; + case AT_STANDARD: /* Waffen, mag. Gegenstände, Kampfzauber */ + if (numattack > 0 || af->magic <= 0) { + weapon *wp = ta.fighter->person[ta.index].missile; + int melee = + count_enemies(b, af, melee_range[0], melee_range[1], + SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND); + if (melee) + wp = preferred_weapon(ta, true); + /* Sonderbehandlungen */ + + if (getreload(ta)) { + ta.fighter->person[ta.index].reload--; + } else { + bool standard_attack = true; + bool reload = false; + /* spezialattacken der waffe nur, wenn erste attacke in der runde. + * sonst helden mit feuerschwertern zu mächtig */ + if (numattack == 0 && wp && wp->type->attack) { + int dead = 0; + standard_attack = wp->type->attack(&ta, wp->type, &dead); + if (!standard_attack) + reload = true; + af->catmsg += dead; + if (!standard_attack && af->person[ta.index].last_action < b->turn) { + af->person[ta.index].last_action = b->turn; + } + } + if (standard_attack) { + bool missile = false; + if (wp && fval(wp->type, WTF_MISSILE)) + missile = true; + if (missile) { + td = select_opponent(b, ta, missile_range[0], missile_range[1]); + } else { + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + } + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + reload = true; + if (hits(ta, td, wp)) { + const char *d; + if (wp == NULL) + d = u_race(au)->def_damage; + else if (is_riding(ta)) + d = wp->type->damage[1]; + else + d = wp->type->damage[0]; + terminate(td, ta, a->type, d, missile); + } + } + if (reload && wp && wp->type->reload && !getreload(ta)) { + int i = setreload(ta); + if (bdebug) { + fprintf(bdebug, "%s/%d reloading %d turns\n", unitid(au), + ta.index, i); + } + } + } + } + break; + case AT_SPELL: /* Extra-Sprüche. Kampfzauber in AT_COMBATSPELL! */ + do_extra_spell(ta, a); + break; + case AT_NATURAL: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + terminate(td, ta, a->type, a->data.dice, false); + } + break; + case AT_DRAIN_ST: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + int c = dice_rand(a->data.dice); + while (c > 0) { + if (rng_int() % 2) { + td.fighter->person[td.index].attack -= 1; + } else { + td.fighter->person[td.index].defence -= 1; + } + c--; + } + } + break; + case AT_DRAIN_EXP: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + drain_exp(td.fighter->unit, dice_rand(a->data.dice)); + } + break; + case AT_DAZZLE: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + dazzle(b, &td); + } + break; + case AT_STRUCTURAL: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (td.fighter->unit->ship) { + /* FIXME should use damage_ship here? */ + td.fighter->unit->ship->damage += + DAMAGE_SCALE * dice_rand(a->data.dice); + } else if (td.fighter->unit->building) { + damage_building(b, td.fighter->unit->building, dice_rand(a->data.dice)); + } + } +} + +void do_attack(fighter * af) +{ + troop ta; + unit *au = af->unit; + side *side = af->side; + battle *b = side->battle; + + ta.fighter = af; + + assert(au && au->number); + /* Da das Zuschlagen auf Einheiten und nicht auf den einzelnen + * Kämpfern beruht, darf die Reihenfolge und Größe der Einheit keine + * Rolle spielen, Das tut sie nur dann, wenn jeder, der am Anfang der + * Runde lebte, auch zuschlagen darf. Ansonsten ist der, der zufällig + * mit einer großen Einheit zuerst drankommt, extrem bevorteilt. */ + ta.index = af->fighting; + + while (ta.index--) { + /* Wir suchen eine beliebige Feind-Einheit aus. An der können + * wir feststellen, ob noch jemand da ist. */ + int apr, attacks = attacks_per_round(ta); + if (!count_enemies(b, af, FIGHT_ROW, LAST_ROW, SELECT_FIND)) + break; + + for (apr = 0; apr != attacks; ++apr) { + int a; + for (a = 0; a != 10 && u_race(au)->attack[a].type != AT_NONE; ++a) { + if (apr > 0) { + /* Wenn die Waffe nachladen muss, oder es sich nicht um einen + * Waffen-Angriff handelt, dann gilt der Speed nicht. */ + if (u_race(au)->attack[a].type != AT_STANDARD) + continue; + else { + weapon *wp = preferred_weapon(ta, true); + if (wp != NULL && wp->type->reload) + continue; + } + } + attack(b, ta, &(u_race(au)->attack[a]), apr); + } + } + } + /* Der letzte Katapultschütze setzt die + * Ladezeit neu und generiert die Meldung. */ + if (af->catmsg >= 0) { + struct message *m = + msg_message("battle::killed", "unit dead", au, af->catmsg); + message_all(b, m); + msg_release(m); + af->catmsg = -1; + } +} + +void do_regenerate(fighter * af) +{ + troop ta; + unit *au = af->unit; + + ta.fighter = af; + ta.index = af->fighting; + + while (ta.index--) { + af->person[ta.index].hp += effskill(au, SK_STAMINA); + af->person[ta.index].hp = MIN(unit_max_hp(au), af->person[ta.index].hp); + } +} + +static void add_tactics(tactics * ta, fighter * fig, int value) +{ + if (value == 0 || value < ta->value) + return; + if (value > ta->value) { + ql_free(ta->fighters); + ta->fighters = 0; + } + ql_push(&ta->fighters, fig); + ql_push(&fig->side->battle->leaders, fig); + ta->value = value; +} + +static double horsebonus(const unit * u) +{ + static const item_type *it_horse = 0; + static const item_type *it_elvenhorse = 0; + static const item_type *it_charger = 0; + region *r = u->region; + int n1 = 0, n2 = 0, n3 = 0; + item *itm = u->items; + int skl = eff_skill(u, SK_RIDING, r); + + if (skl < 1) + return 0.0; + + if (it_horse == 0) { + it_horse = it_find("horse"); + it_elvenhorse = it_find("elvenhorse"); + it_charger = it_find("charger"); + } + + for (; itm; itm = itm->next) { + if (itm->type->flags & ITF_ANIMAL) { + if (itm->type == it_elvenhorse) + n3 += itm->number; + else if (itm->type == it_charger) + n2 += itm->number; + else if (itm->type == it_horse) + n1 += itm->number; + } + } + if (skl >= 5 && n3 >= u->number) + return 0.30; + if (skl >= 3 && n2 + n3 >= u->number) + return 0.20; + if (skl >= 1 && n1 + n2 + n3 >= u->number) + return 0.10; + return 0.0F; +} + +double fleechance(unit * u) +{ + double c = 0.20; /* Fluchtwahrscheinlichkeit in % */ + region *r = u->region; + attrib *a = a_find(u->attribs, &at_fleechance); + /* Einheit u versucht, dem Getümmel zu entkommen */ + + c += (eff_skill(u, SK_STEALTH, r) * 0.05); + c += horsebonus(u); + + if (u_race(u) == new_race[RC_HALFLING]) { + c += 0.20; + c = MIN(c, 0.90); + } else { + c = MIN(c, 0.75); + } + + if (a != NULL) + c += a->data.flt; + + return c; +} + +/** add a new army to the conflict + * beware: armies need to be added _at the beginning_ of the list because + * otherwise join_allies() will get into trouble */ +side *make_side(battle * b, const faction * f, const group * g, + unsigned int flags, const faction * stealthfaction) +{ + side *s1 = b->sides + b->nsides; + bfaction *bf; + + if (fval(b->region->terrain, SEA_REGION)) { + /* every fight in an ocean is short */ + flags |= SIDE_HASGUARDS; + } else { + unit *u; + for (u = b->region->units; u; u = u->next) { + if (is_guard(u, HELP_ALL)) { + if (alliedunit(u, f, HELP_GUARD)) { + flags |= SIDE_HASGUARDS; + break; + } + } + } + } + + s1->battle = b; + s1->group = g; + s1->flags = flags; + s1->stealthfaction = stealthfaction; + for (bf = b->factions; bf; bf = bf->next) { + faction *f2 = bf->faction; + + if (f2 == f) { + s1->bf = bf; + s1->faction = f2; + s1->index = b->nsides++; + s1->nextF = bf->sides; + bf->sides = s1; + assert(b->nsides <= MAXSIDES); + break; + } + } + assert(bf); + return s1; +} + +troop select_ally(fighter * af, int minrow, int maxrow, int allytype) +{ + side *as = af->side; + battle *b = as->battle; + side *ds; + int allies = count_allies(as, minrow, maxrow, SELECT_ADVANCE, allytype); + + if (!allies) { + return no_troop; + } + allies = rng_int() % allies; + + for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { + if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF + && as->faction == ds->faction)) { + fighter *df; + for (df = ds->fighters; df; df = df->next) { + int dr = get_unitrow(df, NULL); + if (dr >= minrow && dr <= maxrow) { + if (df->alive - df->removed > allies) { + troop dt; + assert(allies >= 0); + dt.index = allies; + dt.fighter = df; + return dt; + } + allies -= df->alive; + } + } + } + } + assert(!"we should never have gotten here"); + return no_troop; +} + +static int loot_quota(const unit * src, const unit * dst, + const item_type * type, int n) +{ + static float divisor = -1; + if (dst && src && src->faction != dst->faction) { + if (divisor < 0) { + divisor = get_param_flt(global.parameters, "rules.items.loot_divisor", 1); + assert(divisor == 0 || divisor >= 1); + } + if (divisor >= 1) { + double r = n / divisor; + int x = (int)r; + + r = r - x; + if (chance(r)) + ++x; + + return x; + } + } + return n; +} + +static void loot_items(fighter * corpse) +{ + unit *u = corpse->unit; + item *itm = u->items; + battle *b = corpse->side->battle; + int dead = dead_fighters(corpse); + + if (dead <= 0) + return; + + while (itm) { + float lootfactor = dead / (float)u->number; /* only loot the dead! */ + int maxloot = (int)(itm->number * lootfactor); + if (maxloot > 0) { + int i = MIN(10, maxloot); + for (; i != 0; --i) { + int loot = maxloot / i; + + if (loot > 0) { + fighter *fig = NULL; + int looting = 0; + int maxrow = 0; + /* mustloot: we absolutely, positively must have somebody loot this thing */ + int mustloot = itm->type->flags & (ITF_CURSED | ITF_NOTLOST); + + itm->number -= loot; + maxloot -= loot; + + if (is_monsters(u->faction) && (loot_rules & LOOT_MONSTERS)) { + looting = 1; + } else if (loot_rules & LOOT_OTHERS) { + looting = 1; + } else if (loot_rules & LOOT_SELF) { + looting = 2; + } + if (looting) { + if (mustloot) { + maxrow = LAST_ROW; + } else if (loot_rules & LOOT_KEEPLOOT) { + int lootchance = 50 + b->keeploot; + if (rng_int() % 100 < lootchance) { + maxrow = BEHIND_ROW; + } + } else { + maxrow = LAST_ROW; + } + } + if (maxrow > 0) { + if (looting == 1) { + /* enemies get dibs */ + fig = select_enemy(corpse, FIGHT_ROW, maxrow, 0).fighter; + } + if (!fig) { + /* self and allies get second pick */ + fig = select_ally(corpse, FIGHT_ROW, LAST_ROW, ALLY_SELF).fighter; + } + } + + if (fig) { + int trueloot = + mustloot ? loot : loot_quota(corpse->unit, fig->unit, itm->type, + loot); + if (trueloot > 0) { + item *l = fig->loot; + while (l && l->type != itm->type) + l = l->next; + if (!l) { + l = calloc(sizeof(item), 1); + l->next = fig->loot; + fig->loot = l; + l->type = itm->type; + } + l->number += trueloot; + } + } + } + } + } + itm = itm->next; + } +} + +static bool seematrix(const faction * f, const side * s) +{ + if (f == s->faction) + return true; + if (s->flags & SIDE_STEALTH) + return false; + return true; +} + +static double PopulationDamage(void) +{ + static double value = -1.0; + if (value < 0) { + int damage = + get_param_int(global.parameters, "rules.combat.populationdamage", + BATTLE_KILLS_PEASANTS); + value = damage / 100.0; + } + return value; +} + +static void battle_effects(battle * b, int dead_players) +{ + region *r = b->region; + int dead_peasants = + MIN(rpeasants(r), (int)(dead_players * PopulationDamage())); + if (dead_peasants) { + deathcounts(r, dead_peasants + dead_players); + chaoscounts(r, dead_peasants / 2); + rsetpeasants(r, rpeasants(r) - dead_peasants); + } +} + +static void reorder_fleeing(region * r) +{ + unit **usrc = &r->units; + unit **udst = &r->units; + unit *ufirst = NULL; + unit *u; + + for (; *udst; udst = &u->next) { + u = *udst; + } + + for (u = *usrc; u != ufirst; u = *usrc) { + if (u->next && fval(u, UFL_FLEEING)) { + *usrc = u->next; + *udst = u; + udst = &u->next; + if (!ufirst) + ufirst = u; + } else { + usrc = &u->next; + } + } + *udst = NULL; +} + +static void aftermath(battle * b) +{ + int i; + region *r = b->region; + ship *sh; + side *s; + int dead_players = 0; + bfaction *bf; + bool ships_damaged = (bool) (b->turn + (b->has_tactics_turn ? 1 : 0) > 2); /* only used for ship damage! */ + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + s->dead = 0; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int dead = dead_fighters(df); + int pr_mercy = 0; + + /* Regeneration durch PR_MERCY */ + if (dead > 0 && pr_mercy) { + for (i = 0; i != dead; ++i) { + if (rng_int() % 100 < pr_mercy) { + ++df->alive; + ++s->alive; + ++s->battle->alive; + /* do not change dead here, or loop will not terminate! recalculate later */ + } + } + dead = dead_fighters(df); + } + + /* tote insgesamt: */ + s->dead += dead; + /* Tote, die wiederbelebt werde koennen: */ + if (playerrace(u_race(df->unit))) { + s->casualties += dead; + } + if (df->hits + df->kills) { + struct message *m = + msg_message("killsandhits", "unit hits kills", du, df->hits, + df->kills); + message_faction(b, du->faction, m); + msg_release(m); + } + } + } + + /* POSTCOMBAT */ + do_combatmagic(b, DO_POSTCOMBATSPELL); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + int snumber = 0; + fighter *df; + bool relevant = false; /* Kampf relevant für diese Partei? */ + if (!fval(s, SIDE_HASGUARDS)) { + relevant = true; + } + s->flee = 0; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int dead = dead_fighters(df); + int sum_hp = 0; + int n; + + for (n = 0; n != df->alive; ++n) { + if (df->person[n].hp > 0) { + sum_hp += df->person[n].hp; + } + } + snumber += du->number; + if (relevant) { + int flags = UFL_LONGACTION | UFL_NOTMOVING; + if (du->status == ST_FLEE) { + flags -= UFL_NOTMOVING; + } + fset(du, flags); + } + if (sum_hp + df->run.hp < du->hp) { + /* someone on the ship got damaged, damage the ship */ + ship *sh = du->ship ? du->ship : leftship(du); + if (sh) + fset(sh, SF_DAMAGED); + } + + if (df->alive == du->number) { + du->hp = sum_hp; + continue; /* nichts passiert */ + } else if (df->run.hp) { + if (df->alive == 0) { + /* Report the casualties */ + reportcasualties(b, df, dead); + + /* Zuerst dürfen die Feinde plündern, die mitgenommenen Items + * stehen in fig->run.items. Dann werden die Fliehenden auf + * die leere (tote) alte Einheit gemapt */ + if (!fval(df, FIG_NOLOOT)) { + loot_items(df); + } + scale_number(du, df->run.number); + du->hp = df->run.hp; + setguard(du, GUARD_NONE); + /* must leave ships or buildings, or a stealthy hobbit + * can hold castles indefinitely */ + if (!fval(r->terrain, SEA_REGION)) { + leave(du, true); /* even region owners have to flee */ + } + fset(du, UFL_FLEEING); + } else { + /* nur teilweise geflohene Einheiten mergen sich wieder */ + df->alive += df->run.number; + s->size[0] += df->run.number; + s->size[statusrow(df->status)] += df->run.number; + s->alive += df->run.number; + sum_hp += df->run.hp; + df->run.number = 0; + df->run.hp = 0; + /* df->run.region = NULL; */ + + reportcasualties(b, df, dead); + + scale_number(du, df->alive); + du->hp = sum_hp; + } + } else { + if (df->alive == 0) { + /* alle sind tot, niemand geflohen. Einheit auflösen */ + df->run.number = 0; + df->run.hp = 0; + + /* Report the casualties */ + reportcasualties(b, df, dead); + + /* Distribute Loot */ + loot_items(df); + + setguard(du, GUARD_NONE); + scale_number(du, 0); + } else { + df->run.number = 0; + df->run.hp = 0; + + reportcasualties(b, df, dead); + + scale_number(du, df->alive); + du->hp = sum_hp; + } + } + s->flee += df->run.number; + + if (playerrace(u_race(du))) { + /* tote im kampf werden zu regionsuntoten: + * for each of them, a peasant will die as well */ + dead_players += dead; + } + if (du->hp < du->number) { + log_error("%s has less hitpoints (%u) than people (%u)\n", itoa36(du->no), du->hp, du->number); + du->hp = du->number; + } + } + s->alive += s->healed; + assert(snumber == s->flee + s->alive + s->dead); + } + + battle_effects(b, dead_players); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + message *seen = msg_message("battle::army_report", + "index abbrev dead fled survived", + army_index(s), sideabkz(s, false), s->dead, s->flee, s->alive); + message *unseen = msg_message("battle::army_report", + "index abbrev dead fled survived", + army_index(s), "-?-", s->dead, s->flee, s->alive); + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + message *m = seematrix(f, s) ? seen : unseen; + + message_faction(b, f, m); + } + + msg_release(seen); + msg_release(unseen); + } + + /* Wir benutzen drifted, um uns zu merken, ob ein Schiff + * schonmal Schaden genommen hat. (moved und drifted + * sollten in flags überführt werden */ + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + item *l; + + /* Beute verteilen */ + for (l = df->loot; l; l = l->next) { + const item_type *itype = l->type; + message *m = + msg_message("battle_loot", "unit amount item", du, l->number, + itype->rtype); + message_faction(b, du->faction, m); + msg_release(m); + i_change(&du->items, itype, l->number); + } + + /* Wenn sich die Einheit auf einem Schiff befindet, wird + * dieses Schiff beschädigt. Andernfalls ein Schiff, welches + * evt. zuvor verlassen wurde. */ + if (ships_damaged) { + if (du->ship) + sh = du->ship; + else + sh = leftship(du); + + if (sh && fval(sh, SF_DAMAGED)) { + int n = b->turn - 2; + if (n > 0) { + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.battleround", + 0.05F); + damage_ship(sh, dmg * n); + freset(sh, SF_DAMAGED); + } + } + } + } + } + + if (ships_damaged) { + ship **sp = &r->ships; + + while (*sp) { + ship *sh = *sp; + freset(sh, SF_DAMAGED); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + remove_ship(sp, sh); + } + if (*sp == sh) + sp = &sh->next; + } + } + + reorder_fleeing(r); + + if (bdebug) { + fprintf(bdebug, "The battle lasted %d turns, %s and %s.\n", + b->turn, + b->has_tactics_turn ? "had a tactic turn" : "had no tactic turn", + ships_damaged ? "was relevant" : "was not relevant."); + } +} + +static void battle_punit(unit * u, battle * b) +{ + bfaction *bf; + strlist *S, *x; + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + + S = 0; + spunit(&S, f, u, 4, see_battle); + for (x = S; x; x = x->next) { + fbattlerecord(b, f, x->s); + if (bdebug && u->faction == f) { + fputs(x->s, bdebug); + fputc('\n', bdebug); + } + } + if (S) + freestrlist(S); + } +} + +static void print_fighters(battle * b, const side * s) +{ + fighter *df; + int row; + + for (row = 1; row != NUMROWS; ++row) { + message *m = NULL; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int thisrow = statusrow(df->unit->status); + + if (row == thisrow) { + if (m == NULL) { + m = msg_message("battle::row_header", "row", row); + message_all(b, m); + } + battle_punit(du, b); + } + } + if (m != NULL) + msg_release(m); + } +} + +bool is_attacker(const fighter * fig) +{ + return fval(fig, FIG_ATTACKER) != 0; +} + +static void set_attacker(fighter * fig) +{ + fset(fig, FIG_ATTACKER); +} + +static void print_header(battle * b) +{ + bfaction *bf; + char zText[32 * MAXSIDES]; + + for (bf = b->factions; bf; bf = bf->next) { + message *m; + faction *f = bf->faction; + const char *lastf = NULL; + bool first = false; + side *s; + char *bufp = zText; + size_t size = sizeof(zText) - 1; + int bytes; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + for (df = s->fighters; df; df = df->next) { + if (is_attacker(df)) { + if (first) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (lastf) { + bytes = (int)strlcpy(bufp, (const char *)lastf, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + first = true; + } + if (seematrix(f, s)) + lastf = sidename(s); + else + lastf = LOC(f->locale, "unknown_faction_dative"); + break; + } + } + } + if (first) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, (const char *)LOC(f->locale, "and"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (lastf) { + bytes = (int)strlcpy(bufp, (const char *)lastf, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + m = msg_message("battle::starters", "factions", zText); + message_faction(b, f, m); + msg_release(m); + } +} + +static void print_stats(battle * b) +{ + side *s2; + side *s; + int i = 0; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + bfaction *bf; + + ++i; + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + const char *loc_army = LOC(f->locale, "battle_army"); + char *bufp; + const char *header; + size_t rsize, size; + int komma; + const char *sname = + seematrix(f, s) ? sidename(s) : LOC(f->locale, "unknown_faction"); + message *msg; + char buf[1024]; + + message_faction(b, f, msg_separator); + + msg = msg_message("battle_army", "index name", army_index(s), sname); + message_faction(b, f, msg); + msg_release(msg); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_opponents"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (enemy(s2, s)) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_helpers"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (friendly(s2, s)) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_attack"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (s->relations[s2->index] & E_ATTACKING) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = + slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + } + + if (bdebug && s->faction) { + if (f_get_alliance(s->faction)) { + fprintf(bdebug, "##### %s (%s/%d)\n", s->faction->name, + itoa36(s->faction->no), + s->faction->alliance ? s->faction->alliance->id : 0); + } else { + fprintf(bdebug, "##### %s (%s)\n", s->faction->name, + itoa36(s->faction->no)); + } + } + print_fighters(b, s); + } + + message_all(b, msg_separator); + + /* Besten Taktiker ermitteln */ + + b->max_tactics = 0; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (!ql_empty(s->leader.fighters)) { + b->max_tactics = MAX(b->max_tactics, s->leader.value); + } + } + + if (b->max_tactics > 0) { + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->leader.value == b->max_tactics) { + quicklist *ql; + int qi; + + for (qi = 0, ql = s->leader.fighters; ql; ql_advance(&ql, &qi, 1)) { + fighter *tf = (fighter *) ql_get(ql, qi); + unit *u = tf->unit; + message *m = NULL; + if (!is_attacker(tf)) { + m = msg_message("battle::tactics_lost", "unit", u); + } else { + m = msg_message("battle::tactics_won", "unit", u); + } + message_all(b, m); + msg_release(m); + } + } + } + } +} + +static int weapon_weight(const weapon * w, bool missile) +{ + if (missile == i2b(fval(w->type, WTF_MISSILE))) { + return w->attackskill + w->defenseskill; + } + return 0; +} + +side * get_side(battle * b, const struct unit * u) +{ + side * s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->faction==u->faction) { + fighter * fig; + for (fig=s->fighters;fig;fig=fig->next) { + if (fig->unit==u) { + return s; + } + } + } + } + return 0; +} + +side * find_side(battle * b, const faction * f, const group * g, int flags, const faction * stealthfaction) +{ + side * s; + static int rule_anon_battle = -1; + + if (rule_anon_battle < 0) { + rule_anon_battle = get_param_int(global.parameters, "rules.stealth.anon_battle", 1); + } + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->faction == f && s->group == g) { + int s1flags = flags | SIDE_HASGUARDS; + int s2flags = s->flags | SIDE_HASGUARDS; + if (rule_anon_battle && s->stealthfaction != stealthfaction) { + continue; + } + if (s1flags == s2flags) { + return s; + } + } + } + return 0; +} + +fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) +{ +#define WMAX 20 + weapon weapons[WMAX]; + int owp[WMAX]; + int dwp[WMAX]; + int w = 0; + region *r = b->region; + item *itm; + fighter *fig = NULL; + int h, i, tactics = eff_skill(u, SK_TACTICS, r); + int berserk; + int strongmen; + int speeded = 0, speed = 1; + bool pr_aid = false; + int rest; + const group *g = NULL; + const attrib *a = a_find(u->attribs, &at_otherfaction); + const faction *stealthfaction = a ? get_otherfaction(a) : NULL; + unsigned int flags = 0; + + assert(u->number); + if (fval(u, UFL_ANON_FACTION) != 0) + flags |= SIDE_STEALTH; + if (!(AllianceAuto() & HELP_FIGHT) && fval(u, UFL_GROUP)) { + const attrib *agroup = a_find(u->attribs, &at_group); + if (agroup != NULL) + g = (const group *)agroup->data.v; + } + + /* Illusionen und Zauber kaempfen nicht */ + if (fval(u_race(u), RCF_ILLUSIONARY) || idle(u->faction) || u->number == 0) { + return NULL; + } + if (s1 == NULL) { + s1 = find_side(b, u->faction, g, flags, stealthfaction); + /* aliances are moved out of make_fighter and will be handled later */ + if (!s1) { + s1 = make_side(b, u->faction, g, flags, stealthfaction); + } else if (!stealthfaction) { + s1->stealthfaction = NULL; + } + /* Zu diesem Zeitpunkt ist attacked noch 0, da die Einheit für noch + * keinen Kampf ausgewählt wurde (sonst würde ein fighter existieren) */ + } + fig = (struct fighter*)calloc(1, sizeof(struct fighter)); + + fig->next = s1->fighters; + s1->fighters = fig; + + fig->unit = u; + /* In einer Burg muß man a) nicht Angreifer sein, und b) drin sein, und + * c) noch Platz finden. d) menschanähnlich sein */ + if (attack) { + set_attacker(fig); + } else { + building *bld = u->building; + if (bld && bld->sizeleft >= u->number && playerrace(u_race(u))) { + fig->building = bld; + fig->building->sizeleft -= u->number; + } + } + fig->status = u->status; + fig->side = s1; + fig->alive = u->number; + fig->side->alive += u->number; + fig->side->battle->alive += u->number; + fig->catmsg = -1; + + /* Freigeben nicht vergessen! */ + fig->person = (struct person*)calloc(fig->alive, sizeof(struct person)); + + h = u->hp / u->number; + assert(h); + rest = u->hp % u->number; + + /* Effekte von Sprüchen */ + + { + static const curse_type *speed_ct; + speed_ct = ct_find("speed"); + if (speed_ct) { + curse *c = get_curse(u->attribs, speed_ct); + if (c) { + speeded = get_cursedmen(u, c); + speed = curse_geteffect_int(c); + } + } + } + + /* Effekte von Alchemie */ + berserk = get_effect(u, oldpotiontype[P_BERSERK]); + /* change_effect wird in ageing gemacht */ + + /* Effekte von Artefakten */ + strongmen = MIN(fig->unit->number, get_item(u, I_TROLLBELT)); + + /* Hitpoints, Attack- und Defence-Boni für alle Personen */ + for (i = 0; i < fig->alive; i++) { + assert(i < fig->unit->number); + fig->person[i].hp = h; + if (i < rest) + fig->person[i].hp++; + + if (i < speeded) + fig->person[i].speed = speed; + else + fig->person[i].speed = 1; + + if (i < berserk) { + fig->person[i].attack++; + } + /* Leute mit einem Aid-Prayer bekommen +1 auf fast alles. */ + if (pr_aid) { + fig->person[i].attack++; + fig->person[i].defence++; + fig->person[i].damage++; + fig->person[i].damage_rear++; + fig->person[i].flags |= FL_COURAGE; + } + /* Leute mit Kraftzauber machen +2 Schaden im Nahkampf. */ + if (i < strongmen) { + fig->person[i].damage += 2; + } + } + + /* Für alle Waffengattungen wird bestimmt, wie viele der Personen mit + * ihr kämpfen könnten, und was ihr Wert darin ist. */ + if (u_race(u)->battle_flags & BF_EQUIPMENT) { + int oi = 0, di = 0; + for (itm = u->items; itm && w != WMAX; itm = itm->next) { + const weapon_type *wtype = resource2weapon(itm->type->rtype); + if (wtype == NULL || itm->number == 0) + continue; + weapons[w].attackskill = weapon_skill(wtype, u, true); + weapons[w].defenseskill = weapon_skill(wtype, u, false); + if (weapons[w].attackskill >= 0 || weapons[w].defenseskill >= 0) { + weapons[w].type = wtype; + weapons[w].used = 0; + weapons[w].count = itm->number; + ++w; + } + assert(w != WMAX); + } + fig->weapons = (weapon *) calloc(sizeof(weapon), w + 1); + memcpy(fig->weapons, weapons, w * sizeof(weapon)); + + for (i = 0; i != w; ++i) { + int j, o = 0, d = 0; + for (j = 0; j != i; ++j) { + if (weapon_weight(fig->weapons + j, + true) >= weapon_weight(fig->weapons + i, true)) + ++d; + if (weapon_weight(fig->weapons + j, + false) >= weapon_weight(fig->weapons + i, false)) + ++o; + } + for (j = i + 1; j != w; ++j) { + if (weapon_weight(fig->weapons + j, + true) > weapon_weight(fig->weapons + i, true)) + ++d; + if (weapon_weight(fig->weapons + j, + false) > weapon_weight(fig->weapons + i, false)) + ++o; + } + owp[o] = i; + dwp[d] = i; + } + /* jetzt enthalten owp und dwp eine absteigend schlechter werdende Liste der Waffen + * oi and di are the current index to the sorted owp/dwp arrays + * owp, dwp contain indices to the figther::weapons array */ + + /* hand out melee weapons: */ + for (i = 0; i != fig->alive; ++i) { + int wpless = weapon_skill(NULL, u, true); + while (oi != w + && (fig->weapons[owp[oi]].used == fig->weapons[owp[oi]].count + || fval(fig->weapons[owp[oi]].type, WTF_MISSILE))) { + ++oi; + } + if (oi == w) + break; /* no more weapons available */ + if (weapon_weight(fig->weapons + owp[oi], false) <= wpless) { + continue; /* we fight better with bare hands */ + } + fig->person[i].melee = &fig->weapons[owp[oi]]; + ++fig->weapons[owp[oi]].used; + } + /* hand out missile weapons (from back to front, in case of mixed troops). */ + for (di = 0, i = fig->alive; i-- != 0;) { + while (di != w + && (fig->weapons[dwp[di]].used == fig->weapons[dwp[di]].count + || !fval(fig->weapons[dwp[di]].type, WTF_MISSILE))) { + ++di; + } + if (di == w) + break; /* no more weapons available */ + if (weapon_weight(fig->weapons + dwp[di], true) > 0) { + fig->person[i].missile = &fig->weapons[dwp[di]]; + ++fig->weapons[dwp[di]].used; + } + } + } + + s1->size[statusrow(fig->status)] += u->number; + s1->size[SUM_ROW] += u->number; + if (u_race(u)->battle_flags & BF_NOBLOCK) { + s1->nonblockers[statusrow(fig->status)] += u->number; + } + + if (u_race(fig->unit)->flags & RCF_HORSE) { + fig->horses = fig->unit->number; + fig->elvenhorses = 0; + } else { + static const item_type *it_charger = 0; + if (it_charger == 0) { + it_charger = it_find("charger"); + if (!it_charger) { + it_charger = it_find("horse"); + } + } + fig->horses = i_get(u->items, it_charger); + fig->elvenhorses = get_item(u, I_ELVENHORSE); + } + + if (u_race(u)->battle_flags & BF_EQUIPMENT) { + for (itm = u->items; itm; itm = itm->next) { + if (itm->type->rtype->atype) { + if (i_canuse(u, itm->type)) { + struct armor *adata = malloc(sizeof(armor)), **aptr; + adata->atype = itm->type->rtype->atype; + adata->count = itm->number; + for (aptr = &fig->armors; *aptr; aptr = &(*aptr)->next) { + if (adata->atype->prot > (*aptr)->atype->prot) + break; + } + adata->next = *aptr; + *aptr = adata; + } + } + } + } + + /* Jetzt muß noch geschaut werden, wo die Einheit die jeweils besten + * Werte hat, das kommt aber erst irgendwo später. Ich entscheide + * wärend des Kampfes, welche ich nehme, je nach Gegner. Deswegen auch + * keine addierten boni. */ + + /* Zuerst mal die Spezialbehandlung gewisser Sonderfälle. */ + fig->magic = eff_skill(u, SK_MAGIC, r); + + if (fig->horses) { + if (!fval(r->terrain, CAVALRY_REGION) || r_isforest(r) + || eff_skill(u, SK_RIDING, r) < CavalrySkill() + || u_race(u) == new_race[RC_TROLL] || fval(u, UFL_WERE)) + fig->horses = 0; + } + + if (fig->elvenhorses) { + if (eff_skill(u, SK_RIDING, r) < 5 || u_race(u) == new_race[RC_TROLL] + || fval(u, UFL_WERE)) + fig->elvenhorses = 0; + } + + /* Schauen, wie gut wir in Taktik sind. */ + if (tactics > 0 && u_race(u) == new_race[RC_INSECT]) + tactics -= 1 - (int)log10(fig->side->size[SUM_ROW]); +#ifdef TACTICS_MODIFIER + if (tactics > 0 && statusrow(fig->status) == FIGHT_ROW) + tactics += TACTICS_MODIFIER; + if (tactics > 0 && statusrow(fig->status) > BEHIND_ROW) { + tactics -= TACTICS_MODIFIER; + } +#endif + + if (tactics > 0) { + int bonus = 0; + + for (i = 0; i < fig->alive; i++) { + int p_bonus = 0; + int rnd; + + do { + rnd = rng_int() % 100; + if (rnd >= 40 && rnd <= 69) + p_bonus += 1; + else if (rnd <= 89) + p_bonus += 2; + else + p_bonus += 3; + } while (rnd >= 97); + bonus = MAX(p_bonus, bonus); + } + tactics += bonus; + } + + add_tactics(&fig->side->leader, fig, tactics); + ++b->nfighters; + return fig; +} + +fighter * get_fighter(battle * b, const struct unit * u) +{ + side * s; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + if (s->faction == u->faction) { + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->unit == u) { + return fig; + } + } + } + } + return 0; +} + +static int join_battle(battle * b, unit * u, bool attack, fighter ** cp) +{ + side *s; + fighter *c = NULL; + + if (!attack) { + attrib *a = a_find(u->attribs, &at_fleechance); + if (a != NULL) { + if (rng_double() <= a->data.flt) { + *cp = NULL; + return false; + } + } + } + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + if (s->faction == u->faction) { + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->unit == u) { + c = fig; + if (attack) { + set_attacker(fig); + } + break; + } + } + } + } + if (!c) { + *cp = make_fighter(b, u, NULL, attack); + return *cp != NULL; + } + *cp = c; + return false; +} + +static const char *simplename(region * r) +{ + int i; + static char name[17]; + const char *cp = rname(r, default_locale); + for (i = 0; *cp && i != 16; ++i, ++cp) { + int c = *(unsigned char *)cp; + while (c && !isalpha(c) && !isxspace(c)) { + ++cp; + c = *(unsigned char *)cp; + } + if (isxspace(c)) + name[i] = '_'; + else + name[i] = *cp; + if (c == 0) + break; + } + name[i] = 0; + return name; +} + +battle *make_battle(region * r) +{ + battle *b = (battle *)calloc(1, sizeof(battle)); + unit *u; + bfaction *bf; + building * bld; + static int max_fac_no = 0; /* need this only once */ + + /* Alle Mann raus aus der Burg! */ + for (bld = r->buildings; bld != NULL; bld = bld->next) + bld->sizeleft = bld->size; + + if (battledebug) { + char zText[MAX_PATH]; + char zFilename[MAX_PATH]; + sprintf(zText, "%s/battles", basepath()); + os_mkdir(zText, 0700); + sprintf(zFilename, "%s/battle-%d-%s.log", zText, obs_count, simplename(r)); + bdebug = fopen(zFilename, "w"); + if (!bdebug) + log_error("battles cannot be debugged\n"); + else { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + fwrite(utf8_bom, 1, 3, bdebug); + fprintf(bdebug, "In %s findet ein Kampf statt:\n", rname(r, + default_locale)); + } + obs_count++; + } + + b->region = r; + b->plane = getplane(r); + /* Finde alle Parteien, die den Kampf beobachten können: */ + for (u = r->units; u; u = u->next) { + if (u->number > 0) { + if (!fval(u->faction, FFL_MARK)) { + fset(u->faction, FFL_MARK); + for (bf = b->factions; bf; bf = bf->next) { + if (bf->faction == u->faction) + break; + } + if (!bf) { + bf = (bfaction *)calloc(sizeof(bfaction), 1); + ++b->nfactions; + bf->faction = u->faction; + bf->next = b->factions; + b->factions = bf; + } + } + } + } + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + max_fac_no = MAX(max_fac_no, f->no); + freset(f, FFL_MARK); + } + return b; +} + +static void free_side(side * si) +{ + ql_free(si->leader.fighters); +} + +static void free_fighter(fighter * fig) +{ + while (fig->loot) { + i_free(i_remove(&fig->loot, fig->loot)); + } + while (fig->armors) { + armor *a = fig->armors; + fig->armors = a->next; + free(a); + } + free(fig->person); + free(fig->weapons); + +} + +static void free_battle(battle * b) +{ + int max_fac_no = 0; + + if (bdebug) { + fclose(bdebug); + } + + while (b->factions) { + bfaction *bf = b->factions; + faction *f = bf->faction; + b->factions = bf->next; + max_fac_no = MAX(max_fac_no, f->no); + free(bf); + } + + ql_free(b->leaders); + ql_foreach(b->meffects, free); + ql_free(b->meffects); + + battle_free(b); +} + +static int *get_alive(side * s) +{ +#if 0 + static int alive[NUMROWS]; + fighter *fig; + memset(alive, 0, NUMROWS * sizeof(int)); + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->alive > 0) { + int row = statusrow(fig); + alive[row] += fig->alive; + } + } + return alive; +#endif + return s->size; +} + +static int battle_report(battle * b) +{ + side *s, *s2; + bool cont = false; + bool komma; + bfaction *bf; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->alive - s->removed > 0) { + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (s2->alive - s2->removed > 0 && enemy(s, s2)) { + cont = true; + break; + } + } + if (cont) + break; + } + } + + if (verbosity > 0) + log_printf(stdout, " %d", b->turn); + fflush(stdout); + + for (bf = b->factions; bf; bf = bf->next) { + faction *fac = bf->faction; + char buf[32 * MAXSIDES]; + char *bufp = buf; + int bytes; + size_t size = sizeof(buf) - 1; + message *m; + + message_faction(b, fac, msg_separator); + + if (cont) + m = msg_message("battle::lineup", "turn", b->turn); + else + m = msg_message("battle::after", ""); + message_faction(b, fac, m); + msg_release(m); + + komma = false; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->alive) { + int r, k = 0, *alive = get_alive(s); + int l = FIGHT_ROW; + const char *abbrev = seematrix(fac, s) ? sideabkz(s, false) : "-?-"; + const char *loc_army = LOC(fac->locale, "battle_army"); + char buffer[32]; + + if (komma) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + slprintf(buffer, sizeof(buffer), "%s %2d(%s): ", + loc_army, army_index(s), abbrev); + + bytes = (int)strlcpy(bufp, buffer, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + for (r = FIGHT_ROW; r != NUMROWS; ++r) { + if (alive[r]) { + if (l != FIGHT_ROW) { + bytes = (int)strlcpy(bufp, "+", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + while (k--) { + bytes = (int)strlcpy(bufp, "0+", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + sprintf(buffer, "%d", alive[r]); + + bytes = (int)strlcpy(bufp, buffer, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + k = 0; + l = r + 1; + } else + ++k; + } + + komma = true; + } + } + *bufp = 0; + fbattlerecord(b, fac, buf); + } + return cont; +} + +static void join_allies(battle * b) +{ + region *r = b->region; + unit *u; + side *s, *s_end = b->sides + b->nsides; + /* make_side might be adding a new faction, but it adds them to the end + * of the list, so we're safe in our iteration here if we remember the end + * up front. */ + for (u = r->units; u; u = u->next) { + /* Was ist mit Schiffen? */ + if (u->status != ST_FLEE && u->status != ST_AVOID + && !fval(u, UFL_LONGACTION | UFL_ISNEW) && u->number > 0) { + faction *f = u->faction; + fighter *c = NULL; + + for (s = b->sides; s != s_end; ++s) { + side *se; + /* Wenn alle attackierten noch FFL_NOAID haben, dann kämpfe nicht mit. */ + if (fval(s->faction, FFL_NOAID)) + continue; + if (s->faction != f) { + /* Wenn wir attackiert haben, kommt niemand mehr hinzu: */ + if (s->bf->attacker) + continue; + /* alliiert müssen wir schon sein, sonst ist's eh egal : */ + if (!alliedunit(u, s->faction, HELP_FIGHT)) + continue; + /* wenn die partei verborgen ist, oder gar eine andere + * vorgespiegelt wird, und er sich uns gegenüber nicht zu + * erkennen gibt, helfen wir ihm nicht */ + if (s->stealthfaction) { + if (!allysfm(s, u->faction, HELP_FSTEALTH)) { + continue; + } + } + } + /* einen alliierten angreifen dürfen sie nicht, es sei denn, der + * ist mit einem alliierten verfeindet, der nicht attackiert + * hat: */ + for (se = b->sides; se != s_end; ++se) { + if (u->faction == se->faction) + continue; + if (alliedunit(u, se->faction, HELP_FIGHT) && !se->bf->attacker) { + continue; + } + if (enemy(s, se)) + break; + } + if (se == s_end) + continue; + /* Wenn die Einheit belagert ist, muß auch einer der Alliierten belagert sein: */ + if (besieged(u)) { + fighter *ally; + for (ally = s->fighters; ally; ally = ally->next) { + if (besieged(ally->unit)) { + break; + } + } + if (ally == NULL) + continue; + } + /* keine Einwände, also soll er mitmachen: */ + if (c == NULL) { + if (join_battle(b, u, false, &c)) { + if (battledebug) { + fprintf(bdebug, "%s joins to help %s against %s.\n", + unitname(u), factionname(s->faction), factionname(se->faction)); + } + } else if (c == NULL) { + continue; + } + } + + /* the enemy of my friend is my enemy: */ + for (se = b->sides; se != s_end; ++se) { + if (se->faction != u->faction && enemy(s, se)) { + if (set_enemy(se, c->side, false) && battledebug) { + fprintf(bdebug, + "%u/%s hates %u/%s because they are enemies with %u/%s.\n", + c->side->index, sidename(c->side), se->index, sidename(se), + s->index, sidename(s)); + } + } + } + } + } + } + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + int si; + side *sa; + faction *f = s->faction; + + /* Den Feinden meiner Feinde gebe ich Deckung (gegen gemeinsame Feinde): */ + for (si = 0; s->enemies[si]; ++si) { + side *se = s->enemies[si]; + int ai; + for (ai = 0; se->enemies[ai]; ++ai) { + side *as = se->enemies[ai]; + if (as == s || !enemy(as, s)) { + set_friendly(as, s); + } + } + } + + for (sa = s + 1; sa != b->sides + b->nsides; ++sa) { + plane *pl = rplane(r); + if (enemy(s, sa)) + continue; + if (friendly(s, sa)) + continue; + if (!alliedgroup(pl, f, sa->faction, f->allies, HELP_FIGHT)) + continue; + if (!alliedgroup(pl, sa->faction, f, sa->faction->allies, HELP_FIGHT)) + continue; + + set_friendly(s, sa); + } + } +} + +static void flee(const troop dt) +{ + fighter *fig = dt.fighter; + unit *u = fig->unit; + + fig->run.hp += fig->person[dt.index].hp; + ++fig->run.number; + + setguard(u, GUARD_NONE); + + kill_troop(dt); +} + +static bool start_battle(region * r, battle ** bp) +{ + battle *b = NULL; + unit *u; + bool fighting = false; + + /* list_foreach geht nicht, wegen flucht */ + for (u = r->units; u != NULL; u = u->next) { + if (fval(u, UFL_LONGACTION)) + continue; + if (u->number > 0) { + order *ord; + + for (ord = u->orders; ord; ord = ord->next) { + static bool init = false; + static const curse_type *peace_ct, *slave_ct, *calm_ct; + + if (!init) { + init = true; + peace_ct = ct_find("peacezone"); + slave_ct = ct_find("slavery"); + calm_ct = ct_find("calmmonster"); + } + if (get_keyword(ord) == K_ATTACK) { + unit *u2; + fighter *c1, *c2; + ship *lsh = NULL; + plane *pl = rplane(r); + + if (pl && fval(pl, PFL_NOATTACK)) { + cmistake(u, ord, 271, MSG_BATTLE); + continue; + } + + if ((u_race(u)->battle_flags & BF_CANATTACK) == 0) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "race_no_attack", + "race", u_race(u))); + continue; + } + /** + ** Fehlerbehandlung Angreifer + **/ + if (LongHunger(u)) { + cmistake(u, ord, 225, MSG_BATTLE); + continue; + } + + if (u->status == ST_AVOID || u->status == ST_FLEE) { + cmistake(u, ord, 226, MSG_BATTLE); + continue; + } + + /* ist ein Flüchtling aus einem andern Kampf */ + if (fval(u, UFL_LONGACTION)) + continue; + + if (peace_ct && curse_active(get_curse(r->attribs, peace_ct))) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "peace_active", "")); + continue; + } + + if (slave_ct && curse_active(get_curse(u->attribs, slave_ct))) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "slave_active", "")); + continue; + } + + if ((u->ship != NULL && !fval(r->terrain, SEA_REGION)) + || (lsh = leftship(u)) != NULL) { + if (is_guarded(r, u, GUARD_TRAVELTHRU)) { + if (lsh) { + cmistake(u, ord, 234, MSG_BATTLE); + } else { + /* Fehler: "Das Schiff muß erst verlassen werden" */ + cmistake(u, ord, 19, MSG_BATTLE); + } + continue; + } + } + + /* Ende Fehlerbehandlung Angreifer */ + + init_tokens(ord); + skip_token(); + /* attackierte Einheit ermitteln */ + u2 = getunit(r, u->faction); + + /* Beginn Fehlerbehandlung */ + /* Fehler: "Die Einheit wurde nicht gefunden" */ + if (!u2 || u2->number == 0 || !cansee(u->faction, u->region, u2, 0)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "feedback_unit_not_found", "")); + continue; + } + /* Fehler: "Die Einheit ist eine der unsrigen" */ + if (u2->faction == u->faction) { + cmistake(u, ord, 45, MSG_BATTLE); + continue; + } + /* Fehler: "Die Einheit ist mit uns alliert" */ + if (alliedunit(u, u2->faction, HELP_FIGHT)) { + cmistake(u, ord, 47, MSG_BATTLE); + continue; + } + if (IsImmune(u2->faction)) { + add_message(&u->faction->msgs, + msg_feedback(u, u->thisorder, "newbie_immunity_error", "turns", + NewbieImmunity())); + continue; + } + /* Fehler: "Die Einheit ist mit uns alliert" */ + + if (calm_ct) { + attrib *a = a_find(u->attribs, &at_curse); + bool calm = false; + while (a && a->type == &at_curse) { + curse *c = (curse *) a->data.v; + if (c->type == calm_ct + && curse_geteffect(c) == u2->faction->subscription) { + if (curse_active(c)) { + calm = true; + break; + } + } + a = a->next; + } + if (calm) { + cmistake(u, ord, 47, MSG_BATTLE); + continue; + } + } + /* Ende Fehlerbehandlung */ + if (b == NULL) { + unit *utmp; + for (utmp = r->units; utmp != NULL; utmp = utmp->next) { + fset(utmp->faction, FFL_NOAID); + } + b = make_battle(r); + } + if (join_battle(b, u, true, &c1)) { + if (battledebug) { + fprintf(bdebug, "%s joins by attacking %s.\n", + unitname(u), unitname(u2)); + } + } + if (join_battle(b, u2, false, &c2)) { + if (battledebug) { + fprintf(bdebug, "%s joins because of an attack from %s.\n", + unitname(u2), unitname(u)); + } + } + + /* Hat die attackierte Einheit keinen Noaid-Status, + * wird das Flag von der Faction genommen, andere + * Einheiten greifen ein. */ + if (!fval(u2, UFL_NOAID)) + freset(u2->faction, FFL_NOAID); + + if (c1 != NULL && c2 != NULL) { + /* Merken, wer Angreifer ist, für die Rückzahlung der + * Präcombataura bei kurzem Kampf. */ + c1->side->bf->attacker = true; + + if (set_enemy(c1->side, c2->side, true) && battledebug) { + fprintf(bdebug, "%u/%s hates %u/%s because they attacked them.\n", + c2->side->index, sidename(c2->side), + c1->side->index, sidename(c1->side)); + } + fighting = true; + } + } + } + } + } + *bp = b; + return fighting; +} + +static void battle_stats(FILE * F, battle * b) +{ + typedef struct stat_info { + struct stat_info *next; + const weapon_type *wtype; + int level; + int number; + } stat_info; + side *s; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + stat_info *stats = NULL, *stat; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + troop dt; + stat_info *slast = NULL; + + dt.fighter = df; + for (dt.index = 0; dt.index != du->number; ++dt.index) { + weapon *wp = preferred_weapon(dt, true); + int level = wp ? wp->attackskill : 0; + const weapon_type *wtype = wp ? wp->type : NULL; + stat_info **slist = &stats; + + if (slast && slast->wtype == wtype && slast->level == level) { + ++slast->number; + continue; + } + while (*slist && (*slist)->wtype != wtype) { + slist = &(*slist)->next; + } + while (*slist && (*slist)->wtype == wtype && (*slist)->level > level) { + slist = &(*slist)->next; + } + stat = *slist; + if (stat == NULL || stat->wtype != wtype || stat->level != level) { + stat = (stat_info*)calloc(1, sizeof(stat_info)); + stat->wtype = wtype; + stat->level = level; + stat->next = *slist; + *slist = stat; + } + slast = stat; + ++slast->number; + } + } + + fprintf(F, "##STATS## Heer %u - %s:\n", army_index(s), + factionname(s->faction)); + for (stat = stats; stat != NULL; stat = stat->next) { + fprintf(F, "%s %u : %u\n", + stat->wtype ? stat->wtype->itype->rtype->_name[0] : "none", stat->level, + stat->number); + } + while (stats) { + stat_info *stat = stats; + stats = stat->next; + free(stat); + } + } +} + +/** execute one round of attacks + * fig->fighting is used to determine who attacks, not fig->alive, since + * the latter may be influenced by attacks that already took place. + */ +static void battle_attacks(battle * b) +{ + side *s; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + + if (b->turn != 0 || (b->max_tactics > 0 + && get_tactics(s, NULL) == b->max_tactics)) { + for (fig = s->fighters; fig; fig = fig->next) { + + /* ist in dieser Einheit noch jemand handlungsfähig? */ + if (fig->fighting <= 0) + continue; + + /* Handle the unit's attack on someone */ + do_attack(fig); + } + } + } +} + +/** updates the number of attacking troops in each fighter struct. + * this has to be calculated _before_ the actual attacks take + * place because otherwise dead troops would not strike in the + * round they die. */ +static void battle_update(battle * b) +{ + side *s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + fig->fighting = fig->alive - fig->removed; + } + } +} + +/** attempt to flee from battle before the next round begins + * there's a double attempt before the first round, but only + * one attempt before round zero, the potential tactics round. */ +static void battle_flee(battle * b) +{ + int attempt, flee_ops = 1; + + if (b->turn == 1) + flee_ops = 2; + + for (attempt = 1; attempt <= flee_ops; ++attempt) { + side *s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *u = fig->unit; + troop dt; + int runners = 0; + /* Flucht nicht bei mehr als 600 HP. Damit Wyrme tötbar bleiben. */ + int runhp = MIN(600, (int)(0.9 + unit_max_hp(u) * hpflee(u->status))); + + if (u->ship && fval(u->region->terrain, SEA_REGION)) { + /* keine Flucht von Schiffen auf hoher See */ + continue; + } + if (fval(u_race(u), RCF_UNDEAD) || u_race(u) == new_race[RC_SHADOWKNIGHT]) { + /* Untote fliehen nicht. Warum eigentlich? */ + continue; + } + + dt.fighter = fig; + dt.index = fig->alive - fig->removed; + while (s->size[SUM_ROW] && dt.index != 0) { + double ispaniced = 0.0; + --dt.index; + assert(dt.index >= 0 && dt.index < fig->unit->number); + assert(fig->person[dt.index].hp > 0); + + /* Versuche zu fliehen, wenn + * - Kampfstatus fliehe + * - schwer verwundet und nicht erste kampfrunde + * - in panik (Zauber) + * aber nicht, wenn der Zaubereffekt Held auf dir liegt! + */ + switch (u->status) { + case ST_FLEE: + break; + default: + if ((fig->person[dt.index].flags & FL_HIT) == 0) + continue; + if (b->turn <= 1) + continue; + if (fig->person[dt.index].hp <= runhp) + break; + if (fig->person[dt.index].flags & FL_PANICED) { + if ((fig->person[dt.index].flags & FL_COURAGE) == 0) + break; + } + continue; + } + + if (fig->person[dt.index].flags & FL_PANICED) { + ispaniced = EFFECT_PANIC_SPELL; + } + if (chance(MIN(fleechance(u) + ispaniced, 0.90))) { + ++runners; + flee(dt); + } + } + if (bdebug && runners > 0) { + fprintf(bdebug, "Fleeing: %d from %s\n", runners, + itoa36(fig->unit->no)); + } + } + } + } +} + +void do_battle(region * r) +{ + battle *b = NULL; + bool fighting = false; + ship *sh; + static int init_rules = 0; + + if (!init_rules) { + static_rules(); + init_rules = 1; + } + if (msg_separator == NULL) { + msg_separator = msg_message("battle::section", ""); + } + + fighting = start_battle(r, &b); + + if (b == NULL) + return; + + /* Bevor wir die alliierten hineinziehen, sollten wir schauen, * + * Ob jemand fliehen kann. Dann erübrigt sich das ganze ja + * vielleicht schon. */ + print_header(b); + if (!fighting) { + /* Niemand mehr da, Kampf kann nicht stattfinden. */ + message *m = msg_message("battle::aborted", ""); + message_all(b, m); + msg_release(m); + free_battle(b); + free(b); + return; + } + join_allies(b); + make_heroes(b); + + /* make sure no ships are damaged initially */ + for (sh = r->ships; sh; sh = sh->next) + freset(sh, SF_DAMAGED); + + /* Gibt es eine Taktikrunde ? */ + if (!ql_empty(b->leaders)) { + b->turn = 0; + b->has_tactics_turn = true; + } else { + b->turn = 1; + b->has_tactics_turn = false; + } + + if (b->region->flags & RF_COMBATDEBUG) + battle_stats(bdebug, b); + + /* PRECOMBATSPELLS */ + do_combatmagic(b, DO_PRECOMBATSPELL); + + print_stats(b); /* gibt die Kampfaufstellung aus */ + if (verbosity > 0) + log_printf(stdout, "%s (%d, %d) : ", rname(r, default_locale), r->x, r->y); + + for (; battle_report(b) && b->turn <= max_turns; ++b->turn) { + if (bdebug) { + fprintf(bdebug, "*** Turn: %d\n", b->turn); + } + battle_flee(b); + battle_update(b); + battle_attacks(b); + + } + + if (verbosity > 0) + log_printf(stdout, "\n"); + + /* Auswirkungen berechnen: */ + aftermath(b); + /* Hier ist das Gefecht beendet, und wir können die + * Hilfsstrukturen * wieder löschen: */ + + if (b) { + free_battle(b); + free(b); + } +} + +void battle_init(battle * b) { + assert(b); + memset(b, 0, sizeof(battle)); +} + +void battle_free(battle * b) { + side *s; + + assert(b); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fnext = s->fighters; + while (fnext) { + fighter *fig = fnext; + fnext = fig->next; + free_fighter(fig); + free(fig); + } + free_side(s); + } +} + diff --git a/core/src/kernel/battle.h b/core/src/kernel/battle.h new file mode 100644 index 000000000..eb854caeb --- /dev/null +++ b/core/src/kernel/battle.h @@ -0,0 +1,260 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_BATTLE +#define H_KRNL_BATTLE +#ifdef __cplusplus +extern "C" { +#endif + + /** more defines **/ +#define FS_ENEMY 1 +#define FS_HELP 2 + + /***** Verteidigungslinien. + * Eressea hat 4 Verteidigungslinien. 1 ist vorn, 5. enthält Summen + */ + +#define NUMROWS 5 +#define SUM_ROW 0 +#define FIGHT_ROW 1 +#define BEHIND_ROW 2 +#define AVOID_ROW 3 +#define FLEE_ROW 4 +#define LAST_ROW (NUMROWS-1) +#define FIRST_ROW FIGHT_ROW +#define MAXSIDES 192 /* if there are ever more than this, we're fucked. */ + + struct message; + + typedef struct bfaction { + struct bfaction *next; + struct side *sides; + struct faction *faction; + bool attacker; + } bfaction; + + typedef struct tactics { + struct quicklist *fighters; + int value; + } tactics; + +#define SIDE_STEALTH 1<<0 +#define SIDE_HASGUARDS 1<<1 + typedef struct side { + struct side *nextF; /* next army of same faction */ + struct battle *battle; + struct bfaction *bf; /* battle info that goes with the faction */ + struct faction *faction; /* cache optimization for bf->faction */ + const struct group *group; + struct tactics leader; /* this army's best tactician */ +# define E_ENEMY 1 +# define E_FRIEND 2 +# define E_ATTACKING 4 + unsigned char relations[MAXSIDES]; + struct side *enemies[MAXSIDES]; + struct fighter *fighters; + int index; /* Eintrag der Fraktion in b->matrix/b->enemies */ + int size[NUMROWS]; /* Anzahl Personen in Reihe X. 0 = Summe */ + int nonblockers[NUMROWS]; /* Anzahl nichtblockierender Kämpfer, z.B. Schattenritter. */ + int alive; /* Die Partei hat den Kampf verlassen */ + int removed; /* stoned */ + int flee; + int dead; + int casualties; /* those dead that were real people, not undead! */ + int healed; + unsigned int flags; + const struct faction *stealthfaction; + } side; + + typedef struct battle { + struct quicklist *leaders; + struct region *region; + struct plane *plane; + bfaction *factions; + int nfactions; + int nfighters; + side sides[MAXSIDES]; + int nsides; + struct quicklist *meffects; + int max_tactics; + int turn; + bool has_tactics_turn; + int keeploot; + bool reelarrow; + int alive; + struct { + const struct side *as; + const struct side *vs; + int alive; + int row; + int result; + } rowcache; + struct { + struct side *side; + int status; + int alive; + int minrow, maxrow; + int enemies[8]; + } fast; + } battle; + + typedef struct weapon { + int count, used; + const struct weapon_type *type; + int attackskill:8; + int defenseskill:8; + } weapon; + + /*** fighter::person::flags ***/ +#define FL_TIRED 1 +#define FL_DAZZLED 2 /* durch Untote oder Dämonen eingeschüchtert */ +#define FL_PANICED 4 +#define FL_COURAGE 8 /* Helden fliehen nie */ +#define FL_SLEEPING 16 +#define FL_STUNNED 32 /* eine Runde keinen Angriff */ +#define FL_HIT 64 /* the person at attacked */ + + typedef struct troop { + struct fighter *fighter; + int index; + } troop; + + typedef struct armor { + struct armor *next; + const struct armor_type *atype; + int count; + } armor; + + /*** fighter::flags ***/ +#define FIG_ATTACKER 1<<0 +#define FIG_NOLOOT 1<<1 + typedef struct fighter { + struct fighter *next; + struct side *side; + struct unit *unit; /* Die Einheit, die hier kämpft */ + struct building *building; /* Gebäude, in dem die Einheit evtl. steht */ + status_t status; /* Kampfstatus */ + struct weapon *weapons; + struct armor *armors; /* Anzahl Rüstungen jeden Typs */ + int alive; /* Anzahl der noch nicht Toten in der Einheit */ + int fighting; /* Anzahl der Kämpfer in der aktuellen Runde */ + int removed; /* Anzahl Kaempfer, die nicht tot sind, aber + aus dem Kampf raus sind (zB weil sie + versteinert wurden). Diese werden auch + in alive noch mitgezählt! */ + int magic; /* Magietalent der Einheit */ + int horses; /* Anzahl brauchbarer Pferde der Einheit */ + int elvenhorses; /* Anzahl brauchbarer Elfenpferde der Einheit */ + struct item *loot; + int catmsg; /* Merkt sich, ob Katapultmessage schon generiert. */ + struct person { + int hp; /* Trefferpunkte der Personen */ + int attack:8; /* (Magie) Attackenbonus der Personen */ + int defence:8; /* (Magie) Paradenbonus der Personen */ + int damage:8; /* (Magie) Schadensbonus der Personen im Nahkampf */ + int damage_rear:8; /* (Magie) Schadensbonus der Personen im Fernkampf */ + int flags:8; /* (Magie) Diverse Flags auf Kämpfern */ + int speed:8; /* (Magie) Geschwindigkeitsmultiplkator. */ + int reload:4; /* Anzahl Runden, die die Waffe x noch laden muss. + * dahinter steckt ein array[RL_MAX] wenn er min. eine hat. */ + int last_action:4; /* In welcher Runde haben wir zuletzt etwas getan */ + struct weapon *missile; /* missile weapon */ + struct weapon *melee; /* melee weapon */ + } *person; + unsigned int flags; + struct { + int number; /* number of people who fled */ + int hp; /* accumulated hp of fleeing people */ + } run; + int kills; + int hits; + } fighter; + + /* schilde */ + + enum { + SHIELD_REDUCE, + SHIELD_ARMOR, + SHIELD_WIND, + SHIELD_BLOCK, + SHIELD_MAX + }; + + typedef struct meffect { + fighter *magician; /* Der Zauberer, der den Schild gezaubert hat */ + int typ; /* Wirkungsweise des Schilds */ + int effect; + int duration; + } meffect; + + extern const troop no_troop; + + /* BEGIN battle interface */ + void battle_init(battle * b); + void battle_free(battle * b); + side * find_side(battle * b, const struct faction * f, const struct group * g, int flags, const struct faction * stealthfaction); + side * get_side(battle * b, const struct unit * u); + fighter * get_fighter(battle * b, const struct unit * u); + /* END battle interface */ + + extern void do_battle(struct region *r); + + /* for combat spells and special attacks */ + enum { SELECT_ADVANCE = 0x1, SELECT_DISTANCE = 0x2, SELECT_FIND = 0x4 }; + enum { ALLY_SELF, ALLY_ANY }; + + extern troop select_enemy(struct fighter *af, int minrow, int maxrow, + int select); + extern troop select_ally(struct fighter *af, int minrow, int maxrow, + int allytype); + + extern int count_enemies(struct battle *b, const struct fighter *af, + int minrow, int maxrow, int select); + extern bool terminate(troop dt, troop at, int type, const char *damage, + bool missile); + extern void message_all(battle * b, struct message *m); + extern int hits(troop at, troop dt, weapon * awp); + extern void damage_building(struct battle *b, struct building *bldg, + int damage_abs); + extern struct quicklist *fighters(struct battle *b, const struct side *vs, + int minrow, int maxrow, int mask); + extern int count_allies(const struct side *as, int minrow, int maxrow, + int select, int allytype); + extern int get_unitrow(const struct fighter *af, const struct side *vs); + extern bool helping(const struct side *as, const struct side *ds); + extern void rmfighter(fighter * df, int i); + extern struct fighter *select_corpse(struct battle *b, struct fighter *af); + extern int statusrow(int status); + extern void drain_exp(struct unit *u, int d); + extern void kill_troop(troop dt); + extern void remove_troop(troop dt); /* not the same as the badly named rmtroop */ + extern bool is_attacker(const fighter * fig); + + extern struct battle *make_battle(struct region * r); + extern fighter *make_fighter(struct battle *b, struct unit *u, side * s, + bool attack); + extern struct side *make_side(struct battle * b, const struct faction * f, + const struct group * g, unsigned int flags, + const struct faction * stealthfaction); + extern int skilldiff(troop at, troop dt, int dist); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/battle_test.c b/core/src/kernel/battle_test.c new file mode 100644 index 000000000..8bdc50822 --- /dev/null +++ b/core/src/kernel/battle_test.c @@ -0,0 +1,176 @@ +#include +#include +#include "battle.h" +#include "building.h" +#include "faction.h" +#include "item.h" +#include "race.h" +#include "region.h" +#include "skill.h" +#include "unit.h" + +#include +#include "tests.h" + +static void test_make_fighter(CuTest * tc) +{ + unit *au; + region *r; + fighter *af; + battle *b; + side *as; + faction * f; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(rc_find("human")); + au = test_create_unit(f, r); + skill_enabled[SK_MAGIC] = 1; + skill_enabled[SK_RIDING] = 1; + set_level(au, SK_MAGIC, 3); + set_level(au, SK_RIDING, 3); + au->status = ST_BEHIND; + set_item(au, I_HORSE, 1); + + b = make_battle(r); + as = make_side(b, au->faction, 0, 0, 0); + af = make_fighter(b, au, as, false); + + CuAssertIntEquals(tc, 1, b->nfighters); + CuAssertPtrEquals(tc, 0, af->building); + CuAssertPtrEquals(tc, as, af->side); + CuAssertIntEquals(tc, 0, af->run.hp); + CuAssertIntEquals(tc, ST_BEHIND, af->status); + CuAssertIntEquals(tc, 0, af->run.number); + CuAssertIntEquals(tc, au->hp, af->person[0].hp); + CuAssertIntEquals(tc, 1, af->person[0].speed); + CuAssertIntEquals(tc, au->number, af->alive); + CuAssertIntEquals(tc, 0, af->removed); + CuAssertIntEquals(tc, 3, af->magic); + CuAssertIntEquals(tc, 1, af->horses); + CuAssertIntEquals(tc, 0, af->elvenhorses); +} + +static int add_two(building * b, unit * u) { + return 2; +} + +static void test_defenders_get_building_bonus(CuTest * tc) +{ + unit *du, *au; + region *r; + building * bld; + fighter *df, *af; + battle *b; + side *ds, *as; + int diff; + troop dt, at; + building_type * btype; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + btype = bt_find("castle"); + btype->protection = &add_two; + bld = test_create_building(r, btype); + bld->size = 10; + + du = test_create_unit(test_create_faction(rc_find("human")), r); + au = test_create_unit(test_create_faction(rc_find("human")), r); + u_set_building(du, bld); + + b = make_battle(r); + ds = make_side(b, du->faction, 0, 0, 0); + df = make_fighter(b, du, ds, false); + as = make_side(b, au->faction, 0, 0, 0); + af = make_fighter(b, au, as, true); + + CuAssertPtrEquals(tc, bld, df->building); + CuAssertPtrEquals(tc, 0, af->building); + + dt.fighter = df; + dt.index = 0; + at.fighter = af; + at.index = 0; + + diff = skilldiff(at, dt, 0); + CuAssertIntEquals(tc, -2, diff); + + diff = skilldiff(dt, at, 0); + CuAssertIntEquals(tc, 0, diff); +} + +static void test_attackers_get_no_building_bonus(CuTest * tc) +{ + unit *au; + region *r; + building * bld; + fighter *af; + battle *b; + side *as; + building_type * btype; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + btype = bt_find("castle"); + btype->protection = &add_two; + bld = test_create_building(r, btype); + bld->size = 10; + + au = test_create_unit(test_create_faction(rc_find("human")), r); + u_set_building(au, bld); + + b = make_battle(r); + as = make_side(b, au->faction, 0, 0, 0); + af = make_fighter(b, au, as, true); + + CuAssertPtrEquals(tc, 0, af->building); +} + +static void test_building_bonus_respects_size(CuTest * tc) +{ + unit *au, *du; + region *r; + building * bld; + fighter *af, *df; + battle *b; + side *as; + building_type * btype; + faction * f; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + btype = bt_find("castle"); + btype->protection = &add_two; + bld = test_create_building(r, btype); + bld->size = 10; + + f = test_create_faction(rc_find("human")); + au = test_create_unit(f, r); + scale_number(au, 9); + u_set_building(au, bld); + du = test_create_unit(f, r); + u_set_building(du, bld); + scale_number(du, 2); + + b = make_battle(r); + as = make_side(b, au->faction, 0, 0, 0); + af = make_fighter(b, au, as, false); + df = make_fighter(b, du, as, false); + + CuAssertPtrEquals(tc, bld, af->building); + CuAssertPtrEquals(tc, 0, df->building); +} + +CuSuite *get_battle_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_make_fighter); + SUITE_ADD_TEST(suite, test_defenders_get_building_bonus); + SUITE_ADD_TEST(suite, test_attackers_get_no_building_bonus); + SUITE_ADD_TEST(suite, test_building_bonus_respects_size); + return suite; +} diff --git a/core/src/kernel/binarystore.c b/core/src/kernel/binarystore.c new file mode 100644 index 000000000..2f750f4b4 --- /dev/null +++ b/core/src/kernel/binarystore.c @@ -0,0 +1,274 @@ +/* vi: set ts=2: ++-------------------+ Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel ++-------------------+ +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ +#include +#include "config.h" +#include "textstore.h" + +#include "save.h" +#include "version.h" +#include +#include + +#include +#include +#include +#include + +#define file(store) (FILE *)((store)->userdata) + +#define STREAM_VERSION 2 + +INLINE_FUNCTION size_t pack_int(int v, char *buffer) +{ + int sign = (v < 0); + + if (sign) { + v = ~v + 1; + sign = 0x40; + } + if (v < 0x40) { + buffer[0] = (char)(v | sign); + return 1; + } else if (v < 0x2000) { + buffer[0] = (char)((v >> 6) | 0x80); + buffer[1] = (char)((v & 0x3F) | sign); + return 2; + } else if (v < 0x100000) { + buffer[0] = (char)(((v >> 13) & 0x7f) | 0x80); + buffer[1] = (char)(((v >> 6) & 0x7f) | 0x80); + buffer[2] = (char)((v & 0x3F) | sign); + return 3; + } else if (v < 0x8000000) { + buffer[0] = (char)(((v >> 20) & 0x7f) | 0x80); + buffer[1] = (char)(((v >> 13) & 0x7f) | 0x80); + buffer[2] = (char)(((v >> 6) & 0x7f) | 0x80); + buffer[3] = (char)((v & 0x3F) | sign); + return 4; + } + buffer[0] = (char)(((v >> 27) & 0x7f) | 0x80); + buffer[1] = (char)(((v >> 20) & 0x7f) | 0x80); + buffer[2] = (char)(((v >> 13) & 0x7f) | 0x80); + buffer[3] = (char)(((v >> 6) & 0x7f) | 0x80); + buffer[4] = (char)((v & 0x3F) | sign); + return 5; +} + +INLINE_FUNCTION int unpack_int(const char *buffer) +{ + int i = 0, v = 0; + + while (buffer[i] & 0x80) { + v = (v << 7) | (buffer[i++] & 0x7f); + } + v = (v << 6) | (buffer[i] & 0x3f); + + if (buffer[i] & 0x40) { + v = ~v + 1; + } + return v; +} + +static int bin_w_brk(struct storage *store) +{ + return 0; +} + +static int bin_w_int_pak(struct storage *store, int arg) +{ + char buffer[5]; + size_t size = pack_int(arg, buffer); + return (int)fwrite(buffer, sizeof(char), size, file(store)); +} + +static int bin_r_int_pak(struct storage *store) +{ + int v = 0; + char ch; + + fread(&ch, sizeof(char), 1, file(store)); + while (ch & 0x80) { + v = (v << 7) | (ch & 0x7f); + fread(&ch, sizeof(char), 1, file(store)); + } + v = (v << 6) | (ch & 0x3f); + + if (ch & 0x40) { + v = ~v + 1; + } + return v; +} + +static int bin_w_int(struct storage *store, int arg) +{ + return (int)fwrite(&arg, sizeof(arg), 1, file(store)); +} + +static int bin_r_int(struct storage *store) +{ + int result; + fread(&result, sizeof(result), 1, file(store)); + return result; +} + +static int bin_w_flt(struct storage *store, float arg) +{ + return (int)fwrite(&arg, sizeof(arg), 1, file(store)); +} + +static float bin_r_flt(struct storage *store) +{ + float result; + fread(&result, sizeof(result), 1, file(store)); + return result; +} + +static int bin_w_str(struct storage *store, const char *tok) +{ + int result; + if (tok == NULL || tok[0] == 0) { + result = store->w_int(store, 0); + } else { + int size = (int)strlen(tok); + result = store->w_int(store, size); + result += (int)fwrite(tok, size, 1, file(store)); + } + return result; +} + +#define FIX_INVALID_CHARS /* required for data pre-574 */ +static char *bin_r_str(struct storage *store) +{ + int len; + + len = store->r_int(store); + if (len >= 0) { + char *result = malloc(len + 1); + + fread(result, sizeof(char), len, file(store)); + result[len] = 0; +#ifdef FIX_INVALID_CHARS + { + char *p = strpbrk(result, "\n\r"); + while (p) { + log_error("Invalid character %d in input string \"%s\".\n", *p, result); + strcpy(p, p + 1); + p = strpbrk(p, "\n\r"); + } + } +#endif + return result; + } else if (len < 0) { + log_error("invalid string-length %d in input.\n", len); + } + return NULL; +} + +static void bin_r_str_buf(struct storage *store, char *result, size_t size) +{ + int i; + size_t rd, len; + + i = store->r_int(store); + assert(i >= 0); + if (i == 0) { + result[0] = 0; + } else { + len = (size_t) i; + rd = MIN(len, size - 1); + fread(result, sizeof(char), rd, file(store)); + if (rd < len) { + fseek(file(store), (long)(len - rd), SEEK_CUR); + result[size - 1] = 0; + } else { + result[len] = 0; + } +#ifdef FIX_INVALID_CHARS + { + char *p = strpbrk(result, "\n\r"); + while (p) { + log_error("Invalid character %d in input string \"%s\".\n", *p, result); + strcpy(p, p + 1); + p = strpbrk(p, "\n\r"); + } + } +#endif + } +} + +static int bin_w_bin(struct storage *store, void *arg, size_t size) +{ + int result; + int len = (int)size; + + result = store->w_int(store, len); + if (len > 0) { + result += (int)fwrite(arg, len, 1, file(store)); + } + return result; +} + +static void bin_r_bin(struct storage *store, void *result, size_t size) +{ + int len = store->r_int(store); + if (len > 0) { + if ((size_t) len > size) { + log_error("destination buffer too small %d %u.\n", len, size); + fseek(file(store), len, SEEK_CUR); + } else { + fread(result, len, 1, file(store)); + } + } +} + +static int bin_open(struct storage *store, const char *filename, int mode) +{ + const char *modes[] = { 0, "rb", "wb", "ab" }; + FILE *F = fopen(filename, modes[mode]); + store->userdata = F; + store->encoding = XML_CHAR_ENCODING_UTF8; /* always utf8 it is */ + if (F) { + if (mode == IO_READ) { + int stream_version = 0; + store->version = bin_r_int(store); + if (store->version >= INTPAK_VERSION) { + stream_version = bin_r_int(store); + } + if (stream_version <= 1) { + store->r_id = bin_r_int; + store->w_id = bin_w_int; + } + if (stream_version == 0) { + store->r_int = bin_r_int; + store->w_int = bin_w_int; + } + } else if (store->encoding == XML_CHAR_ENCODING_UTF8) { + store->version = RELEASE_VERSION; + bin_w_int(store, RELEASE_VERSION); + bin_w_int(store, STREAM_VERSION); + } + } + return (F == NULL); +} + +static int bin_close(struct storage *store) +{ + return fclose(file(store)); +} + +const storage binary_store = { + bin_w_brk, /* newline (ignore) */ + bin_w_int_pak, bin_r_int_pak, /* int storage */ + bin_w_flt, bin_r_flt, /* float storage */ + bin_w_int_pak, bin_r_int_pak, /* id storage */ + bin_w_str, bin_r_str, bin_r_str_buf, /* token storage */ + bin_w_str, bin_r_str, bin_r_str_buf, /* string storage */ + bin_w_bin, bin_r_bin, /* binary storage */ + bin_open, bin_close, + 0, 0, NULL +}; diff --git a/core/src/kernel/binarystore.h b/core/src/kernel/binarystore.h new file mode 100644 index 000000000..c2ebc1c7b --- /dev/null +++ b/core/src/kernel/binarystore.h @@ -0,0 +1,23 @@ +/* vi: set ts=2: ++-------------------+ Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel ++-------------------+ +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifndef H_KERNEL_BINSTORE +#define H_KERNEL_BINSTORE +#ifdef __cplusplus +extern "C" { +#endif + +#include + + extern const storage binary_store; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/build.c b/core/src/kernel/build.c new file mode 100644 index 000000000..1a6f52a11 --- /dev/null +++ b/core/src/kernel/build.c @@ -0,0 +1,965 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "build.h" + +/* kernel includes */ +#include "alchemy.h" +#include "alliance.h" +#include "connection.h" +#include "building.h" +#include "curse.h" +#include "faction.h" +#include "group.h" +#include "item.h" +#include "magic.h" +#include "message.h" +#include "move.h" +#include "order.h" +#include "pool.h" +#include "race.h" +#include "region.h" +#include "ship.h" +#include "skill.h" +#include "terrain.h" +#include "terrainid.h" +#include "unit.h" + +/* from libutil */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* from libc */ +#include +#include +#include +#include +#include +#include + +/* attributes inclues */ +#include +#include + +#define STONERECYCLE 50 +/* Name, MaxGroesse, MinBauTalent, Kapazitaet, {Eisen, Holz, Stein, BauSilber, + * Laen, Mallorn}, UnterSilber, UnterSpezialTyp, UnterSpezial */ + +struct building *getbuilding(const struct region *r) +{ + building *b = findbuilding(getid()); + if (b == NULL || r != b->region) + return NULL; + return b; +} + +ship *getship(const struct region * r) +{ + ship *sh, *sx = findship(getshipid()); + for (sh = r->ships; sh; sh = sh->next) { + if (sh == sx) + return sh; + } + return NULL; +} + +/* ------------------------------------------------------------- */ + +/* ------------------------------------------------------------- */ + +static void destroy_road(unit * u, int nmax, struct order *ord) +{ + direction_t d = getdirection(u->faction->locale); + unit *u2; + region *r = u->region; + short n = (short)nmax; + + if (nmax > SHRT_MAX) + n = SHRT_MAX; + else if (nmax < 0) + n = 0; + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction != u->faction && is_guard(u2, GUARD_TAX) + && cansee(u2->faction, u->region, u, 0) + && !alliedunit(u, u2->faction, HELP_GUARD)) { + cmistake(u, ord, 70, MSG_EVENT); + return; + } + } + + if (d == NODIRECTION) { + /* Die Richtung wurde nicht erkannt */ + cmistake(u, ord, 71, MSG_PRODUCE); + } else { + short road = rroad(r, d); + n = MIN(n, road); + if (n != 0) { + region *r2 = rconnect(r, d); + int willdo = eff_skill(u, SK_ROAD_BUILDING, r) * u->number; + willdo = MIN(willdo, n); + if (willdo == 0) { + /* TODO: error message */ + } + if (willdo > SHRT_MAX) + road = 0; + else + road = road - (short)willdo; + rsetroad(r, d, road); + ADDMSG(&u->faction->msgs, msg_message("destroy_road", + "unit from to", u, r, r2)); + } + } +} + +int destroy_cmd(unit * u, struct order *ord) +{ + ship *sh; + unit *u2; + region *r = u->region; + const construction *con = NULL; + int size = 0; + const char *s; + int n = INT_MAX; + + if (u->number < 1) + return 0; + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + + if (findparam(s, u->faction->locale) == P_ROAD) { + destroy_road(u, INT_MAX, ord); + return 0; + } + + if (s && *s) { + n = atoi((const char *)s); + if (n <= 0) { + cmistake(u, ord, 288, MSG_PRODUCE); + return 0; + } + } + + if (getparam(u->faction->locale) == P_ROAD) { + destroy_road(u, n, ord); + return 0; + } + + if (u->building) { + building *b = u->building; + + if (u!=building_owner(b)) { + cmistake(u, ord, 138, MSG_PRODUCE); + return 0; + } + if (fval(b->type, BTF_INDESTRUCTIBLE)) { + cmistake(u, ord, 138, MSG_PRODUCE); + return 0; + } + if (n >= b->size) { + /* destroy completly */ + /* all units leave the building */ + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->building == b) { + leave_building(u2); + } + } + ADDMSG(&u->faction->msgs, msg_message("destroy", "building unit", b, u)); + con = b->type->construction; + remove_building(&r->buildings, b); + } else { + /* partial destroy */ + b->size -= n; + ADDMSG(&u->faction->msgs, msg_message("destroy_partial", + "building unit", b, u)); + } + } else if (u->ship) { + sh = u->ship; + + if (u!=ship_owner(sh)) { + cmistake(u, ord, 138, MSG_PRODUCE); + return 0; + } + if (fval(r->terrain, SEA_REGION)) { + cmistake(u, ord, 14, MSG_EVENT); + return 0; + } + + if (n >= (sh->size * 100) / sh->type->construction->maxsize) { + /* destroy completly */ + /* all units leave the ship */ + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->ship == sh) { + leave_ship(u2); + } + } + ADDMSG(&u->faction->msgs, msg_message("shipdestroy", + "unit region ship", u, r, sh)); + con = sh->type->construction; + remove_ship(&sh->region->ships, sh); + } else { + /* partial destroy */ + sh->size -= (sh->type->construction->maxsize * n) / 100; + ADDMSG(&u->faction->msgs, msg_message("shipdestroy_partial", + "unit region ship", u, r, sh)); + } + } else { + cmistake(u, ord, 138, MSG_PRODUCE); + return 0; + } + + if (con) { + /* TODO: Nicht an ZERSTÖRE mit Punktangabe angepaßt! */ + int c; + for (c = 0; con->materials[c].number; ++c) { + const requirement *rq = con->materials + c; + int recycle = (int)(rq->recycle * rq->number * size / con->reqsize); + if (recycle) { + change_resource(u, rq->rtype, recycle); + } + } + } + return 0; +} + +/* ------------------------------------------------------------- */ + +void build_road(region * r, unit * u, int size, direction_t d) +{ + int n, left; + region *rn = rconnect(r, d); + + assert(u->number); + if (!eff_skill(u, SK_ROAD_BUILDING, r)) { + cmistake(u, u->thisorder, 103, MSG_PRODUCE); + return; + } + if (besieged(u)) { + cmistake(u, u->thisorder, 60, MSG_PRODUCE); + return; + } + + if (rn == NULL || rn->terrain->max_road < 0) { + cmistake(u, u->thisorder, 94, MSG_PRODUCE); + return; + } + + if (r->terrain->max_road < 0) { + cmistake(u, u->thisorder, 94, MSG_PRODUCE); + return; + } + + if (r->terrain == newterrain(T_SWAMP)) { + /* wenn kein Damm existiert */ + static const struct building_type *bt_dam; + if (!bt_dam) + bt_dam = bt_find("dam"); + assert(bt_dam); + if (!buildingtype_exists(r, bt_dam, true)) { + cmistake(u, u->thisorder, 132, MSG_PRODUCE); + return; + } + } else if (r->terrain == newterrain(T_DESERT)) { + static const struct building_type *bt_caravan; + if (!bt_caravan) + bt_caravan = bt_find("caravan"); + assert(bt_caravan); + /* wenn keine Karawanserei existiert */ + if (!buildingtype_exists(r, bt_caravan, true)) { + cmistake(u, u->thisorder, 133, MSG_PRODUCE); + return; + } + } else if (r->terrain == newterrain(T_GLACIER)) { + static const struct building_type *bt_tunnel; + if (!bt_tunnel) + bt_tunnel = bt_find("tunnel"); + assert(bt_tunnel); + /* wenn kein Tunnel existiert */ + if (!buildingtype_exists(r, bt_tunnel, true)) { + cmistake(u, u->thisorder, 131, MSG_PRODUCE); + return; + } + } + + /* left kann man noch bauen */ + left = r->terrain->max_road - rroad(r, d); + + /* hoffentlich ist r->road <= r->terrain->max_road, n also >= 0 */ + if (left <= 0) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "error_roads_finished", "")); + return; + } + + if (size > 0) + left = MIN(size, left); + /* baumaximum anhand der rohstoffe */ + if (u_race(u) == new_race[RC_STONEGOLEM]) { + n = u->number * GOLEM_STONE; + } else { + n = get_pooled(u, oldresourcetype[R_STONE], GET_DEFAULT, left); + if (n == 0) { + cmistake(u, u->thisorder, 151, MSG_PRODUCE); + return; + } + } + left = MIN(n, left); + + /* n = maximum by skill. try to maximize it */ + n = u->number * eff_skill(u, SK_ROAD_BUILDING, r); + if (n < left) { + item *itm = *i_find(&u->items, olditemtype[I_RING_OF_NIMBLEFINGER]); + if (itm != NULL && itm->number > 0) { + int rings = MIN(u->number, itm->number); + n = n * ((roqf_factor() - 1) * rings + u->number) / u->number; + } + } + if (n < left) { + int dm = get_effect(u, oldpotiontype[P_DOMORE]); + if (dm != 0) { + int sk = eff_skill(u, SK_ROAD_BUILDING, r); + int todo = (left - n + sk - 1) / sk; + todo = MIN(todo, u->number); + dm = MIN(dm, todo); + change_effect(u, oldpotiontype[P_DOMORE], -dm); + n += dm * sk; + } /* Auswirkung Schaffenstrunk */ + } + + /* make minimum of possible and available: */ + n = MIN(left, n); + + /* n is now modified by several special effects, so we have to + * minimize it again to make sure the road will not grow beyond + * maximum. */ + rsetroad(r, d, rroad(r, d) + (short)n); + + if (u_race(u) == new_race[RC_STONEGOLEM]) { + int golemsused = n / GOLEM_STONE; + if (n % GOLEM_STONE != 0) { + ++golemsused; + } + scale_number(u, u->number - golemsused); + } else { + use_pooled(u, oldresourcetype[R_STONE], GET_DEFAULT, n); + /* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */ + produceexp(u, SK_ROAD_BUILDING, MIN(n, u->number)); + } + ADDMSG(&u->faction->msgs, msg_message("buildroad", + "region unit size", r, u, n)); +} + +/* ------------------------------------------------------------- */ + +/* ** ** ** ** ** ** * + * new build rules * + * ** ** ** ** ** ** */ + +static int required(int size, int msize, int maxneed) + /* um size von msize Punkten zu bauen, + * braucht man required von maxneed resourcen */ +{ + int used; + + used = size * maxneed / msize; + if (size * maxneed % msize) + ++used; + return used; +} + +static int +matmod(const attrib * a, const unit * u, const resource_type * material, + int value) +{ + for (a = a_find((attrib *) a, &at_matmod); a && a->type == &at_matmod; + a = a->next) { + mm_fun fun = (mm_fun) a->data.f; + value = fun(u, material, value); + if (value < 0) + return value; /* pass errors to caller */ + } + return value; +} + +int roqf_factor(void) +{ + int value = -1; + if (value < 0) { + value = get_param_int(global.parameters, "rules.economy.roqf", 10); + } + return value; +} + +/** Use up resources for building an object. +* Build up to 'size' points of 'type', where 'completed' +* of the first object have already been finished. return the +* actual size that could be built. +*/ +int build(unit * u, const construction * ctype, int completed, int want) +{ + const construction *type = ctype; + int skills = INT_MAX; /* number of skill points remainig */ + int basesk = 0; + int made = 0; + + if (want <= 0) + return 0; + if (type == NULL) + return 0; + if (type->improvement == NULL && completed == type->maxsize) + return ECOMPLETE; + if (type->btype != NULL) { + building *b; + if (!u->building || u->building->type != type->btype) { + return EBUILDINGREQ; + } + b = inside_building(u); + if (b == NULL) + return EBUILDINGREQ; + } + + if (type->skill != NOSKILL) { + int effsk; + int dm = get_effect(u, oldpotiontype[P_DOMORE]); + + assert(u->number); + basesk = effskill(u, type->skill); + if (basesk == 0) + return ENEEDSKILL; + + effsk = basesk; + if (inside_building(u)) { + effsk = skillmod(u->building->type->attribs, u, u->region, type->skill, + effsk, SMF_PRODUCTION); + } + effsk = skillmod(type->attribs, u, u->region, type->skill, + effsk, SMF_PRODUCTION); + if (effsk < 0) + return effsk; /* pass errors to caller */ + if (effsk == 0) + return ENEEDSKILL; + + skills = effsk * u->number; + + /* technically, nimblefinge and domore should be in a global set of + * "game"-attributes, (as at_skillmod) but for a while, we're leaving + * them in here. */ + + if (dm != 0) { + /* Auswirkung Schaffenstrunk */ + dm = MIN(dm, u->number); + change_effect(u, oldpotiontype[P_DOMORE], -dm); + skills += dm * effsk; + } + } + for (; want > 0 && skills > 0;) { + int c, n; + + /* skip over everything that's already been done: + * type->improvement==NULL means no more improvements, but no size limits + * type->improvement==type means build another object of the same time + * while material lasts type->improvement==x means build x when type + * is finished */ + while (type->improvement != NULL && + type->improvement != type && + type->maxsize > 0 && type->maxsize <= completed) { + completed -= type->maxsize; + type = type->improvement; + } + if (type == NULL) { + if (made == 0) + return ECOMPLETE; + break; /* completed */ + } + + /* Hier ist entweder maxsize == -1, oder completed < maxsize. + * Andernfalls ist das Datenfile oder sonstwas kaputt... + * (enno): Nein, das ist für Dinge, bei denen die nächste Ausbaustufe + * die gleiche wie die vorherige ist. z.b. gegenstände. + */ + if (type->maxsize > 1) { + completed = completed % type->maxsize; + } else { + completed = 0; + assert(type->reqsize >= 1); + } + + if (basesk < type->minskill) { + if (made == 0) + return ELOWSKILL; /* not good enough to go on */ + } + + /* n = maximum buildable size */ + if (type->minskill > 1) { + n = skills / type->minskill; + } else { + n = skills; + } + /* Flinkfingerring wirkt nicht auf Mengenbegrenzte (magische) + * Talente */ + if (skill_limit(u->faction, type->skill) == INT_MAX) { + int i = 0; + item *itm = *i_find(&u->items, olditemtype[I_RING_OF_NIMBLEFINGER]); + if (itm != NULL) + i = itm->number; + if (i > 0) { + int rings = MIN(u->number, i); + n = n * ((roqf_factor() - 1) * rings + u->number) / u->number; + } + } + + if (want > 0) { + n = MIN(want, n); + } + + if (type->maxsize > 0) { + n = MIN(type->maxsize - completed, n); + if (type->improvement == NULL) { + want = n; + } + } + + if (type->materials) + for (c = 0; n > 0 && type->materials[c].number; c++) { + const struct resource_type *rtype = type->materials[c].rtype; + int need, prebuilt; + int canuse = get_pooled(u, rtype, GET_DEFAULT, INT_MAX); + + if (inside_building(u)) { + canuse = matmod(u->building->type->attribs, u, rtype, canuse); + } + + if (canuse < 0) + return canuse; /* pass errors to caller */ + canuse = matmod(type->attribs, u, rtype, canuse); + if (type->reqsize > 1) { + prebuilt = + required(completed, type->reqsize, type->materials[c].number); + for (; n;) { + need = + required(completed + n, type->reqsize, type->materials[c].number); + if (need - prebuilt <= canuse) + break; + --n; /* TODO: optimieren? */ + } + } else { + int maxn = canuse / type->materials[c].number; + if (maxn < n) + n = maxn; + } + } + if (n <= 0) { + if (made == 0) + return ENOMATERIALS; + else + break; + } + if (type->materials) + for (c = 0; type->materials[c].number; c++) { + const struct resource_type *rtype = type->materials[c].rtype; + int prebuilt = + required(completed, type->reqsize, type->materials[c].number); + int need = + required(completed + n, type->reqsize, type->materials[c].number); + int multi = 1; + int canuse = 100; /* normalization */ + if (inside_building(u)) + canuse = matmod(u->building->type->attribs, u, rtype, canuse); + if (canuse < 0) + return canuse; /* pass errors to caller */ + canuse = matmod(type->attribs, u, rtype, canuse); + + assert(canuse % 100 == 0 + || !"only constant multipliers are implemented in build()"); + multi = canuse / 100; + if (canuse < 0) + return canuse; /* pass errors to caller */ + + use_pooled(u, rtype, GET_DEFAULT, + (need - prebuilt + multi - 1) / multi); + } + made += n; + skills -= n * type->minskill; + want -= n; + completed = completed + n; + } + /* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */ + produceexp(u, ctype->skill, MIN(made, u->number)); + + return made; +} + +message *msg_materials_required(unit * u, order * ord, + const construction * ctype, int multi) +{ + int c; + /* something missing from the list of materials */ + resource *reslist = NULL; + + if (multi <= 0 || multi == INT_MAX) + multi = 1; + for (c = 0; ctype->materials[c].number; ++c) { + resource *res = malloc(sizeof(resource)); + res->number = multi * ctype->materials[c].number / ctype->reqsize; + res->type = ctype->materials[c].rtype; + res->next = reslist; + reslist = res; + } + return msg_feedback(u, ord, "build_required", "required", reslist); +} + +int maxbuild(const unit * u, const construction * cons) + /* calculate maximum size that can be built from available material */ + /* !! ignores maximum objectsize and improvements... */ +{ + int c; + int maximum = INT_MAX; + for (c = 0; cons->materials[c].number; c++) { + const resource_type *rtype = cons->materials[c].rtype; + int have = get_pooled(u, rtype, GET_DEFAULT, INT_MAX); + int need = required(1, cons->reqsize, cons->materials[c].number); + if (have < need) { + return 0; + } else + maximum = MIN(maximum, have / need); + } + return maximum; +} + +/** old build routines */ + +void +build_building(unit * u, const building_type * btype, int want, order * ord) +{ + region *r = u->region; + int n = want, built = 0, id; + building *b = NULL; + /* einmalige Korrektur */ + const char *btname; + order *new_order = NULL; + const struct locale *lang = u->faction->locale; + static int rule_other = -1; + + assert(u->number); + if (eff_skill(u, SK_BUILDING, r) == 0) { + cmistake(u, ord, 101, MSG_PRODUCE); + return; + } + + /* Falls eine Nummer angegeben worden ist, und ein Gebaeude mit der + * betreffenden Nummer existiert, ist b nun gueltig. Wenn keine Burg + * gefunden wurde, dann wird nicht einfach eine neue erbaut. Ansonsten + * baut man an der eigenen burg weiter. */ + + /* Wenn die angegebene Nummer falsch ist, KEINE Burg bauen! */ + id = getid(); + if (id > 0) { /* eine Nummer angegeben, keine neue Burg bauen */ + b = findbuilding(id); + if (!b || b->region != u->region) { /* eine Burg mit dieser Nummer gibt es hier nicht */ + /* vieleicht Tippfehler und die eigene Burg ist gemeint? */ + if (u->building && u->building->type == btype) { + b = u->building; + } else { + /* keine neue Burg anfangen wenn eine Nummer angegeben war */ + cmistake(u, ord, 6, MSG_PRODUCE); + return; + } + } + } else if (u->building && u->building->type == btype) { + b = u->building; + } + + if (b) + btype = b->type; + + if (fval(btype, BTF_UNIQUE) && buildingtype_exists(r, btype, false)) { + /* only one of these per region */ + cmistake(u, ord, 93, MSG_PRODUCE); + return; + } + if (besieged(u)) { + /* units under siege can not build */ + cmistake(u, ord, 60, MSG_PRODUCE); + return; + } + if (btype->flags & BTF_NOBUILD) { + /* special building, cannot be built */ + cmistake(u, ord, 221, MSG_PRODUCE); + return; + } + if (r->terrain->max_road <= 0) { + /* special terrain, cannot build */ + cmistake(u, ord, 221, MSG_PRODUCE); + return; + } + if (btype->flags & BTF_ONEPERTURN) { + if (b && fval(b, BLD_EXPANDED)) { + cmistake(u, ord, 318, MSG_PRODUCE); + return; + } + n = 1; + } + if (b) { + if (rule_other < 0) { + rule_other = + get_param_int(global.parameters, "rules.build.other_buildings", 1); + } + if (!rule_other) { + unit *owner = building_owner(b); + if (!owner || owner->faction != u->faction) { + cmistake(u, ord, 1222, MSG_PRODUCE); + return; + } + } + } + + if (b) + built = b->size; + if (n <= 0 || n == INT_MAX) { + if (b == NULL) { + if (btype->maxsize > 0) { + n = btype->maxsize - built; + } else { + n = INT_MAX; + } + } else { + if (b->type->maxsize > 0) { + n = b->type->maxsize - built; + } else { + n = INT_MAX; + } + } + } + built = build(u, btype->construction, built, n); + + switch (built) { + case ECOMPLETE: + /* the building is already complete */ + cmistake(u, ord, 4, MSG_PRODUCE); + return; + case ENOMATERIALS: + ADDMSG(&u->faction->msgs, msg_materials_required(u, ord, + btype->construction, want)); + return; + case ELOWSKILL: + case ENEEDSKILL: + /* no skill, or not enough skill points to build */ + cmistake(u, ord, 50, MSG_PRODUCE); + return; + } + + /* at this point, the building size is increased. */ + if (b == NULL) { + /* build a new building */ + b = new_building(btype, r, lang); + b->type = btype; + fset(b, BLD_MAINTAINED); + + /* Die Einheit befindet sich automatisch im Inneren der neuen Burg. */ + if (leave(u, false)) { + u_set_building(u, b); + assert(building_owner(b)==u); + } +#ifdef WDW_PYRAMID + if (b->type == bt_find("pyramid") && f_get_alliance(u->faction) != NULL) { + attrib *a = a_add(&b->attribs, a_new(&at_alliance)); + a->data.i = u->faction->alliance->id; + } +#endif + + } + + btname = LOC(lang, btype->_name); + + if (want - built <= 0) { + /* gebäude fertig */ + new_order = default_order(lang); + } else if (want != INT_MAX) { + /* reduzierte restgröße */ + const char *hasspace = strchr(btname, ' '); + if (hasspace) { + new_order = + create_order(K_MAKE, lang, "%d \"%s\" %i", n - built, btname, b->no); + } else { + new_order = + create_order(K_MAKE, lang, "%d %s %i", n - built, btname, b->no); + } + } else if (btname) { + /* Neues Haus, Befehl mit Gebäudename */ + const char *hasspace = strchr(btname, ' '); + if (hasspace) { + new_order = create_order(K_MAKE, lang, "\"%s\" %i", btname, b->no); + } else { + new_order = create_order(K_MAKE, lang, "%s %i", btname, b->no); + } + } + + if (new_order) { + replace_order(&u->orders, ord, new_order); + free_order(new_order); + } + + b->size += built; + fset(b, BLD_EXPANDED); + + update_lighthouse(b); + + ADDMSG(&u->faction->msgs, msg_message("buildbuilding", + "building unit size", b, u, built)); +} + +static void build_ship(unit * u, ship * sh, int want) +{ + const construction *construction = sh->type->construction; + int size = (sh->size * DAMAGE_SCALE - sh->damage) / DAMAGE_SCALE; + int n; + int can = build(u, construction, size, want); + + if ((n = construction->maxsize - sh->size) > 0 && can > 0) { + if (can >= n) { + sh->size += n; + can -= n; + } else { + sh->size += can; + n = can; + can = 0; + } + } + + if (sh->damage && can) { + int repair = MIN(sh->damage, can * DAMAGE_SCALE); + n += repair / DAMAGE_SCALE; + if (repair % DAMAGE_SCALE) + ++n; + sh->damage = sh->damage - repair; + } + + if (n) + ADDMSG(&u->faction->msgs, + msg_message("buildship", "ship unit size", sh, u, n)); +} + +void +create_ship(region * r, unit * u, const struct ship_type *newtype, int want, + order * ord) +{ + ship *sh; + int msize; + const construction *cons = newtype->construction; + order *new_order; + + if (!eff_skill(u, SK_SHIPBUILDING, r)) { + cmistake(u, ord, 100, MSG_PRODUCE); + return; + } + if (besieged(u)) { + cmistake(u, ord, 60, MSG_PRODUCE); + return; + } + + /* check if skill and material for 1 size is available */ + if (eff_skill(u, cons->skill, r) < cons->minskill) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "error_build_skill_low", "value name", cons->minskill, + newtype->name[1])); + return; + } + + msize = maxbuild(u, cons); + if (msize == 0) { + cmistake(u, ord, 88, MSG_PRODUCE); + return; + } + if (want > 0) + want = MIN(want, msize); + else + want = msize; + + sh = new_ship(newtype, r, u->faction->locale); + + if (leave(u, false)) { + if (fval(u_race(u), RCF_CANSAIL)) { + u_set_ship(u, sh); + } + } + new_order = + create_order(K_MAKE, u->faction->locale, "%s %i", LOC(u->faction->locale, + parameters[P_SHIP]), sh->no); + replace_order(&u->orders, ord, new_order); + free_order(new_order); + + build_ship(u, sh, want); +} + +void continue_ship(region * r, unit * u, int want) +{ + const construction *cons; + ship *sh; + int msize; + + if (!eff_skill(u, SK_SHIPBUILDING, r)) { + cmistake(u, u->thisorder, 100, MSG_PRODUCE); + return; + } + + /* Die Schiffsnummer bzw der Schiffstyp wird eingelesen */ + sh = getship(r); + + if (!sh) + sh = u->ship; + + if (!sh) { + cmistake(u, u->thisorder, 20, MSG_PRODUCE); + return; + } + cons = sh->type->construction; + assert(cons->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ + if (sh->size == cons->maxsize && !sh->damage) { + cmistake(u, u->thisorder, 16, MSG_PRODUCE); + return; + } + if (eff_skill(u, cons->skill, r) < cons->minskill) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "error_build_skill_low", "value name", cons->minskill, + sh->type->name[1])); + return; + } + msize = maxbuild(u, cons); + if (msize == 0) { + cmistake(u, u->thisorder, 88, MSG_PRODUCE); + return; + } + if (want > 0) + want = MIN(want, msize); + else + want = msize; + + build_ship(u, sh, want); +} + diff --git a/core/src/kernel/build.h b/core/src/kernel/build.h new file mode 100644 index 000000000..1e7b7d623 --- /dev/null +++ b/core/src/kernel/build.h @@ -0,0 +1,96 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_BUILD +#define H_KRNL_BUILD + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Die enums fuer Gebauede werden nie gebraucht, nur bei der Bestimmung + * des Schutzes durch eine Burg wird die Reihenfolge und MAXBUILDINGS + * wichtig + */ + + struct xml_tag; + + typedef struct requirement { + const struct resource_type *rtype; + int number; + double recycle; /* recycling quota */ + } requirement; + + typedef struct construction { + skill_t skill; /* skill req'd per point of size */ + int minskill; /* skill req'd per point of size */ + + int maxsize; /* maximum size of this type */ + int reqsize; /* size of object using up 1 set of requirement. */ + requirement *materials; /* material req'd to build one object */ + const struct building_type *btype; + /* building type required to make this thing */ + + struct construction *improvement; + /* next level, if upgradable. if more than one of these items + * can be built (weapons, armour) per turn, must not be NULL, + * but point to the same type again: + * const_sword.improvement = &const_sword + * last level of a building points to NULL, as do objects of + * an unlimited size. + */ + struct attrib *attribs; + /* stores skill modifiers and other attributes */ + + } construction; + + extern int destroy_cmd(struct unit *u, struct order *ord); + extern int leave_cmd(struct unit *u, struct order *ord); + + void build_road(struct region *r, struct unit *u, int size, direction_t d); + void create_ship(struct region *r, struct unit *u, + const struct ship_type *newtype, int size, struct order *ord); + void continue_ship(struct region *r, struct unit *u, int size); + + struct building *getbuilding(const struct region *r); + struct ship *getship(const struct region *r); + + void reportevent(struct region *r, char *s); + + void shash(struct ship *sh); + void sunhash(struct ship *sh); + extern int roqf_factor(void); + + extern int build(struct unit *u, const construction * ctype, int completed, + int want); + extern int maxbuild(const struct unit *u, const construction * cons); + extern struct message *msg_materials_required(struct unit *u, + struct order *ord, const struct construction *ctype, int multi); +/** error messages that build may return: */ +#define ELOWSKILL -1 +#define ENEEDSKILL -2 +#define ECOMPLETE -3 +#define ENOMATERIALS -4 +#define EBUILDINGREQ -5 + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/building.c b/core/src/kernel/building.c new file mode 100644 index 000000000..4ba7cad10 --- /dev/null +++ b/core/src/kernel/building.c @@ -0,0 +1,717 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include + +#include +#include "building.h" + +/* kernel includes */ +#include "item.h" +#include "curse.h" /* für C_NOCOST */ +#include "unit.h" +#include "faction.h" +#include "region.h" +#include "skill.h" +#include "magic.h" +#include "save.h" +#include "version.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +/* attributes includes */ +#include + +static const char *NULLSTRING = "(null)"; + +static void lc_init(struct attrib *a) +{ + a->data.v = calloc(1, sizeof(building_action)); +} + +static void lc_done(struct attrib *a) +{ + building_action *data = (building_action *) a->data.v; + if (data->fname) + free(data->fname); + if (data->param) + free(data->param); + free(data); +} + +static void +lc_write(const struct attrib *a, const void *owner, struct storage *store) +{ + building_action *data = (building_action *) a->data.v; + const char *fname = data->fname; + const char *fparam = data->param; + building *b = data->b; + + write_building_reference(b, store); + store->w_tok(store, fname); + store->w_tok(store, fparam ? fparam : NULLSTRING); +} + +static int lc_read(struct attrib *a, void *owner, struct storage *store) +{ + building_action *data = (building_action *) a->data.v; + int result = + read_reference(&data->b, store, read_building_reference, resolve_building); + if (store->version < UNICODE_VERSION) { + data->fname = store->r_str(store); + } else { + data->fname = store->r_tok(store); + } + if (store->version >= BACTION_VERSION) { + char lbuf[256]; + if (store->version < UNICODE_VERSION) { + store->r_str_buf(store, lbuf, sizeof(lbuf)); + } else { + store->r_tok_buf(store, lbuf, sizeof(lbuf)); + } + if (strcmp(lbuf, NULLSTRING) == 0) + data->param = NULL; + else + data->param = strdup(lbuf); + } else { + data->param = strdup(NULLSTRING); + } + if (result == 0 && !data->b) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +attrib_type at_building_action = { + "lcbuilding", + lc_init, lc_done, + NULL, + lc_write, lc_read +}; + +typedef struct building_typelist { + struct building_typelist *next; + building_type *type; +} building_typelist; + +quicklist *buildingtypes = NULL; + +/* Returns a building type for the (internal) name */ +building_type *bt_find(const char *name) +{ + quicklist *ql; + int qi; + + assert(name); + + for (qi = 0, ql = buildingtypes; ql; ql_advance(&ql, &qi, 1)) { + building_type *btype = (building_type *) ql_get(ql, qi); + if (strcmp(btype->_name, name) == 0) + return btype; + } + return NULL; +} + +void bt_register(building_type * type) +{ + if (type->init) { + type->init(type); + } + ql_push(&buildingtypes, (void *)type); +} + +int buildingcapacity(const building * b) +{ + if (b->type->capacity >= 0) { + if (b->type->maxcapacity >= 0) { + return MIN(b->type->maxcapacity, b->size * b->type->capacity); + } + return b->size * b->type->capacity; + } + if (b->size >= b->type->maxsize) { + if (b->type->maxcapacity >= 0) { + return b->type->maxcapacity; + } + } + return 0; +} + +attrib_type at_building_generic_type = { + "building_generic_type", NULL, NULL, NULL, a_writestring, a_readstring, + ATF_UNIQUE +}; + +/* Returns the (internal) name for a building of given size and type. Especially, returns the correct + * name if it depends on the size (as for Eressea castles). + */ +const char *buildingtype(const building_type * btype, const building * b, + int bsize) +{ + const char *s = NULL; + static bool init_generic = false; + static const struct building_type *bt_generic; + + if (!init_generic) { + init_generic = true; + bt_generic = bt_find("generic"); + } + + if (btype == bt_generic) { + const attrib *a = a_find(b->attribs, &at_building_generic_type); + if (a) + s = (const char *)a->data.v; + } + + if (btype->name) + s = btype->name(btype, b, bsize); + if (s == NULL) + s = btype->_name; + return s; +} + +#define BMAXHASH 7919 +static building *buildhash[BMAXHASH]; +void bhash(building * b) +{ + building *old = buildhash[b->no % BMAXHASH]; + + buildhash[b->no % BMAXHASH] = b; + b->nexthash = old; +} + +void bunhash(building * b) +{ + building **show; + + for (show = &buildhash[b->no % BMAXHASH]; *show; show = &(*show)->nexthash) { + if ((*show)->no == b->no) + break; + } + if (*show) { + assert(*show == b); + *show = (*show)->nexthash; + b->nexthash = 0; + } +} + +static building *bfindhash(int i) +{ + building *old; + + for (old = buildhash[i % BMAXHASH]; old; old = old->nexthash) + if (old->no == i) + return old; + return 0; +} + +building *findbuilding(int i) +{ + return bfindhash(i); +} + +/* ** old building types ** */ + +static int sm_smithy(const unit * u, const region * r, skill_t sk, int value) +{ /* skillmod */ + if (sk == SK_WEAPONSMITH || sk == SK_ARMORER) { + if (u->region == r) + return value + 1; + } + return value; +} + +static int mm_smithy(const unit * u, const resource_type * rtype, int value) +{ /* material-mod */ + if (rtype == oldresourcetype[R_IRON]) + return value * 2; + return value; +} + +static void init_smithy(struct building_type *bt) +{ + a_add(&bt->attribs, make_skillmod(NOSKILL, SMF_PRODUCTION, sm_smithy, 1.0, + 0)); + a_add(&bt->attribs, make_matmod(mm_smithy)); +} + +static const char *castle_name_i(const struct building_type *btype, + const struct building *b, int bsize, const char *fname[]) +{ + int i = bt_effsize(btype, b, bsize); + + return fname[i]; +} + +static const char *castle_name_2(const struct building_type *btype, + const struct building *b, int bsize) +{ + const char *fname[] = { + "site", + "fortification", + "tower", + "castle", + "fortress", + "citadel" + }; + return castle_name_i(btype, b, bsize, fname); +} + +static const char *castle_name(const struct building_type *btype, + const struct building *b, int bsize) +{ + const char *fname[] = { + "site", + "tradepost", + "fortification", + "tower", + "castle", + "fortress", + "citadel" + }; + return castle_name_i(btype, b, bsize, fname); +} + +static const char *fort_name(const struct building_type *btype, + const struct building *b, int bsize) +{ + const char *fname[] = { + "scaffolding", + "guardhouse", + "guardtower", + }; + return castle_name_i(btype, b, bsize, fname); +} + +#ifdef WDW_PYRAMID + +static const char *pyramid_name(const struct building_type *btype, int bsize) +{ + static char p_name_buf[32]; + int level = 0; + const construction *ctype; + + ctype = btype->construction; + + while (ctype && ctype->maxsize != -1 && ctype->maxsize <= bsize) { + bsize -= ctype->maxsize; + ctype = ctype->improvement; + ++level; + } + + sprintf(p_name_buf, "pyramid%d", level); + + return p_name_buf; +} + +int wdw_pyramid_level(const struct building *b) +{ + const construction *ctype = b->type->construction; + int completed = b->size; + int level = 0; + + while (ctype->improvement != NULL && + ctype->improvement != ctype && + ctype->maxsize > 0 && ctype->maxsize <= completed) { + ++level; + completed -= ctype->maxsize; + ctype = ctype->improvement; + } + + return level; +} +#endif + +/* for finding out what was meant by a particular building string */ + +static local_names *bnames; + +/* Find the building type for a given localized name (as seen by the user). Useful for parsing + * orders. The inverse of locale_string(lang, btype->_name), sort of. */ +const building_type *findbuildingtype(const char *name, + const struct locale *lang) +{ + variant type; + local_names *bn = bnames; + + while (bn) { + if (bn->lang == lang) + break; + bn = bn->next; + } + if (!bn) { + quicklist *ql = buildingtypes; + int qi; + + bn = calloc(sizeof(local_names), 1); + bn->next = bnames; + bn->lang = lang; + + for (qi = 0, ql = buildingtypes; ql; ql_advance(&ql, &qi, 1)) { + building_type *btype = (building_type *) ql_get(ql, qi); + + const char *n = locale_string(lang, btype->_name); + type.v = (void *)btype; + addtoken(&bn->names, n, type); + } + bnames = bn; + } + if (findtoken(bn->names, name, &type) == E_TOK_NOMATCH) + return NULL; + return (const building_type *)type.v; +} + +static int eressea_building_protection(building * b, unit * u) +{ + int beff = buildingeffsize(b, false) - 1; + /* -1 because the tradepost has no protection value */ + + return beff; +} + +static int meropis_building_protection(building * b, unit * u) +{ + return 2; +} + +void register_buildings(void) +{ + register_function((pf_generic) & eressea_building_protection, + "eressea_building_protection"); + register_function((pf_generic) & meropis_building_protection, + "meropis_building_protection"); + register_function((pf_generic) & init_smithy, "init_smithy"); + register_function((pf_generic) & castle_name, "castle_name"); + register_function((pf_generic) & castle_name_2, "castle_name_2"); + register_function((pf_generic) & fort_name, "fort_name"); +#ifdef WDW_PYRAMID + register_function((pf_generic) & pyramid_name, "pyramid_name"); +#endif +} + +void write_building_reference(const struct building *b, struct storage *store) +{ + store->w_id(store, (b && b->region) ? b->no : 0); +} + +int resolve_building(variant id, void *address) +{ + int result = 0; + building *b = NULL; + if (id.i != 0) { + b = findbuilding(id.i); + if (b == NULL) { + result = -1; + } + } + *(building **) address = b; + return result; +} + +variant read_building_reference(struct storage * store) +{ + variant result; + result.i = store->r_id(store); + return result; +} + +building *new_building(const struct building_type * btype, region * r, + const struct locale * lang) +{ + building **bptr = &r->buildings; + building *b = (building *) calloc(1, sizeof(building)); + static bool init_lighthouse = false; + static const struct building_type *bt_lighthouse = 0; + const char *bname = 0; + char buffer[32]; + + if (!init_lighthouse) { + bt_lighthouse = bt_find("lighthouse"); + init_lighthouse = true; + } + + b->flags = BLD_WORKING | BLD_MAINTAINED; + b->no = newcontainerid(); + bhash(b); + + b->type = btype; + b->region = r; + while (*bptr) + bptr = &(*bptr)->next; + *bptr = b; + + if (b->type == bt_lighthouse) { + r->flags |= RF_LIGHTHOUSE; + } + if (b->type->name) { + bname = LOC(lang, buildingtype(btype, b, 0)); + } + if (!bname) { + bname = LOC(lang, btype->_name); + } + if (!bname) { + bname = LOC(lang, parameters[P_GEBAEUDE]); + } + if (!bname) { + bname = parameters[P_GEBAEUDE]; + } + assert(bname); + slprintf(buffer, sizeof(buffer), "%s %s", bname, buildingid(b)); + b->name = strdup(bname); + return b; +} + +static building *deleted_buildings; + +/** remove a building from the region. + * remove_building lets units leave the building + */ +void remove_building(building ** blist, building * b) +{ + unit *u; + static const struct building_type *bt_caravan, *bt_dam, *bt_tunnel; + static bool init = false; + + if (!init) { + init = true; + bt_caravan = bt_find("caravan"); + bt_dam = bt_find("dam"); + bt_tunnel = bt_find("tunnel"); + } + + assert(bfindhash(b->no)); + + handle_event(b->attribs, "destroy", b); + for (u = b->region->units; u; u = u->next) { + if (u->building == b) + leave(u, true); + } + + b->size = 0; + update_lighthouse(b); + bunhash(b); + + /* Falls Karawanserei, Damm oder Tunnel einstürzen, wird die schon + * gebaute Straße zur Hälfte vernichtet */ + if (b->type == bt_caravan || b->type == bt_dam || b->type == bt_tunnel) { + region *r = b->region; + int d; + for (d = 0; d != MAXDIRECTIONS; ++d) { + direction_t dir = (direction_t)d; + if (rroad(r, dir) > 0) { + rsetroad(r, dir, rroad(r, dir) / 2); + } + } + } + + /* Stattdessen nur aus Liste entfernen, aber im Speicher halten. */ + while (*blist && *blist != b) { + blist = &(*blist)->next; + } + *blist = b->next; + b->region = NULL; + b->next = deleted_buildings; + deleted_buildings = b; +} + +void free_building(building * b) +{ + while (b->attribs) + a_remove(&b->attribs, b->attribs); + free(b->name); + free(b->display); + free(b); +} + +void free_buildings(void) +{ + while (deleted_buildings) { + building *b = deleted_buildings; + deleted_buildings = b->next; + } +} + +extern struct attrib_type at_icastle; + +/** returns the building's build stage (NOT size in people). + * only makes sense for castles or similar buildings with multiple + * stages */ +int buildingeffsize(const building * b, int img) +{ + const struct building_type *btype = NULL; + + if (b == NULL) + return 0; + + btype = b->type; + if (img) { + const attrib *a = a_find(b->attribs, &at_icastle); + if (a) { + btype = (const struct building_type *)a->data.v; + } + } + return bt_effsize(btype, b, b->size); +} + +int bt_effsize(const building_type * btype, const building * b, int bsize) +{ + int i = bsize, n = 0; + const construction *cons = btype->construction; + + /* TECH DEBT: simplest thing that works for E3 dwarf/halfling faction rules */ + if (b && get_param_int(global.parameters, "rules.dwarf_castles", 0) + && strcmp(btype->_name, "castle") == 0) { + unit *u = building_owner(b); + if (u && u->faction->race == new_race[RC_HALFLING]) { + i = bsize * 10 / 8; + } + } + + if (!cons || !cons->improvement) { + return 0; + } + + while (cons && cons->maxsize != -1 && i >= cons->maxsize) { + i -= cons->maxsize; + cons = cons->improvement; + ++n; + } + + return n; +} + +const char *write_buildingname(const building * b, char *ibuf, size_t size) +{ + slprintf(ibuf, size, "%s (%s)", b->name, itoa36(b->no)); + return ibuf; +} + +const char *buildingname(const building * b) +{ + typedef char name[OBJECTIDSIZE + 1]; + static name idbuf[8]; + static int nextbuf = 0; + char *ibuf = idbuf[(++nextbuf) % 8]; + return write_buildingname(b, ibuf, sizeof(name)); +} + +void building_set_owner(struct unit * owner) +{ + assert(owner && owner->building); + owner->building->_owner = owner; +} + +static unit *building_owner_ex(const building * bld, const struct faction * last_owner) +{ + unit *u, *heir = 0; + + /* Eigentümer tot oder kein Eigentümer vorhanden. Erste lebende Einheit + * nehmen. */ + for (u = bld->region->units; u; u = u->next) { + if (u->building == bld) { + if (u->number > 0) { + if (heir && last_owner && heir->faction!=last_owner && u->faction==last_owner) { + heir = u; + break; /* we found someone from the same faction who is not dead. let's take this guy */ + } + else if (!heir) { + heir = u; /* you'll do in an emergency */ + } + } + } + } + return heir; +} + +unit *building_owner(const building * bld) +{ + unit *owner = bld->_owner; + if (!owner || (owner->building!=bld || owner->number<=0)) { + unit * heir = building_owner_ex(bld, owner?owner->faction:0); + return (heir && heir->number>0) ? heir : 0; + } + return owner; +} + +void building_update_owner(building * bld) { + unit * owner = bld->_owner; + bld->_owner = building_owner_ex(bld, owner?owner->faction:0); +} + +const char *building_getname(const building * self) +{ + return self->name; +} + +void building_setname(building * self, const char *name) +{ + free(self->name); + if (name) + self->name = strdup(name); + else + self->name = NULL; +} + +void building_addaction(building * b, const char *fname, const char *param) +{ + attrib *a = a_add(&b->attribs, a_new(&at_building_action)); + building_action *data = (building_action *) a->data.v; + data->b = b; + data->fname = strdup(fname); + if (param) { + data->param = strdup(param); + } +} + +region *building_getregion(const building * b) +{ + return b->region; +} + +void building_setregion(building * b, region * r) +{ + building **blist = &b->region->buildings; + while (*blist && *blist != b) { + blist = &(*blist)->next; + } + *blist = b->next; + b->next = NULL; + + blist = &r->buildings; + while (*blist && *blist != b) + blist = &(*blist)->next; + *blist = b; + + b->region = r; +} diff --git a/core/src/kernel/building.h b/core/src/kernel/building.h new file mode 100644 index 000000000..f78ba1db8 --- /dev/null +++ b/core/src/kernel/building.h @@ -0,0 +1,183 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_BUILDING +#define H_KRNL_BUILDING + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* maintenance::flags */ +#define MTF_NONE 0x00 +#define MTF_VARIABLE 0x01 /* resource usage scales with size */ +#define MTF_VITAL 0x02 /* if resource missing, building may crash */ + + typedef struct maintenance { + const struct resource_type *rtype; /* type of resource required */ + int number; /* amount of resources */ + unsigned int flags; /* misc. flags */ + } maintenance; + +/* building_type::flags */ +#define BTF_NONE 0x00 +#define BTF_INDESTRUCTIBLE 0x01 /* cannot be torm down */ +#define BTF_NOBUILD 0x02 /* special, can't be built */ +#define BTF_UNIQUE 0x04 /* only one per struct region (harbour) */ +#define BTF_DECAY 0x08 /* decays when not occupied */ +#define BTF_DYNAMIC 0x10 /* dynamic type, needs bt_write */ +#define BTF_MAGIC 0x40 /* magical effect */ +#define BTF_ONEPERTURN 0x80 /* one one sizepoint can be added per turn */ +#define BTF_NAMECHANGE 0x100 /* name and description can be changed more than once */ + + typedef struct building_type { + const char *_name; + + int flags; /* flags */ + int capacity; /* Kapazität pro Größenpunkt */ + int maxcapacity; /* Max. Kapazität */ + int maxsize; /* how big can it get, with all the extensions? */ + int magres; /* how well it resists against spells */ + int magresbonus; /* bonus it gives the target against spells */ + int fumblebonus; /* bonus that reduces fumbling */ + double auraregen; /* modifier for aura regeneration inside building */ + struct maintenance *maintenance; /* array of requirements */ + struct construction *construction; /* construction of 1 building-level */ + + const char *(*name) (const struct building_type *, + const struct building * b, int size); + void (*init) (struct building_type *); + void (*age) (struct building *); + int (*protection) (struct building *, struct unit *); + double (*taxes) (const struct building *, int size); + struct attrib *attribs; + } building_type; + + extern struct quicklist *buildingtypes; + + extern building_type *bt_find(const char *name); + extern void register_buildings(void); + extern void bt_register(struct building_type *type); + extern int bt_effsize(const struct building_type *btype, + const struct building *b, int bsize); + +/* buildingt => building_type + * Name => locale_string(name) + * MaxGroesse => levels + * MinBauTalent => construction->minskill + * Kapazitaet => capacity, maxcapacity + * Materialien => construction->materials + * UnterSilber, UnterSpezialTyp, UnterSpezial => maintenance + * per_size => !maintenance->fixed + */ +#define BFL_NONE 0x00 +#define BLD_MAINTAINED 0x01 /* vital maintenance paid for */ +#define BLD_WORKING 0x02 /* full maintenance paid, it works */ +#define BLD_UNGUARDED 0x04 /* you can enter this building anytime */ +#define BLD_EXPANDED 0x08 /* has been expanded this turn */ +#define BLD_SELECT 0x10 /* formerly FL_DH */ +#define BLD_DONTPAY 0x20 /* PAY NOT */ + +#define BLD_SAVEMASK 0x00 /* mask for persistent flags */ + + typedef struct building { + struct building *next; + struct building *nexthash; + + const struct building_type *type; + struct region *region; + struct unit *_owner; /* you should always use building_owner(), never this naked pointer */ + char *name; + char *display; + struct attrib *attribs; + int no; + int size; + int sizeleft; /* is only used during battle. should be a temporary attribute */ + int besieged; /* should be an attribute */ + unsigned int flags; + } building; + + extern struct attrib_type at_building_generic_type; + extern const char *buildingtype(const building_type * btype, + const struct building *b, int bsize); + extern const char *write_buildingname(const building * b, char *ibuf, + size_t size); + extern int buildingcapacity(const struct building *b); + extern struct building *new_building(const struct building_type *typ, + struct region *r, const struct locale *lang); + void build_building(struct unit *u, const struct building_type *typ, int size, + struct order *ord); + +/* Alte Gebäudetypen: */ + +/* old functions, still in build.c: */ + int buildingeffsize(const building * b, int imaginary); + void bhash(struct building *b); + void bunhash(struct building *b); + int buildingcapacity(const struct building *b); + + extern void remove_building(struct building **blist, struct building *b); + extern void free_building(struct building *b); + extern void free_buildings(void); + + const struct building_type *findbuildingtype(const char *name, + const struct locale *lang); + +#include "build.h" +#define NOBUILDING NULL + + extern int resolve_building(variant data, void *address); + extern void write_building_reference(const struct building *b, + struct storage *store); + extern variant read_building_reference(struct storage *store); + + extern struct building *findbuilding(int n); + + extern struct unit *building_owner(const struct building *b); + extern void building_set_owner(struct unit * u); + extern void building_update_owner(struct building * bld); + + extern struct attrib_type at_building_action; + void building_addaction(struct building *b, const char *fname, + const char *param); + +#ifdef WDW_PYRAMID + extern int wdw_pyramid_level(const struct building *b); +#endif + + typedef struct building_action { + building *b; + char *fname; + char *param; + } building_action; + + extern const char *buildingname(const struct building *b); + + extern const char *building_getname(const struct building *b); + extern void building_setname(struct building *self, const char *name); + + struct region *building_getregion(const struct building *b); + void building_setregion(struct building *bld, struct region *r); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/building_test.c b/core/src/kernel/building_test.c new file mode 100644 index 000000000..fa11cb2e4 --- /dev/null +++ b/core/src/kernel/building_test.c @@ -0,0 +1,363 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static void test_register_building(CuTest * tc) +{ + building_type *btype; + + test_cleanup(); + + btype = (building_type *)calloc(sizeof(building_type), 1); + btype->_name = strdup("herp"); + bt_register(btype); + + CuAssertPtrNotNull(tc, bt_find("herp")); +} + +static void test_building_set_owner(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u1, *u2; + struct faction *f; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + btype = bt_find("castle"); + f = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + u1 = test_create_unit(f, r); + u_set_building(u1, bld); + CuAssertPtrEquals(tc, u1, building_owner(bld)); + + u2 = test_create_unit(f, r); + u_set_building(u2, bld); + CuAssertPtrEquals(tc, u1, building_owner(bld)); + building_set_owner(u2); + CuAssertPtrEquals(tc, u2, building_owner(bld)); +} + +static void test_buildingowner_goes_to_next_when_empty(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u, *u2; + struct faction *f; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u = test_create_unit(f, r); + u2 = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_building(u, bld); + u_set_building(u2, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + u->number = 0; + CuAssertPtrEquals(tc, u2, building_owner(bld)); +} + +static void test_buildingowner_goes_to_other_when_empty(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u, *u2; + struct faction *f; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u2 = test_create_unit(f, r); + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_building(u, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + u_set_building(u2, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + u->number = 0; + CuAssertPtrEquals(tc, u2, building_owner(bld)); +} + +static void test_buildingowner_goes_to_same_faction_when_empty(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u, *u2, *u3; + struct faction *f1, *f2; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f1 = test_create_faction(human); + f2 = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u2 = test_create_unit(f2, r); + u3 = test_create_unit(f1, r); + u = test_create_unit(f1, r); + CuAssertPtrNotNull(tc, u); + u_set_building(u, bld); + u_set_building(u2, bld); + u_set_building(u3, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + u->number = 0; + CuAssertPtrEquals(tc, u3, building_owner(bld)); + u3->number = 0; + CuAssertPtrEquals(tc, u2, building_owner(bld)); +} + +static void test_buildingowner_goes_to_next_after_leave(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u, *u2; + struct faction *f; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u = test_create_unit(f, r); + u2 = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_building(u, bld); + u_set_building(u2, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + leave_building(u); + CuAssertPtrEquals(tc, u2, building_owner(bld)); +} + +static void test_buildingowner_goes_to_other_after_leave(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u, *u2; + struct faction *f; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u2 = test_create_unit(f, r); + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_building(u, bld); + u_set_building(u2, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + leave_building(u); + CuAssertPtrEquals(tc, u2, building_owner(bld)); +} + +static void test_buildingowner_goes_to_same_faction_after_leave(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u, *u2, *u3; + struct faction *f1, *f2; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f1 = test_create_faction(human); + f2 = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u2 = test_create_unit(f2, r); + u3 = test_create_unit(f1, r); + u = test_create_unit(f1, r); + CuAssertPtrNotNull(tc, u); + u_set_building(u, bld); + u_set_building(u2, bld); + u_set_building(u3, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + leave_building(u); + CuAssertPtrEquals(tc, u3, building_owner(bld)); + leave_building(u3); + CuAssertPtrEquals(tc, u2, building_owner(bld)); + leave_building(u2); + CuAssertPtrEquals(tc, 0, building_owner(bld)); +} + +static void test_buildingowner_resets_when_empty(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u; + struct faction *f; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_building(u, bld); + CuAssertPtrEquals(tc, u, building_owner(bld)); + u->number = 0; + CuAssertPtrEquals(tc, 0, building_owner(bld)); + u->number = 1; + CuAssertPtrEquals(tc, u, building_owner(bld)); +} + +void test_buildingowner_goes_to_empty_unit_after_leave(CuTest * tc) +{ + struct region *r; + struct building *bld; + struct unit *u1, *u2, *u3; + struct faction *f1; + const struct building_type *btype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + btype = bt_find("castle"); + CuAssertPtrNotNull(tc, btype); + + f1 = test_create_faction(human); + r = findregion(0, 0); + + bld = test_create_building(r, btype); + CuAssertPtrNotNull(tc, bld); + + u1 = test_create_unit(f1, r); + u2 = test_create_unit(f1, r); + u3 = test_create_unit(f1, r); + u_set_building(u1, bld); + u_set_building(u2, bld); + u_set_building(u3, bld); + + CuAssertPtrEquals(tc, u1, building_owner(bld)); + u2->number = 0; + leave_building(u1); + CuAssertPtrEquals(tc, u3, building_owner(bld)); + leave_building(u3); + CuAssertPtrEquals(tc, 0, building_owner(bld)); + u2->number = 1; + CuAssertPtrEquals(tc, u2, building_owner(bld)); +} + +CuSuite *get_building_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_register_building); + SUITE_ADD_TEST(suite, test_building_set_owner); + SUITE_ADD_TEST(suite, test_buildingowner_resets_when_empty); + SUITE_ADD_TEST(suite, test_buildingowner_goes_to_next_when_empty); + SUITE_ADD_TEST(suite, test_buildingowner_goes_to_other_when_empty); + SUITE_ADD_TEST(suite, test_buildingowner_goes_to_same_faction_when_empty); + SUITE_ADD_TEST(suite, test_buildingowner_goes_to_next_after_leave); + SUITE_ADD_TEST(suite, test_buildingowner_goes_to_other_after_leave); + SUITE_ADD_TEST(suite, test_buildingowner_goes_to_same_faction_after_leave); + SUITE_ADD_TEST(suite, test_buildingowner_goes_to_empty_unit_after_leave); + return suite; +} diff --git a/core/src/kernel/calendar.c b/core/src/kernel/calendar.c new file mode 100644 index 000000000..5a09ff738 --- /dev/null +++ b/core/src/kernel/calendar.c @@ -0,0 +1,58 @@ +#include +#include "calendar.h" + +#include + +int first_turn = 0; +int first_month = 0; +int weeks_per_month = 0; +int months_per_year = 0; +char **seasonnames = NULL; +char **weeknames = NULL; +char **weeknames2 = NULL; +char **monthnames = NULL; +int *month_season = NULL; +char *agename = NULL; +int seasons = 0; + +const gamedate *get_gamedate(int turn, gamedate * gd) +{ + int weeks_per_year = months_per_year * weeks_per_month; + int t = turn - first_turn; + + assert(gd); + if (t < 0) + t = turn; + + gd->week = t % weeks_per_month; /* 0 - weeks_per_month-1 */ + gd->month = (t / weeks_per_month + first_month) % months_per_year; /* 0 - months_per_year-1 */ + gd->year = t / (weeks_per_year) + 1; + gd->season = month_season[gd->month]; + return gd; +} + +void calendar_cleanup(void) +{ + int i; + + free(agename); + + for (i = 0; i != seasons; ++i) { + free(seasonnames[i]); + } + free(seasonnames); + + for (i = 0; i != months_per_year; ++i) { + free(monthnames[i]); + } + free(storms); + free(month_season); + free(monthnames); + + for (i = 0; i != weeks_per_month; ++i) { + free(weeknames[i]); + free(weeknames2[i]); + } + free(weeknames); + free(weeknames2); +} diff --git a/core/src/kernel/calendar.h b/core/src/kernel/calendar.h new file mode 100644 index 000000000..6085627fa --- /dev/null +++ b/core/src/kernel/calendar.h @@ -0,0 +1,44 @@ +#ifndef KRNL_CALENDAR_H +#define KRNL_CALENDAR_H + +#ifdef __cplusplus +extern "C" { +#endif + + enum { + SEASON_WINTER, + SEASON_SPRING, + SEASON_SUMMER, + SEASON_AUTUMN + }; + + extern char *agename; + extern int first_turn; + extern int first_month; + + extern int seasons; + extern char **seasonnames; + + extern int months_per_year; + extern char **monthnames; + extern int *month_season; + extern int *storms; /* in movement.c */ + + extern char **weeknames; + extern char **weeknames2; + extern int weeks_per_month; + + typedef struct gamedate { + int year; + int season; + int month; + int week; + } gamedate; + + extern const gamedate *get_gamedate(int turn, gamedate * gd); + extern void calendar_cleanup(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/command.c b/core/src/kernel/command.c new file mode 100644 index 000000000..68d1fb75d --- /dev/null +++ b/core/src/kernel/command.c @@ -0,0 +1,101 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ +#include +#include +#include "command.h" + +#include + +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +typedef struct command { + parser fun; + void *nodes; +} command; + +void *stree_find(const syntaxtree * stree, const struct locale *lang) +{ + while (stree) { + if (stree->lang == lang) + return stree->root; + stree = stree->next; + } + return NULL; +} + +syntaxtree *stree_create(void) +{ + syntaxtree *sroot = NULL; + const struct locale *lang = locales; + while (lang) { + syntaxtree *stree = (syntaxtree *) malloc(sizeof(syntaxtree)); + stree->lang = lang; + stree->next = sroot; + stree->root = 0; + sroot = stree; + lang = nextlocale(lang); + } + return sroot; +} + +void +add_command(void **keys, void *tnext, + const char *str, parser fun) +{ + command *cmd = (command *) malloc(sizeof(command)); + variant var; + + cmd->fun = fun; + cmd->nodes = tnext; + var.v = cmd; + addtoken(keys, str, var); +} + +static int do_command_i(const void *keys, struct unit *u, struct order *ord) +{ + const char *c; + variant var; + + c = getstrtoken(); + if (findtoken(keys, c, &var) == E_TOK_SUCCESS) { + command *cmd = (command *) var.v; + if (cmd->nodes && *c) { + assert(!cmd->fun); + return do_command_i(cmd->nodes, u, ord); + } else if (cmd->fun) { + cmd->fun(cmd->nodes, u, ord); + return E_TOK_SUCCESS; + } + } + return E_TOK_NOMATCH; +} + +void do_command(const void *keys, struct unit *u, struct order *ord) +{ + init_tokens(ord); + skip_token(); + if (do_command_i(keys, u, ord) != E_TOK_SUCCESS) { + char *cmd = getcommand(ord); + log_warning("%s failed command '%s'\n", unitname(u), cmd); + free(cmd); + } +} diff --git a/core/src/kernel/command.h b/core/src/kernel/command.h new file mode 100644 index 000000000..30f02a085 --- /dev/null +++ b/core/src/kernel/command.h @@ -0,0 +1,41 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ +#ifndef H_UTIL_COMMAND_H +#define H_UTIL_COMMAND_H +#ifdef __cplusplus +extern "C" { +#endif + + struct locale; + struct order; + struct unit; + + typedef struct syntaxtree { + const struct locale *lang; + void *root; + struct syntaxtree *next; + } syntaxtree; + + typedef void (*parser) (const void *nodes, struct unit * u, struct order *); + extern void add_command(void **troot, void *tnext, + const char *str, parser fun); + extern void do_command(const void *troot, struct unit *u, struct order *); + + extern struct syntaxtree *stree_create(void); + extern void *stree_find(const struct syntaxtree *stree, + const struct locale *lang); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/config.c b/core/src/kernel/config.c new file mode 100644 index 000000000..626043cc1 --- /dev/null +++ b/core/src/kernel/config.c @@ -0,0 +1,3153 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include + +/* attributes includes */ +#include +#include + +/* kernel includes */ +#include "alliance.h" +#include "ally.h" +#include "alchemy.h" +#include "battle.h" +#include "connection.h" +#include "building.h" +#include "calendar.h" +#include "curse.h" +#include "faction.h" +#include "group.h" +#include "item.h" +#include "magic.h" +#include "message.h" +#include "move.h" +#include "names.h" +#include "objtypes.h" +#include "order.h" +#include "plane.h" +#include "pool.h" +#include "race.h" +#include "region.h" +#include "save.h" +#include "ship.h" +#include "skill.h" +#include "terrain.h" +#include "unit.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libxml includes */ +#include +#include + +/* external libraries */ +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct settings global = { + "Eressea", /* gamename */ +}; + +FILE *logfile; +FILE *updatelog; +const struct race *new_race[MAXRACES]; +bool sqlpatch = false; +bool battledebug = false; +int turn = 0; + +int NewbieImmunity(void) +{ + static int value = -1; + static int gamecookie = -1; + if (value < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + value = get_param_int(global.parameters, "NewbieImmunity", 0); + } + return value; +} + +bool IsImmune(const faction * f) +{ + return !fval(f, FFL_NPC) && f->age < NewbieImmunity(); +} + +static int MaxAge(void) +{ + static int value = -1; + static int gamecookie = -1; + if (value < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + value = get_param_int(global.parameters, "MaxAge", 0); + } + return value; +} + +static int ally_flag(const char *s, int help_mask) +{ + if ((help_mask & HELP_MONEY) && strcmp(s, "money") == 0) + return HELP_MONEY; + if ((help_mask & HELP_FIGHT) && strcmp(s, "fight") == 0) + return HELP_FIGHT; + if ((help_mask & HELP_GIVE) && strcmp(s, "give") == 0) + return HELP_GIVE; + if ((help_mask & HELP_GUARD) && strcmp(s, "guard") == 0) + return HELP_GUARD; + if ((help_mask & HELP_FSTEALTH) && strcmp(s, "stealth") == 0) + return HELP_FSTEALTH; + if ((help_mask & HELP_TRAVEL) && strcmp(s, "travel") == 0) + return HELP_TRAVEL; + return 0; +} + +bool ExpensiveMigrants(void) +{ + static int value = -1; + static int gamecookie = -1; + if (value < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + value = get_param_int(global.parameters, "study.expensivemigrants", 0); + } + return value; +} + +/** Specifies automatic alliance modes. + * If this returns a value then the bits set are immutable between alliance + * partners (faction::alliance) and cannot be changed with the HELP command. + */ +int AllianceAuto(void) +{ + static int value = -1; + static int gamecookie = -1; + if (value < 0 || gamecookie != global.cookie) { + const char *str = get_param(global.parameters, "alliance.auto"); + gamecookie = global.cookie; + value = 0; + if (str != NULL) { + char *sstr = strdup(str); + char *tok = strtok(sstr, " "); + while (tok) { + value |= ally_flag(tok, -1); + tok = strtok(NULL, " "); + } + free(sstr); + } + } + return value & HelpMask(); +} + +/** Limits the available help modes + * The bitfield returned by this function specifies the available help modes + * in this game (so you can, for example, disable HELP GIVE globally). + * Disabling a status will disable the command sequence entirely (order parsing + * uses this function). + */ +int HelpMask(void) +{ + static int rule = -1; + static int gamecookie = -1; + if (rule < 0 || gamecookie != global.cookie) { + const char *str = get_param(global.parameters, "rules.help.mask"); + gamecookie = global.cookie; + rule = 0; + if (str != NULL) { + char *sstr = strdup(str); + char *tok = strtok(sstr, " "); + while (tok) { + rule |= ally_flag(tok, -1); + tok = strtok(NULL, " "); + } + free(sstr); + } else { + rule = HELP_ALL; + } + } + return rule; +} + +int AllianceRestricted(void) +{ + static int rule = -1; + static int gamecookie = -1; + if (rule < 0 || gamecookie != global.cookie) { + const char *str = get_param(global.parameters, "alliance.restricted"); + gamecookie = global.cookie; + rule = 0; + if (str != NULL) { + char *sstr = strdup(str); + char *tok = strtok(sstr, " "); + while (tok) { + rule |= ally_flag(tok, -1); + tok = strtok(NULL, " "); + } + free(sstr); + } + rule &= HelpMask(); + } + return rule; +} + +int LongHunger(const struct unit *u) +{ + static int gamecookie = -1; + static int rule = -1; + if (u != NULL) { + if (!fval(u, UFL_HUNGER)) + return false; +#ifdef NEW_DAEMONHUNGER_RULE + if (u_race(u) == new_race[RC_DAEMON]) + return false; +#endif + } + if (rule < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + rule = get_param_int(global.parameters, "hunger.long", 0); + } + return rule; +} + +int SkillCap(skill_t sk) +{ + static int gamecookie = -1; + static int rule = -1; + if (sk == SK_MAGIC) + return 0; /* no caps on magic */ + if (rule < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + rule = get_param_int(global.parameters, "skill.maxlevel", 0); + } + return rule; +} + +int NMRTimeout(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + rule = get_param_int(global.parameters, "nmr.timeout", 0); + } + return rule; +} + +race_t old_race(const struct race * rc) +{ + race_t i; + for (i = 0; i != MAXRACES; ++i) { + if (new_race[i] == rc) + return i; + } + return NORACE; +} + +helpmode helpmodes[] = { + {"all", HELP_ALL} + , + {"money", HELP_MONEY} + , + {"fight", HELP_FIGHT} + , + {"observe", HELP_OBSERVE} + , + {"give", HELP_GIVE} + , + {"guard", HELP_GUARD} + , + {"stealth", HELP_FSTEALTH} + , + {"travel", HELP_TRAVEL} + , + {NULL, 0} +}; + +const char *directions[MAXDIRECTIONS + 2] = { + "northwest", + "northeast", + "east", + "southeast", + "southwest", + "west", + "", + "pause" +}; + +/** Returns the English name of the race, which is what the database uses. + */ +const char *dbrace(const struct race *rc) +{ + static char zText[32]; + char *zPtr = zText; + + /* the english names are all in ASCII, so we don't need to worry about UTF8 */ + strcpy(zText, (const char *)LOC(find_locale("en"), rc_name(rc, 0))); + while (*zPtr) { + *zPtr = (char)(toupper(*zPtr)); + ++zPtr; + } + return zText; +} + +const char *parameters[MAXPARAMS] = { + "LOCALE", + "ALLES", + "JEDEM", + "BAUERN", + "BURG", + "EINHEIT", + "PRIVAT", + "HINTEN", + "KOMMANDO", + "KRAEUTER", + "NICHT", + "NAECHSTER", + "PARTEI", + "ERESSEA", + "PERSONEN", + "REGION", + "SCHIFF", + "SILBER", + "STRASSEN", + "TEMPORAERE", + "FLIEHE", + "GEBAEUDE", + "GIB", /* Für HELFE */ + "KAEMPFE", + "DURCHREISE", + "BEWACHE", + "ZAUBER", + "PAUSE", + "VORNE", + "AGGRESSIV", + "DEFENSIV", + "STUFE", + "HELFE", + "FREMDES", + "AURA", + "UM", + "BEISTAND", + "GNADE", + "HINTER", + "VOR", + "ANZAHL", + "GEGENSTAENDE", + "TRAENKE", + "GRUPPE", + "PARTEITARNUNG", + "BAEUME", + "XEPOTION", + "XEBALLOON", + "XELAEN", + "ALLIANZ" +}; + +const char *keywords[MAXKEYWORDS] = { + "//", + "BANNER", + "ARBEITEN", + "ATTACKIEREN", + "BEKLAUEN", + "BELAGERE", + "BENENNEN", + "BENUTZEN", + "BESCHREIBEN", + "BETRETEN", + "BEWACHEN", + "BOTSCHAFT", + "ENDE", + "FAHREN", + "NUMMER", + "FOLGEN", + "FORSCHEN", + "GIB", + "HELFEN", + "KAEMPFEN", + "KAMPFZAUBER", + "KAUFEN", + "KONTAKTIEREN", + "LEHREN", + "LERNEN", + "MACHEN", + "NACH", + "PASSWORT", + "REKRUTIEREN", + "RESERVIEREN", + "ROUTE", + "SABOTIEREN", + "OPTION", + "SPIONIEREN", + "STIRB", + "TARNEN", + "TRANSPORTIEREN", + "TREIBEN", + "UNTERHALTEN", + "VERKAUFEN", + "VERLASSEN", + "VERGESSEN", + "ZAUBERE", + "ZEIGEN", + "ZERSTOEREN", + "ZUECHTEN", + "DEFAULT", + "URSPRUNG", + "EMAIL", + "PIRATERIE", + "GRUPPE", + "SORTIEREN", + "GM", + "INFO", + "PRAEFIX", + "PFLANZEN", + "ALLIANZ", + "BEANSPRUCHEN", + "PROMOTION", + "BEZAHLEN", +}; + +const char *report_options[MAX_MSG] = { + "Kampf", + "Ereignisse", + "Bewegung", + "Einkommen", + "Handel", + "Produktion", + "Orkvermehrung", + "Zauber", + "", + "" +}; + +const char *message_levels[ML_MAX] = { + "Wichtig", + "Debug", + "Fehler", + "Warnungen", + "Infos" +}; + +const char *options[MAXOPTIONS] = { + "AUSWERTUNG", + "COMPUTER", + "ZUGVORLAGE", + NULL, + "STATISTIK", + "DEBUG", + "ZIPPED", + "ZEITUNG", /* Option hat Sonderbehandlung! */ + NULL, + "ADRESSEN", + "BZIP2", + "PUNKTE", + "SHOWSKCHANGE", + "XML" +}; + +static int allied_skillcount(const faction * f, skill_t sk) +{ + int num = 0; + alliance *a = f_get_alliance(f); + quicklist *members = a->members; + int qi; + + for (qi = 0; members; ql_advance(&members, &qi, 1)) { + faction *m = (faction *) ql_get(members, qi); + num += count_skill(m, sk); + } + return num; +} + +static int allied_skilllimit(const faction * f, skill_t sk) +{ + static int value = -1; + if (value < 0) { + value = get_param_int(global.parameters, "alliance.skilllimit", 0); + } + return value; +} + +static void init_maxmagicians(struct attrib *a) +{ + a->data.i = MAXMAGICIANS; +} + +static attrib_type at_maxmagicians = { + "maxmagicians", + init_maxmagicians, + NULL, + NULL, + a_writeint, + a_readint, + ATF_UNIQUE +}; + +static void init_npcfaction(struct attrib *a) +{ + a->data.i = 1; +} + +static attrib_type at_npcfaction = { + "npcfaction", + init_npcfaction, + NULL, + NULL, + a_writeint, + a_readint, + ATF_UNIQUE +}; + +int max_magicians(const faction * f) +{ + int m = + get_param_int(global.parameters, "rules.maxskills.magic", MAXMAGICIANS); + attrib *a; + + if ((a = a_find(f->attribs, &at_maxmagicians)) != NULL) { + m = a->data.i; + } + if (f->race == new_race[RC_ELF]) + ++m; + return m; +} + +int skill_limit(faction * f, skill_t sk) +{ + int m = INT_MAX; + int al = allied_skilllimit(f, sk); + if (al > 0) { + if (sk != SK_ALCHEMY && sk != SK_MAGIC) + return INT_MAX; + if (f_get_alliance(f)) { + int ac = listlen(f->alliance->members); /* number of factions */ + int fl = (al + ac - 1) / ac; /* faction limit, rounded up */ + /* the faction limit may not be achievable because it would break the alliance-limit */ + int sc = al - allied_skillcount(f, sk); + if (sc <= 0) + return 0; + return fl; + } + } + if (sk == SK_MAGIC) { + m = max_magicians(f); + } else if (sk == SK_ALCHEMY) { + m = get_param_int(global.parameters, "rules.maxskills.alchemy", + MAXALCHEMISTS); + } + return m; +} + +int count_skill(faction * f, skill_t sk) +{ + int n = 0; + unit *u; + + for (u = f->units; u; u = u->nextF) { + if (has_skill(u, sk)) { + if (!is_familiar(u)) { + n += u->number; + } + } + } + return n; +} + +int verbosity = 1; + +FILE *debug; + +static int ShipSpeedBonus(const unit * u) +{ + static int level = -1; + if (level == -1) { + level = + get_param_int(global.parameters, "movement.shipspeed.skillbonus", 0); + } + if (level > 0) { + ship *sh = u->ship; + int skl = effskill(u, SK_SAILING); + int minsk = (sh->type->cptskill + 1) / 2; + return (skl - minsk) / level; + } + return 0; +} + +int shipspeed(const ship * sh, const unit * u) +{ + double k = sh->type->range; + static const curse_type *stormwind_ct, *nodrift_ct; + static bool init; + attrib *a; + curse *c; + + if (!init) { + init = true; + stormwind_ct = ct_find("stormwind"); + nodrift_ct = ct_find("nodrift"); + } + + assert(u->ship == sh); + assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ + if (sh->size != sh->type->construction->maxsize) + return 0; + + if (curse_active(get_curse(sh->attribs, stormwind_ct))) + k *= 2; + if (curse_active(get_curse(sh->attribs, nodrift_ct))) + k += 1; + + if (u->faction->race == u_race(u)) { + /* race bonus for this faction? */ + if (fval(u_race(u), RCF_SHIPSPEED)) { + k += 1; + } + } + + k += ShipSpeedBonus(u); + + a = a_find(sh->attribs, &at_speedup); + while (a != NULL && a->type == &at_speedup) { + k += a->data.sa[0]; + a = a->next; + } + + c = get_curse(sh->attribs, ct_find("shipspeedup")); + while (c) { + k += curse_geteffect(c); + c = c->nexthash; + } + +#ifdef SHIPSPEED + k *= SHIPSPEED; +#endif + +#ifdef SHIPDAMAGE + if (sh->damage) + k = + (k * (sh->size * DAMAGE_SCALE - sh->damage) + sh->size * DAMAGE_SCALE - + 1) / (sh->size * DAMAGE_SCALE); +#endif + + return (int)k; +} + +#define FMAXHASH 2039 +faction *factionhash[FMAXHASH]; + +void fhash(faction * f) +{ + int index = f->no % FMAXHASH; + f->nexthash = factionhash[index]; + factionhash[index] = f; +} + +void funhash(faction * f) +{ + int index = f->no % FMAXHASH; + faction **fp = factionhash + index; + while (*fp && (*fp) != f) + fp = &(*fp)->nexthash; + *fp = f->nexthash; +} + +static faction *ffindhash(int no) +{ + int index = no % FMAXHASH; + faction *f = factionhash[index]; + while (f && f->no != no) + f = f->nexthash; + return f; +} + +/* ----------------------------------------------------------------------- */ + +void verify_data(void) +{ +#ifndef NDEBUG + int lf = -1; + faction *f; + unit *u; + int mage, alchemist; + + if (verbosity >= 1) + puts(" - Überprüfe Daten auf Korrektheit..."); + + for (f = factions; f; f = f->next) { + mage = 0; + alchemist = 0; + for (u = f->units; u; u = u->nextF) { + if (eff_skill(u, SK_MAGIC, u->region)) { + mage += u->number; + } + if (eff_skill(u, SK_ALCHEMY, u->region)) + alchemist += u->number; + if (u->number > UNIT_MAXSIZE) { + if (lf != f->no) { + lf = f->no; + log_printf(stdout, "Partei %s:\n", factionid(f)); + } + log_warning("Einheit %s hat %d Personen\n", unitid(u), u->number); + } + } + if (f->no != 0 && ((mage > 3 && f->race != new_race[RC_ELF]) || mage > 4)) + log_error("Partei %s hat %d Magier.\n", factionid(f), mage); + if (alchemist > 3) + log_error("Partei %s hat %d Alchemisten.\n", factionid(f), alchemist); + } +#endif +} + +int distribute(int old, int new_value, int n) +{ + int i; + int t; + assert(new_value <= old); + + if (old == 0) + return 0; + + t = (n / old) * new_value; + for (i = (n % old); i; i--) + if (rng_int() % old < new_value) + t++; + + return t; +} + +int change_hitpoints(unit * u, int value) +{ + int hp = u->hp; + + hp += value; + + /* Jede Person benötigt mindestens 1 HP */ + if (hp < u->number) { + if (hp < 0) { /* Einheit tot */ + hp = 0; + } + scale_number(u, hp); + } + u->hp = hp; + return hp; +} + +unsigned int atoip(const char *s) +{ + int n; + + n = atoi(s); + + if (n < 0) + n = 0; + + return n; +} + +region *findunitregion(const unit * su) +{ +#ifndef SLOW_REGION + return su->region; +#else + region *r; + const unit *u; + + for (r = regions; r; r = r->next) { + for (u = r->units; u; u = u->next) { + if (su == u) { + return r; + } + } + } + + /* This should never happen */ + assert(!"Die unit wurde nicht gefunden"); + + return (region *) NULL; +#endif +} + +int effskill(const unit * u, skill_t sk) +{ + return eff_skill(u, sk, u->region); +} + +int eff_stealth(const unit * u, const region * r) +{ + int e = 0; + + /* Auf Schiffen keine Tarnung! */ + if (!u->ship && skill_enabled[SK_STEALTH]) { + e = eff_skill(u, SK_STEALTH, r); + + if (fval(u, UFL_STEALTH)) { + int es = u_geteffstealth(u); + if (es >= 0 && es < e) + return es; + } + } + return e; +} + +bool unit_has_cursed_item(unit * u) +{ + item *itm = u->items; + while (itm) { + if (fval(itm->type, ITF_CURSED) && itm->number > 0) + return true; + itm = itm->next; + } + return false; +} + +static void init_gms(void) +{ + faction *f; + + for (f = factions; f; f = f->next) { + const attrib *a = a_findc(f->attribs, &at_gm); + + if (a != NULL) + fset(f, FFL_GM); + } +} + +static int +autoalliance(const plane * pl, const faction * sf, const faction * f2) +{ + static bool init = false; + if (!init) { + init_gms(); + init = true; + } + if (pl && (pl->flags & PFL_FRIENDLY)) + return HELP_ALL; + /* if f2 is a gm in this plane, everyone has an auto-help to it */ + if (fval(f2, FFL_GM)) { + attrib *a = a_find(f2->attribs, &at_gm); + + while (a) { + const plane *p = (const plane *)a->data.v; + if (p == pl) + return HELP_ALL; + a = a->next; + } + } + + if (f_get_alliance(sf) != NULL && AllianceAuto()) { + if (sf->alliance == f2->alliance) + return AllianceAuto(); + } + + return 0; +} + +static int ally_mode(const ally * sf, int mode) +{ + if (sf == NULL) + return 0; + return sf->status & mode; +} + +int +alliedgroup(const struct plane *pl, const struct faction *f, + const struct faction *f2, const struct ally *sf, int mode) +{ + while (sf && sf->faction != f2) + sf = sf->next; + if (sf == NULL) { + mode = mode & autoalliance(pl, f, f2); + } + mode = ally_mode(sf, mode) | (mode & autoalliance(pl, f, f2)); + if (AllianceRestricted()) { + if (a_findc(f->attribs, &at_npcfaction)) { + return mode; + } + if (a_findc(f2->attribs, &at_npcfaction)) { + return mode; + } + if (f->alliance != f2->alliance) { + mode &= ~AllianceRestricted(); + } + } + return mode; +} + +int +alliedfaction(const struct plane *pl, const struct faction *f, + const struct faction *f2, int mode) +{ + return alliedgroup(pl, f, f2, f->allies, mode); +} + +/* Die Gruppe von Einheit u hat helfe zu f2 gesetzt. */ +int alliedunit(const unit * u, const faction * f2, int mode) +{ + ally *sf; + int automode; + + assert(u->region); /* the unit should be in a region, but it's possible that u->number==0 (TEMP units) */ + if (u->faction == f2) + return mode; + if (u->faction != NULL && f2 != NULL) { + plane *pl; + + if (mode & HELP_FIGHT) { + if ((u->flags & UFL_DEFENDER) || (u->faction->flags & FFL_DEFENDER)) { + faction *owner = region_get_owner(u->region); + /* helps the owner of the region */ + if (owner == f2) { + return HELP_FIGHT; + } + } + } + + pl = rplane(u->region); + automode = mode & autoalliance(pl, u->faction, f2); + + if (pl != NULL && (pl->flags & PFL_NOALLIANCES)) + mode = (mode & automode) | (mode & HELP_GIVE); + + sf = u->faction->allies; + if (fval(u, UFL_GROUP)) { + const attrib *a = a_findc(u->attribs, &at_group); + if (a != NULL) + sf = ((group *) a->data.v)->allies; + } + return alliedgroup(pl, u->faction, f2, sf, mode); + } + return 0; +} + +bool +seefaction(const faction * f, const region * r, const unit * u, int modifier) +{ + if (((f == u->faction) || !fval(u, UFL_ANON_FACTION)) + && cansee(f, r, u, modifier)) + return true; + return false; +} + +bool +cansee(const faction * f, const region * r, const unit * u, int modifier) + /* r kann != u->region sein, wenn es um durchreisen geht */ + /* und es muss niemand aus f in der region sein, wenn sie vom Turm + * erblickt wird */ +{ + int stealth, rings; + unit *u2 = r->units; + static const item_type *itype_grail; + static bool init; + + if (!init) { + init = true; + itype_grail = it_find("grail"); + } + + if (u->faction == f || omniscient(f)) { + return true; + } else if (fval(u_race(u), RCF_INVISIBLE)) { + return false; + } else if (u->number == 0) { + attrib *a = a_find(u->attribs, &at_creator); + if (a) { /* u is an empty temporary unit. In this special case + we look at the creating unit. */ + u = (unit *) a->data.v; + } else { + return false; + } + } + + if (leftship(u)) + return true; + if (itype_grail != NULL && i_get(u->items, itype_grail)) + return true; + + while (u2 && u2->faction != f) + u2 = u2->next; + if (u2 == NULL) + return false; + + /* simple visibility, just gotta have a unit in the region to see 'em */ + if (is_guard(u, GUARD_ALL) != 0 || usiege(u) || u->building || u->ship) { + return true; + } + + rings = invisible(u, NULL); + stealth = eff_stealth(u, r) - modifier; + + while (u2) { + if (rings < u->number || invisible(u, u2) < u->number) { + if (skill_enabled[SK_PERCEPTION]) { + int observation = eff_skill(u2, SK_PERCEPTION, r); + + if (observation >= stealth) { + return true; + } + } else { + return true; + } + } + + /* find next unit in our faction */ + do { + u2 = u2->next; + } while (u2 && u2->faction != f); + } + return false; +} + +bool cansee_unit(const unit * u, const unit * target, int modifier) +/* target->region kann != u->region sein, wenn es um durchreisen geht */ +{ + if (fval(u_race(target), RCF_INVISIBLE) || target->number == 0) + return false; + else if (target->faction == u->faction) + return true; + else { + int n, rings, o; + + if (is_guard(target, GUARD_ALL) != 0 || usiege(target) || target->building + || target->ship) { + return true; + } + + n = eff_stealth(target, target->region) - modifier; + rings = invisible(target, NULL); + if (rings == 0 && n <= 0) { + return true; + } + + if (rings && invisible(target, u) >= target->number) { + return false; + } + if (skill_enabled[SK_PERCEPTION]) { + o = eff_skill(u, SK_PERCEPTION, target->region); + if (o >= n) { + return true; + } + } else { + return true; + } + } + return false; +} + +bool +cansee_durchgezogen(const faction * f, const region * r, const unit * u, + int modifier) +/* r kann != u->region sein, wenn es um durchreisen geht */ +/* und es muss niemand aus f in der region sein, wenn sie vom Turm + * erblickt wird */ +{ + int n; + unit *u2; + + if (fval(u_race(u), RCF_INVISIBLE) || u->number == 0) + return false; + else if (u->faction == f) + return true; + else { + int rings; + + if (is_guard(u, GUARD_ALL) != 0 || usiege(u) || u->building || u->ship) { + return true; + } + + n = eff_stealth(u, r) - modifier; + rings = invisible(u, NULL); + if (rings == 0 && n <= 0) { + return true; + } + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction == f) { + int o; + + if (rings && invisible(u, u2) >= u->number) + continue; + + o = eff_skill(u2, SK_PERCEPTION, r); + + if (o >= n) { + return true; + } + } + } + } + return false; +} + +#ifndef NDEBUG +const char *strcheck(const char *s, size_t maxlen) +{ + static char buffer[16 * 1024]; + if (strlen(s) > maxlen) { + assert(maxlen < 16 * 1024); + log_warning("[strcheck] String wurde auf %d Zeichen verkürzt:\n%s\n", (int)maxlen, s); + strlcpy(buffer, s, maxlen); + return buffer; + } + return s; +} +#endif + +static attrib_type at_lighthouse = { + "lighthouse" + /* Rest ist NULL; temporäres, nicht alterndes Attribut */ +}; + +/* update_lighthouse: call this function whenever the size of a lighthouse changes + * it adds temporary markers to the surrounding regions. + * The existence of markers says nothing about the quality of the observer in + * the lighthouse, for this may change more frequently. + */ +void update_lighthouse(building * lh) +{ + static bool init_lighthouse = false; + static const struct building_type *bt_lighthouse = 0; + + if (!init_lighthouse) { + bt_lighthouse = bt_find("lighthouse"); + if (bt_lighthouse == NULL) + return; + init_lighthouse = true; + } + + if (lh->type == bt_lighthouse) { + region *r = lh->region; + int d = (int)log10(lh->size) + 1; + int x; + + if (lh->size > 0) { + r->flags |= RF_LIGHTHOUSE; + } + + for (x = -d; x <= d; ++x) { + int y; + for (y = -d; y <= d; ++y) { + attrib *a; + region *r2; + int px = r->x + x, py = r->y + y; + pnormalize(&px, &py, rplane(r)); + r2 = findregion(px, py); + if (r2 == NULL) + continue; + if (!fval(r2->terrain, SEA_REGION)) + continue; + if (distance(r, r2) > d) + continue; + a = a_find(r2->attribs, &at_lighthouse); + while (a && a->type == &at_lighthouse) { + building *b = (building *) a->data.v; + if (b == lh) + break; + a = a->next; + } + if (!a) { + a = a_add(&r2->attribs, a_new(&at_lighthouse)); + a->data.v = (void *)lh; + } + } + } + } +} + +int count_all(const faction * f) +{ +#ifndef NDEBUG + int n = 0; + unit *u; + for (u = f->units; u; u = u->nextF) { + if (playerrace(u_race(u))) { + n += u->number; + assert(f == u->faction); + } + } + if (f->num_people != n) { + log_error("# of people in %s is != num_people: %d should be %d.\n", factionid(f), f->num_people, n); + } +#endif + return f->num_people; +} + +int count_migrants(const faction * f) +{ + unit *u = f->units; + int n = 0; + while (u) { + assert(u->faction == f); + if (u_race(u) != f->race && u_race(u) != new_race[RC_ILLUSION] + && u_race(u) != new_race[RC_SPELL] + && !!playerrace(u_race(u)) && !(is_cursed(u->attribs, C_SLAVE, 0))) { + n += u->number; + } + u = u->nextF; + } + return n; +} + +int count_maxmigrants(const faction * f) +{ + static int migrants = -1; + + if (migrants < 0) { + migrants = get_param_int(global.parameters, "rules.migrants", INT_MAX); + } + if (migrants == INT_MAX) { + int x = 0; + if (f->race == new_race[RC_HUMAN]) { + int nsize = count_all(f); + if (nsize > 0) { + x = (int)(log10(nsize / 50.0) * 20); + if (x < 0) + x = 0; + } + } + return x; + } + return migrants; +} + +void +parse(keyword_t kword, int (*dofun) (unit *, struct order *), bool thisorder) +{ + region *r; + + for (r = regions; r; r = r->next) { + unit **up = &r->units; + while (*up) { + unit *u = *up; + order **ordp = &u->orders; + if (thisorder) + ordp = &u->thisorder; + while (*ordp) { + order *ord = *ordp; + if (get_keyword(ord) == kword) { + if (dofun(u, ord) != 0) + break; + if (u->orders == NULL) + break; + } + if (thisorder) + break; + if (*ordp == ord) + ordp = &ord->next; + } + if (*up == u) + up = &u->next; + } + } +} + +const char *igetstrtoken(const char *initstr) +{ + if (initstr != NULL) { + init_tokens_str(initstr, NULL); + } + + return getstrtoken(); +} + +unsigned int getuint(void) +{ + return atoip((const char *)getstrtoken()); +} + +int getint(void) +{ + return atoi((const char *)getstrtoken()); +} + +const struct race *findrace(const char *s, const struct locale *lang) +{ + void **tokens = get_translations(lang, UT_RACES); + variant token; + + assert(lang); + if (tokens && findtoken(*tokens, s, &token) == E_TOK_SUCCESS) { + return (const struct race *)token.v; + } + return NULL; +} + +int findoption(const char *s, const struct locale *lang) +{ + void **tokens = get_translations(lang, UT_OPTIONS); + variant token; + + if (findtoken(*tokens, s, &token) == E_TOK_SUCCESS) { + return (direction_t) token.i; + } + return NODIRECTION; +} + +skill_t findskill(const char *s, const struct locale * lang) +{ + param_t result = NOSKILL; + char buffer[64]; + char * str = transliterate(buffer, sizeof(buffer)-sizeof(int), s); + + if (str) { + int i; + const void * match; + void **tokens = get_translations(lang, UT_SKILLS); + critbit_tree *cb = (critbit_tree *)*tokens; + if (cb_find_prefix(cb, str, strlen(str), &match, 1, 0)) { + cb_get_kv(match, &i, sizeof(int)); + result = (skill_t)i; + } + } + return result; +} + +keyword_t findkeyword(const char *s, const struct locale * lang) +{ + keyword_t result = NOKEYWORD; + char buffer[64]; + + while (*s == '@') ++s; + + if (*s) { + char * str = transliterate(buffer, sizeof(buffer)-sizeof(int), s); + + if (str) { + int i; + const void * match; + void **tokens = get_translations(lang, UT_KEYWORDS); + critbit_tree *cb = (critbit_tree *)*tokens; + if (cb_find_prefix(cb, str, strlen(str), &match, 1, 0)) { + cb_get_kv(match, &i, sizeof(int)); + result = (keyword_t)i; + return global.disabled[result] ? NOKEYWORD : result; + } + } + } + return NOKEYWORD; +} + +param_t findparam(const char *s, const struct locale * lang) +{ + param_t result = NOPARAM; + char buffer[64]; + char * str = transliterate(buffer, sizeof(buffer)-sizeof(int), s); + + if (str && *str) { + int i; + const void * match; + void **tokens = get_translations(lang, UT_PARAMS); + critbit_tree *cb = (critbit_tree *)*tokens; + if (cb_find_prefix(cb, str, strlen(str), &match, 1, 0)) { + cb_get_kv(match, &i, sizeof(int)); + result = (param_t)i; + } + } + return result; +} + +param_t findparam_ex(const char *s, const struct locale * lang) +{ + param_t result = findparam(s, lang); + + if (result==NOPARAM) { + const building_type *btype = findbuildingtype(s, lang); + if (btype != NULL) + return P_GEBAEUDE; + } + return (result == P_BUILDING) ? P_GEBAEUDE : result; +} + +bool isparam(const char *s, const struct locale * lang, param_t param) +{ + if (s[0]>'@') { + param_t p = (param==P_GEBAEUDE) ? findparam_ex(s, lang) : findparam(s, lang); + return p==param; + } + return false; +} + +param_t getparam(const struct locale * lang) +{ + return findparam(getstrtoken(), lang); +} + +faction *findfaction(int n) +{ + faction *f = ffindhash(n); + return f; +} + +faction *getfaction(void) +{ + return findfaction(getid()); +} + +unit *findunitr(const region * r, int n) +{ + unit *u; + + /* findunit regional! */ + + for (u = r->units; u; u = u->next) + if (u->no == n) + return u; + + return 0; +} + +unit *findunit(int n) +{ + if (n <= 0) { + return NULL; + } + return ufindhash(n); +} + +unit *findunitg(int n, const region * hint) +{ + + /* Abfangen von Syntaxfehlern. */ + if (n <= 0) + return NULL; + + /* findunit global! */ + hint = 0; + return ufindhash(n); +} + +unit *getnewunit(const region * r, const faction * f) +{ + int n; + n = getid(); + + return findnewunit(r, f, n); +} + +static int read_newunitid(const faction * f, const region * r) +{ + int n; + unit *u2; + n = getid(); + if (n == 0) + return -1; + + u2 = findnewunit(r, f, n); + if (u2) + return u2->no; + + return -1; +} + +int read_unitid(const faction * f, const region * r) +{ + const char *s = getstrtoken(); + + /* Da s nun nur einen string enthaelt, suchen wir ihn direkt in der + * paramliste. machen wir das nicht, dann wird getnewunit in s nach der + * nummer suchen, doch dort steht bei temp-units nur "temp" drinnen! */ + + if (!s || *s == 0) { + return -1; + } + if (isparam(s, f->locale, P_TEMP)) { + return read_newunitid(f, r); + } + return atoi36((const char *)s); +} + +/* exported symbol */ +bool getunitpeasants; +unit *getunitg(const region * r, const faction * f) +{ + int n = read_unitid(f, r); + + if (n == 0) { + getunitpeasants = 1; + return NULL; + } + getunitpeasants = 0; + if (n < 0) + return 0; + + return findunit(n); +} + +unit *getunit(const region * r, const faction * f) +{ + int n = read_unitid(f, r); + unit *u2; + + if (n == 0) { + getunitpeasants = 1; + return NULL; + } + getunitpeasants = 0; + if (n < 0) + return 0; + + u2 = findunit(n); + if (u2 != NULL && u2->region == r) { + /* there used to be a 'u2->flags & UFL_ISNEW || u2->number>0' condition + * here, but it got removed because of a bug that made units disappear: + * http://eressea.upb.de/mantis/bug_view_page.php?bug_id=0000172 + */ + return u2; + } + + return NULL; +} + +/* - String Listen --------------------------------------------- */ +void addstrlist(strlist ** SP, const char *s) +{ + strlist *slist = malloc(sizeof(strlist)); + slist->next = NULL; + slist->s = strdup(s); + addlist(SP, slist); +} + +void freestrlist(strlist * s) +{ + strlist *q, *p = s; + while (p) { + q = p->next; + free(p->s); + free(p); + p = q; + } +} + +/* - Meldungen und Fehler ------------------------------------------------- */ + +bool lomem = false; + +/* - Namen der Strukturen -------------------------------------- */ +typedef char name[OBJECTIDSIZE + 1]; +static name idbuf[8]; +static int nextbuf = 0; + +char *estring_i(char *ibuf) +{ + char *p = ibuf; + + while (*p) { + if (isxspace(*(unsigned *)p) == ' ') { + *p = '~'; + } + ++p; + } + return ibuf; +} + +char *estring(const char *s) +{ + char *ibuf = idbuf[(++nextbuf) % 8]; + + strlcpy(ibuf, s, sizeof(name)); + return estring_i(ibuf); +} + +char *cstring_i(char *ibuf) +{ + char *p = ibuf; + + while (*p) { + if (*p == '~') { + *p = ' '; + } + ++p; + } + return ibuf; +} + +char *cstring(const char *s) +{ + char *ibuf = idbuf[(++nextbuf) % 8]; + + strlcpy(ibuf, s, sizeof(name)); + return cstring_i(ibuf); +} + +building *largestbuilding(const region * r, cmp_building_cb cmp_gt, + bool imaginary) +{ + building *b, *best = NULL; + + for (b = rbuildings(r); b; b = b->next) { + if (cmp_gt(b, best) <= 0) + continue; + if (!imaginary) { + const attrib *a = a_find(b->attribs, &at_icastle); + if (a) + continue; + } + best = b; + } + return best; +} + +char *write_unitname(const unit * u, char *buffer, size_t size) +{ + slprintf(buffer, size, "%s (%s)", (const char *)u->name, itoa36(u->no)); + buffer[size - 1] = 0; + return buffer; +} + +const char *unitname(const unit * u) +{ + char *ubuf = idbuf[(++nextbuf) % 8]; + return write_unitname(u, ubuf, sizeof(name)); +} + +/* -- Erschaffung neuer Einheiten ------------------------------ */ + +extern faction *dfindhash(int i); + +static const char *forbidden[] = { "t", "te", "tem", "temp", NULL }; + +int forbiddenid(int id) +{ + static int *forbid = NULL; + static size_t len; + size_t i; + if (id <= 0) + return 1; + if (!forbid) { + while (forbidden[len]) + ++len; + forbid = calloc(len, sizeof(int)); + for (i = 0; i != len; ++i) { + forbid[i] = strtol(forbidden[i], NULL, 36); + } + } + for (i = 0; i != len; ++i) + if (id == forbid[i]) + return 1; + return 0; +} + +/* ID's für Einheiten und Zauber */ +int newunitid(void) +{ + int random_unit_no; + int start_random_no; + random_unit_no = 1 + (rng_int() % MAX_UNIT_NR); + start_random_no = random_unit_no; + + while (ufindhash(random_unit_no) || dfindhash(random_unit_no) + || cfindhash(random_unit_no) + || forbiddenid(random_unit_no)) { + random_unit_no++; + if (random_unit_no == MAX_UNIT_NR + 1) { + random_unit_no = 1; + } + if (random_unit_no == start_random_no) { + random_unit_no = (int)MAX_UNIT_NR + 1; + } + } + return random_unit_no; +} + +int newcontainerid(void) +{ + int random_no; + int start_random_no; + + random_no = 1 + (rng_int() % MAX_CONTAINER_NR); + start_random_no = random_no; + + while (findship(random_no) || findbuilding(random_no)) { + random_no++; + if (random_no == MAX_CONTAINER_NR + 1) { + random_no = 1; + } + if (random_no == start_random_no) { + random_no = (int)MAX_CONTAINER_NR + 1; + } + } + return random_no; +} + +unit *createunit(region * r, faction * f, int number, const struct race * rc) +{ + assert(rc); + return create_unit(r, f, number, rc, 0, NULL, NULL); +} + +bool idle(faction * f) +{ + return (bool) (f ? false : true); +} + +int maxworkingpeasants(const struct region *r) +{ + int i = production(r) * MAXPEASANTS_PER_AREA + - ((rtrees(r, 2) + rtrees(r, 1) / 2) * TREESIZE); + return MAX(i, 0); +} + +int lighthouse_range(const building * b, const faction * f) +{ + int d = 0; + if (fval(b, BLD_WORKING) && b->size >= 10) { + int maxd = (int)log10(b->size) + 1; + + if (skill_enabled[SK_PERCEPTION]) { + region *r = b->region; + int c = 0; + unit *u; + for (u = r->units; u; u = u->next) { + if (u->building == b) { + c += u->number; + if (c > buildingcapacity(b)) + break; + if (f == NULL || u->faction == f) { + int sk = eff_skill(u, SK_PERCEPTION, r) / 3; + d = MAX(d, sk); + d = MIN(maxd, d); + if (d == maxd) + break; + } + } else if (c) + break; /* first unit that's no longer in the house ends the search */ + } + } else { + /* E3A rule: no perception req'd */ + return maxd; + } + } + return d; +} + +bool check_leuchtturm(region * r, faction * f) +{ + attrib *a; + + if (!fval(r->terrain, SEA_REGION)) + return false; + + for (a = a_find(r->attribs, &at_lighthouse); a && a->type == &at_lighthouse; + a = a->next) { + building *b = (building *) a->data.v; + + assert(b->type == bt_find("lighthouse")); + if (fval(b, BLD_WORKING) && b->size >= 10) { + int maxd = (int)log10(b->size) + 1; + + if (skill_enabled[SK_PERCEPTION]) { + region *r2 = b->region; + unit *u; + int c = 0; + int d = 0; + + for (u = r2->units; u; u = u->next) { + if (u->building == b) { + c += u->number; + if (c > buildingcapacity(b)) + break; + if (f == NULL || u->faction == f) { + if (!d) + d = distance(r, r2); + if (maxd < d) + break; + if (eff_skill(u, SK_PERCEPTION, r) >= d * 3) + return true; + } + } else if (c) + break; /* first unit that's no longer in the house ends the search */ + } + } else { + /* E3A rule: no perception req'd */ + return maxd; + } + } + } + + return false; +} + +region *lastregion(faction * f) +{ +#ifdef SMART_INTERVALS + unit *u = f->units; + region *r = f->last; + + if (u == NULL) + return NULL; + if (r != NULL) + return r->next; + + /* it is safe to start in the region of the first unit. */ + f->last = u->region; + /* if regions have indices, we can skip ahead: */ + for (u = u->nextF; u != NULL; u = u->nextF) { + r = u->region; + if (r->index > f->last->index) + f->last = r; + } + + /* we continue from the best region and look for travelthru etc. */ + for (r = f->last->next; r; r = r->next) { + plane *p = rplane(r); + + /* search the region for travelthru-attributes: */ + if (fval(r, RF_TRAVELUNIT)) { + attrib *ru = a_find(r->attribs, &at_travelunit); + while (ru && ru->type == &at_travelunit) { + u = (unit *) ru->data.v; + if (u->faction == f) { + f->last = r; + break; + } + ru = ru->next; + } + } + if (f->last == r) + continue; + if (check_leuchtturm(r, f)) + f->last = r; + if (p && is_watcher(p, f)) { + f->last = r; + } + } + return f->last->next; +#else + return NULL; +#endif +} + +region *firstregion(faction * f) +{ +#ifdef SMART_INTERVALS + region *r = f->first; + + if (f->units == NULL) + return NULL; + if (r != NULL) + return r; + + return f->first = regions; +#else + return regions; +#endif +} + +void **blk_list[1024]; +int list_index; +int blk_index; + +static void gc_done(void) +{ + int i, k; + for (i = 0; i != list_index; ++i) { + for (k = 0; k != 1024; ++k) + free(blk_list[i][k]); + free(blk_list[i]); + } + for (k = 0; k != blk_index; ++k) + free(blk_list[list_index][k]); + free(blk_list[list_index]); + +} + +void *gc_add(void *p) +{ + if (blk_index == 0) { + blk_list[list_index] = (void **)malloc(1024 * sizeof(void *)); + } + blk_list[list_index][blk_index] = p; + blk_index = (blk_index + 1) % 1024; + if (!blk_index) + ++list_index; + return p; +} + +static void init_directions(void ** root, const struct locale *lang) +{ + /* mit dieser routine kann man mehrere namen für eine direction geben, + * das ist für die hexes ideal. */ + const struct { + const char *name; + int direction; + } dirs[] = { + { + "dir_ne", D_NORTHEAST}, { + "dir_nw", D_NORTHWEST}, { + "dir_se", D_SOUTHEAST}, { + "dir_sw", D_SOUTHWEST}, { + "dir_east", D_EAST}, { + "dir_west", D_WEST}, { + "northeast", D_NORTHEAST}, { + "northwest", D_NORTHWEST}, { + "southeast", D_SOUTHEAST}, { + "southwest", D_SOUTHWEST}, { + "east", D_EAST}, { + "west", D_WEST}, { + "PAUSE", D_PAUSE}, { + NULL, NODIRECTION} + }; + int i; + void **tokens = get_translations(lang, UT_DIRECTIONS); + + for (i = 0; dirs[i].direction != NODIRECTION; ++i) { + variant token; + token.i = dirs[i].direction; + addtoken(tokens, LOC(lang, dirs[i].name), token); + } +} + +direction_t finddirection(const char *s, const struct locale *lang) +{ + void **tokens = get_translations(lang, UT_DIRECTIONS); + variant token; + + if (findtoken(*tokens, s, &token) == E_TOK_SUCCESS) { + return (direction_t) token.i; + } + return NODIRECTION; +} + +direction_t getdirection(const struct locale * lang) +{ + return finddirection(getstrtoken(), lang); +} + +static void init_translations(const struct locale *lang, int ut, const char * (*string_cb)(int i), int maxstrings) +{ + char buffer[256]; + void **tokens; + int i; + + assert(string_cb); + assert(maxstrings>0); + tokens = get_translations(lang, ut); + for (i = 0; i != maxstrings; ++i) { + const char * s = string_cb(i); + const char * key = s ? locale_string(lang, s) : 0; + if (key) { + char * str = transliterate(buffer, sizeof(buffer)-sizeof(int), key); + if (str) { + critbit_tree * cb = (critbit_tree *)*tokens; + size_t len = strlen(str); + if (!cb) { + *tokens = cb = (critbit_tree *)calloc(1, sizeof(critbit_tree *)); + } + len = cb_new_kv(str, len, &i, sizeof(int), buffer); + cb_insert(cb, buffer, len); + } else { + log_error("could not transliterate '%s'\n", key); + } + } + } +} + +static const char * keyword_key(int i) +{ + assert(i=0); + return keywords[i]; +} + +static const char * parameter_key(int i) +{ + assert(i=0); + return parameters[i]; +} + +static const char * skill_key(int sk) +{ + assert(sk=0); + return skill_enabled[sk] ? mkname("skill", skillnames[sk]) : 0; +} + +static void init_locale(const struct locale *lang) +{ + variant var; + int i; + const struct race *rc; + const terrain_type *terrain; + void **tokens; + + tokens = get_translations(lang, UT_MAGIC); + if (tokens) { + const char *str = get_param(global.parameters, "rules.magic.playerschools"); + char *sstr, *tok; + if (str == NULL) { + str = "gwyrrd illaun draig cerddor tybied"; + } + + sstr = strdup(str); + tok = strtok(sstr, " "); + while (tok) { + for (i = 0; i != MAXMAGIETYP; ++i) { + if (strcmp(tok, magic_school[i]) == 0) + break; + } + assert(i != MAXMAGIETYP); + var.i = i; + addtoken(tokens, LOC(lang, mkname("school", tok)), var); + tok = strtok(NULL, " "); + } + free(sstr); + } + + tokens = get_translations(lang, UT_DIRECTIONS); + init_directions(tokens, lang); + + tokens = get_translations(lang, UT_RACES); + for (rc = races; rc; rc = rc->next) { + var.v = (void *)rc; + addtoken(tokens, LOC(lang, rc_name(rc, 1)), var); + addtoken(tokens, LOC(lang, rc_name(rc, 0)), var); + } + + init_translations(lang, UT_PARAMS, parameter_key, MAXPARAMS); + init_translations(lang, UT_SKILLS, skill_key, MAXSKILLS); + init_translations(lang, UT_KEYWORDS, keyword_key, MAXKEYWORDS); + + tokens = get_translations(lang, UT_OPTIONS); + for (i = 0; i != MAXOPTIONS; ++i) { + var.i = i; + if (options[i]) + addtoken(tokens, LOC(lang, options[i]), var); + } + + tokens = get_translations(lang, UT_TERRAINS); + for (terrain = terrains(); terrain != NULL; terrain = terrain->next) { + var.v = (void *)terrain; + addtoken(tokens, LOC(lang, terrain->_name), var); + } +} + +typedef struct param { + struct param *next; + char *name; + char *data; +} param; + +int getid(void) +{ + const char *str = (const char *)getstrtoken(); + int i = atoi36(str); + if (i < 0) { + return -1; + } + return i; +} + +const char *get_param(const struct param *p, const char *key) +{ + while (p != NULL) { + if (strcmp(p->name, key) == 0) + return p->data; + p = p->next; + } + return NULL; +} + +int get_param_int(const struct param *p, const char *key, int def) +{ + while (p != NULL) { + if (strcmp(p->name, key) == 0) + return atoi(p->data); + p = p->next; + } + return def; +} + +static const char *g_datadir; +const char *datapath(void) +{ + static char zText[MAX_PATH]; + if (g_datadir) + return g_datadir; + return strcat(strcpy(zText, basepath()), "/data"); +} + +void set_datapath(const char *path) +{ + g_datadir = path; +} + +static const char *g_reportdir; +const char *reportpath(void) +{ + static char zText[MAX_PATH]; + if (g_reportdir) + return g_reportdir; + return strcat(strcpy(zText, basepath()), "/reports"); +} + +void set_reportpath(const char *path) +{ + g_reportdir = path; +} + +static const char *g_basedir; +const char *basepath(void) +{ + if (g_basedir) + return g_basedir; + return "."; +} + +void set_basepath(const char *path) +{ + g_basedir = path; +} + +float get_param_flt(const struct param *p, const char *key, float def) +{ + while (p != NULL) { + if (strcmp(p->name, key) == 0) + return (float)atof(p->data); + p = p->next; + } + return def; +} + +void set_param(struct param **p, const char *key, const char *data) +{ + ++global.cookie; + while (*p != NULL) { + if (strcmp((*p)->name, key) == 0) { + free((*p)->data); + (*p)->data = strdup(data); + return; + } + p = &(*p)->next; + } + *p = malloc(sizeof(param)); + (*p)->name = strdup(key); + (*p)->data = strdup(data); + (*p)->next = NULL; +} + +void kernel_done(void) +{ + /* calling this function releases memory assigned to static variables, etc. + * calling it is optional, e.g. a release server will most likely not do it. + */ + translation_done(); + gc_done(); + sql_done(); +} + +const char *localenames[] = { + "de", "en", + NULL +}; + +void init_locales(void) +{ + int l; + for (l = 0; localenames[l]; ++l) { + const struct locale *lang = find_locale(localenames[l]); + if (lang) + init_locale(lang); + } +} + +/* TODO: soll hier weg */ +extern struct attrib_type at_shiptrail; + +attrib_type at_germs = { + "germs", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + a_writeshorts, + a_readshorts, + ATF_UNIQUE +}; + +/*********************/ +/* at_guard */ +/*********************/ +attrib_type at_guard = { + "guard", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + a_writeint, + a_readint, + ATF_UNIQUE +}; + +void setstatus(struct unit *u, int status) +{ + assert(status >= ST_AGGRO && status <= ST_FLEE); + if (u->status != status) { + u->status = (status_t) status; + } +} + +void setguard(unit * u, unsigned int flags) +{ + /* setzt die guard-flags der Einheit */ + attrib *a = NULL; + assert(flags == 0 || !fval(u, UFL_MOVED)); + assert(flags == 0 || u->status < ST_FLEE); + if (fval(u, UFL_GUARD)) { + a = a_find(u->attribs, &at_guard); + } + if (flags == GUARD_NONE) { + freset(u, UFL_GUARD); + if (a) + a_remove(&u->attribs, a); + return; + } + fset(u, UFL_GUARD); + fset(u->region, RF_GUARDED); + if ((int)flags == guard_flags(u)) { + if (a) + a_remove(&u->attribs, a); + } else { + if (!a) + a = a_add(&u->attribs, a_new(&at_guard)); + a->data.i = (int)flags; + } +} + +unsigned int getguard(const unit * u) +{ + attrib *a; + + assert(fval(u, UFL_GUARD) || (u->building && u==building_owner(u->building)) + || !"you're doing it wrong! check is_guard first"); + a = a_find(u->attribs, &at_guard); + if (a) { + return (unsigned int)a->data.i; + } + return guard_flags(u); +} + +#ifndef HAVE_STRDUP +char *strdup(const char *s) +{ + return strcpy((char *)malloc(sizeof(char) * (strlen(s) + 1)), s); +} +#endif + +void remove_empty_factions(void) +{ + faction **fp, *f3; + + for (fp = &factions; *fp;) { + faction *f = *fp; + /* monster (0) werden nicht entfernt. alive kann beim readgame + * () auf 0 gesetzt werden, wenn monsters keine einheiten mehr + * haben. */ + if ((f->units == NULL || f->alive == 0) && !is_monsters(f)) { + ursprung *ur = f->ursprung; + while (ur && ur->id != 0) + ur = ur->next; + if (verbosity >= 2) + log_printf(stdout, "\t%s\n", factionname(f)); + + /* Einfach in eine Datei schreiben und später vermailen */ + + if (updatelog) + fprintf(updatelog, "dropout %s\n", itoa36(f->no)); + + for (f3 = factions; f3; f3 = f3->next) { + ally *sf; + group *g; + ally **sfp = &f3->allies; + while (*sfp) { + sf = *sfp; + if (sf->faction == f || sf->faction == NULL) { + *sfp = sf->next; + free(sf); + } else + sfp = &(*sfp)->next; + } + for (g = f3->groups; g; g = g->next) { + sfp = &g->allies; + while (*sfp) { + sf = *sfp; + if (sf->faction == f || sf->faction == NULL) { + *sfp = sf->next; + free(sf); + } else + sfp = &(*sfp)->next; + } + } + } + if (f->subscription) { + sql_print(("UPDATE subscriptions set status='DEAD' where id=%u;\n", + f->subscription)); + } + + *fp = f->next; + funhash(f); + free_faction(f); + free(f); + } else + fp = &(*fp)->next; + } +} + +void remove_empty_units_in_region(region * r) +{ + unit **up = &r->units; + + while (*up) { + unit *u = *up; + + if (u->number) { + faction *f = u->faction; + if (f == NULL || !f->alive) { + set_number(u, 0); + } + if (MaxAge() > 0) { + if ((!fval(f, FFL_NOTIMEOUT) && f->age > MaxAge())) { + set_number(u, 0); + } + } + } + if ((u->number == 0 && u_race(u) != new_race[RC_SPELL]) || (u->age <= 0 + && u_race(u) == new_race[RC_SPELL])) { + remove_unit(up, u); + } + if (*up == u) + up = &u->next; + } +} + +void remove_empty_units(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + remove_empty_units_in_region(r); + } +} + +bool faction_id_is_unused(int id) +{ + return findfaction(id) == NULL; +} + +int weight(const unit * u) +{ + int w, n = 0, in_bag = 0; + + item *itm; + for (itm = u->items; itm; itm = itm->next) { + w = itm->type->weight * itm->number; + n += w; + if (!fval(itm->type, ITF_BIG)) + in_bag += w; + } + + n += u->number * u_race(u)->weight; + + w = get_item(u, I_BAG_OF_HOLDING) * BAGCAPACITY; + if (w > in_bag) + w = in_bag; + n -= w; + + return n; +} + +void make_undead_unit(unit * u) +{ + free_orders(&u->orders); + name_unit(u); + fset(u, UFL_ISNEW); +} + +unsigned int guard_flags(const unit * u) +{ + unsigned int flags = + GUARD_CREWS | GUARD_LANDING | GUARD_TRAVELTHRU | GUARD_TAX; +#if GUARD_DISABLES_PRODUCTION == 1 + flags |= GUARD_PRODUCE; +#endif +#if GUARD_DISABLES_RECRUIT == 1 + flags |= GUARD_RECRUIT; +#endif + switch (old_race(u_race(u))) { + case RC_ELF: + if (u->faction->race != u_race(u)) + break; + /* else fallthrough */ + case RC_TREEMAN: + flags |= GUARD_TREES; + break; + case RC_IRONKEEPER: + flags = GUARD_MINING; + break; + default: + /* TODO: This should be configuration variables, all of it */ + break; + } + return flags; +} + +void guard(unit * u, unsigned int mask) +{ + unsigned int flags = guard_flags(u); + setguard(u, flags & mask); +} + +int besieged(const unit * u) +{ + /* belagert kann man in schiffen und burgen werden */ + return (u && !global.disabled[K_BESIEGE] + && u->building && u->building->besieged + && u->building->besieged >= u->building->size * SIEGEFACTOR); +} + +int lifestyle(const unit * u) +{ + int need; + plane *pl; + static int gamecookie = -1; + if (gamecookie != global.cookie) { + gamecookie = global.cookie; + } + + if (is_monsters(u->faction)) + return 0; + + need = maintenance_cost(u); + + pl = rplane(u->region); + if (pl && fval(pl, PFL_NOFEED)) + return 0; + + return need; +} + +bool has_horses(const struct unit * u) +{ + item *itm = u->items; + for (; itm; itm = itm->next) { + if (itm->type->flags & ITF_ANIMAL) + return true; + } + return false; +} + +bool hunger(int number, unit * u) +{ + region *r = u->region; + int dead = 0, hpsub = 0; + int hp = u->hp / u->number; + static const char *damage = 0; + static const char *rcdamage = 0; + static const race *rc = 0; + + if (!damage) { + damage = get_param(global.parameters, "hunger.damage"); + if (damage == NULL) + damage = "1d12+12"; + } + if (rc != u_race(u)) { + rcdamage = get_param(u_race(u)->parameters, "hunger.damage"); + rc = u_race(u); + } + + while (number--) { + int dam = dice_rand(rcdamage ? rcdamage : damage); + if (dam >= hp) { + ++dead; + } else { + hpsub += dam; + } + } + + if (dead) { + /* Gestorbene aus der Einheit nehmen, + * Sie bekommen keine Beerdingung. */ + ADDMSG(&u->faction->msgs, msg_message("starvation", + "unit region dead live", u, r, dead, u->number - dead)); + + scale_number(u, u->number - dead); + deathcounts(r, dead); + } + if (hpsub > 0) { + /* Jetzt die Schäden der nicht gestorbenen abziehen. */ + u->hp -= hpsub; + /* Meldung nur, wenn noch keine für Tote generiert. */ + if (dead == 0) { + /* Durch unzureichende Ernährung wird %s geschwächt */ + ADDMSG(&u->faction->msgs, msg_message("malnourish", "unit region", u, r)); + } + } + return (dead || hpsub); +} + +void plagues(region * r, bool ismagic) +{ + int peasants; + int i; + int dead = 0; + + /* Seuchenwahrscheinlichkeit in % */ + + if (!ismagic) { + double mwp = MAX(maxworkingpeasants(r), 1); + double prob = + pow(rpeasants(r) / (mwp * wage(r, NULL, NULL, turn) * 0.13), 4.0) + * PLAGUE_CHANCE; + + if (rng_double() >= prob) + return; + } + + peasants = rpeasants(r); + dead = (int)(0.5F + PLAGUE_VICTIMS * peasants); + for (i = dead; i != 0; i--) { + if (rng_double() < PLAGUE_HEALCHANCE && rmoney(r) >= PLAGUE_HEALCOST) { + rsetmoney(r, rmoney(r) - PLAGUE_HEALCOST); + --dead; + } + } + + if (dead > 0) { + message *msg = add_message(&r->msgs, msg_message("pest", "dead", dead)); + msg_release(msg); + deathcounts(r, dead); + rsetpeasants(r, peasants - dead); + } +} + +/* Lohn bei den einzelnen Burgstufen für Normale Typen, Orks, Bauern, + * Modifikation für Städter. */ + +static const int wagetable[7][4] = { + {10, 10, 11, -7}, /* Baustelle */ + {10, 10, 11, -5}, /* Handelsposten */ + {11, 11, 12, -3}, /* Befestigung */ + {12, 11, 13, -1}, /* Turm */ + {13, 12, 14, 0}, /* Burg */ + {14, 12, 15, 1}, /* Festung */ + {15, 13, 16, 2} /* Zitadelle */ +}; + +int cmp_wage(const struct building *b, const building * a) +{ + static const struct building_type *bt_castle; + if (!bt_castle) + bt_castle = bt_find("castle"); + if (b->type == bt_castle) { + if (!a) + return 1; + if (b->size > a->size) + return 1; + if (b->size == a->size) + return 0; + } + return -1; +} + +bool is_owner_building(const struct building * b) +{ + region *r = b->region; + if (b->type->taxes && r->land && r->land->ownership) { + unit *u = building_owner(b); + return u && u->faction == r->land->ownership->owner; + } + return false; +} + +int cmp_taxes(const building * b, const building * a) +{ + faction *f = region_get_owner(b->region); + if (b->type->taxes) { + unit *u = building_owner(b); + if (!u) { + return -1; + } else if (a) { + int newsize = buildingeffsize(b, false); + double newtaxes = b->type->taxes(b, newsize); + int oldsize = buildingeffsize(a, false); + double oldtaxes = a->type->taxes(a, oldsize); + + if (newtaxes < oldtaxes) + return -1; + else if (newtaxes > oldtaxes) + return 1; + else if (b->size < a->size) + return -1; + else if (b->size > a->size) + return 1; + else { + if (u && u->faction == f) { + u = building_owner(a); + if (u && u->faction == f) + return -1; + return 1; + } + } + } else { + return 1; + } + } + return -1; +} + +int cmp_current_owner(const building * b, const building * a) +{ + faction *f = region_get_owner(b->region); + + assert(rule_region_owners()); + if (f && b->type->taxes) { + unit *u = building_owner(b); + if (!u || u->faction != f) + return -1; + if (a) { + int newsize = buildingeffsize(b, false); + double newtaxes = b->type->taxes(b, newsize); + int oldsize = buildingeffsize(a, false); + double oldtaxes = a->type->taxes(a, oldsize); + + if (newtaxes != oldtaxes) + return (newtaxes > oldtaxes) ? 1 : -1; + if (newsize != oldsize) + return newsize - oldsize; + return (b->size - a->size); + } else { + return 1; + } + } + return -1; +} + +int rule_stealth_faction(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + rule = get_param_int(global.parameters, "rules.stealth.faction", 0xFF); + gamecookie = global.cookie; + assert(rule >= 0); + } + return rule; +} + +int rule_region_owners(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + rule = get_param_int(global.parameters, "rules.region_owners", 0); + gamecookie = global.cookie; + assert(rule >= 0); + } + return rule; +} + +int rule_auto_taxation(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + rule = + get_param_int(global.parameters, "rules.economy.taxation", TAX_ORDER); + gamecookie = global.cookie; + assert(rule >= 0); + } + return rule; +} + +int rule_blessed_harvest(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + rule = + get_param_int(global.parameters, "rules.magic.blessed_harvest", + HARVEST_WORK); + gamecookie = global.cookie; + assert(rule >= 0); + } + return rule; +} + +int rule_alliance_limit(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + rule = get_param_int(global.parameters, "rules.limit.alliance", 0); + gamecookie = global.cookie; + assert(rule >= 0); + } + return rule; +} + +int rule_faction_limit(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + rule = get_param_int(global.parameters, "rules.limit.faction", 0); + gamecookie = global.cookie; + assert(rule >= 0); + } + return rule; +} + +int rule_transfermen(void) +{ + static int gamecookie = -1; + static int rule = -1; + if (rule < 0 || gamecookie != global.cookie) { + rule = get_param_int(global.parameters, "rules.transfermen", 1); + gamecookie = global.cookie; + assert(rule >= 0); + } + return rule; +} + +static int +default_wage(const region * r, const faction * f, const race * rc, int in_turn) +{ + building *b = largestbuilding(r, &cmp_wage, false); + int esize = 0; + curse *c; + double wage; + attrib *a; + const building_type *artsculpture_type = bt_find("artsculpture"); + static const curse_type *drought_ct, *blessedharvest_ct; + static bool init; + + if (!init) { + init = true; + drought_ct = ct_find("drought"); + blessedharvest_ct = ct_find("blessedharvest"); + } + + if (b != NULL) { + /* TODO: this reveals imaginary castles */ + esize = buildingeffsize(b, false); + } + + if (f != NULL) { + int index = 0; + if (rc == new_race[RC_ORC] || rc == new_race[RC_SNOTLING]) { + index = 1; + } + wage = wagetable[esize][index]; + } else { + if (is_mourning(r, in_turn)) { + wage = 10; + } else if (fval(r->terrain, SEA_REGION)) { + wage = 11; + } else if (fval(r, RF_ORCIFIED)) { + wage = wagetable[esize][1]; + } else { + wage = wagetable[esize][2]; + } + if (rule_blessed_harvest() == HARVEST_WORK) { + /* E1 rules */ + wage += curse_geteffect(get_curse(r->attribs, blessedharvest_ct)); + } + } + + /* Artsculpture: Income +5 */ + for (b = r->buildings; b; b = b->next) { + if (b->type == artsculpture_type) { + wage += 5; + } + } + + /* Godcurse: Income -10 */ + if (curse_active(get_curse(r->attribs, ct_find("godcursezone")))) { + wage = MAX(0, wage - 10); + } + + /* Bei einer Dürre verdient man nur noch ein Viertel */ + if (drought_ct) { + c = get_curse(r->attribs, drought_ct); + if (curse_active(c)) + wage /= curse_geteffect(c); + } + + a = a_find(r->attribs, &at_reduceproduction); + if (a) + wage = (wage * a->data.sa[0]) / 100; + + return (int)wage; +} + +static int +minimum_wage(const region * r, const faction * f, const race * rc, int in_turn) +{ + if (f && rc) { + return rc->maintenance; + } + return default_wage(r, f, rc, in_turn); +} + +/* Gibt Arbeitslohn für entsprechende Rasse zurück, oder für +* die Bauern wenn f == NULL. */ +int wage(const region * r, const faction * f, const race * rc, int in_turn) +{ + if (global.functions.wage) { + return global.functions.wage(r, f, rc, in_turn); + } + return default_wage(r, f, rc, in_turn); +} + +#define MAINTENANCE 10 +int maintenance_cost(const struct unit *u) +{ + if (u == NULL) + return MAINTENANCE; + if (global.functions.maintenance) { + int retval = global.functions.maintenance(u); + if (retval >= 0) + return retval; + } + return u_race(u)->maintenance * u->number; +} + +message *movement_error(unit * u, const char *token, order * ord, + int error_code) +{ + direction_t d; + switch (error_code) { + case E_MOVE_BLOCKED: + d = finddirection(token, u->faction->locale); + return msg_message("moveblocked", "unit direction", u, d); + case E_MOVE_NOREGION: + return msg_feedback(u, ord, "unknowndirection", "dirname", token); + } + return NULL; +} + +int movewhere(const unit * u, const char *token, region * r, region ** resultp) +{ + region *r2; + direction_t d; + + if (*token == '\0') { + *resultp = NULL; + return E_MOVE_OK; + } + + d = finddirection(token, u->faction->locale); + switch (d) { + case D_PAUSE: + *resultp = r; + break; + + case NODIRECTION: + r2 = find_special_direction(r, token, u->faction->locale); + if (r2 == NULL) { + return E_MOVE_NOREGION; + } + *resultp = r2; + break; + + default: + r2 = rconnect(r, d); + if (r2 == NULL || move_blocked(u, r, r2)) { + return E_MOVE_BLOCKED; + } + *resultp = r2; + } + return E_MOVE_OK; +} + +bool move_blocked(const unit * u, const region * r, const region * r2) +{ + connection *b; + curse *c; + static const curse_type *fogtrap_ct = NULL; + + if (r2 == NULL) + return true; + b = get_borders(r, r2); + while (b) { + if (b->type->block && b->type->block(b, u, r)) + return true; + b = b->next; + } + + if (fogtrap_ct == NULL) + fogtrap_ct = ct_find("fogtrap"); + c = get_curse(r->attribs, fogtrap_ct); + if (curse_active(c)) + return true; + return false; +} + +void add_income(unit * u, int type, int want, int qty) +{ + if (want == INT_MAX) + want = qty; + ADDMSG(&u->faction->msgs, msg_message("income", + "unit region mode wanted amount", u, u->region, type, want, qty)); +} + +int produceexp(struct unit *u, skill_t sk, int n) +{ + if (global.producexpchance > 0.0F) { + if (n == 0 || !playerrace(u_race(u))) + return 0; + learn_skill(u, sk, global.producexpchance); + } + return 0; +} + +int lovar(double xpct_x2) +{ + int n = (int)(xpct_x2 * 500) + 1; + if (n == 0) + return 0; + return (rng_int() % n + rng_int() % n) / 1000; +} + +bool has_limited_skills(const struct unit * u) +{ + if (has_skill(u, SK_MAGIC) || has_skill(u, SK_ALCHEMY) || + has_skill(u, SK_TACTICS) || has_skill(u, SK_HERBALISM) || + has_skill(u, SK_SPY)) { + return true; + } else { + return false; + } +} + +void attrib_init(void) +{ + /* Alle speicherbaren Attribute müssen hier registriert werden */ + at_register(&at_shiptrail); + at_register(&at_familiar); + at_register(&at_familiarmage); + at_register(&at_clone); + at_register(&at_clonemage); + at_register(&at_eventhandler); + at_register(&at_stealth); + at_register(&at_mage); + at_register(&at_countdown); + at_register(&at_curse); + + at_register(&at_seenspell); + + /* neue REGION-Attribute */ + at_register(&at_direction); + at_register(&at_moveblock); + at_register(&at_deathcount); + at_register(&at_chaoscount); + at_register(&at_woodcount); + + /* neue UNIT-Attribute */ + at_register(&at_siege); + at_register(&at_effect); + at_register(&at_private); + + at_register(&at_icastle); + at_register(&at_guard); + at_register(&at_group); + + at_register(&at_building_generic_type); + at_register(&at_maxmagicians); + at_register(&at_npcfaction); + + /* connection-typen */ + register_bordertype(&bt_noway); + register_bordertype(&bt_fogwall); + register_bordertype(&bt_wall); + register_bordertype(&bt_illusionwall); + register_bordertype(&bt_road); + register_bordertype(&bt_questportal); + + register_function((pf_generic) & minimum_wage, "minimum_wage"); + + at_register(&at_germs); + + at_deprecate("xontormiaexpress", a_readint); /* required for old datafiles */ + at_register(&at_speedup); + at_register(&at_building_action); +} + +void kernel_init(void) +{ + char zBuffer[MAX_PATH]; + attrib_init(); + translation_init(); + + if (sqlpatch) { + sprintf(zBuffer, "%s/patch-%d.sql", datapath(), turn); + sql_init(zBuffer); + } +} + +static order * defaults[MAXLOCALES]; + +order *default_order(const struct locale *lang) +{ + int i = locale_index(lang); + order *result = 0; + assert(iattribs, C_DEPRESSION, 0)) { + return 0; + } + + n = rmoney(r) / ENTERTAINFRACTION; + + if (is_cursed(r->attribs, C_GENEROUS, 0)) { + n *= get_curseeffect(r->attribs, C_GENEROUS, 0); + } + + return (int)n; +} + +int rule_give(void) +{ + static int value = -1; + if (value < 0) { + value = get_param_int(global.parameters, "rules.give", GIVE_DEFAULT); + } + return value; +} + +int markets_module(void) +{ + static int value = -1; + if (value < 0) { + value = get_param_int(global.parameters, "modules.markets", 0); + } + return value; +} + +/** releases all memory associated with the game state. + * call this function before calling read_game() to load a new game + * if you have a previously loaded state in memory. + */ +void free_gamedata(void) +{ + int i; + free_units(); + free_regions(); + free_borders(); + + for (i=0;i!=MAXLOCALES;++i) { + if (defaults[i]) { + free_order(defaults[i]); + defaults[i] = 0; + } + } + while (alliances) { + alliance *al = alliances; + alliances = al->next; + free_alliance(al); + } + while (factions) { + faction *f = factions; + factions = f->next; + funhash(f); + free_faction(f); + free(f); + } + + while (planes) { + plane *pl = planes; + planes = planes->next; + free(pl->name); + free(pl); + } + + while (global.attribs) { + a_remove(&global.attribs, global.attribs); + } + ++global.cookie; /* readgame() already does this, but sjust in case */ +} + +void load_inifile(dictionary * d) +{ + const char *reportdir = reportpath(); + const char *datadir = datapath(); + const char *basedir = basepath(); + const char *str; + + assert(d); + + str = iniparser_getstring(d, "eressea:base", basedir); + if (str != basedir) + set_basepath(str); + str = iniparser_getstring(d, "eressea:report", reportdir); + if (str != reportdir) + set_reportpath(str); + str = iniparser_getstring(d, "eressea:data", datadir); + if (str != datadir) + set_datapath(str); + + lomem = iniparser_getint(d, "eressea:lomem", lomem) ? 1 : 0; + + str = iniparser_getstring(d, "eressea:encoding", NULL); + if (str) + enc_gamedata = xmlParseCharEncoding(str); + + verbosity = iniparser_getint(d, "eressea:verbose", 2); + sqlpatch = iniparser_getint(d, "eressea:sqlpatch", false); + battledebug = iniparser_getint(d, "eressea:debug", battledebug) ? 1 : 0; + + str = iniparser_getstring(d, "eressea:locales", "de,en"); + make_locales(str); + + /* excerpt from [config] (the rest is used in bindings.c) */ + game_name = iniparser_getstring(d, "config:game", game_name); + + global.inifile = d; +} diff --git a/core/src/kernel/config.h b/core/src/kernel/config.h new file mode 100644 index 000000000..acc67637b --- /dev/null +++ b/core/src/kernel/config.h @@ -0,0 +1,459 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef ERESSEA_H +#define ERESSEA_H + +#ifdef __cplusplus +extern "C" { +#endif + + /* this should always be the first thing included after platform.h */ +#include "types.h" + + struct _dictionary_; + + /* experimental gameplay features (that don't affect the savefile) */ + /* TODO: move these settings to settings.h or into configuration files */ +#define GOBLINKILL /* Goblin-Spezialklau kann tödlich enden */ +#define HERBS_ROT /* herbs owned by units have a chance to rot. */ +#define SHIPDAMAGE /* Schiffsbeschädigungen */ +#define INSECT_POTION /* Spezialtrank für Insekten */ +#define ORCIFICATION /* giving snotlings to the peasants gets counted */ + +#define ALLIED(f1, f2) (f1==f2 || (f1->alliance && f1->alliance==f2->alliance)) + +/* for some good prime numbers, check http://www.math.niu.edu/~rusin/known-math/98/pi_x */ +#ifndef MAXREGIONS +# define MAXREGIONS 524287 /* must be prime for hashing. 262139 was a little small */ +#endif +#ifndef MAXUNITS +# define MAXUNITS 1048573 /* must be prime for hashing. 524287 was >90% full */ +#endif + +#define MAXPEASANTS_PER_AREA 10 /* number of peasants per region-size */ +#define TREESIZE (MAXPEASANTS_PER_AREA-2) /* space used by trees (in #peasants) */ + +#define PEASANTFORCE 0.75 /* Chance einer Vermehrung trotz 90% Auslastung */ +#define HERBROTCHANCE 5 /* Verrottchance für Kräuter (ifdef HERBS_ROT) */ + +/* Gebäudegröße = Minimalbelagerer */ +#define SIEGEFACTOR 2 + +/** Magic */ +#define MAXMAGICIANS 3 +#define MAXALCHEMISTS 3 + +/** Plagues **/ +#define PLAGUE_CHANCE 0.1F /* Seuchenwahrscheinlichkeit (siehe plagues()) */ +#define PLAGUE_VICTIMS 0.2F /* % Betroffene */ +#define PLAGUE_HEALCHANCE 0.25F /* Wahrscheinlichkeit Heilung */ +#define PLAGUE_HEALCOST 30 /* Heilkosten */ + +/* Chance of a monster attack */ +#define MONSTERATTACK 0.4F + +/* Bewegungsweiten: */ +#define BP_WALKING 4 +#define BP_RIDING 6 +#define BP_UNICORN 9 +#define BP_DRAGON 4 + +#define BP_NORMAL 3 +#define BP_ROAD 2 + +#define PERSON_WEIGHT 1000 /* weight of a "normal" human unit */ +#define STAMINA_AFFECTS_HP 1<<0 + +/** + * Hier endet der Teil von config.h, der die defines für die + * Spielwelt Eressea enthält, und beginnen die allgemeinen Routinen + */ + +#define ENCCHANCE 10 /* %-Chance für einmalige Zufallsbegegnung */ + +#define DISPLAYSIZE 8191 /* max. Länge einer Beschreibung, ohne trailing 0 */ +#define NAMESIZE 127 /* max. Länge eines Namens, ohne trailing 0 */ +#define IDSIZE 15 /* max. Länge einer no (als String), ohne trailing 0 */ +#define KEYWORDSIZE 15 /* max. Länge eines Keyword, ohne trailing 0 */ +#define OBJECTIDSIZE (NAMESIZE+5+IDSIZE) /* max. Länge der Strings, die + * von struct unitname, etc. zurückgegeben werden. ohne die 0 */ + +#define BAGCAPACITY 20000 /* soviel paßt in einen Bag of Holding */ +#define STRENGTHCAPACITY 50000 /* zusätzliche Tragkraft beim Kraftzauber (deprecated) */ +#define STRENGTHMULTIPLIER 50 /* multiplier for trollbelt */ + +/* ----------------- Befehle ----------------------------------- */ + + extern const char *keywords[MAXKEYWORDS]; + extern const char *parameters[MAXPARAMS]; + +/** report options **/ +#define want(option) (1<flags & (i)) +#define fset(u, i) ((u)->flags |= (i)) +#define freset(u, i) ((u)->flags &= ~(i)) + + extern int turn; + extern int verbosity; + +/* parteinummern */ + extern bool faction_id_is_unused(int); + +/* leuchtturm */ + extern bool check_leuchtturm(struct region *r, struct faction *f); + extern void update_lighthouse(struct building *lh); + extern int lighthouse_range(const struct building *b, + const struct faction *f); + +/* skills */ + extern int skill_limit(struct faction *f, skill_t sk); + extern int count_skill(struct faction *f, skill_t sk); + +/* direction, geography */ + extern const char *directions[]; + extern direction_t finddirection(const char *s, const struct locale *); + + extern int findoption(const char *s, const struct locale *lang); + +/* special units */ + void make_undead_unit(struct unit *); + + void addstrlist(strlist ** SP, const char *s); + + int armedmen(const struct unit *u, bool siege_weapons); + + unsigned int atoip(const char *s); + unsigned int getuint(void); + int getint(void); + + direction_t getdirection(const struct locale *); + + extern const char *igetstrtoken(const char *s); + + extern skill_t findskill(const char *s, const struct locale *lang); + + extern keyword_t findkeyword(const char *s, const struct locale *lang); + + param_t findparam(const char *s, const struct locale *lang); + param_t findparam_ex(const char *s, const struct locale * lang); + bool isparam(const char *s, const struct locale * lang, param_t param); + param_t getparam(const struct locale *lang); + + extern int getid(void); +#define unitid(x) itoa36((x)->no) + +#define getshipid() getid() +#define getfactionid() getid() + +#define buildingid(x) itoa36((x)->no) +#define shipid(x) itoa36((x)->no) +#define factionid(x) itoa36((x)->no) +#define curseid(x) itoa36((x)->no) + + extern bool cansee(const struct faction *f, const struct region *r, + const struct unit *u, int modifier); + bool cansee_durchgezogen(const struct faction *f, const struct region *r, + const struct unit *u, int modifier); + extern bool cansee_unit(const struct unit *u, const struct unit *target, + int modifier); + bool seefaction(const struct faction *f, const struct region *r, + const struct unit *u, int modifier); + extern int effskill(const struct unit *u, skill_t sk); + + extern int lovar(double xpct_x2); + /* returns a value between [0..xpct_2], generated with two dice */ + + int distribute(int old, int new_value, int n); + + int newunitid(void); + int forbiddenid(int id); + int newcontainerid(void); + + extern struct unit *createunit(struct region *r, struct faction *f, + int number, const struct race *rc); + extern void create_unitid(struct unit *u, int id); + extern bool getunitpeasants; + extern struct unit *getunitg(const struct region *r, const struct faction *f); + extern struct unit *getunit(const struct region *r, const struct faction *f); + + extern int read_unitid(const struct faction *f, const struct region *r); + + extern int alliedunit(const struct unit *u, const struct faction *f2, + int mode); + extern int alliedfaction(const struct plane *pl, const struct faction *f, + const struct faction *f2, int mode); + extern int alliedgroup(const struct plane *pl, const struct faction *f, + const struct faction *f2, const struct ally *sf, int mode); + + struct faction *findfaction(int n); + struct faction *getfaction(void); + + struct unit *findunitg(int n, const struct region *hint); + struct unit *findunit(int n); + + struct unit *findunitr(const struct region *r, int n); + struct region *findunitregion(const struct unit *su); + + extern char *estring(const char *s); + extern char *estring_i(char *s); + extern char *cstring(const char *s); + extern char *cstring_i(char *s); + extern const char *unitname(const struct unit *u); + extern char *write_unitname(const struct unit *u, char *buffer, size_t size); + + typedef int (*cmp_building_cb) (const struct building * b, + const struct building * a); + struct building *largestbuilding(const struct region *r, cmp_building_cb, + bool imaginary); + int cmp_wage(const struct building *b, const struct building *bother); + int cmp_taxes(const struct building *b, const struct building *bother); + int cmp_current_owner(const struct building *b, + const struct building *bother); + +#define TAX_ORDER 0x00 +#define TAX_OWNER 0x01 + int rule_auto_taxation(void); + int rule_transfermen(void); + int rule_region_owners(void); + int rule_stealth_faction(void); +#define HARVEST_WORK 0x00 +#define HARVEST_TAXES 0x01 + int rule_blessed_harvest(void); + extern int rule_give(void); + extern int rule_alliance_limit(void); + extern int rule_faction_limit(void); + + extern int count_all(const struct faction *f); + extern int count_migrants(const struct faction *f); + extern int count_maxmigrants(const struct faction *f); + + extern bool has_limited_skills(const struct unit *u); + extern const struct race *findrace(const char *, const struct locale *); + + int eff_stealth(const struct unit *u, const struct region *r); + int ispresent(const struct faction *f, const struct region *r); + + int check_option(struct faction *f, int option); + extern void parse(keyword_t kword, int (*dofun) (struct unit *, + struct order *), bool thisorder); + +/* Anzahl Personen in einer Einheit festlegen. NUR (!) mit dieser Routine, + * sonst großes Unglück. Durch asserts an ein paar Stellen abgesichert. */ + void verify_data(void); + + void freestrlist(strlist * s); + + int change_hitpoints(struct unit *u, int value); + + int weight(const struct unit *u); + void changeblockchaos(void); + +/* intervall, in dem die regionen der partei zu finden sind */ + extern struct region *firstregion(struct faction *f); + extern struct region *lastregion(struct faction *f); + + void fhash(struct faction *f); + void funhash(struct faction *f); + + bool idle(struct faction *f); + bool unit_has_cursed_item(struct unit *u); + +/* simple garbage collection: */ + void *gc_add(void *p); + +/* grammatik-flags: */ +#define GF_NONE 0 + /* singular, ohne was dran */ +#define GF_PLURAL 1 + /* Angaben in Mehrzahl */ +#define GF_ARTICLE 8 + /* der, die, eine */ +#define GF_SPECIFIC 16 + /* der, die, das vs. ein, eine */ +#define GF_DETAILED 32 + /* mehr Informationen. z.b. straße zu 50% */ +#define GF_PURE 64 + /* untranslated */ + +#define GUARD_NONE 0 +#define GUARD_TAX 1 + /* Verhindert Steuereintreiben */ +#define GUARD_MINING 2 + /* Verhindert Bergbau */ +#define GUARD_TREES 4 + /* Verhindert Waldarbeiten */ +#define GUARD_TRAVELTHRU 8 + /* Blockiert Durchreisende */ +#define GUARD_LANDING 16 + /* Verhindert Ausstieg + Weiterreise */ +#define GUARD_CREWS 32 + /* Verhindert Unterhaltung auf Schiffen */ +#define GUARD_RECRUIT 64 + /* Verhindert Rekrutieren */ +#define GUARD_PRODUCE 128 + /* Verhindert Abbau von Resourcen mit RTF_LIMITED */ +#define GUARD_ALL 0xFFFF + + extern void setstatus(struct unit *u, int status); +/* !< sets combatstatus of a unit */ + extern void setguard(struct unit *u, unsigned int flags); +/* !< setzt die guard-flags der Einheit */ + extern unsigned int getguard(const struct unit *u); + /* liest die guard-flags der Einheit */ + extern void guard(struct unit *u, unsigned int mask); + /* Einheit setzt "BEWACHE", rassenspezifzisch. + * 'mask' kann einzelne flags zusätzlich und-maskieren. + */ + unsigned int guard_flags(const struct unit *u); + + extern bool hunger(int number, struct unit *u); + extern int lifestyle(const struct unit *); + extern int besieged(const struct unit *u); + extern int maxworkingpeasants(const struct region *r); + extern bool has_horses(const struct unit *u); + extern int markets_module(void); + extern int wage(const struct region *r, const struct faction *f, + const struct race *rc, int in_turn); + extern int maintenance_cost(const struct unit *u); + extern struct message *movement_error(struct unit *u, const char *token, + struct order *ord, int error_code); + extern bool move_blocked(const struct unit *u, const struct region *src, + const struct region *dest); + extern void add_income(struct unit *u, int type, int want, int qty); + +/* movewhere error codes */ + enum { + E_MOVE_OK = 0, /* possible to move */ + E_MOVE_NOREGION, /* no region exists in this direction */ + E_MOVE_BLOCKED /* cannot see this region, there is a blocking connection. */ + }; + extern int movewhere(const struct unit *u, const char *token, + struct region *r, struct region **resultp); + + extern const char *datapath(void); + extern void set_datapath(const char *path); + + extern const char *basepath(void); + extern void set_basepath(const char *); + void load_inifile(struct _dictionary_ *d); + + extern const char *reportpath(void); + extern void set_reportpath(const char *); + + extern void kernel_init(void); + extern void kernel_done(void); + + extern const char *localenames[]; + +/** compatibility: **/ + extern race_t old_race(const struct race *); + extern const struct race *new_race[]; + +/* globale settings des Spieles */ + typedef struct settings { + const char *gamename; + struct attrib *attribs; + unsigned int data_turn; + bool disabled[MAXKEYWORDS]; + struct param *parameters; + void *vm_state; + float producexpchance; + int cookie; + struct _dictionary_ *inifile; + + struct global_functions { + int (*wage) (const struct region * r, const struct faction * f, + const struct race * rc, int in_turn); + int (*maintenance) (const struct unit * u); + } functions; + } settings; + extern settings global; + + extern int produceexp(struct unit *u, skill_t sk, int n); + + extern bool battledebug; + extern bool sqlpatch; + extern bool lomem; /* save memory */ + + extern const char *dbrace(const struct race *rc); + + extern void set_param(struct param **p, const char *name, const char *data); + extern const char *get_param(const struct param *p, const char *name); + extern int get_param_int(const struct param *p, const char *name, int def); + extern float get_param_flt(const struct param *p, const char *name, + float def); + + extern bool ExpensiveMigrants(void); + extern int NMRTimeout(void); + extern int LongHunger(const struct unit *u); + extern int SkillCap(skill_t sk); + extern int NewbieImmunity(void); + extern bool IsImmune(const struct faction *f); + extern int AllianceAuto(void); /* flags that allied factions get automatically */ + extern int AllianceRestricted(void); /* flags restricted to allied factions */ + extern int HelpMask(void); /* flags restricted to allied factions */ + extern struct order *default_order(const struct locale *lang); + extern int entertainmoney(const struct region *r); + + extern void plagues(struct region *r, bool ismagic); + typedef struct helpmode { + const char *name; + int status; + } helpmode; + + extern struct helpmode helpmodes[]; + +#define GIVE_SELF 1 +#define GIVE_PEASANTS 2 +#define GIVE_LUXURIES 4 +#define GIVE_HERBS 8 +#define GIVE_GOODS 16 +#define GIVE_ONDEATH 32 +#define GIVE_ALLITEMS (GIVE_GOODS|GIVE_HERBS|GIVE_LUXURIES) +#define GIVE_DEFAULT (GIVE_SELF|GIVE_PEASANTS|GIVE_LUXURIES|GIVE_HERBS|GIVE_GOODS) + + extern struct attrib_type at_guard; + extern void free_gamedata(void); +#if 1 /* disable to count all units */ +# define count_unit(u) playerrace(u_race(u)) +#else +# define count_unit(u) 1 +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/connection.c b/core/src/kernel/connection.c new file mode 100644 index 000000000..7bbb2e75a --- /dev/null +++ b/core/src/kernel/connection.c @@ -0,0 +1,684 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "connection.h" + +#include "region.h" +#include "save.h" +#include "terrain.h" +#include "unit.h" +#include "version.h" + +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +unsigned int nextborder = 0; + +#define BORDER_MAXHASH 8191 +connection *borders[BORDER_MAXHASH]; +border_type *bordertypes; + +void (*border_convert_cb) (struct connection * con, struct attrib * attr) = 0; + +void free_borders(void) +{ + int i; + for (i = 0; i != BORDER_MAXHASH; ++i) { + while (borders[i]) { + connection *b = borders[i]; + borders[i] = b->nexthash; + while (b) { + connection *bf = b; + b = b->next; + assert(b == NULL || b->nexthash == NULL); + if (bf->type->destroy) { + bf->type->destroy(bf); + } + free(bf); + } + } + } +} + +connection *find_border(unsigned int id) +{ + int key; + for (key = 0; key != BORDER_MAXHASH; key++) { + connection *bhash; + for (bhash = borders[key]; bhash != NULL; bhash = bhash->nexthash) { + connection *b; + for (b = bhash; b; b = b->next) { + if (b->id == id) + return b; + } + } + } + return NULL; +} + +int resolve_borderid(variant id, void *addr) +{ + int result = 0; + connection *b = NULL; + if (id.i != 0) { + b = find_border(id.i); + if (b == NULL) { + result = -1; + } + } + *(connection **) addr = b; + return result; +} + +static connection **get_borders_i(const region * r1, const region * r2) +{ + connection **bp; + int key = reg_hashkey(r1); + int k2 = reg_hashkey(r2); + + key = MIN(k2, key) % BORDER_MAXHASH; + bp = &borders[key]; + while (*bp) { + connection *b = *bp; + if ((b->from == r1 && b->to == r2) || (b->from == r2 && b->to == r1)) + break; + bp = &b->nexthash; + } + return bp; +} + +connection *get_borders(const region * r1, const region * r2) +{ + connection **bp = get_borders_i(r1, r2); + return *bp; +} + +connection *new_border(border_type * type, region * from, region * to) +{ + connection *b = calloc(1, sizeof(struct connection)); + + if (from && to) { + connection **bp = get_borders_i(from, to); + while (*bp) + bp = &(*bp)->next; + *bp = b; + } + b->type = type; + b->from = from; + b->to = to; + b->id = ++nextborder; + + if (type->init) + type->init(b); + return b; +} + +void erase_border(connection * b) +{ + if (b->from && b->to) { + connection **bp = get_borders_i(b->from, b->to); + assert(*bp != NULL || !"error: connection is not registered"); + if (*bp == b) { + /* it is the first in the list, so it is in the nexthash list */ + if (b->next) { + *bp = b->next; + (*bp)->nexthash = b->nexthash; + } else { + *bp = b->nexthash; + } + } else { + while (*bp && *bp != b) { + bp = &(*bp)->next; + } + assert(*bp == b || !"error: connection is not registered"); + *bp = b->next; + } + } + if (b->type->destroy) { + b->type->destroy(b); + } + free(b); +} + +void register_bordertype(border_type * type) +{ + border_type **btp = &bordertypes; + + while (*btp && *btp != type) + btp = &(*btp)->next; + if (*btp) + return; + *btp = type; +} + +border_type *find_bordertype(const char *name) +{ + border_type *bt = bordertypes; + + while (bt && strcmp(bt->__name, name)) + bt = bt->next; + return bt; +} + +void b_read(connection * b, storage * store) +{ + int result = 0; + switch (b->type->datatype) { + case VAR_NONE: + case VAR_INT: + b->data.i = store->r_int(store); + break; + case VAR_SHORTA: + b->data.sa[0] = (short)store->r_int(store); + b->data.sa[1] = (short)store->r_int(store); + break; + case VAR_VOIDPTR: + default: + assert(!"invalid variant type in connection"); + result = 0; + } + assert(result >= 0 || "EOF encountered?"); +} + +void b_write(const connection * b, storage * store) +{ + switch (b->type->datatype) { + case VAR_NONE: + case VAR_INT: + store->w_int(store, b->data.i); + break; + case VAR_SHORTA: + store->w_int(store, b->data.sa[0]); + store->w_int(store, b->data.sa[1]); + break; + case VAR_VOIDPTR: + default: + assert(!"invalid variant type in connection"); + } +} + +bool b_transparent(const connection * b, const struct faction *f) +{ + unused(b); + unused(f); + return true; +} + +bool b_opaque(const connection * b, const struct faction * f) +{ + unused(b); + unused(f); + return false; +} + +bool b_blockall(const connection * b, const unit * u, const region * r) +{ + unused(u); + unused(r); + unused(b); + return true; +} + +bool b_blocknone(const connection * b, const unit * u, const region * r) +{ + unused(u); + unused(r); + unused(b); + return false; +} + +bool b_rvisible(const connection * b, const region * r) +{ + return (bool) (b->to == r || b->from == r); +} + +bool b_fvisible(const connection * b, const struct faction * f, + const region * r) +{ + unused(r); + unused(f); + unused(b); + return true; +} + +bool b_uvisible(const connection * b, const unit * u) +{ + unused(u); + unused(b); + return true; +} + +bool b_rinvisible(const connection * b, const region * r) +{ + unused(r); + unused(b); + return false; +} + +bool b_finvisible(const connection * b, const struct faction * f, + const region * r) +{ + unused(r); + unused(f); + unused(b); + return false; +} + +bool b_uinvisible(const connection * b, const unit * u) +{ + unused(u); + unused(b); + return false; +} + +/**************************************/ +/* at_countdown - legacy, do not use */ +/**************************************/ + +attrib_type at_countdown = { + "countdown", + DEFAULT_INIT, + DEFAULT_FINALIZE, + NULL, + NULL, + a_readint +}; + +void age_borders(void) +{ + quicklist *deleted = NULL, *ql; + int i; + + for (i = 0; i != BORDER_MAXHASH; ++i) { + connection *bhash = borders[i]; + for (; bhash; bhash = bhash->nexthash) { + connection *b = bhash; + for (; b; b = b->next) { + if (b->type->age) { + if (b->type->age(b) == AT_AGE_REMOVE) { + ql_push(&deleted, b); + } + } + } + } + } + for (ql = deleted, i = 0; ql; ql_advance(&ql, &i, 1)) { + connection *b = (connection *) ql_get(ql, i); + erase_border(b); + } + ql_free(deleted); +} + +/******** + * implementation of a couple of borders. this shouldn't really be in here, so + * let's keep it separate from the more general stuff above + ********/ + +#include "faction.h" + +static const char *b_namewall(const connection * b, const region * r, + const struct faction *f, int gflags) +{ + const char *bname = "wall"; + + unused(f); + unused(r); + unused(b); + if (gflags & GF_ARTICLE) + bname = "a_wall"; + if (gflags & GF_PURE) + return bname; + return LOC(f->locale, mkname("border", bname)); +} + +border_type bt_wall = { + "wall", VAR_INT, + b_opaque, + NULL, /* init */ + NULL, /* destroy */ + b_read, /* read */ + b_write, /* write */ + b_blockall, /* block */ + b_namewall, /* name */ + b_rvisible, /* rvisible */ + b_fvisible, /* fvisible */ + b_uvisible, /* uvisible */ +}; + +border_type bt_noway = { + "noway", VAR_INT, + b_transparent, + NULL, /* init */ + NULL, /* destroy */ + b_read, /* read */ + b_write, /* write */ + b_blockall, /* block */ + NULL, /* name */ + b_rinvisible, /* rvisible */ + b_finvisible, /* fvisible */ + b_uinvisible, /* uvisible */ +}; + +static const char *b_namefogwall(const connection * b, const region * r, + const struct faction *f, int gflags) +{ + unused(f); + unused(b); + unused(r); + if (gflags & GF_PURE) + return "fogwall"; + if (gflags & GF_ARTICLE) + return LOC(f->locale, mkname("border", "a_fogwall")); + return LOC(f->locale, mkname("border", "fogwall")); +} + +static bool +b_blockfogwall(const connection * b, const unit * u, const region * r) +{ + unused(b); + unused(r); + if (!u) + return true; + return (bool) (effskill(u, SK_PERCEPTION) > 4); /* Das ist die alte Nebelwand */ +} + +/** Legacy type used in old Eressea games, no longer in use. */ +border_type bt_fogwall = { + "fogwall", VAR_INT, + b_transparent, /* transparent */ + NULL, /* init */ + NULL, /* destroy */ + b_read, /* read */ + b_write, /* write */ + b_blockfogwall, /* block */ + b_namefogwall, /* name */ + b_rvisible, /* rvisible */ + b_fvisible, /* fvisible */ + b_uvisible, /* uvisible */ +}; + +static const char *b_nameillusionwall(const connection * b, const region * r, + const struct faction *f, int gflags) +{ + int fno = b->data.i; + unused(b); + unused(r); + if (gflags & GF_PURE) + return (f && fno == f->no) ? "illusionwall" : "wall"; + if (gflags & GF_ARTICLE) { + return LOC(f->locale, mkname("border", (f + && fno == f->subscription) ? "an_illusionwall" : "a_wall")); + } + return LOC(f->locale, mkname("border", (f + && fno == f->no) ? "illusionwall" : "wall")); +} + +border_type bt_illusionwall = { + "illusionwall", VAR_INT, + b_opaque, + NULL, /* init */ + NULL, /* destroy */ + b_read, /* read */ + b_write, /* write */ + b_blocknone, /* block */ + b_nameillusionwall, /* name */ + b_rvisible, /* rvisible */ + b_fvisible, /* fvisible */ + b_uvisible, /* uvisible */ +}; + +/*** + * special quest door + ***/ + +bool b_blockquestportal(const connection * b, const unit * u, + const region * r) +{ + if (b->data.i > 0) + return true; + return false; +} + +static const char *b_namequestportal(const connection * b, const region * r, + const struct faction *f, int gflags) +{ + const char *bname; + int lock = b->data.i; + unused(b); + unused(r); + + if (gflags & GF_ARTICLE) { + if (lock > 0) { + bname = "a_gate_locked"; + } else { + bname = "a_gate_open"; + } + } else { + if (lock > 0) { + bname = "gate_locked"; + } else { + bname = "gate_open"; + } + } + if (gflags & GF_PURE) + return bname; + return LOC(f->locale, mkname("border", bname)); +} + +border_type bt_questportal = { + "questportal", VAR_INT, + b_opaque, + NULL, /* init */ + NULL, /* destroy */ + b_read, /* read */ + b_write, /* write */ + b_blockquestportal, /* block */ + b_namequestportal, /* name */ + b_rvisible, /* rvisible */ + b_fvisible, /* fvisible */ + b_uvisible, /* uvisible */ +}; + +/*** + * roads. meant to replace the old at_road or r->road attribute + ***/ + +static const char *b_nameroad(const connection * b, const region * r, + const struct faction *f, int gflags) +{ + region *r2 = (r == b->to) ? b->from : b->to; + int local = (r == b->from) ? b->data.sa[0] : b->data.sa[1]; + static char buffer[64]; + + unused(f); + if (gflags & GF_PURE) + return "road"; + if (gflags & GF_ARTICLE) { + if (!(gflags & GF_DETAILED)) + return LOC(f->locale, mkname("border", "a_road")); + else if (r->terrain->max_road <= local) { + int remote = (r2 == b->from) ? b->data.sa[0] : b->data.sa[1]; + if (r2->terrain->max_road <= remote) { + return LOC(f->locale, mkname("border", "a_road")); + } else { + return LOC(f->locale, mkname("border", "an_incomplete_road")); + } + } else { + int percent = MAX(1, 100 * local / r->terrain->max_road); + if (local) { + slprintf(buffer, sizeof(buffer), LOC(f->locale, mkname("border", + "a_road_percent")), percent); + } else { + return LOC(f->locale, mkname("border", "a_road_connection")); + } + } + } else if (gflags & GF_PLURAL) + return LOC(f->locale, mkname("border", "roads")); + else + return LOC(f->locale, mkname("border", "road")); + return buffer; +} + +static void b_readroad(connection * b, storage * store) +{ + b->data.sa[0] = (short)store->r_int(store); + b->data.sa[1] = (short)store->r_int(store); +} + +static void b_writeroad(const connection * b, storage * store) +{ + store->w_int(store, b->data.sa[0]); + store->w_int(store, b->data.sa[1]); +} + +static bool b_validroad(const connection * b) +{ + if (b->data.sa[0] == SHRT_MAX) + return false; + return true; +} + +static bool b_rvisibleroad(const connection * b, const region * r) +{ + int x = b->data.i; + x = (r == b->from) ? b->data.sa[0] : b->data.sa[1]; + if (x == 0) { + return false; + } + if (b->to != r && b->from != r) { + return false; + } + return true; +} + +border_type bt_road = { + "road", VAR_INT, + b_transparent, + NULL, /* init */ + NULL, /* destroy */ + b_readroad, /* read */ + b_writeroad, /* write */ + b_blocknone, /* block */ + b_nameroad, /* name */ + b_rvisibleroad, /* rvisible */ + b_finvisible, /* fvisible */ + b_uinvisible, /* uvisible */ + b_validroad /* valid */ +}; + +void write_borders(struct storage *store) +{ + int i; + for (i = 0; i != BORDER_MAXHASH; ++i) { + connection *bhash; + for (bhash = borders[i]; bhash; bhash = bhash->nexthash) { + connection *b; + for (b = bhash; b != NULL; b = b->next) { + if (b->type->valid && !b->type->valid(b)) + continue; + store->w_tok(store, b->type->__name); + store->w_int(store, b->id); + store->w_int(store, b->from->uid); + store->w_int(store, b->to->uid); + + if (b->type->write) + b->type->write(b, store); + store->w_brk(store); + } + } + } + store->w_tok(store, "end"); +} + +int read_borders(struct storage *store) +{ + for (;;) { + unsigned int bid = 0; + char zText[32]; + connection *b; + region *from, *to; + border_type *type; + + store->r_tok_buf(store, zText, sizeof(zText)); + if (!strcmp(zText, "end")) + break; + bid = store->r_int(store); + if (store->version < UIDHASH_VERSION) { + short fx, fy, tx, ty; + fx = (short)store->r_int(store); + fy = (short)store->r_int(store); + tx = (short)store->r_int(store); + ty = (short)store->r_int(store); + from = findregion(fx, fy); + to = findregion(tx, ty); + } else { + unsigned int fid = (unsigned int)store->r_int(store); + unsigned int tid = (unsigned int)store->r_int(store); + from = findregionbyid(fid); + to = findregionbyid(tid); + } + + type = find_bordertype(zText); + if (type == NULL) { + log_error("[read_borders] unknown connection type %s in %s\n", zText, regionname(from, NULL)); + assert(type || !"connection type not registered"); + } + + if (to == from && type && from) { + direction_t dir = (direction_t) (rng_int() % MAXDIRECTIONS); + region *r = rconnect(from, dir); + log_error("[read_borders] invalid %s in %s\n", type->__name, regionname(from, NULL)); + if (r != NULL) + to = r; + } + b = new_border(type, from, to); + nextborder--; /* new_border erhöht den Wert */ + b->id = bid; + assert(bid <= nextborder); + if (type->read) + type->read(b, store); + if (store->version < NOBORDERATTRIBS_VERSION) { + attrib *a = NULL; + int result = a_read(store, &a, b); + if (border_convert_cb) + border_convert_cb(b, a); + while (a) { + a_remove(&a, a); + } + if (result < 0) + return result; + } + if ((type->read && !type->write) || !to || !from) { + log_warning("erase invalid border '%s' between '%s' and '%s'\n", type->__name, regionname(from, 0), regionname(to, 0)); + erase_border(b); + } + } + return 0; +} diff --git a/core/src/kernel/connection.h b/core/src/kernel/connection.h new file mode 100644 index 000000000..b582377c5 --- /dev/null +++ b/core/src/kernel/connection.h @@ -0,0 +1,146 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_BORDER +#define H_KRNL_BORDER + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + extern unsigned int nextborder; + + typedef struct connection { + struct border_type *type; /* the type of this connection */ + struct connection *next; /* next connection between these regions */ + struct connection *nexthash; /* next connection between these regions */ + struct region *from, *to; /* borders can be directed edges */ + variant data; + unsigned int id; /* unique id */ + } connection; + + typedef struct border_type { + const char *__name; /* internal use only */ + variant_type datatype; + bool(*transparent) (const connection *, const struct faction *); + /* is it possible to see through this? */ + void (*init) (connection *); + /* constructor: initialize the connection. allocate extra memory if needed */ + void (*destroy) (connection *); + /* destructor: remove all extra memory for destruction */ + void (*read) (connection *, struct storage *); + void (*write) (const connection *, struct storage *); + bool(*block) (const connection *, const struct unit *, + const struct region * r); + /* return true if it blocks movement of u from + * r to the opposite struct region. + * warning: struct unit may be NULL. + */ + const char *(*name) (const connection * b, const struct region * r, + const struct faction * f, int gflags); + /* for example "a wall of fog" or "a doorway from r1 to r2" + * may depend on the struct faction, for example "a wall" may + * turn out to be "an illusionary wall" + */ + bool(*rvisible) (const connection *, const struct region *); + /* is it visible to everyone in r ? + * if not, it may still be fvisible() for some f. + */ + bool(*fvisible) (const connection *, const struct faction *, + const struct region *); + /* is it visible to units of f in r? + * the function shall not check for + * existence of a struct unit in r. Example: a spell + * may be visible only to the struct faction that created it and thus + * appear in it's report (if there is a struct unit of f in r, which + * the reporting function will have to assure). + * if not true, it may still be uvisible() for some u. + */ + bool(*uvisible) (const connection *, const struct unit *); + /* is it visible to u ? + * a doorway may only be visible to a struct unit with perception > 5 + */ + bool(*valid) (const connection *); + /* is the connection in a valid state, + * or should it be erased at the end of this turn to save space? + */ + struct region *(*move) (const connection *, struct unit * u, + struct region * from, struct region * to, bool routing); + /* executed when the units traverses this connection */ + int (*age) (struct connection *); + /* return 0 if connection needs to be removed. >0 if still aging, <0 if not aging */ + struct border_type *next; /* for internal use only */ + } border_type; + + extern connection *find_border(unsigned int id); + int resolve_borderid(variant data, void *addr); + extern void free_borders(void); + + extern connection *get_borders(const struct region *r1, + const struct region *r2); + /* returns the list of borders between r1 and r2 or r2 and r1 */ + extern connection *new_border(border_type * type, struct region *from, + struct region *to); + /* creates a connection of the specified type */ + extern void erase_border(connection * b); + /* remove the connection from memory */ + extern border_type *find_bordertype(const char *name); + extern void register_bordertype(border_type * type); + /* register a new bordertype */ + + extern int read_borders(struct storage *store); + extern void write_borders(struct storage *store); + extern void age_borders(void); + + /* provide default implementations for some member functions: */ + extern void b_read(connection * b, struct storage *store); + extern void b_write(const connection * b, struct storage *store); + extern bool b_blockall(const connection *, const struct unit *, + const struct region *); + extern bool b_blocknone(const connection *, const struct unit *, + const struct region *); + extern bool b_rvisible(const connection *, const struct region *r); + extern bool b_fvisible(const connection *, const struct faction *f, + const struct region *); + extern bool b_uvisible(const connection *, const struct unit *u); + extern bool b_rinvisible(const connection *, const struct region *r); + extern bool b_finvisible(const connection *, const struct faction *f, + const struct region *); + extern bool b_uinvisible(const connection *, const struct unit *u); + extern bool b_transparent(const connection *, const struct faction *); + extern bool b_opaque(const connection *, const struct faction *); /* !transparent */ + + extern border_type bt_fogwall; + extern border_type bt_noway; + extern border_type bt_wall; + extern border_type bt_illusionwall; + extern border_type bt_road; + extern border_type bt_questportal; + + extern struct attrib_type at_countdown; + + /* game-specific callbacks */ + extern void (*border_convert_cb) (struct connection * con, + struct attrib * attr); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/curse.c b/core/src/kernel/curse.c new file mode 100644 index 000000000..a903d8bed --- /dev/null +++ b/core/src/kernel/curse.c @@ -0,0 +1,812 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "curse.h" + +/* kernel includes */ +#include "building.h" +#include "faction.h" +#include "magic.h" +#include "message.h" +#include "objtypes.h" +#include "race.h" +#include "region.h" +#include "ship.h" +#include "skill.h" +#include "unit.h" +#include "version.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +#include + +#define MAXENTITYHASH 7919 +curse *cursehash[MAXENTITYHASH]; + +void c_setflag(curse * c, unsigned int flags) +{ + assert(c); + c->flags = (c->flags & ~flags) | (flags & (c->type->flags ^ flags)); +} + +/* -------------------------------------------------------------------------- */ +void c_clearflag(curse * c, unsigned int flags) +{ + assert(c); + c->flags = (c->flags & ~flags) | (c->type->flags & flags); +} + +void chash(curse * c) +{ + curse *old = cursehash[c->no % MAXENTITYHASH]; + + cursehash[c->no % MAXENTITYHASH] = c; + c->nexthash = old; +} + +static void cunhash(curse * c) +{ + curse **show; + + for (show = &cursehash[c->no % MAXENTITYHASH]; *show; + show = &(*show)->nexthash) { + if ((*show)->no == c->no) + break; + } + if (*show) { + assert(*show == c); + *show = (*show)->nexthash; + c->nexthash = 0; + } +} + +curse *cfindhash(int i) +{ + curse *old; + + for (old = cursehash[i % MAXENTITYHASH]; old; old = old->nexthash) + if (old->no == i) + return old; + return NULL; +} + +/* ------------------------------------------------------------- */ +/* at_curse */ +void curse_init(attrib * a) +{ + a->data.v = calloc(1, sizeof(curse)); +} + +int curse_age(attrib * a) +{ + curse *c = (curse *) a->data.v; + int result = 0; + + if (c_flags(c) & CURSE_NOAGE) { + c->duration = INT_MAX; + } + if (c->type->age) { + result = c->type->age(c); + } + if (result != 0) { + c->duration = 0; + } else if (c->duration != INT_MAX) { + c->duration = MAX(0, c->duration - 1); + } + return c->duration; +} + +void destroy_curse(curse * c) +{ + cunhash(c); + free(c); +} + +void curse_done(attrib * a) +{ + destroy_curse((curse *) a->data.v); +} + +/** reads curses that have been removed from the code */ +static int read_ccompat(const char *cursename, struct storage *store) +{ + struct compat { + const char *name; + const char *tokens; + } *seek, old_curses[] = { { + "disorientationzone", ""}, { + "shipdisorientation", ""}, { + NULL, NULL}}; + for (seek = old_curses; seek->name; ++seek) { + if (strcmp(seek->tokens, cursename) == 0) { + const char *p; + for (p = seek->name; p; ++p) { + switch (*p) { + case 'd': + store->r_int(store); + break; + case 's': + store->r_str(store); + break; + case 't': + store->r_tok(store); + break; + case 'i': + store->r_id(store); + break; + case 'f': + store->r_flt(store); + break; + } + } + return 0; + } + } + return -1; +} + +int curse_read(attrib * a, void *owner, struct storage *store) +{ + curse *c = (curse *) a->data.v; + int ur; + char cursename[64]; + unsigned int flags; + + c->no = store->r_int(store); + chash(c); + store->r_tok_buf(store, cursename, sizeof(cursename)); + flags = store->r_int(store); + c->duration = store->r_int(store); + if (store->version >= CURSEVIGOURISFLOAT_VERSION) { + c->vigour = store->r_flt(store); + } else { + int vigour = store->r_int(store); + c->vigour = vigour; + } + if (store->version < INTPAK_VERSION) { + ur = read_reference(&c->magician, store, read_int, resolve_unit); + } else { + ur = read_reference(&c->magician, store, read_unit_reference, resolve_unit); + } + if (store->version < CURSEFLOAT_VERSION) { + c->effect = (double)store->r_int(store); + } else { + c->effect = store->r_flt(store); + } + c->type = ct_find(cursename); + if (c->type == NULL) { + int result = read_ccompat(cursename, store); + if (result != 0) { + log_error("missing curse %s, no compatibility code either.\n", cursename); + } + assert(result == 0); + return AT_READ_FAIL; + } + if (store->version < CURSEFLAGS_VERSION) { + c_setflag(c, flags); + } else { + c->flags = flags; + } + c_clearflag(c, CURSE_ISNEW); + + if (c->type->read) + c->type->read(store, c, owner); + else if (c->type->typ == CURSETYP_UNIT) { + c->data.i = store->r_int(store); + } + if (c->type->typ == CURSETYP_REGION) { + int rr = + read_reference(&c->data.v, store, read_region_reference, + RESOLVE_REGION(store->version)); + if (ur == 0 && rr == 0 && !c->data.v) { + return AT_READ_FAIL; + } + } + + return AT_READ_OK; +} + +void curse_write(const attrib * a, const void *owner, struct storage *store) +{ + unsigned int flags; + curse *c = (curse *) a->data.v; + const curse_type *ct = c->type; + unit *mage = (c->magician && c->magician->number) ? c->magician : NULL; + + /* copied from c_clearflag */ + flags = (c->flags & ~CURSE_ISNEW) | (c->type->flags & CURSE_ISNEW); + + store->w_int(store, c->no); + store->w_tok(store, ct->cname); + store->w_int(store, flags); + store->w_int(store, c->duration); + store->w_flt(store, (float)c->vigour); + write_unit_reference(mage, store); + store->w_flt(store, (float)c->effect); + + if (c->type->write) + c->type->write(store, c, owner); + else if (c->type->typ == CURSETYP_UNIT) { + store->w_int(store, c->data.i); + } + if (c->type->typ == CURSETYP_REGION) { + write_region_reference((region *) c->data.v, store); + } +} + +attrib_type at_curse = { + "curse", + curse_init, + curse_done, + curse_age, + curse_write, + curse_read, + ATF_CURSE +}; + +/* ------------------------------------------------------------- */ +/* Spruch identifizieren */ + +#include +#include + +static quicklist *cursetypes[256]; + +void ct_register(const curse_type * ct) +{ + unsigned int hash = tolower(ct->cname[0]); + quicklist **ctlp = cursetypes + hash; + + ql_set_insert(ctlp, (void *)ct); +} + +const curse_type *ct_find(const char *c) +{ + unsigned int hash = tolower(c[0]); + quicklist *ctl = cursetypes[hash]; + int qi; + + for (qi = 0; ctl; ql_advance(&ctl, &qi, 1)) { + curse_type *type = (curse_type *) ql_get(ctl, qi); + + if (strcmp(c, type->cname) == 0) { + return type; + } else { + size_t k = MIN(strlen(c), strlen(type->cname)); + if (!strncasecmp(c, type->cname, k)) { + return type; + } + } + } + return NULL; +} + +/* ------------------------------------------------------------- */ +/* get_curse identifiziert eine Verzauberung über die ID und gibt + * einen pointer auf die struct zurück. + */ + +bool cmp_curse(const attrib * a, const void *data) +{ + const curse *c = (const curse *)data; + if (a->type->flags & ATF_CURSE) { + if (!data || c == (curse *) a->data.v) + return true; + } + return false; +} + +bool cmp_cursetype(const attrib * a, const void *data) +{ + const curse_type *ct = (const curse_type *)data; + if (a->type->flags & ATF_CURSE) { + if (!data || ct == ((curse *) a->data.v)->type) + return true; + } + return false; +} + +curse *get_cursex(attrib * ap, const curse_type * ctype, variant data, + bool(*compare) (const curse *, variant)) +{ + attrib *a = a_select(ap, ctype, cmp_cursetype); + while (a) { + curse *c = (curse *) a->data.v; + if (compare(c, data)) + return c; + a = a_select(a->next, ctype, cmp_cursetype); + } + return NULL; +} + +curse *get_curse(attrib * ap, const curse_type * ctype) +{ + attrib *a = ap; + while (a) { + if (a->type->flags & ATF_CURSE) { + const attrib_type *at = a->type; + while (a && a->type == at) { + curse *c = (curse *) a->data.v; + if (c->type == ctype) + return c; + a = a->next; + } + } else { + a = a->nexttype; + } + } + return NULL; +} + +/* ------------------------------------------------------------- */ +/* findet einen curse global anhand seiner 'curse-Einheitnummer' */ + +curse *findcurse(int cid) +{ + return cfindhash(cid); +} + +/* ------------------------------------------------------------- */ +void remove_curse(attrib ** ap, const curse * c) +{ + attrib *a = a_select(*ap, c, cmp_curse); + if (a) + a_remove(ap, a); +} + +/* gibt die allgemeine Stärke der Verzauberung zurück. id2 wird wie + * oben benutzt. Dies ist nicht die Wirkung, sondern die Kraft und + * damit der gegen Antimagie wirkende Widerstand einer Verzauberung */ +static double get_cursevigour(const curse * c) +{ + if (c) + return c->vigour; + return 0; +} + +/* setzt die Stärke der Verzauberung auf i */ +static void set_cursevigour(curse * c, double vigour) +{ + assert(c && vigour > 0); + c->vigour = vigour; +} + +/* verändert die Stärke der Verzauberung um +i und gibt die neue + * Stärke zurück. Sollte die Zauberstärke unter Null sinken, löst er + * sich auf. + */ +double curse_changevigour(attrib ** ap, curse * c, double vigour) +{ + vigour += get_cursevigour(c); + + if (vigour <= 0) { + remove_curse(ap, c); + vigour = 0; + } else { + set_cursevigour(c, vigour); + } + return vigour; +} + +/* ------------------------------------------------------------- */ + +double curse_geteffect(const curse * c) +{ + if (c == NULL) + return 0; + if (c_flags(c) & CURSE_ISNEW) + return 0; + return c->effect; +} + +int curse_geteffect_int(const curse * c) +{ + double effect = curse_geteffect(c); + if (effect - (int)effect != 0) { + log_error("curse has an integer attribute with float value: '%s' = %lf", + c->type->cname, effect); + } + return (int)effect; +} + +/* ------------------------------------------------------------- */ +static void +set_curseingmagician(struct unit *magician, struct attrib *ap_target, + const curse_type * ct) +{ + curse *c = get_curse(ap_target, ct); + if (c) { + c->magician = magician; + } +} + +/* ------------------------------------------------------------- */ +/* gibt bei Personenbeschränkten Verzauberungen die Anzahl der + * betroffenen Personen zurück. Ansonsten wird 0 zurückgegeben. */ +int get_cursedmen(unit * u, const curse * c) +{ + int cursedmen = u->number; + + if (!c) + return 0; + + /* je nach curse_type andere data struct */ + if (c->type->typ == CURSETYP_UNIT) { + cursedmen = c->data.i; + } + + return MIN(u->number, cursedmen); +} + +/* setzt die Anzahl der betroffenen Personen auf cursedmen */ +static void set_cursedmen(curse * c, int cursedmen) +{ + if (!c) + return; + + /* je nach curse_type andere data struct */ + if (c->type->typ == CURSETYP_UNIT) { + c->data.i = cursedmen; + } +} + +/* ------------------------------------------------------------- */ +/* Legt eine neue Verzauberung an. Sollte es schon einen Zauber + * dieses Typs geben, gibt es den bestehenden zurück. + */ +static curse *make_curse(unit * mage, attrib ** ap, const curse_type * ct, + double vigour, int duration, double effect, int men) +{ + curse *c; + attrib *a; + + a = a_new(&at_curse); + a_add(ap, a); + c = (curse *) a->data.v; + + c->type = ct; + c->flags = 0; + c->vigour = vigour; + c->duration = duration; + c->effect = effect; + c->magician = mage; + + c->no = newunitid(); + chash(c); + + switch (c->type->typ) { + case CURSETYP_NORM: + break; + + case CURSETYP_UNIT: + { + c->data.i = men; + break; + } + + } + return c; +} + +/* Mapperfunktion für das Anlegen neuer curse. Automatisch wird zum + * passenden Typ verzweigt und die relevanten Variablen weitergegeben. + */ +curse *create_curse(unit * magician, attrib ** ap, const curse_type * ct, + double vigour, int duration, double effect, int men) +{ + curse *c; + + /* die Kraft eines Spruchs darf nicht 0 sein */ + assert(vigour > 0); + + c = get_curse(*ap, ct); + + if (c && (c_flags(c) & CURSE_ONLYONE)) { + return NULL; + } + assert(c == NULL || ct == c->type); + + /* es gibt schon eins diese Typs */ + if (c && ct->mergeflags != NO_MERGE) { + if (ct->mergeflags & M_DURATION) { + c->duration = MAX(c->duration, duration); + } + if (ct->mergeflags & M_SUMDURATION) { + c->duration += duration; + } + if (ct->mergeflags & M_SUMEFFECT) { + c->effect += effect; + } + if (ct->mergeflags & M_MAXEFFECT) { + c->effect = MAX(c->effect, effect); + } + if (ct->mergeflags & M_VIGOUR) { + c->vigour = MAX(vigour, c->vigour); + } + if (ct->mergeflags & M_VIGOUR_ADD) { + c->vigour = vigour + c->vigour; + } + if (ct->mergeflags & M_MEN) { + switch (ct->typ) { + case CURSETYP_UNIT: + { + c->data.i += men; + } + } + } + set_curseingmagician(magician, *ap, ct); + } else { + c = make_curse(magician, ap, ct, vigour, duration, effect, men); + } + return c; +} + +/* ------------------------------------------------------------- */ +/* hier müssen alle c-typen, die auf Einheiten gezaubert werden können, + * berücksichtigt werden */ + +static void do_transfer_curse(curse * c, unit * u, unit * u2, int n) +{ + int cursedmen = 0; + int men = get_cursedmen(u, c); + bool dogive = false; + const curse_type *ct = c->type; + + switch ((ct->flags | c->flags) & CURSE_SPREADMASK) { + case CURSE_SPREADALWAYS: + dogive = true; + men = u2->number + n; + break; + + case CURSE_SPREADMODULO: + { + int i; + int u_number = u->number; + for (i = 0; i < n + 1 && u_number > 0; i++) { + if (rng_int() % u_number < cursedmen) { + ++men; + --cursedmen; + dogive = true; + } + --u_number; + } + break; + } + case CURSE_SPREADCHANCE: + if (chance(u2->number / (double)(u2->number + n))) { + men = u2->number + n; + dogive = true; + } + break; + case CURSE_SPREADNEVER: + break; + } + + if (dogive) { + curse *cnew = make_curse(c->magician, &u2->attribs, c->type, c->vigour, + c->duration, c->effect, men); + cnew->flags = c->flags; + + if (ct->typ == CURSETYP_UNIT) + set_cursedmen(cnew, men); + } +} + +void transfer_curse(unit * u, unit * u2, int n) +{ + attrib *a; + + a = a_find(u->attribs, &at_curse); + while (a && a->type == &at_curse) { + curse *c = (curse *) a->data.v; + do_transfer_curse(c, u, u2, n); + a = a->next; + } +} + +/* ------------------------------------------------------------- */ + +bool curse_active(const curse * c) +{ + if (!c) + return false; + if (c_flags(c) & CURSE_ISNEW) + return false; + if (c->vigour <= 0) + return false; + + return true; +} + +bool is_cursed_internal(attrib * ap, const curse_type * ct) +{ + curse *c = get_curse(ap, ct); + + if (!c) + return false; + + return true; +} + +bool is_cursed_with(const attrib * ap, const curse * c) +{ + const attrib *a = ap; + + while (a) { + if ((a->type->flags & ATF_CURSE) && (c == (const curse *)a->data.v)) { + return true; + } + a = a->next; + } + + return false; +} + +/* ------------------------------------------------------------- */ +/* cursedata */ +/* ------------------------------------------------------------- */ +/* + * typedef struct curse_type { + * const char *cname; (Name der Zauberwirkung, Identifizierung des curse) + * int typ; + * spread_t spread; + * unsigned int mergeflags; + * int (*curseinfo)(const struct locale*, const void*, int, curse*, int); + * void (*change_vigour)(curse*, double); + * int (*read)(struct storage * store, curse * c); + * int (*write)(struct storage * store, const curse * c); + * } curse_type; + */ + +int resolve_curse(variant id, void *address) +{ + int result = 0; + curse *c = NULL; + if (id.i != 0) { + c = cfindhash(id.i); + if (c == NULL) { + result = -1; + } + } + *(curse **) address = c; + return result; +} + +static const char *oldnames[MAXCURSE] = { + /* OBS: when removing curses, remember to update read_ccompat() */ + "fogtrap", + "antimagiczone", + "farvision", + "gbdream", + "auraboost", + "maelstrom", + "blessedharvest", + "drought", + "badlearn", + "stormwind", + "flyingship", + "nodrift", + "depression", + "magicwalls", + "strongwall", + "astralblock", + "generous", + "peacezone", + "magicstreet", + "magicrunes", + "badmagicresistancezone", + "goodmagicresistancezone", + "slavery", + "calmmonster", + "oldrace", + "fumble", + "riotzone", + "nocostbuilding", + "godcursezone", + "speed", + "orcish", + "magicboost", + "insectfur", + "strength", + "worse", + "magicresistance", + "itemcloak", + "sparkle", + "skillmod" +}; + +const char *oldcursename(int id) +{ + return oldnames[id]; +} + +/* ------------------------------------------------------------- */ +message *cinfo_simple(const void *obj, objtype_t typ, const struct curse * c, + int self) +{ + struct message *msg; + + unused(typ); + unused(self); + unused(obj); + + msg = msg_message(mkname("curseinfo", c->type->cname), "id", c->no); + if (msg == NULL) { + log_error("There is no curseinfo for %s.\n", c->type->cname); + } + return msg; +} + +/* ------------------------------------------------------------- */ +/* Antimagie - curse auflösen */ +/* ------------------------------------------------------------- */ + +/* Wenn der Curse schwächer ist als der cast_level, dann wird er +* aufgelöst, bzw seine Kraft (vigour) auf 0 gesetzt. +* Ist der cast_level zu gering, hat die Antimagie nur mit einer Chance +* von 100-20*Stufenunterschied % eine Wirkung auf den Curse. Dann wird +* die Kraft des Curse um die halbe Stärke der Antimagie reduziert. +* Zurückgegeben wird der noch unverbrauchte Rest von force. +*/ +double destr_curse(curse * c, int cast_level, double force) +{ + if (cast_level < c->vigour) { /* Zauber ist nicht stark genug */ + double probability = 0.1 + (cast_level - c->vigour) * 0.2; + /* pro Stufe Unterschied -20% */ + if (chance(probability)) { + force -= c->vigour; + if (c->type->change_vigour) { + c->type->change_vigour(c, -(cast_level + 1 / 2)); + } else { + c->vigour -= cast_level + 1 / 2; + } + } + } else { /* Zauber ist stärker als curse */ + if (force >= c->vigour) { /* reicht die Kraft noch aus? */ + force -= c->vigour; + if (c->type->change_vigour) { + c->type->change_vigour(c, -c->vigour); + } else { + c->vigour = 0; + } + + } + } + return force; +} diff --git a/core/src/kernel/curse.h b/core/src/kernel/curse.h new file mode 100644 index 000000000..a4b2f9c80 --- /dev/null +++ b/core/src/kernel/curse.h @@ -0,0 +1,333 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef CURSE_H +#define CURSE_H + +#include +#include "objtypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Sprueche in der struct region und auf Einheiten, Schiffen oder Burgen + * (struct attribute) + */ + +/* Brainstorming Überarbeitung curse + * + * Ziel: Keine Enum-Liste, flexible, leicht erweiterbare Curse-Objekte + * + * Was wird gebraucht? + * - Eindeutige Kennung für globale Suche + * - eine Wirkung, die sich einfach 'anwenden' läßt, dabei flexibel ist, + * Raum läßt für variable Boni, Anzahl betroffener Personen, + * spezielle Effekte oder anderes + * - einfacher Zugriff auf allgemeine Funktionen wie zb Alterung, aber + * auch Antimagieverhalten + * - Ausgabe von Beschreibungen in verschiedenen Sprachen + * - definiertes gekapseltes Verhalten zb bei Zusammenlegung von + * Einheiten, Übergabe von Personen, Mehrfachverzauberung + * - (Rück-)Referenzen auf Objekt, Verursacher (Magier), ? + * + * Vieleicht wäre ein Wirkungsklassensystem sinnvoll, so das im übrigen + * source einfach alle curse-attribs abgefragt werden können und bei + * gewünschter Wirkungsklasse angewendet, also nicht für jeden curse + * spezielle Änderungen im übrigen source notwendig sind. + * + * Die (Wirkungs-)Typen sollten die wichtigen Funktionen speziell + * belegen können, zb Alterung, Ausgabetexte, Merge-Verhalten + * + * Was sind wichtige individuelle Eigenschaften? + * - Referenzen auf Objekt und Verursacher + * - Referenzen für globale Liste + * > die Wirkung: + * - Dauer + * - Widerstandskraft, zb gegen Antimagie + * - Seiteneffekte zb Flag ONLYONE, Unverträglichkeiten + * - Alterungsverhalten zb Flag NOAGE + * - Effektverhalten, zb Bonus (variabel) + * - bei Einheitenzaubern die Zahl der betroffenen Personen + * + * Dabei sind nur die beiden letzten Punkte wirklich reine individuelle + * Wirkung, die anderen sind eher allgemeine Kennzeichen eines jeden + * Curse, die individuell belegt sind. + * ONLYONE und NOAGE könnten auch Eigenschaften des Typs sein, nicht des + * individuellen curse. Allgemein ist Alterung wohl eher eine + * Typeigenschaft als die eines individuellen curse. + * Dagegen ist der Widerstand gegen Antimagie sowohl abhängig von der + * Güte des Verursachers, also des Magiers zum Zeitpunkt des Zaubers, + * als auch vom Typ, der gegen bestimmte Arten des 'Fluchbrechens' immun + * sein könnte. + * + * Was sind wichtige Typeigenschaften? + * - Verhalten bei Personenübergaben + * - allgemeine Wirkung + * - Beschreibungstexte + * - Verhalten bei Antimagie + * - Alterung + * - Speicherung des C-Objekts + * - Laden des C-Objekts + * - Erzeugen des C-Objekts + * - Löschen und Aufräumen des C-Objekts + * - Funktionen zur Änderung der Werte + * + * */ + +#include + +/* ------------------------------------------------------------- */ +/* Zauberwirkungen */ +/* nicht vergessen curse_type zu aktualisieren und Reihenfolge beachten! + */ + + enum { +/* struct's vom typ curse: */ + C_FOGTRAP, + C_ANTIMAGICZONE, + C_FARVISION, + C_GBDREAM, + C_AURA, + C_MAELSTROM, + C_BLESSEDHARVEST, + C_DROUGHT, + C_BADLEARN, + C_SHIP_SPEEDUP, /* 9 - Sturmwind-Zauber */ + C_SHIP_FLYING, /* 10 - Luftschiff-Zauber */ + C_SHIP_NODRIFT, /* 11 - GünstigeWinde-Zauber */ + C_DEPRESSION, + C_MAGICWALLS, /* 13 - Heimstein */ + C_STRONGWALL, /* 14 - Feste Mauer - Precombat */ + C_ASTRALBLOCK, /* 15 - Astralblock */ + C_GENEROUS, /* 16 - Unterhaltung vermehren */ + C_PEACE, /* 17 - Regionsweit Attacken verhindern */ + C_MAGICSTREET, /* 19 - magisches Straßennetz */ + C_RESIST_MAGIC, /* 20 - verändert Magieresistenz von Objekten */ + C_SONG_BADMR, /* 21 - verändert Magieresistenz */ + C_SONG_GOODMR, /* 22 - verändert Magieresistenz */ + C_SLAVE, /* 23 - dient fremder Partei */ + C_CALM, /* 25 - Beinflussung */ + C_OLDRACE, + C_FUMBLE, + C_RIOT, /*region in Aufruhr */ + C_NOCOST, + C_CURSED_BY_THE_GODS, +/* struct's vom untertyp curse_unit: */ + C_SPEED, /* Beschleunigt */ + C_ORC, + C_MBOOST, + C_KAELTESCHUTZ, + C_STRENGTH, + C_ALLSKILLS, + C_MAGICRESISTANCE, /* 44 - verändert Magieresistenz */ + C_ITEMCLOAK, + C_SPARKLE, +/* struct's vom untertyp curse_skill: */ + C_SKILL, + MAXCURSE /* OBS: when removing curses, remember to update read_ccompat() */ + }; + +/* ------------------------------------------------------------- */ +/* Flags */ + +/* Verhalten von Zaubern auf Units beim Übergeben von Personen */ + typedef enum { + CURSE_ISNEW = 0x01, /* wirkt in der zauberrunde nicht (default) */ + CURSE_NOAGE = 0x02, /* wirkt ewig */ + CURSE_IMMUNE = 0x04, /* ignoriert Antimagie */ + CURSE_ONLYONE = 0x08, /* Verhindert, das ein weiterer Zauber dieser Art auf das Objekt gezaubert wird */ + + /* the following are mutually exclusive */ + CURSE_SPREADNEVER = 0x00, /* wird nie mit übertragen */ + CURSE_SPREADALWAYS = 0x10, /* wird immer mit übertragen */ + CURSE_SPREADMODULO = 0x20, /* personenweise weitergabe */ + CURSE_SPREADCHANCE = 0x30 /* Ansteckungschance je nach Mengenverhältnis */ + } curseflags; + +#define CURSE_FLAGSMASK 0x0F +#define CURSE_SPREADMASK 0x30 + +/* typ von struct */ + enum { + CURSETYP_NORM, + CURSETYP_UNIT, + CURSETYP_REGION, /* stores the region in c->data.v */ + MAXCURSETYP + }; + +/* Verhalten beim Zusammenfassen mit einem schon bestehenden Zauber + * gleichen Typs */ + +#define NO_MERGE 0 /* erzeugt jedesmal einen neuen Zauber */ +#define M_DURATION 1 /* Die Zauberdauer ist die maximale Dauer beider */ +#define M_SUMDURATION 2 /* die Dauer des Zaubers wird summiert */ +#define M_MAXEFFECT 4 /* der Effekt ist der maximale Effekt beider */ +#define M_SUMEFFECT 8 /* der Effekt summiert sich */ +#define M_MEN 16 /* die Anzahl der betroffenen Personen summiert + sich */ +#define M_VIGOUR 32 /* das Maximum der beiden Stärken wird die + Stärke des neuen Zaubers */ +#define M_VIGOUR_ADD 64 /* Vigour wird addiert */ + +/* ------------------------------------------------------------- */ +/* Allgemeine Zauberwirkungen */ + + typedef struct curse { + struct curse *nexthash; + int no; /* 'Einheitennummer' dieses Curse */ + const struct curse_type *type; /* Zeiger auf ein curse_type-struct */ + unsigned int flags; /* WARNING: these are XORed with type->flags! */ + int duration; /* Dauer der Verzauberung. Wird jede Runde vermindert */ + double vigour; /* Stärke der Verzauberung, Widerstand gegen Antimagie */ + struct unit *magician; /* Pointer auf den Magier, der den Spruch gewirkt hat */ + double effect; + variant data; /* pointer auf spezielle curse-unterstructs */ + } curse; + +#define c_flags(c) ((c)->type->flags ^ (c)->flags) + +/* ------------------------------------------------------------- */ + + typedef struct curse_type { + const char *cname; /* Name der Zauberwirkung, Identifizierung des curse */ + int typ; + unsigned int flags; + unsigned int mergeflags; + struct message *(*curseinfo) (const void *, objtype_t, const struct curse *, + int); + void (*change_vigour) (curse *, double); + int (*read) (struct storage * store, curse * c, void *target); + int (*write) (struct storage * store, const struct curse * c, + const void *target); + int (*cansee) (const struct faction *, const void *, objtype_t, + const struct curse *, int); + int (*age) (curse *); + } curse_type; + + extern struct attrib_type at_curse; + void curse_write(const struct attrib *a, const void *owner, + struct storage *store); + int curse_read(struct attrib *a, void *owner, struct storage *store); + +/* ------------------------------------------------------------- */ +/* Kommentare: + * Bei einigen Typen von Verzauberungen (z.B.Skillmodif.) muss neben + * der curse-id noch ein weiterer Identifizierer angegeben werden (id2). + * + * Wenn der Typ korrekt definiert wurde, erfolgt die Verzweigung zum + * korrekten Typus automatisch, und die unterschiedlichen struct-typen + * sind nach aussen unsichtbar. + * +* allgemeine Funktionen * +*/ + + curse *create_curse(struct unit *magician, struct attrib **ap, + const curse_type * ctype, double vigour, int duration, double ceffect, + int men); + /* Verzweigt automatisch zum passenden struct-typ. Sollte es schon + * einen Zauber dieses Typs geben, so wird der neue dazuaddiert. Die + * Zahl der verzauberten Personen sollte beim Aufruf der Funktion + * nochmal gesondert auf min(get_cursedmen, u->number) gesetzt werden. + */ + + extern void destroy_curse(curse * c); + + bool is_cursed_internal(struct attrib *ap, const curse_type * ctype); + /* ignoriert CURSE_ISNEW */ + + extern void remove_curse(struct attrib **ap, const struct curse *c); + /* löscht einen konkreten Spruch auf einem Objekt. + */ + + extern int curse_geteffect_int(const struct curse *c); + extern double curse_geteffect(const struct curse *c); + /* gibt die Auswirkungen der Verzauberungen zurück. zB bei + * Skillmodifiziernden Verzauberungen ist hier der Modifizierer + * gespeichert. Wird automatisch beim Anlegen eines neuen curse + * gesetzt. Gibt immer den ersten Treffer von ap aus zurück. + */ + + extern double curse_changevigour(struct attrib **ap, curse * c, double i); + /* verändert die Stärke der Verzauberung um i */ + + extern int get_cursedmen(struct unit *u, const struct curse *c); + /* gibt bei Personenbeschränkten Verzauberungen die Anzahl der + * betroffenen Personen zurück. Ansonsten wird 0 zurückgegeben. */ + + extern void c_setflag(curse * c, unsigned int flag); + extern void c_clearflag(curse * c, unsigned int flags); + /* setzt/loescht Spezialflag einer Verzauberung (zB 'dauert ewig') */ + + void transfer_curse(struct unit *u, struct unit *u2, int n); + /* sorgt dafür, das bei der Übergabe von Personen die curse-attribute + * korrekt gehandhabt werden. Je nach internen Flag kann dies + * unterschiedlich gewünscht sein + * */ + + extern struct curse *get_cursex(struct attrib *ap, const curse_type * ctype, + variant data, bool(*compare) (const struct curse *, variant)); + /* gibt pointer auf die erste curse-struct zurück, deren Typ ctype ist, + * und für die compare() true liefert, oder einen NULL-pointer. + * */ + extern struct curse *get_curse(struct attrib *ap, const curse_type * ctype); + /* gibt pointer auf die erste curse-struct zurück, deren Typ ctype ist, + * oder einen NULL-pointer + * */ + + int find_cursebyname(const char *c); + const curse_type *ct_find(const char *c); + void ct_register(const curse_type *); +/* Regionszauber */ + + curse *cfindhash(int i); + + curse *findcurse(int curseid); + + extern void curse_init(struct attrib *a); + extern void curse_done(struct attrib *a); + extern int curse_age(struct attrib *a); + + extern bool cmp_curse(const struct attrib *a, const void *data); + extern bool cmp_cursetype(const struct attrib *a, const void *data); + + extern double destr_curse(struct curse *c, int cast_level, double force); + + extern int resolve_curse(variant data, void *address); + extern bool is_cursed_with(const struct attrib *ap, const struct curse *c); + + extern bool curse_active(const struct curse *c); + /* gibt true, wenn der Curse nicht NULL oder inaktiv ist */ + +/*** COMPATIBILITY MACROS. DO NOT USE FOR NEW CODE, REPLACE IN OLD CODE: */ + extern const char *oldcursename(int id); + extern struct message *cinfo_simple(const void *obj, objtype_t typ, + const struct curse *c, int self); + +#define is_cursed(a, id, id2) \ + curse_active(get_curse(a, ct_find(oldcursename(id)))) +#define get_curseeffect(a, id, id2) \ + curse_geteffect(get_curse(a, ct_find(oldcursename(id)))) + +/* eressea-defined attribute-type flags */ +#define ATF_CURSE ATF_USER_DEFINED + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/curse_test.c b/core/src/kernel/curse_test.c new file mode 100644 index 000000000..a7f88218b --- /dev/null +++ b/core/src/kernel/curse_test.c @@ -0,0 +1,30 @@ +#include +#include "types.h" +#include "curse.h" + +#include + +#include + +static void test_curse(CuTest * tc) +{ + attrib *attrs = NULL; + curse *c, *result; + int cid; + + curse_type ct_dummy = { "dummy", CURSETYP_NORM, 0, M_SUMEFFECT, NULL }; + c = create_curse(NULL, &attrs, &ct_dummy, 1.0, 1, 1, 1); + cid = c->no; + result = findcurse(cid); + CuAssertPtrEquals(tc, c, result); + destroy_curse(c); + result = findcurse(cid); + CuAssertPtrEquals(tc, NULL, result); +} + +CuSuite *get_curse_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_curse); + return suite; +} diff --git a/core/src/kernel/equipment.c b/core/src/kernel/equipment.c new file mode 100644 index 000000000..42c505499 --- /dev/null +++ b/core/src/kernel/equipment.c @@ -0,0 +1,218 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "equipment.h" + +/* kernel includes */ +#include "item.h" +#include "unit.h" +#include "faction.h" +#include "race.h" +#include "spellbook.h" + +/* util includes */ +#include +#include +#include + +/* libc includes */ +#include +#include + +static equipment *equipment_sets; + +equipment *create_equipment(const char *eqname) +{ + equipment **eqp = &equipment_sets; + for (;;) { + struct equipment *eq = *eqp; + int i = eq ? strcmp(eq->name, eqname) : 1; + if (i > 0) { + eq = (equipment *)calloc(1, sizeof(equipment)); + eq->name = strdup(eqname); + eq->next = *eqp; + memset(eq->skills, 0, sizeof(eq->skills)); + *eqp = eq; + break; + } else if (i == 0) { + break; + } + eqp = &eq->next; + } + return *eqp; +} + +equipment *get_equipment(const char *eqname) +{ + equipment *eq = equipment_sets; + for (; eq; eq = eq->next) { + int i = strcmp(eq->name, eqname); + if (i == 0) + return eq; + else if (i > 0) + break; + } + return NULL; +} + +void equipment_setskill(equipment * eq, skill_t sk, const char *value) +{ + if (eq != NULL) { + if (value != NULL) { + eq->skills[sk] = strdup(value); + } else if (eq->skills[sk]) { + free(eq->skills[sk]); + } + } +} + +void equipment_addspell(equipment * eq, struct spell * sp, int level) +{ + if (eq) { + if (!eq->spellbook) { + eq->spellbook = create_spellbook(0); + } + spellbook_add(eq->spellbook, sp, level); + } +} + +void +equipment_setitem(equipment * eq, const item_type * itype, const char *value) +{ + if (eq != NULL) { + if (itype != NULL) { + itemdata *idata = eq->items; + while (idata && idata->itype != itype) { + idata = idata->next; + } + if (idata == NULL) { + idata = (itemdata *) malloc(sizeof(itemdata)); + idata->itype = itype; + idata->value = strdup(value); + idata->next = eq->items; + eq->items = idata; + } + } + } +} + +void +equipment_setcallback(struct equipment *eq, + void (*callback) (const struct equipment *, struct unit *)) +{ + eq->callback = callback; +} + +void equip_unit(struct unit *u, const struct equipment *eq) +{ + equip_unit_mask(u, eq, EQUIP_ALL); +} + +void equip_unit_mask(struct unit *u, const struct equipment *eq, int mask) +{ + if (eq) { + + if (mask & EQUIP_SKILLS) { + int sk; + for (sk = 0; sk != MAXSKILLS; ++sk) { + if (eq->skills[sk] != NULL) { + int i = dice_rand(eq->skills[sk]); + if (i > 0) + set_level(u, (skill_t)sk, i); + } + } + } + + if (mask & EQUIP_SPELLS) { + if (eq->spellbook) { + quicklist * ql = eq->spellbook->spells; + int qi; + sc_mage * mage = get_mage(u); + + for (qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry *sbe = (spellbook_entry *) ql_get(ql, qi); + unit_add_spell(u, mage, sbe->sp, sbe->level); + } + } + } + + if (mask & EQUIP_ITEMS) { + itemdata *idata; + for (idata = eq->items; idata != NULL; idata = idata->next) { + int i = u->number * dice_rand(idata->value); + if (i > 0) { + i_add(&u->items, i_new(idata->itype, i)); + } + } + } + + if (eq->subsets) { + int i; + for (i = 0; eq->subsets[i].sets; ++i) { + if (chance(eq->subsets[i].chance)) { + float rnd = (1 + rng_int() % 1000) / 1000.0f; + int k; + for (k = 0; eq->subsets[i].sets[k].set; ++k) { + if (rnd <= eq->subsets[i].sets[k].chance) { + equip_unit_mask(u, eq->subsets[i].sets[k].set, mask); + break; + } + rnd -= eq->subsets[i].sets[k].chance; + } + } + } + } + + if (mask & EQUIP_SPECIAL) { + if (eq->callback) + eq->callback(eq, u); + } + } +} + +void equip_items(struct item **items, const struct equipment *eq) +{ + if (eq) { + itemdata *idata; + + for (idata = eq->items; idata != NULL; idata = idata->next) { + int i = dice_rand(idata->value); + if (i > 0) { + i_add(items, i_new(idata->itype, i)); + } + } + if (eq->subsets) { + int i; + for (i = 0; eq->subsets[i].sets; ++i) { + if (chance(eq->subsets[i].chance)) { + float rnd = (1 + rng_int() % 1000) / 1000.0f; + int k; + for (k = 0; eq->subsets[i].sets[k].set; ++k) { + if (rnd <= eq->subsets[i].sets[k].chance) { + equip_items(items, eq->subsets[i].sets[k].set); + break; + } + rnd -= eq->subsets[i].sets[k].chance; + } + } + } + } + } +} diff --git a/core/src/kernel/equipment.h b/core/src/kernel/equipment.h new file mode 100644 index 000000000..c6fd146c6 --- /dev/null +++ b/core/src/kernel/equipment.h @@ -0,0 +1,76 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_EQUIPMENT_H +#define H_KRNL_EQUIPMENT_H +#ifdef __cplusplus +extern "C" { +#endif + struct spell; + + typedef struct itemdata { + const struct item_type *itype; + char *value; + struct itemdata *next; + } itemdata; + + typedef struct subsetitem { + struct equipment *set; + float chance; + } subsetitem; + + typedef struct subset { + float chance; + subsetitem *sets; + } subset; + + typedef struct equipment { + char *name; + struct itemdata *items; + char *skills[MAXSKILLS]; + struct spellbook *spellbook; + struct subset *subsets; + struct equipment *next; + void (*callback) (const struct equipment *, struct unit *); + } equipment; + + extern struct equipment *create_equipment(const char *eqname); + extern struct equipment *get_equipment(const char *eqname); + + extern void equipment_setitem(struct equipment *eq, + const struct item_type *itype, const char *value); + extern void equipment_setskill(struct equipment *eq, skill_t sk, + const char *value); + extern void equipment_addspell(struct equipment *eq, struct spell *sp, int level); + extern void equipment_setcallback(struct equipment *eq, + void (*callback) (const struct equipment *, struct unit *)); + + extern void equip_unit(struct unit *u, const struct equipment *eq); +#define EQUIP_SKILLS (1<<1) +#define EQUIP_SPELLS (1<<2) +#define EQUIP_ITEMS (1<<3) +#define EQUIP_SPECIAL (1<<4) +#define EQUIP_ALL (0xFF) + extern void equip_unit_mask(struct unit *u, const struct equipment *eq, + int mask); + extern void equip_items(struct item **items, const struct equipment *eq); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/equipment_test.c b/core/src/kernel/equipment_test.c new file mode 100644 index 000000000..a526c5abc --- /dev/null +++ b/core/src/kernel/equipment_test.c @@ -0,0 +1,56 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +void test_equipment(CuTest * tc) +{ + equipment * eq; + unit * u; + const item_type * it_horses; + const char * names[] = {"horse", "horse_p"}; + spell *sp; + sc_mage * mage; + + test_cleanup(); + skill_enabled[SK_MAGIC] = 1; + it_horses = test_create_itemtype(names); + CuAssertPtrNotNull(tc, it_horses); + sp = create_spell("testspell", 0); + CuAssertPtrNotNull(tc, sp); + + CuAssertPtrEquals(tc, 0, get_equipment("herpderp")); + eq = create_equipment("herpderp"); + CuAssertPtrEquals(tc, eq, get_equipment("herpderp")); + + equipment_setitem(eq, it_horses, "1"); + equipment_setskill(eq, SK_MAGIC, "5"); + equipment_addspell(eq, sp, 1); + + u = test_create_unit(0, 0); + equip_unit_mask(u, eq, EQUIP_ALL); + CuAssertIntEquals(tc, 1, i_get(u->items, it_horses)); + CuAssertIntEquals(tc, 5, get_level(u, SK_MAGIC)); + + mage = get_mage(u); + CuAssertPtrNotNull(tc, mage); + CuAssertPtrNotNull(tc, mage->spellbook); + CuAssertTrue(tc, u_hasspell(u, sp)); +} + +CuSuite *get_equipment_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_equipment); + return suite; +} diff --git a/core/src/kernel/faction.c b/core/src/kernel/faction.c new file mode 100755 index 000000000..e90a44264 --- /dev/null +++ b/core/src/kernel/faction.c @@ -0,0 +1,528 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "faction.h" + +#include "alliance.h" +#include "ally.h" +#include "equipment.h" +#include "group.h" +#include "item.h" +#include "message.h" +#include "plane.h" +#include "race.h" +#include "region.h" +#include "spellbook.h" +#include "terrain.h" +#include "unit.h" +#include "version.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +faction *factions; + +/** remove the faction from memory. + * this frees all memory that's only accessible through the faction, + * but you should still call funhash and remove the faction from the + * global list. + */ +void free_faction(faction * f) +{ + if (f->msgs) + free_messagelist(f->msgs); + while (f->battles) { + struct bmsg *bm = f->battles; + f->battles = bm->next; + if (bm->msgs) + free_messagelist(bm->msgs); + free(bm); + } + + while (f->groups) { + group *g = f->groups; + f->groups = g->next; + free_group(g); + } + freelist(f->allies); + + free(f->email); + free(f->banner); + free(f->passw); + free(f->override); + free(f->name); + + while (f->attribs) { + a_remove(&f->attribs, f->attribs); + } + + i_freeall(&f->items); + + freelist(f->ursprung); +} + +faction *get_monsters(void) +{ + static faction *monsters; + static int gamecookie = -1; + + if (gamecookie != global.cookie) { + monsters = NULL; + gamecookie = global.cookie; + } + + if (!monsters) { + faction *f; + for (f = factions; f; f = f->next) { + if (f->flags & FFL_NPC) { + return monsters = f; + } + } + if (!monsters) { + /* shit! */ + monsters = findfaction(666); + } + if (monsters) { + fset(monsters, FFL_NPC | FFL_NOIDLEOUT); + } + } + return monsters; +} + +const unit *random_unit_in_faction(const faction * f) +{ + unit *u; + int c = 0, u_nr; + + for (u = f->units; u; u = u->next) + c++; + + u_nr = rng_int() % c; + c = 0; + + for (u = f->units; u; u = u->next) + if (u_nr == c) + return u; + + /* Hier sollte er nie ankommen */ + return NULL; +} + +const char *factionname(const faction * f) +{ + typedef char name[OBJECTIDSIZE + 1]; + static name idbuf[8]; + static int nextbuf = 0; + + char *ibuf = idbuf[(++nextbuf) % 8]; + + if (f && f->name) { + slprintf(ibuf, sizeof(name), "%s (%s)", f->name, itoa36(f->no)); + } else { + strcpy(ibuf, "Unbekannte Partei (?)"); + } + return ibuf; +} + +int resolve_faction(variant id, void *address) +{ + int result = 0; + faction *f = NULL; + if (id.i != 0) { + f = findfaction(id.i); + if (f == NULL) { + result = -1; + } + } + *(faction **) address = f; + return result; +} + +#define MAX_FACTION_ID (36*36*36*36) + +static int unused_faction_id(void) +{ + int id = rng_int() % MAX_FACTION_ID; + + while (!faction_id_is_unused(id)) { + id++; + if (id == MAX_FACTION_ID) + id = 0; + } + + return id; +} + +faction *addfaction(const char *email, const char *password, + const struct race * frace, const struct locale * loc, int subscription) +{ + faction *f = calloc(sizeof(faction), 1); + char buf[128]; + + assert(frace); + + if (set_email(&f->email, email) != 0) { + log_error("Invalid email address for faction %s: %s\n", itoa36(f->no), email); + } + + f->override = strdup(itoa36(rng_int())); + faction_setpassword(f, password); + + f->alliance_joindate = turn; + f->lastorders = turn; + f->alive = 1; + f->age = 0; + f->race = frace; + f->magiegebiet = 0; + f->locale = loc; + f->subscription = subscription; + + f->options = + want(O_REPORT) | want(O_ZUGVORLAGE) | want(O_COMPUTER) | want(O_COMPRESS) | + want(O_ADRESSEN) | want(O_STATISTICS); + + f->no = unused_faction_id(); + if (rule_region_owners()) { + alliance *al = makealliance(f->no, NULL); + setalliance(f, al); + } + addlist(&factions, f); + fhash(f); + + slprintf(buf, sizeof(buf), "%s %s", LOC(loc, "factiondefault"), factionid(f)); + f->name = strdup(buf); + + return f; +} + +unit *addplayer(region * r, faction * f) +{ + unit *u; + char buffer[32]; + + assert(f->units == NULL); + set_ursprung(f, 0, r->x, r->y); + u = createunit(r, f, 1, f->race); + equip_items(&u->faction->items, get_equipment("new_faction")); + equip_unit(u, get_equipment("first_unit")); + sprintf(buffer, "first_%s", u_race(u)->_name[0]); + equip_unit(u, get_equipment(buffer)); + u->hp = unit_max_hp(u) * u->number; + fset(u, UFL_ISNEW); + if (f->race == new_race[RC_DAEMON]) { + race_t urc; + do { + urc = (race_t) (rng_int() % MAXRACES); + } while (new_race[urc] == NULL || urc == RC_DAEMON + || !playerrace(new_race[urc])); + u->irace = new_race[urc]; + } + + return u; +} + +bool checkpasswd(const faction * f, const char *passwd, bool shortp) +{ + if (unicode_utf8_strcasecmp(f->passw, passwd) == 0) + return true; + if (unicode_utf8_strcasecmp(f->override, passwd) == 0) + return true; + return false; +} + +variant read_faction_reference(struct storage * store) +{ + variant id; + id.i = store->r_id(store); + return id; +} + +void write_faction_reference(const faction * f, struct storage *store) +{ + store->w_id(store, f ? f->no : 0); +} + +void destroyfaction(faction * f) +{ + unit *u = f->units; + faction *ff; + + if (!f->alive) { + return; + } + fset(f, FFL_QUIT); + + if (f->spellbook) { + spellbook_clear(f->spellbook); + free(f->spellbook); + f->spellbook = 0; + } + while (f->battles) { + struct bmsg *bm = f->battles; + f->battles = bm->next; + if (bm->msgs) { + free_messagelist(bm->msgs); + } + free(bm); + } + + while (u) { + /* give away your stuff, make zombies if you cannot (quest items) */ + int result = gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS); + if (result != 0) { + unit *zombie = u; + u = u->nextF; + make_zombie(zombie); + } else { + region *r = u->region; + + if (!fval(r->terrain, SEA_REGION) && !!playerrace(u_race(u))) { + const race *rc = u_race(u); + int m = rmoney(r); + + if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) { + int p = rpeasants(u->region); + int h = rhorses(u->region); + item *itm; + + /* Personen gehen nur an die Bauern, wenn sie auch von dort + * stammen */ + if (rc->ec_flags & ECF_REC_HORSES) { /* Zentauren an die Pferde */ + h += u->number; + } else { /* Orks zählen nur zur Hälfte */ + p += (int)(u->number * rc->recruit_multi); + } + for (itm = u->items; itm; itm = itm->next) { + if (itm->type->flags & ITF_ANIMAL) { + h += itm->number; + } + } + rsetpeasants(r, p); + rsethorses(r, h); + } + m += get_money(u); + rsetmoney(r, m); + } + set_number(u, 0); + u = u->nextF; + } + } + f->alive = 0; +/* no way! f->units = NULL; */ + handle_event(f->attribs, "destroy", f); + for (ff = factions; ff; ff = ff->next) { + group *g; + ally *sf, *sfn; + + /* Alle HELFE für die Partei löschen */ + for (sf = ff->allies; sf; sf = sf->next) { + if (sf->faction == f) { + removelist(&ff->allies, sf); + break; + } + } + for (g = ff->groups; g; g = g->next) { + for (sf = g->allies; sf;) { + sfn = sf->next; + if (sf->faction == f) { + removelist(&g->allies, sf); + break; + } + sf = sfn; + } + } + } + + /* units of other factions that were disguised as this faction + * have their disguise replaced by ordinary faction hiding. */ + if (rule_stealth_faction()) { + region *rc; + for (rc = regions; rc; rc = rc->next) { + for (u = rc->units; u; u = u->next) { + attrib *a = a_find(u->attribs, &at_otherfaction); + if (!a) + continue; + if (get_otherfaction(a) == f) { + a_removeall(&u->attribs, &at_otherfaction); + fset(u, UFL_ANON_FACTION); + } + } + } + } +} + +int get_alliance(const faction * a, const faction * b) +{ + const ally *sf = a->allies; + for (; sf != NULL; sf = sf->next) { + if (sf->faction == b) { + return sf->status; + } + } + return 0; +} + +void set_alliance(faction * a, faction * b, int status) +{ + ally **sfp; + sfp = &a->allies; + while (*sfp) { + ally *sf = *sfp; + if (sf->faction == b) + break; + sfp = &sf->next; + } + if (*sfp == NULL) { + ally *sf = *sfp = malloc(sizeof(ally)); + sf->next = NULL; + sf->status = status; + sf->faction = b; + return; + } + (*sfp)->status |= status; +} + +void renumber_faction(faction * f, int no) +{ + if (f->subscription) { + sql_print(("UPDATE subscriptions set faction='%s' where id=%u;\n", + itoa36(no), f->subscription)); + } + funhash(f); + f->no = no; + fhash(f); + fset(f, FFL_NEWID); +} + +#ifdef SMART_INTERVALS +void update_interval(struct faction *f, struct region *r) +{ + if (r == NULL || f == NULL) + return; + if (f->first == NULL || f->first->index > r->index) { + f->first = r; + } + if (f->last == NULL || f->last->index <= r->index) { + f->last = r; + } +} +#endif + +const char *faction_getname(const faction * self) +{ + return self->name ? self->name : ""; +} + +void faction_setname(faction * self, const char *name) +{ + free(self->name); + if (name) + self->name = strdup(name); +} + +const char *faction_getemail(const faction * self) +{ + return self->email ? self->email : ""; +} + +void faction_setemail(faction * self, const char *email) +{ + free(self->email); + if (email) + self->email = strdup(email); +} + +const char *faction_getbanner(const faction * self) +{ + return self->banner ? self->banner : ""; +} + +void faction_setbanner(faction * self, const char *banner) +{ + free(self->banner); + if (banner) + self->banner = strdup(banner); +} + +void faction_setpassword(faction * f, const char *passw) +{ + free(f->passw); + if (passw) + f->passw = strdup(passw); + else + f->passw = strdup(itoa36(rng_int())); +} + +bool valid_race(const struct faction *f, const struct race *rc) +{ + if (f->race == rc) + return true; + else { + const char *str = get_param(f->race->parameters, "other_race"); + if (str) + return (bool) (rc_find(str) == rc); + return false; + } +} + +const char *faction_getpassword(const faction * f) +{ + return f->passw; +} + +struct alliance *f_get_alliance(const struct faction *f) +{ + if (f->alliance && !(f->alliance->flags & ALF_NON_ALLIED)) { + return f->alliance; + } + return NULL; +} + +struct spellbook * faction_get_spellbook(struct faction *f) +{ + if (f->spellbook) { + return f->spellbook; + } + if (f->magiegebiet!=M_GRAY) { + return get_spellbook(magic_school[f->magiegebiet]); + } + return 0; +} diff --git a/core/src/kernel/faction.h b/core/src/kernel/faction.h new file mode 100644 index 000000000..cc7239e82 --- /dev/null +++ b/core/src/kernel/faction.h @@ -0,0 +1,159 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_FACTION +#define H_KRNL_FACTION +#ifdef __cplusplus +extern "C" { +#endif + + struct player; + struct alliance; + struct item; + struct seen_region; + +/* SMART_INTERVALS: define to speed up finding the interval of regions that a + faction is in. defining this speeds up the turn by 30-40% */ +#define SMART_INTERVALS + +/* faction flags */ +#define FFL_NEWID (1<<0) /* Die Partei hat bereits einmal ihre no gewechselt */ +#define FFL_ISNEW (1<<1) +#define FFL_RESTART (1<<2) +#define FFL_QUIT (1<<3) +#define FFL_DEFENDER (1<<10) +#define FFL_SELECT (1<<18) /* ehemals f->dh, u->dh, r->dh, etc... */ +#define FFL_NOAID (1<<21) /* Hilfsflag Kampf */ +#define FFL_MARK (1<<23) /* für markierende algorithmen, die das + * hinterher auch wieder löschen müssen! + * (FFL_SELECT muss man vorher initialisieren, + * FL_MARK hinterher löschen) */ +#define FFL_NOIDLEOUT (1<<24) /* Partei stirbt nicht an NMRs */ +#define FFL_OVERRIDE (1<<27) /* Override-Passwort wurde benutzt */ +#define FFL_DBENTRY (1<<28) /* Partei ist in Datenbank eingetragen */ +#define FFL_NOTIMEOUT (1<<29) /* ignore MaxAge() */ +#define FFL_GM (1<<30) /* eine Partei mit Sonderrechten */ +#define FFL_NPC (1<<31) /* eine Partei mit Monstern */ + +#define FFL_SAVEMASK (FFL_DEFENDER|FFL_NEWID|FFL_GM|FFL_NPC|FFL_NOTIMEOUT|FFL_DBENTRY|FFL_NOTIMEOUT) + + struct faction *get_monsters(void); +#define is_monsters(f) ((f)->flags&FFL_NPC) + + typedef struct faction { + struct faction *next; + struct faction *nexthash; + + struct player *owner; +#ifdef SMART_INTERVALS + struct region *first; + struct region *last; +#endif + int no; + int subscription; + unsigned int flags; + char *name; + char *banner; + char *email; + char *passw; + char *override; + int max_spelllevel; + struct spellbook *spellbook; + const struct locale *locale; + int lastorders; + int age; + struct ursprung *ursprung; + const struct race *race; + magic_t magiegebiet; + int newbies; + int num_people; /* Anzahl Personen ohne Monster */ + int num_total; /* Anzahl Personen mit Monstern */ + int options; + int no_units; + struct ally *allies; + struct group *groups; + bool alive; /* enno: sollte ein flag werden */ + int nregions; + int money; +#if SCORE_MODULE + int score; +#endif + struct alliance *alliance; + int alliance_joindate; /* the turn on which the faction joined its current alliance (or left the last one) */ +#ifdef VICTORY_DELAY + unsigned char victory_delay; +#endif + struct unit *units; + struct attrib *attribs; + struct message_list *msgs; + struct bmsg { + struct bmsg *next; + struct region *r; + struct message_list *msgs; + } *battles; + struct item *items; /* items this faction can claim */ + struct seen_region **seen; + struct quicklist *seen_factions; + } faction; + + extern struct faction *factions; + + extern const struct unit *random_unit_in_faction(const struct faction *f); + extern const char *factionname(const struct faction *f); + extern struct unit *addplayer(struct region *r, faction * f); + extern struct faction *addfaction(const char *email, const char *password, + const struct race *frace, const struct locale *loc, int subscription); + extern bool checkpasswd(const faction * f, const char *passwd, + bool shortp); + extern void destroyfaction(faction * f); + + extern void set_alliance(struct faction *a, struct faction *b, int status); + extern int get_alliance(const struct faction *a, const struct faction *b); + + extern struct alliance *f_get_alliance(const struct faction *f); + + extern void write_faction_reference(const struct faction *f, + struct storage *store); + extern variant read_faction_reference(struct storage *store); + extern int resolve_faction(variant data, void *addr); + + extern void renumber_faction(faction * f, int no); + void free_faction(struct faction *f); + +#ifdef SMART_INTERVALS + extern void update_interval(struct faction *f, struct region *r); +#endif + + const char *faction_getbanner(const struct faction *self); + void faction_setbanner(struct faction *self, const char *name); + + const char *faction_getname(const struct faction *self); + void faction_setname(struct faction *self, const char *name); + + const char *faction_getemail(const struct faction *self); + void faction_setemail(struct faction *self, const char *email); + + const char *faction_getpassword(const struct faction *self); + void faction_setpassword(struct faction *self, const char *password); + bool valid_race(const struct faction *f, const struct race *rc); + + struct spellbook * faction_get_spellbook(struct faction *f); +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/group.c b/core/src/kernel/group.c new file mode 100755 index 000000000..0e49fd4aa --- /dev/null +++ b/core/src/kernel/group.c @@ -0,0 +1,249 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "group.h" + +/* kernel includes */ +#include "ally.h" +#include "faction.h" +#include "save.h" +#include "unit.h" +#include "version.h" + +/* attrib includes */ +#include + +/* util includes */ +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +#define GMAXHASH 2039 +static group *ghash[GMAXHASH]; +static int maxgid; + +static group *new_group(faction * f, const char *name, int gid) +{ + group **gp = &f->groups; + int index = gid % GMAXHASH; + group *g = calloc(sizeof(group), 1); + + while (*gp) + gp = &(*gp)->next; + *gp = g; + + maxgid = MAX(gid, maxgid); + g->name = strdup(name); + g->gid = gid; + + g->nexthash = ghash[index]; + return ghash[index] = g; +} + +static void init_group(faction * f, group * g) +{ + ally *a, **an; + + an = &g->allies; + for (a = f->allies; a; a = a->next) + if (a->faction) { + ally *ga = calloc(sizeof(ally), 1); + *ga = *a; + *an = ga; + an = &ga->next; + } +} + +static group *find_groupbyname(group * g, const char *name) +{ + while (g && unicode_utf8_strcasecmp(name, g->name) != 0) + g = g->next; + return g; +} + +static group *find_group(int gid) +{ + int index = gid % GMAXHASH; + group *g = ghash[index]; + while (g && g->gid != gid) + g = g->nexthash; + return g; +} + +static int read_group(attrib * a, void *owner, struct storage *store) +{ + group *g; + int gid = store->r_int(store); + a->data.v = g = find_group(gid); + if (g != 0) { + g->members++; + return AT_READ_OK; + } + return AT_READ_FAIL; +} + +static void +write_group(const attrib * a, const void *owner, struct storage *store) +{ + group *g = (group *) a->data.v; + store->w_int(store, g->gid); +} + +attrib_type at_group = { /* attribute for units assigned to a group */ +"grp", + DEFAULT_INIT, + DEFAULT_FINALIZE, DEFAULT_AGE, write_group, read_group, ATF_UNIQUE}; + +void free_group(group * g) +{ + int index = g->gid % GMAXHASH; + group **g_ptr = ghash + index; + while (*g_ptr && (*g_ptr)->gid != g->gid) + g_ptr = &(*g_ptr)->nexthash; + assert(*g_ptr == g); + *g_ptr = g->nexthash; + + while (g->allies) { + ally *a = g->allies; + g->allies = a->next; + free(a); + } + free(g->name); + free(g); +} + +group * get_group(const struct unit *u) +{ + if (fval(u, UFL_GROUP)) { + attrib * a = a_find(u->attribs, &at_group); + if (a) { + return (group *) a->data.v; + } + } + return 0; +} + +void set_group(struct unit *u, struct group *g) +{ + attrib *a = NULL; + + if (fval(u, UFL_GROUP)) { + a = a_find(u->attribs, &at_group); + } + + if (a) { + group *og = (group *) a->data.v; + if (og == g) + return; + --og->members; + } + + if (g) { + if (!a) { + a = a_add(&u->attribs, a_new(&at_group)); + fset(u, UFL_GROUP); + } + a->data.v = g; + g->members++; + } else if (a) { + a_remove(&u->attribs, a); + freset(u, UFL_GROUP); + } +} + +bool join_group(unit * u, const char *name) +{ + group *g = NULL; + + if (name && name[0]) { + g = find_groupbyname(u->faction->groups, name); + if (g == NULL) { + g = new_group(u->faction, name, ++maxgid); + init_group(u->faction, g); + } + } + + set_group(u, g); + return true; +} + +void write_groups(struct storage *store, group * g) +{ + while (g) { + ally *a; + store->w_int(store, g->gid); + store->w_str(store, g->name); + for (a = g->allies; a; a = a->next) { + if (a->faction) { + write_faction_reference(a->faction, store); + store->w_int(store, a->status); + } + } + store->w_id(store, 0); + a_write(store, g->attribs, g); + store->w_brk(store); + g = g->next; + } + store->w_int(store, 0); +} + +void read_groups(struct storage *store, faction * f) +{ + for (;;) { + ally **pa; + group *g; + int gid; + char buf[1024]; + + gid = store->r_int(store); + if (gid == 0) + break; + store->r_str_buf(store, buf, sizeof(buf)); + g = new_group(f, buf, gid); + pa = &g->allies; + for (;;) { + ally *a; + variant fid; + fid.i = store->r_id(store); + if (fid.i <= 0) + break; + if (store->version < STORAGE_VERSION && fid.i == 0) + break; + a = malloc(sizeof(ally)); + *pa = a; + pa = &a->next; + a->status = store->r_int(store); + + a->faction = findfaction(fid.i); + if (!a->faction) + ur_add(fid, &a->faction, resolve_faction); + } + *pa = 0; + a_read(store, &g->attribs, g); + } +} diff --git a/core/src/kernel/group.h b/core/src/kernel/group.h new file mode 100755 index 000000000..17b44ab3d --- /dev/null +++ b/core/src/kernel/group.h @@ -0,0 +1,52 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KERNEL_GROUP +#define H_KERNEL_GROUP +#ifdef __cplusplus +extern "C" { +#endif + +/* bitfield value for group::flags */ +#define GFL_ALIVE 0x01 /* There is at least one struct unit in the group */ + + typedef struct group { + struct group *next; + struct group *nexthash; + struct faction *f; + struct attrib *attribs; + char *name; + struct ally *allies; + int flags; + int gid; + int members; + } group; + + extern struct attrib_type at_group; /* attribute for units assigned to a group */ + extern bool join_group(struct unit *u, const char *name); + extern void set_group(struct unit *u, struct group *g); + extern struct group * get_group(const struct unit *u); + extern void free_group(struct group *g); + + extern void write_groups(struct storage *F, struct group *g); + extern void read_groups(struct storage *F, struct faction *f); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/item.c b/core/src/kernel/item.c new file mode 100644 index 000000000..76ed653d8 --- /dev/null +++ b/core/src/kernel/item.c @@ -0,0 +1,1263 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "item.h" + +#include + +#include "alchemy.h" +#include "build.h" +#include "curse.h" +#include "faction.h" +#include "message.h" +#include "pool.h" +#include "race.h" +#include "region.h" +#include "save.h" +#include "skill.h" +#include "terrain.h" +#include "unit.h" + +/* triggers includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +static critbit_tree inames[MAXLOCALES]; +static critbit_tree rnames[MAXLOCALES]; +static critbit_tree cb_resources; +static critbit_tree cb_items; +luxury_type *luxurytypes; +potion_type *potiontypes; + +static int res_changeaura(unit * u, const resource_type * rtype, int delta) +{ + assert(rtype != NULL); + return change_spellpoints(u, delta); +} + +static int res_changeperson(unit * u, const resource_type * rtype, int delta) +{ + assert(rtype != NULL || !"not implemented"); + if (u->number + delta >=0) { + scale_number(u, u->number + delta); + } else { + scale_number(u, 0); + } + return u->number; +} + +static int res_changepermaura(unit * u, const resource_type * rtype, int delta) +{ + assert(rtype != NULL); + return change_maxspellpoints(u, delta); +} + +static int res_changehp(unit * u, const resource_type * rtype, int delta) +{ + assert(rtype != NULL); + u->hp += delta; + return u->hp; +} + +static int res_changepeasants(unit * u, const resource_type * rtype, int delta) +{ + assert(rtype != NULL && u->region->land); + u->region->land->peasants += delta; + return u->region->land->peasants; +} + +static int res_changeitem(unit * u, const resource_type * rtype, int delta) +{ + int num; + if (rtype == oldresourcetype[R_STONE] && u_race(u) == new_race[RC_STONEGOLEM] + && delta <= 0) { + int reduce = delta / GOLEM_STONE; + if (delta % GOLEM_STONE != 0) + --reduce; + scale_number(u, u->number + reduce); + num = u->number; + } else if (rtype == oldresourcetype[R_IRON] + && u_race(u) == new_race[RC_IRONGOLEM] && delta <= 0) { + int reduce = delta / GOLEM_IRON; + if (delta % GOLEM_IRON != 0) + --reduce; + scale_number(u, u->number + reduce); + num = u->number; + } else { + const item_type *itype = resource2item(rtype); + item *i; + assert(itype != NULL); + i = i_change(&u->items, itype, delta); + if (i == NULL) + return 0; + num = i->number; + } + return num; +} + +const char *resourcename(const resource_type * rtype, int flags) +{ + int i = 0; + + if (rtype) { + if (rtype->name) + return rtype->name(rtype, flags); + + if (flags & NMF_PLURAL) + i = 1; + if (flags & NMF_APPEARANCE && rtype->_appearance[i]) { + return rtype->_appearance[i]; + } + return rtype->_name[i]; + } + return "none"; +} + +resource_type *new_resourcetype(const char **names, const char **appearances, + int flags) +{ + resource_type *rtype = rt_find(names[0]); + + if (rtype == NULL) { + int i; + rtype = (resource_type *)calloc(sizeof(resource_type), 1); + + for (i = 0; i != 2; ++i) { + rtype->_name[i] = strdup(names[i]); + if (appearances) + rtype->_appearance[i] = strdup(appearances[i]); + else + rtype->_appearance[i] = NULL; + } + } +#ifndef NDEBUG + else { + /* TODO: check that this is the same type */ + } +#endif + rtype->flags |= flags; + return rtype; +} + +void it_register(item_type * itype) +{ + char buffer[64]; + const char * name = itype->rtype->_name[0]; + size_t len = strlen(name); + + assert(lenrtype); + } +} + +item_type *new_itemtype(resource_type * rtype, + int iflags, int weight, int capacity) +{ + item_type *itype; + assert(resource2item(rtype) == NULL); + assert(rtype->flags & RTF_ITEM); + + itype = calloc(sizeof(item_type), 1); + + itype->rtype = rtype; + rtype->itype = itype; + itype->weight = weight; + itype->capacity = capacity; + itype->flags |= iflags; + it_register(itype); + + rtype->uchange = res_changeitem; + + return itype; +} + +static void lt_register(luxury_type * ltype) +{ + ltype->itype->rtype->ltype = ltype; + ltype->next = luxurytypes; + luxurytypes = ltype; +} + +luxury_type *new_luxurytype(item_type * itype, int price) +{ + luxury_type *ltype; + + assert(resource2luxury(itype->rtype) == NULL); + + ltype = calloc(sizeof(luxury_type), 1); + ltype->itype = itype; + ltype->price = price; + lt_register(ltype); + + return ltype; +} + +weapon_type *new_weapontype(item_type * itype, + int wflags, double magres, const char *damage[], int offmod, int defmod, + int reload, skill_t sk, int minskill) +{ + weapon_type *wtype; + + assert(resource2weapon(itype->rtype) == NULL); + + wtype = calloc(sizeof(weapon_type), 1); + if (damage) { + wtype->damage[0] = strdup(damage[0]); + wtype->damage[1] = strdup(damage[1]); + } + wtype->defmod = defmod; + wtype->flags |= wflags; + wtype->itype = itype; + wtype->magres = magres; + wtype->minskill = minskill; + wtype->offmod = offmod; + wtype->reload = reload; + wtype->skill = sk; + itype->rtype->wtype = wtype; + + return wtype; +} + +armor_type *new_armortype(item_type * itype, double penalty, double magres, + int prot, unsigned int flags) +{ + armor_type *atype; + + assert(itype->rtype->atype == NULL); + + atype = calloc(sizeof(armor_type), 1); + + atype->itype = itype; + atype->penalty = penalty; + atype->magres = magres; + atype->prot = prot; + atype->flags = flags; + itype->rtype->atype = atype; + + return atype; +} + +static void pt_register(potion_type * ptype) +{ + ptype->itype->rtype->ptype = ptype; + ptype->next = potiontypes; + potiontypes = ptype; +} + +potion_type *new_potiontype(item_type * itype, int level) +{ + potion_type *ptype; + + assert(resource2potion(itype->rtype) == NULL); + + ptype = (potion_type *)calloc(sizeof(potion_type), 1); + ptype->itype = itype; + ptype->level = level; + pt_register(ptype); + + return ptype; +} + +void rt_register(resource_type * rtype) +{ + char buffer[64]; + const char * name = rtype->_name[0]; + size_t len = strlen(name); + + assert(lenrtype : NULL; +} + +const item_type *resource2item(const resource_type * rtype) +{ + return rtype ? rtype->itype : NULL; +} + +const weapon_type *resource2weapon(const resource_type * rtype) +{ + return rtype->wtype; +} + +const luxury_type *resource2luxury(const resource_type * rtype) +{ +#ifdef AT_LTYPE + attrib *a = a_find(rtype->attribs, &at_ltype); + if (a) + return (const luxury_type *)a->data.v; + return NULL; +#else + return rtype->ltype; +#endif +} + +const potion_type *resource2potion(const resource_type * rtype) +{ +#ifdef AT_PTYPE + attrib *a = a_find(rtype->attribs, &at_ptype); + if (a) + return (const potion_type *)a->data.v; + return NULL; +#else + return rtype->ptype; +#endif +} + +resource_type *rt_find(const char *name) +{ + const void * matches; + resource_type *result = 0; + + if (cb_find_prefix(&cb_resources, name, strlen(name)+1, &matches, 1, 0)) { + cb_get_kv(matches, &result, sizeof(result)); + } + return result; +} + +static const char *it_aliases[][2] = { + {"Runenschwert", "runesword"}, + {"p12", "truthpotion"}, + {"p1", "goliathwater"}, + {"p4", "ointment"}, + {"p5", "peasantblood"}, + {"p8", "nestwarmth"}, + {"diamond", "adamantium"}, + {"diamondaxe", "adamantiumaxe"}, + {"diamondplate", "adamantiumplate"}, + {"aoh", "ao_healing"}, + {NULL, NULL}, +}; + +static const char *it_alias(const char *zname) +{ + int i; + for (i = 0; it_aliases[i][0]; ++i) { + if (strcmp(it_aliases[i][0], zname) == 0) + return it_aliases[i][1]; + } + return zname; +} + +item_type *it_find(const char *zname) +{ + const char *name = it_alias(zname); + const void * matches; + item_type *result = 0; + + if (cb_find_prefix(&cb_items, name, strlen(name)+1, &matches, 1, 0)) { + cb_get_kv(matches, &result, sizeof(result)); + } + return result; +} + +item **i_find(item ** i, const item_type * it) +{ + while (*i && (*i)->type != it) + i = &(*i)->next; + return i; +} + +item *const *i_findc(item * const *i, const item_type * it) +{ + while (*i && (*i)->type != it) { + i = &(*i)->next; + } + return i; +} + +int i_get(const item * i, const item_type * it) +{ + i = *i_find((item **) & i, it); + if (i) + return i->number; + return 0; +} + +item *i_add(item ** pi, item * i) +{ + assert(i && i->type && !i->next); + while (*pi) { + int d = strcmp((*pi)->type->rtype->_name[0], i->type->rtype->_name[0]); + if (d >= 0) + break; + pi = &(*pi)->next; + } + if (*pi && (*pi)->type == i->type) { + (*pi)->number += i->number; + assert((*pi)->number >= 0); + i_free(i); + } else { + i->next = *pi; + *pi = i; + } + return *pi; +} + +void i_merge(item ** pi, item ** si) +{ + item *i = *si; + while (i) { + item *itmp; + while (*pi) { + int d = strcmp((*pi)->type->rtype->_name[0], i->type->rtype->_name[0]); + if (d >= 0) + break; + pi = &(*pi)->next; + } + if (*pi && (*pi)->type == i->type) { + (*pi)->number += i->number; + assert((*pi)->number >= 0); + i_free(i_remove(&i, i)); + } else { + itmp = i->next; + i->next = *pi; + *pi = i; + i = itmp; + } + } + *si = NULL; +} + +item *i_change(item ** pi, const item_type * itype, int delta) +{ + assert(itype); + while (*pi) { + int d = strcmp((*pi)->type->rtype->_name[0], itype->rtype->_name[0]); + if (d >= 0) + break; + pi = &(*pi)->next; + } + if (!*pi || (*pi)->type != itype) { + item *i; + if (delta == 0) + return NULL; + i = i_new(itype, delta); + i->next = *pi; + *pi = i; + } else { + item *i = *pi; + i->number += delta; + if (i->number < 0) { + log_error("serious accounting error. number of items is %d.\n", i->number); + /* FIXME what's this supposed to mean?? + assert(i >= 0); + */ + i->number = 0; + } + if (i->number == 0) { + *pi = i->next; + i_free(i); + return NULL; + } + } + return *pi; +} + +item *i_remove(item ** pi, item * i) +{ + assert(i); + while ((*pi)->type != i->type) + pi = &(*pi)->next; + assert(*pi); + *pi = i->next; + i->next = NULL; + return i; +} + +static item *icache; +static int icache_size; +#define ICACHE_MAX 100 + +void i_free(item * i) +{ + if (icache_size >= ICACHE_MAX) { + free(i); + } else { + i->next = icache; + icache = i; + ++icache_size; + } +} + +void i_freeall(item ** i) +{ + item *in; + + while (*i) { + in = (*i)->next; + i_free(*i); + *i = in; + } +} + +item *i_new(const item_type * itype, int size) +{ + item *i; + if (icache_size > 0) { + i = icache; + icache = i->next; + --icache_size; + } else { + i = malloc(sizeof(item)); + } + assert(itype); + i->next = NULL; + i->type = itype; + i->number = size; + assert(i->number >= 0); + return i; +} + +#include "region.h" + +static int +give_horses(unit * s, unit * d, const item_type * itype, int n, + struct order *ord) +{ + if (d == NULL) { + int use = use_pooled(s, item2resource(itype), GET_SLACK, n); + if (use < n) + use += + use_pooled(s, item2resource(itype), GET_RESERVE | GET_POOLED_SLACK, + n - use); + rsethorses(s->region, rhorses(s->region) + use); + return 0; + } + return -1; /* use the mechanism */ +} + +static int +give_money(unit * s, unit * d, const item_type * itype, int n, + struct order *ord) +{ + if (d == NULL) { + int use = use_pooled(s, item2resource(itype), GET_SLACK, n); + if (use < n) + use += + use_pooled(s, item2resource(itype), GET_RESERVE | GET_POOLED_SLACK, + n - use); + rsetmoney(s->region, rmoney(s->region) + use); + return 0; + } + return -1; /* use the mechanism */ +} + +#define R_MINOTHER R_SILVER +#define R_MINHERB R_PLAIN_1 +#define R_MINPOTION R_FAST +#define R_MINITEM R_IRON +#define MAXITEMS MAX_ITEMS +#define MAXRESOURCES MAX_RESOURCES +#define MAXHERBS MAX_HERBS +#define MAXPOTIONS MAX_POTIONS +#define MAXHERBSPERPOTION 6 +#define FIRSTLUXURY (I_BALM) +#define LASTLUXURY (I_INCENSE +1) +#define MAXLUXURIES (LASTLUXURY - FIRSTLUXURY) + +const item_type *olditemtype[MAXITEMS + 1]; +const resource_type *oldresourcetype[MAXRESOURCES + 1]; +const potion_type *oldpotiontype[MAXPOTIONS + 1]; + +/*** alte items ***/ + +int get_item(const unit * u, item_t it) +{ + const item_type *type = olditemtype[it]; + const item *i = *i_findc(&u->items, type); + if (i) + assert(i->number >= 0); + return i ? i->number : 0; +} + +int set_item(unit * u, item_t it, int value) +{ + const item_type *type = olditemtype[it]; + item *i; + + assert(type); + i = *i_find(&u->items, type); + if (!i) { + i = i_add(&u->items, i_new(type, value)); + } else { + i->number = value; + assert(i->number >= 0); + } + return value; +} + +static int +use_birthdayamulet(unit * u, const struct item_type *itype, int amount, + struct order *ord) +{ + direction_t d; + message *msg = msg_message("meow", ""); + + unused(ord); + unused(amount); + unused(itype); + + add_message(&u->region->msgs, msg); + for (d = 0; d < MAXDIRECTIONS; d++) { + region *tr = rconnect(u->region, d); + if (tr) + add_message(&tr->msgs, msg); + } + msg_release(msg); + return 0; +} + +/* t_item::flags */ +#define FL_ITEM_CURSED (1<<0) +#define FL_ITEM_NOTLOST (1<<1) +#define FL_ITEM_NOTINBAG (1<<2) /* nicht im Bag Of Holding */ +#define FL_ITEM_ANIMAL (1<<3) /* ist ein Tier */ +#define FL_ITEM_MOUNT ((1<<4) | FL_ITEM_ANIMAL) /* ist ein Reittier */ + +/* ------------------------------------------------------------- */ +/* Kann auch von Nichtmagier benutzt werden, modifiziert Taktik für diese + * Runde um -1 - 4 Punkte. */ +static int +use_tacticcrystal(unit * u, const struct item_type *itype, int amount, + struct order *ord) +{ + int i; + for (i = 0; i != amount; ++i) { + int duration = 1; /* wirkt nur eine Runde */ + float power = 5; /* Widerstand gegen Antimagiesprüche, ist in diesem + Fall egal, da der curse für den Kampf gelten soll, + der vor den Antimagiezaubern passiert */ + curse *c; + double effect; + + effect = rng_int() % 6 - 1; + c = create_curse(u, &u->attribs, ct_find("skillmod"), power, + duration, effect, u->number); + c->data.i = SK_TACTICS; + unused(ord); + } + use_pooled(u, itype->rtype, GET_DEFAULT, amount); + ADDMSG(&u->faction->msgs, msg_message("use_tacticcrystal", + "unit region", u, u->region)); + return 0; +} + +typedef struct t_item { + const char *name; + /* [0]: Einzahl für eigene; [1]: Mehrzahl für eigene; + * [2]: Einzahl für Fremde; [3]: Mehrzahl für Fremde */ + bool is_resource; + skill_t skill; + int minskill; + int gewicht; + int preis; + unsigned int flags; + void (*benutze_funktion) (struct region *, struct unit *, int amount, + struct order *); +} t_item; + +const char *itemnames[MAXITEMS] = { + "iron", "stone", "horse", "ao_healing", + "aots", "roi", "rop", "ao_chastity", + "laen", "fairyboot", "aoc", "pegasus", + "elvenhorse", "dolphin", "roqf", "trollbelt", + "presspass", "aurafocus", "sphereofinv", "magicbag", + "magicherbbag", "dreameye" +}; + +#include "move.h" + +static int +mod_elves_only(const unit * u, const region * r, skill_t sk, int value) +{ + if (u_race(u) == new_race[RC_ELF]) + return value; + unused(r); + return -118; +} + +static int +mod_dwarves_only(const unit * u, const region * r, skill_t sk, int value) +{ + if (u_race(u) == new_race[RC_DWARF]) + return value; + unused(r); + return -118; +} + +static void init_olditems(void) +{ + item_t i; + + for (i = 0; i != MAXITEMS; ++i) { + /* item is defined in XML file, but IT_XYZ enum still in use */ + const item_type *itype = it_find(itemnames[i]); + + if (itype) { + olditemtype[i] = itype; + oldresourcetype[i] = itype->rtype; + } + } +} + +static int heal(unit * user, int effect) +{ + int req = unit_max_hp(user) * user->number - user->hp; + if (req > 0) { + req = MIN(req, effect); + effect -= req; + user->hp += req; + } + return effect; +} + +void +register_item_give(int (*foo) (struct unit *, struct unit *, + const struct item_type *, int, struct order *), const char *name) +{ + register_function((pf_generic) foo, name); +} + +void +register_item_use(int (*foo) (struct unit *, const struct item_type *, int, + struct order *), const char *name) +{ + register_function((pf_generic) foo, name); +} + +void +register_item_useonother(int (*foo) (struct unit *, int, + const struct item_type *, int, struct order *), const char *name) +{ + register_function((pf_generic) foo, name); +} + +static int +use_healingpotion(struct unit *user, const struct item_type *itype, int amount, + struct order *ord) +{ + int effect = amount * 400; + unit *u = user->region->units; + effect = heal(user, effect); + while (effect > 0 && u != NULL) { + if (u->faction == user->faction) { + effect = heal(u, effect); + } + u = u->next; + } + use_pooled(user, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + amount); + usetpotionuse(user, itype->rtype->ptype); + + ADDMSG(&user->faction->msgs, msg_message("usepotion", + "unit potion", user, itype->rtype)); + return 0; +} + +static int +use_warmthpotion(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + if (u->faction->race == new_race[RC_INSECT]) { + fset(u, UFL_WARMTH); + } else { + /* nur für insekten: */ + cmistake(u, ord, 163, MSG_EVENT); + return ECUSTOM; + } + use_pooled(u, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + amount); + usetpotionuse(u, itype->rtype->ptype); + + ADDMSG(&u->faction->msgs, msg_message("usepotion", + "unit potion", u, itype->rtype)); + return 0; +} + +static int +use_foolpotion(struct unit *u, int targetno, const struct item_type *itype, + int amount, struct order *ord) +{ + unit *target = findunit(targetno); + if (target == NULL || u->region != target->region) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + return ECUSTOM; + } + if (effskill(u, SK_STEALTH) <= effskill(target, SK_PERCEPTION)) { + cmistake(u, ord, 64, MSG_EVENT); + return ECUSTOM; + } + ADDMSG(&u->faction->msgs, msg_message("givedumb", + "unit recipient amount", u, target, amount)); + + change_effect(target, itype->rtype->ptype, amount); + use_pooled(u, itype->rtype, GET_DEFAULT, amount); + return 0; +} + +static int +use_bloodpotion(struct unit *u, const struct item_type *itype, int amount, + struct order *ord) +{ + if (u_race(u) == new_race[RC_DAEMON]) { + change_effect(u, itype->rtype->ptype, 100 * amount); + } else { + const race *irace = u_irace(u); + if (irace == u_race(u)) { + static race *rcfailure; + if (!rcfailure) { + rcfailure = rc_find("smurf"); + if (!rcfailure) + rcfailure = rc_find("toad"); + } + if (rcfailure) { + trigger *trestore = trigger_changerace(u, u_race(u), irace); + if (trestore) { + int duration = 2 + rng_int() % 8; + + add_trigger(&u->attribs, "timer", trigger_timeout(duration, + trestore)); + u->irace = NULL; + u_setrace(u, rcfailure); + } + } + } + } + use_pooled(u, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + amount); + usetpotionuse(u, itype->rtype->ptype); + + ADDMSG(&u->faction->msgs, msg_message("usepotion", + "unit potion", u, itype->rtype)); + return 0; +} + +#include +static int +use_mistletoe(struct unit *user, const struct item_type *itype, int amount, + struct order *ord) +{ + int mtoes = + get_pooled(user, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + user->number); + + if (user->number > mtoes) { + ADDMSG(&user->faction->msgs, msg_message("use_singleperson", + "unit item region command", user, itype->rtype, user->region, ord)); + return -1; + } + use_pooled(user, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + user->number); + a_add(&user->attribs, make_fleechance((float)1.0)); + ADDMSG(&user->faction->msgs, + msg_message("use_item", "unit item", user, itype->rtype)); + + return 0; +} + +static int +use_magicboost(struct unit *user, const struct item_type *itype, int amount, + struct order *ord) +{ + int mtoes = + get_pooled(user, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + user->number); + faction *f = user->faction; + if (user->number > mtoes) { + ADDMSG(&user->faction->msgs, msg_message("use_singleperson", + "unit item region command", user, itype->rtype, user->region, ord)); + return -1; + } + if (!is_mage(user) || find_key(f->attribs, atoi36("mbst")) != NULL) { + cmistake(user, user->thisorder, 214, MSG_EVENT); + return -1; + } + use_pooled(user, itype->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, + user->number); + + a_add(&f->attribs, make_key(atoi36("mbst"))); + set_level(user, sk_find("magic"), 3); + + ADDMSG(&user->faction->msgs, msg_message("use_item", + "unit item", user, itype->rtype)); + + return 0; +} + +static int +use_snowball(struct unit *user, const struct item_type *itype, int amount, + struct order *ord) +{ + return 0; +} + +static void init_oldpotions(void) +{ + const char *potionnames[MAX_POTIONS] = { + "p0", "goliathwater", "p2", "p3", "ointment", "peasantblood", "p6", + "p7", "nestwarmth", "p9", "p10", "p11", "truthpotion", "p13", "p14" + }; + int p; + + for (p = 0; p != MAXPOTIONS; ++p) { + item_type *itype = it_find(potionnames[p]); + if (itype != NULL) { + oldpotiontype[p] = itype->rtype->ptype; + } + } +} + +resource_type *r_silver; +resource_type *r_aura; +resource_type *r_permaura; +resource_type *r_unit; +static resource_type *r_hp; + +item_type *i_silver; + +static const char *names[] = { + "money", "money_p", + "person", "person_p", + "permaura", "permaura_p", + "hp", "hp_p", + "peasant", "peasant_p", + "aura", "aura_p", + "unit", "unit_p" +}; + +void init_resources(void) +{ + resource_type *rtype; + if (r_hp) { + return; + } + + rtype = new_resourcetype(names + 8, NULL, RTF_NONE); + rtype->uchange = res_changepeasants; + rt_register(rtype); + + /* silver was never an item: */ + r_silver = new_resourcetype(&names[0], NULL, RTF_ITEM | RTF_POOLED); + i_silver = new_itemtype(r_silver, ITF_NONE, 1 /*weight */ , 0); + r_silver->uchange = res_changeitem; + i_silver->give = give_money; + oldresourcetype[R_SILVER] = r_silver; + + r_permaura = new_resourcetype(&names[4], NULL, RTF_NONE); + r_permaura->uchange = res_changepermaura; + rt_register(r_permaura); + oldresourcetype[R_PERMAURA] = r_permaura; + + r_hp = new_resourcetype(&names[6], NULL, RTF_NONE); + r_hp->uchange = res_changehp; + rt_register(r_hp); + + r_aura = new_resourcetype(&names[10], NULL, RTF_NONE); + r_aura->uchange = res_changeaura; + rt_register(r_aura); + oldresourcetype[R_AURA] = r_aura; + + r_unit = new_resourcetype(&names[12], NULL, RTF_NONE); + r_unit->uchange = res_changeperson; + rt_register(r_unit); + + /* alte typen registrieren: */ + init_olditems(); + init_oldpotions(); +} + +int get_money(const unit * u) +{ + const item *i = u->items; + while (i && i->type != i_silver) + i = i->next; + if (i == NULL) + return 0; + return i->number; +} + +int set_money(unit * u, int v) +{ + item **ip = &u->items; + while (*ip && (*ip)->type != i_silver) + ip = &(*ip)->next; + if ((*ip) == NULL && v) { + i_add(&u->items, i_new(i_silver, v)); + return v; + } + if ((*ip) != NULL) { + if (v) { + (*ip)->number = v; + assert((*ip)->number >= 0); + } else + i_remove(ip, *ip); + } + return v; +} + +int change_money(unit * u, int v) +{ + item **ip = &u->items; + while (*ip && (*ip)->type != i_silver) + ip = &(*ip)->next; + if ((*ip) == NULL && v) { + i_add(&u->items, i_new(i_silver, v)); + return v; + } + if ((*ip) != NULL) { + item *i = *ip; + if (i->number + v != 0) { + i->number += v; + assert(i->number >= 0); + return i->number; + } else + i_free(i_remove(ip, *ip)); + } + return 0; +} + +static int add_resourcename_cb(const void * match, const void * key, size_t keylen, void *data) +{ + struct locale * lang = (struct locale *)data; + int i = locale_index(lang); + critbit_tree * cb = rnames+i; + resource_type *rtype; + + cb_get_kv(match, &rtype, sizeof(rtype)); + for (i = 0; i!=2;++i) { + char buffer[128]; + const char * name = locale_string(lang, rtype->_name[i]); + + if (name && transliterate(buffer, sizeof(buffer), name)) { + size_t len = strlen(buffer); + assert(len+sizeof(rtype)root) { + /* first-time initialization of resource names for this locale */ + cb_foreach(&cb_resources, "", 0, add_resourcename_cb, (void *)lang); + } + if (cb_find_prefix(cb, buffer, strlen(buffer), &match, 1, 0)) { + const resource_type * rtype = 0; + cb_get_kv(match, (void*)&rtype, sizeof(rtype)); + return rtype; + } + } else { + log_debug("findresourcetype: transliterate failed for '%s'\n", name); + } + return 0; +} + +attrib_type at_showitem = { + "showitem" +}; + +static int add_itemname_cb(const void * match, const void * key, size_t keylen, void *data) +{ + struct locale * lang = (struct locale *)data; + int i = locale_index(lang); + critbit_tree * cb = inames+i; + item_type *itype; + + cb_get_kv(match, &itype, sizeof(itype)); + for (i = 0; i!=2;++i) { + char buffer[128]; + const char * name = locale_string(lang, itype->rtype->_name[i]); + + if (name && transliterate(buffer, sizeof(buffer), name)) { + size_t len = strlen(buffer); + assert(len+sizeof(itype)root) { + /* first-time initialization of item names for this locale */ + cb_foreach(&cb_items, "", 0, add_itemname_cb, (void *)lang); + } + if (cb_find_prefix(cb, buffer, strlen(buffer), &match, 1, 0)) { + const item_type * itype = 0; + cb_get_kv(match, (void*)&itype, sizeof(itype)); + return itype; + } + } else { + log_debug("finditemtype: transliterate failed for '%s'\n", name); + } + return 0; +} + +static void init_resourcelimit(attrib * a) +{ + a->data.v = calloc(sizeof(resource_limit), 1); +} + +static void finalize_resourcelimit(attrib * a) +{ + free(a->data.v); +} + +attrib_type at_resourcelimit = { + "resourcelimit", + init_resourcelimit, + finalize_resourcelimit, +}; + +static item *default_spoil(const struct race *rc, int size) +{ + item *itm = NULL; + + if (rng_int() % 100 < RACESPOILCHANCE) { + char spoilname[32]; + const item_type *itype; + + sprintf(spoilname, "%sspoil", rc->_name[0]); + itype = it_find(spoilname); + if (itype != NULL) { + i_add(&itm, i_new(itype, size)); + } + } + return itm; +} + +#ifndef DISABLE_TESTS +int free_itype_cb(const void * match, const void * key, size_t keylen, void *cbdata) { + item_type *itype; + cb_get_kv(match, &itype, sizeof(itype)); + free(itype->construction); + free(itype); + return 0; +} + +int free_rtype_cb(const void * match, const void * key, size_t keylen, void *cbdata) { + resource_type *rtype; + cb_get_kv(match, &rtype, sizeof(rtype)); + free(rtype->_name[0]); + free(rtype->_name[1]); + free(rtype->_appearance[0]); + free(rtype->_appearance[1]); + free(rtype); + return 0; +} + +void test_clear_resources(void) +{ + int i; + + memset((void *)olditemtype, 0, sizeof(olditemtype)); + memset((void *)oldresourcetype, 0, sizeof(oldresourcetype)); + memset((void *)oldpotiontype, 0, sizeof(oldpotiontype)); + + cb_foreach(&cb_items, "", 0, free_itype_cb, 0); + cb_clear(&cb_items); + cb_foreach(&cb_resources, "", 0, free_rtype_cb, 0); + cb_clear(&cb_resources); + + r_hp = r_silver = r_aura = r_permaura = r_unit = 0; + i_silver = 0; + + for (i=0; i!=MAXLOCALES; ++i) { + cb_clear(inames+i); + } +} +#endif + +void register_resources(void) +{ + static bool registered = false; + if (registered) return; + registered = true; + + register_function((pf_generic) mod_elves_only, "mod_elves_only"); + register_function((pf_generic) mod_dwarves_only, "mod_dwarves_only"); + register_function((pf_generic) res_changeitem, "changeitem"); + register_function((pf_generic) res_changeperson, "changeperson"); + register_function((pf_generic) res_changepeasants, "changepeasants"); + register_function((pf_generic) res_changepermaura, "changepermaura"); + register_function((pf_generic) res_changehp, "changehp"); + register_function((pf_generic) res_changeaura, "changeaura"); + register_function((pf_generic) default_spoil, "defaultdrops"); + + register_item_use(use_potion, "usepotion"); + register_item_use(use_potion_delayed, "usepotion_delayed"); + register_item_use(use_tacticcrystal, "use_tacticcrystal"); + register_item_use(use_birthdayamulet, "use_birthdayamulet"); + register_item_use(use_warmthpotion, "usewarmthpotion"); + register_item_use(use_bloodpotion, "usebloodpotion"); + register_item_use(use_healingpotion, "usehealingpotion"); + register_item_useonother(use_foolpotion, "usefoolpotion"); + register_item_use(use_mistletoe, "usemistletoe"); + register_item_use(use_magicboost, "usemagicboost"); + register_item_use(use_snowball, "usesnowball"); + + register_item_give(give_horses, "givehorses"); + + /* make sure noone has deleted an I_ tpe without deleting the R_ type that goes with it! */ + assert((int)I_TACTICCRYSTAL == (int)R_TACTICCRYSTAL); +} diff --git a/core/src/kernel/item.h b/core/src/kernel/item.h new file mode 100644 index 000000000..be6d18272 --- /dev/null +++ b/core/src/kernel/item.h @@ -0,0 +1,369 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_ITEM +#define H_KRNL_ITEM + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + struct unit; + struct attrib; + struct attrib_type; + struct region; + struct resource_type; + struct locale; + struct troop; + + typedef struct item { + struct item *next; + const struct item_type *type; + int number; + } item; + + typedef struct resource { + const struct resource_type *type; + int number; + struct resource *next; + } resource; + +/* bitfield values for resource_type::flags */ +#define RTF_NONE 0 +#define RTF_ITEM (1<<0) /* this resource is an item */ +#define RTF_LIMITED (1<<1) /* a resource that's freely available, but in + * limited supply */ +#define RTF_POOLED (1<<2) /* resource is available in pool */ + +/* flags for resource_type::name() */ +#define NMF_PLURAL 0x01 +#define NMF_APPEARANCE 0x02 + + typedef int (*rtype_uchange) (struct unit * user, + const struct resource_type * rtype, int delta); + typedef int (*rtype_uget) (const struct unit * user, + const struct resource_type * rtype); + typedef char *(*rtype_name) (const struct resource_type * rtype, int flags); + typedef struct resource_type { + /* --- constants --- */ + char *_name[2]; /* wie es heißt */ + char *_appearance[2]; /* wie es für andere aussieht */ + unsigned int flags; + /* --- functions --- */ + rtype_uchange uchange; + rtype_uget uget; + rtype_name name; + /* --- pointers --- */ + struct attrib *attribs; + struct item_type *itype; + struct potion_type *ptype; + struct luxury_type *ltype; + struct weapon_type *wtype; + struct armor_type *atype; + } resource_type; + + extern const char *resourcename(const resource_type * rtype, int flags); + extern const resource_type *findresourcetype(const char *name, + const struct locale *lang); + +/* resource-limits for regions */ +#define RMF_SKILL 0x01 /* int, bonus on resource production skill */ +#define RMF_SAVEMATERIAL 0x02 /* float, multiplier on resource usage */ +#define RMF_SAVERESOURCE 0x03 /* int, bonus on resource production skill */ +#define RMF_REQUIREDBUILDING 0x04 /* building, required to build */ + + typedef struct resource_mod { + variant value; + const struct building_type *btype; + const struct race *race; + unsigned int flags; + } resource_mod; + + extern struct attrib_type at_resourcelimit; + typedef int (*rlimit_limit) (const struct region * r, + const struct resource_type * rtype); + typedef void (*rlimit_produce) (struct region * r, + const struct resource_type * rtype, int n); + typedef struct resource_limit { + rlimit_limit limit; + rlimit_produce produce; + unsigned int guard; /* how to guard against theft */ + int value; + resource_mod *modifiers; + } resource_limit; + +/* bitfield values for item_type::flags */ +#define ITF_NONE 0x0000 +#define ITF_HERB 0x0001 /* this item is a herb */ +#define ITF_CURSED 0x0010 /* cursed object, cannot be given away */ +#define ITF_NOTLOST 0x0020 /* special object (quests), cannot be lost through death etc. */ +#define ITF_BIG 0x0040 /* big item, e.g. does not fit in a bag of holding */ +#define ITF_ANIMAL 0x0080 /* an animal */ +#define ITF_VEHICLE 0x0100 /* a vehicle, drawn by two animals */ + +/* error codes for item_type::use */ +#define ECUSTOM -1 +#define ENOITEM -2 +#define ENOSKILL -3 +#define EUNUSABLE -4 + + typedef struct item_type { + resource_type *rtype; + /* --- constants --- */ + unsigned int flags; + int weight; + int capacity; + struct construction *construction; + /* --- functions --- */ + bool(*canuse) (const struct unit * user, + const struct item_type * itype); + int (*use) (struct unit * user, const struct item_type * itype, int amount, + struct order * ord); + int (*useonother) (struct unit * user, int targetno, + const struct item_type * itype, int amount, struct order * ord); + int (*give) (struct unit * src, struct unit * dest, + const struct item_type * itm, int number, struct order * ord); +#if SCORE_MODULE + int score; +#endif + } item_type; + + extern const item_type *finditemtype(const char *name, const struct locale *lang); + + typedef struct luxury_type { + struct luxury_type *next; + const item_type *itype; + int price; + } luxury_type; + extern luxury_type *luxurytypes; + + typedef struct potion_type { + struct potion_type *next; + const item_type *itype; + int level; + } potion_type; + extern potion_type *potiontypes; + +#define WMF_WALKING 0x0001 +#define WMF_RIDING 0x0002 +#define WMF_ANYONE 0x000F /* convenience */ + +#define WMF_AGAINST_RIDING 0x0010 +#define WMF_AGAINST_WALKING 0x0020 +#define WMF_AGAINST_ANYONE 0x00F0 /* convenience */ + +#define WMF_OFFENSIVE 0x0100 +#define WMF_DEFENSIVE 0x0200 +#define WMF_ANYTIME 0x0F00 /* convenience */ + +#define WMF_DAMAGE 0x1000 +#define WMF_SKILL 0x2000 +#define WMF_MISSILE_TARGET 0x4000 + + struct race_list; + typedef struct weapon_mod { + int value; + unsigned int flags; + struct race_list *races; + } weapon_mod; + +#define ATF_NONE 0x00 +#define ATF_SHIELD 0x01 +#define ATF_LAEN 0x02 + + typedef struct armor_type { + const item_type *itype; + double penalty; + double magres; + int prot; + float projectile; /* chance, dass ein projektil abprallt */ + unsigned int flags; + } armor_type; + +#define WTF_NONE 0x00 +#define WTF_MISSILE 0x01 +#define WTF_MAGICAL 0x02 +#define WTF_PIERCE 0x04 +#define WTF_CUT 0x08 +#define WTF_BLUNT 0x10 +#define WTF_SIEGE 0x20 +#define WTF_ARMORPIERCING 0x40 /* armor has only half value */ +#define WTF_HORSEBONUS 0x80 +#define WTF_USESHIELD 0x100 + + typedef struct weapon_type { + const item_type *itype; + const char *damage[2]; + unsigned int flags; + skill_t skill; + int minskill; + int offmod; + int defmod; + double magres; + int reload; /* time to reload this weapon */ + weapon_mod *modifiers; + /* --- functions --- */ + bool(*attack) (const struct troop *, const struct weapon_type *, + int *deaths); + } weapon_type; + + extern void rt_register(resource_type * it); + extern resource_type *rt_find(const char *name); + extern item_type *it_find(const char *name); + + extern void it_register(item_type * it); + extern void wt_register(weapon_type * wt); + + extern const item_type *resource2item(const resource_type * rtype); + extern const resource_type *item2resource(const item_type * i); + + extern const weapon_type *resource2weapon(const resource_type * i); + extern const potion_type *resource2potion(const resource_type * i); + extern const luxury_type *resource2luxury(const resource_type * i); + + extern item **i_find(item ** pi, const item_type * it); + extern item *const *i_findc(item * const *pi, const item_type * it); + extern item *i_add(item ** pi, item * it); + extern void i_merge(item ** pi, item ** si); + extern item *i_remove(item ** pi, item * it); + extern void i_free(item * i); + extern void i_freeall(item ** i); + extern item *i_new(const item_type * it, int number); + +/* convenience: */ + extern item *i_change(item ** items, const item_type * it, int delta); + extern int i_get(const item * i, const item_type * it); + +/* creation */ + extern resource_type *new_resourcetype(const char **names, + const char **appearances, int flags); + extern item_type *new_itemtype(resource_type * rtype, int iflags, int weight, + int capacity); + extern luxury_type *new_luxurytype(item_type * itype, int price); + extern weapon_type *new_weapontype(item_type * itype, int wflags, + double magres, const char *damage[], int offmod, int defmod, int reload, + skill_t sk, int minskill); + extern armor_type *new_armortype(item_type * itype, double penalty, + double magres, int prot, unsigned int flags); + extern potion_type *new_potiontype(item_type * itype, int level); + +/* for lack of another file: */ + +/* sonstige resourcen */ + extern resource_type *r_silver; + extern resource_type *r_aura; + extern resource_type *r_permaura; + extern resource_type *r_unit; + + enum { + I_IRON, /* 0 */ + I_STONE, + I_HORSE, + /* alte Artefakte */ + I_AMULET_OF_HEALING, + I_AMULET_OF_TRUE_SEEING, + I_RING_OF_INVISIBILITY, + I_RING_OF_POWER, + I_CHASTITY_BELT, /* bleibt */ + I_LAEN, + I_FEENSTIEFEL, + I_BIRTHDAYAMULET, + I_PEGASUS, + I_ELVENHORSE, + I_DOLPHIN, + I_RING_OF_NIMBLEFINGER, + I_TROLLBELT, + I_PRESSCARD, + I_AURAKULUM, + I_SPHERE_OF_INVISIBILITY, + I_BAG_OF_HOLDING, + I_SACK_OF_CONSERVATION, + I_TACTICCRYSTAL, + MAX_ITEMS /* do not use outside item.c ! */ + }; + + enum { + /* ITEMS: */ + R_IRON, + R_STONE, + R_HORSE, + /**/ R_AMULET_OF_HEALING, + R_AMULET_OF_TRUE_SEEING, + R_RING_OF_INVISIBILITY, + R_RING_OF_POWER, + R_CHASTITY_BELT, + R_EOG, + R_FEENSTIEFEL, + R_BIRTHDAYAMULET, + R_PEGASUS, + R_UNICORN, + R_DOLPHIN, + R_RING_OF_NIMBLEFINGER, + R_TROLLBELT, + R_PRESSCARD, + R_AURAKULUM, + R_SPHERE_OF_INVISIBILITY, + R_BAG_OF_HOLDING, + R_SACK_OF_CONSERVATION, + R_TACTICCRYSTAL, + + /* SONSTIGE */ + R_SILVER, + R_AURA, /* Aura */ + R_PERMAURA, /* Permanente Aura */ + + MAX_RESOURCES, /* do not use outside item.c ! */ + NORESOURCE = -1 + }; + + extern const struct potion_type *oldpotiontype[]; + extern const struct item_type *olditemtype[]; + extern const struct resource_type *oldresourcetype[]; + + int get_item(const struct unit *, item_t); + int set_item(struct unit *, item_t, int); + + int get_money(const struct unit *); + int set_money(struct unit *, int); + int change_money(struct unit *, int); + + extern struct attrib_type at_showitem; /* show this potion's description */ + + extern void register_resources(void); + extern void init_resources(void); + extern void init_itemtypes(void); + + extern void register_item_give(int (*foo) (struct unit *, struct unit *, + const struct item_type *, int, struct order *), const char *name); + extern void register_item_use(int (*foo) (struct unit *, + const struct item_type *, int, struct order *), const char *name); + extern void register_item_useonother(int (*foo) (struct unit *, int, + const struct item_type *, int, struct order *), const char *name); + + extern struct item_type *i_silver; + +#ifndef DISABLE_TESTS + void test_clear_resources(void); +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _ITEM_H */ diff --git a/core/src/kernel/item_test.c b/core/src/kernel/item_test.c new file mode 100644 index 000000000..c9a144ede --- /dev/null +++ b/core/src/kernel/item_test.c @@ -0,0 +1,118 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +static void test_uchange(CuTest * tc, unit * u, const resource_type * rtype) { + int n; + change_resource(u, rtype, 4); + n = get_resource(u, rtype); + CuAssertPtrNotNull(tc, rtype->uchange); + CuAssertIntEquals(tc, n, rtype->uchange(u, rtype, 0)); + CuAssertIntEquals(tc, n-3, rtype->uchange(u, rtype, -3)); + CuAssertIntEquals(tc, n-3, get_resource(u, rtype)); + CuAssertIntEquals(tc, 0, rtype->uchange(u, rtype, -n)); +} + +void test_change_item(CuTest * tc) +{ + unit * u; + + test_cleanup(); + register_resources(); + init_resources(); + test_create_world(); + + u = test_create_unit(0, 0); + test_uchange(tc, u, olditemtype[I_IRON]->rtype); +} + +void test_change_person(CuTest * tc) +{ + unit * u; + + test_cleanup(); + + register_resources(); + init_resources(); + test_create_world(); + + u = test_create_unit(0, 0); + test_uchange(tc, u, r_unit); +} + +void test_resource_type(CuTest * tc) +{ + struct item_type *itype; + const char *names[2] = { 0 , 0 }; + + test_cleanup(); + + CuAssertPtrEquals(tc, 0, rt_find("herpderp")); + + names[0] = names[1] = "herpderp"; + test_create_itemtype(names); + + names[0] = names[1] = "herp"; + itype = test_create_itemtype(names); + + names[0] = names[1] = "herpes"; + test_create_itemtype(names); + + CuAssertPtrEquals(tc, itype, it_find("herp")); + CuAssertPtrEquals(tc, itype->rtype, rt_find("herp")); +} + +void test_finditemtype(CuTest * tc) +{ + const item_type *itype, *iresult; + struct locale * lang; + + test_cleanup(); + test_create_world(); + + lang = find_locale("de"); + locale_setstring(lang, "horse", "Pferd"); + itype = it_find("horse"); + iresult = finditemtype("Pferd", lang); + CuAssertPtrNotNull(tc, iresult); + CuAssertPtrEquals(tc, (void*)itype, (void*)iresult); +} + +void test_findresourcetype(CuTest * tc) +{ + const resource_type *rtype, *rresult; + struct locale * lang; + + test_cleanup(); + test_create_world(); + + lang = find_locale("de"); + locale_setstring(lang, "horse", "Pferd"); + locale_setstring(lang, "peasant", "Bauer"); + + rtype = rt_find("horse"); + rresult = findresourcetype("Pferd", lang); + CuAssertPtrNotNull(tc, rresult); + CuAssertPtrEquals(tc, (void*)rtype, (void*)rresult); + + CuAssertPtrNotNull(tc, findresourcetype("Bauer", lang)); +} + +CuSuite *get_item_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_change_item); + SUITE_ADD_TEST(suite, test_change_person); + SUITE_ADD_TEST(suite, test_resource_type); + SUITE_ADD_TEST(suite, test_finditemtype); + SUITE_ADD_TEST(suite, test_findresourcetype); + return suite; +} diff --git a/core/src/kernel/magic.c b/core/src/kernel/magic.c new file mode 100644 index 000000000..ef7bfa5f3 --- /dev/null +++ b/core/src/kernel/magic.c @@ -0,0 +1,2946 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include +#include "magic.h" + +#include "building.h" +#include "curse.h" +#include "faction.h" +#include "item.h" +#include "message.h" +#include "objtypes.h" +#include "order.h" +#include "pathfinder.h" +#include "plane.h" +#include "pool.h" +#include "race.h" +#include "region.h" +#include "save.h" +#include "ship.h" +#include "skill.h" +#include "spell.h" +#include "spellbook.h" +#include "teleport.h" +#include "terrain.h" +#include "unit.h" +#include "version.h" + +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include + +const char *magic_school[MAXMAGIETYP] = { + "gray", + "illaun", + "tybied", + "cerddor", + "gwyrrd", + "draig", + "common" +}; + +static void a_init_reportspell(struct attrib *a) { + a->data.v = calloc(1, sizeof(spellbook_entry)); +} + +static void a_finalize_reportspell(struct attrib *a) { + free(a->data.v); +} + +attrib_type at_reportspell = { + "reportspell", + a_init_reportspell, + a_finalize_reportspell, + 0, NO_WRITE, NO_READ +}; +/** + ** at_icastle + ** TODO: separate castle-appearance from illusion-effects + **/ + +static double MagicRegeneration(void) +{ + static double value = -1.0; + if (value < 0) { + const char *str = get_param(global.parameters, "magic.regeneration"); + value = str ? atof(str) : 1.0; + } + return value; +} + +double MagicPower(void) +{ + static double value = -1.0; + if (value < 0) { + const char *str = get_param(global.parameters, "magic.power"); + value = str ? atof(str) : 1.0; + } + return value; +} + +static int a_readicastle(attrib * a, void *owner, struct storage *store) +{ + icastle_data *data = (icastle_data *) a->data.v; + variant bno; + char token[32]; + store->r_tok_buf(store, token, sizeof(token)); + bno.i = store->r_int(store); + data->time = store->r_int(store); + data->building = findbuilding(bno.i); + if (!data->building) { + /* this shouldn't happen, but just in case it does: */ + ur_add(bno, &data->building, resolve_building); + } + data->type = bt_find(token); + return AT_READ_OK; +} + +static void +a_writeicastle(const attrib * a, const void *owner, struct storage *store) +{ + icastle_data *data = (icastle_data *) a->data.v; + store->w_tok(store, data->type->_name); + store->w_int(store, data->building->no); + store->w_int(store, data->time); +} + +static int a_ageicastle(struct attrib *a) +{ + icastle_data *data = (icastle_data *) a->data.v; + if (data->time <= 0) { + building *b = data->building; + region *r = b->region; + ADDMSG(&r->msgs, msg_message("icastle_dissolve", "building", b)); + /* remove_building lets units leave the building */ + remove_building(&r->buildings, b); + return AT_AGE_REMOVE; + } else + data->time--; + return AT_AGE_KEEP; +} + +static void a_initicastle(struct attrib *a) +{ + a->data.v = calloc(sizeof(icastle_data), 1); +} + +static void a_finalizeicastle(struct attrib *a) +{ + free(a->data.v); +} + +attrib_type at_icastle = { + "zauber_icastle", + a_initicastle, + a_finalizeicastle, + a_ageicastle, + a_writeicastle, + a_readicastle +}; + +/* ------------------------------------------------------------- */ + +extern int dice(int count, int value); + +/* ------------------------------------------------------------- */ +/* aus dem alten System übriggebliegene Funktionen, die bei der + * Umwandlung von alt nach neu gebraucht werden */ +/* ------------------------------------------------------------- */ + +static void init_mage(attrib * a) +{ + a->data.v = calloc(sizeof(sc_mage), 1); +} + +static void free_mage(attrib * a) +{ + sc_mage *mage = (sc_mage *) a->data.v; + if (mage->spellbook) { + spellbook_clear(mage->spellbook); + free(mage->spellbook); + } + free(mage); +} + +int FactionSpells(void) +{ + static int rules_factionspells = -1; + if (rules_factionspells < 0) { + rules_factionspells = + get_param_int(global.parameters, "rules.magic.factionlist", 0); + } + return rules_factionspells; +} + +void read_spells(struct quicklist **slistp, magic_t mtype, + struct storage *store) +{ + for (;;) { + spell *sp; + char spname[64]; + + if (store->version < SPELLNAME_VERSION) { + int i = store->r_int(store); + if (i < 0) + break; + sp = find_spellbyid((unsigned int) i); + } else { + store->r_tok_buf(store, spname, sizeof(spname)); + if (strcmp(spname, "end") == 0) + break; + sp = find_spell(spname); + if (!sp) { + log_error("read_spells: could not find spell '%s' in school '%s'\n", spname, magic_school[mtype]); + } + } + if (sp) { + add_spell(slistp, sp); + } + } +} + +int get_spell_level_mage(const spell * sp, void * cbdata) +{ + sc_mage *mage = (sc_mage *)cbdata; + spellbook *book = get_spellbook(magic_school[mage->magietyp]); + spellbook_entry *sbe = spellbook_get(book, sp); + return sbe ? sbe->level : 0; +} + +static int read_mage(attrib * a, void *owner, struct storage *store) +{ + int i, mtype; + sc_mage *mage = (sc_mage *) a->data.v; + char spname[64]; + + mtype = store->r_int(store); + mage->spellpoints = store->r_int(store); + mage->spchange = store->r_int(store); + mage->magietyp = (magic_t) mtype; + for (i = 0; i != MAXCOMBATSPELLS; ++i) { + spell *sp = NULL; + int level = 0; + if (store->version < SPELLNAME_VERSION) { + int spid; + spid = store->r_int(store); + level = store->r_int(store); + if (spid >= 0) { + sp = find_spellbyid((unsigned int) spid); + } + } else { + store->r_tok_buf(store, spname, sizeof(spname)); + level = store->r_int(store); + + if (strcmp("none", spname) != 0) { + sp = find_spell(spname); + if (!sp) { + log_error("read_mage: could not find combat spell '%s' in school '%s'\n", spname, magic_school[mage->magietyp]); + } + } + } + if (sp && level >= 0) { + int slot = -1; + if (sp->sptyp & PRECOMBATSPELL) + slot = 0; + else if (sp->sptyp & COMBATSPELL) + slot = 1; + else if (sp->sptyp & POSTCOMBATSPELL) + slot = 2; + if (slot >= 0) { + mage->combatspells[slot].level = level; + mage->combatspells[slot].sp = sp; + } + } + } + if (mage->magietyp==M_GRAY) { + read_spellbook(&mage->spellbook, store, get_spell_level_mage, mage); + } else { + read_spellbook(0, store, 0, mage); + } + return AT_READ_OK; +} + +void write_spells(struct quicklist *slist, struct storage *store) +{ + quicklist *ql; + int qi; + + for (ql = slist, qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spell *sp = (spell *) ql_get(ql, qi); + store->w_tok(store, sp->sname); + } + store->w_tok(store, "end"); +} + +static void +write_mage(const attrib * a, const void *owner, struct storage *store) +{ + int i; + sc_mage *mage = (sc_mage *) a->data.v; + + store->w_int(store, mage->magietyp); + store->w_int(store, mage->spellpoints); + store->w_int(store, mage->spchange); + for (i = 0; i != MAXCOMBATSPELLS; ++i) { + store->w_tok(store, + mage->combatspells[i].sp ? mage->combatspells[i].sp->sname : "none"); + store->w_int(store, mage->combatspells[i].level); + } + write_spellbook(mage->spellbook, store); +} + +attrib_type at_mage = { + "mage", + init_mage, + free_mage, + NULL, + write_mage, + read_mage, + ATF_UNIQUE +}; + +bool is_mage(const unit * u) +{ + return i2b(get_mage(u) != NULL); +} + +sc_mage *get_mage(const unit * u) +{ + if (has_skill(u, SK_MAGIC)) { + attrib *a = a_find(u->attribs, &at_mage); + if (a) + return a->data.v; + } + return (sc_mage *) NULL; +} + +/* ------------------------------------------------------------- */ +/* Ausgabe der Spruchbeschreibungen +* Anzeige des Spruchs nur, wenn die Stufe des besten Magiers vorher +* kleiner war (u->faction->seenspells). Ansonsten muss nur geprüft +* werden, ob dieser Magier den Spruch schon kennt, und andernfalls der +* Spruch zu seiner List-of-known-spells hinzugefügt werden. +*/ + +static int read_seenspell(attrib * a, void *owner, struct storage *store) +{ + int i; + spell *sp = 0; + char token[32]; + + store->r_tok_buf(store, token, sizeof(token)); + i = atoi(token); + if (i != 0) { + sp = find_spellbyid((unsigned int) i); + } else { + if (store->versionr_int(store); /* ignore mtype */ + } + sp = find_spell(token); + if (!sp) { + log_error("read_seenspell: could not find spell '%s'\n", token); + } + } + if (!sp) { + return AT_READ_FAIL; + } + a->data.v = sp; + return AT_READ_OK; +} + +static void +write_seenspell(const attrib * a, const void *owner, struct storage *store) +{ + const spell *sp = (const spell *)a->data.v; + store->w_tok(store, sp->sname); +} + +attrib_type at_seenspell = { + "seenspell", NULL, NULL, NULL, write_seenspell, read_seenspell +}; + +#define MAXSPELLS 256 + +static bool already_seen(const faction * f, const spell * sp) +{ + attrib *a; + + for (a = a_find(f->attribs, &at_seenspell); a && a->type == &at_seenspell; + a = a->next) { + if (a->data.v == sp) + return true; + } + return false; +} + +void show_new_spells(faction * f, int level, const spellbook *book) +{ + if (book) { + quicklist *ql = book->spells; + int qi; + + for (qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry *sbe = (spellbook_entry *) ql_get(ql, qi); + if (sbe->level <= level) { + + if (!already_seen(f, sbe->sp)) { + attrib * a = a_new(&at_reportspell); + spellbook_entry * entry = (spellbook_entry *)a->data.v; + entry->level = sbe->level; + entry->sp = sbe->sp; + a_add(&f->attribs, a); + a_add(&f->attribs, a_new(&at_seenspell))->data.v = sbe->sp; + } + } + } + } +} + +/** update the spellbook with a new level +* Written for E3 +*/ +void pick_random_spells(faction * f, int level, spellbook * book, int num_spells) +{ + spellbook_entry *commonspells[MAXSPELLS]; + int qi, numspells = 0; + quicklist *ql; + + if (level <= f->max_spelllevel) { + return; + } + + for (qi = 0, ql = book->spells; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry * sbe = (spellbook_entry *) ql_get(ql, qi); + if (sbe->level <= level) { + commonspells[numspells++] = sbe; + } + } + + while (numspells > 0 && level > f->max_spelllevel) { + int i; + + ++f->max_spelllevel; + for (i = 0; i < num_spells; ++i) { + int maxspell = numspells; + int spellno = -1; + spellbook_entry *sbe = 0; + while (!sbe && maxspell>0) { + spellno = rng_int() % maxspell; + sbe = commonspells[spellno]; + if (sbe->level>f->max_spelllevel) { + commonspells[spellno] = commonspells[--maxspell]; + commonspells[maxspell] = sbe; + sbe = 0; + } else if (f->spellbook && spellbook_get(f->spellbook, sbe->sp)) { + commonspells[spellno] = commonspells[--numspells]; + if (maxspell>numspells) { + maxspell = numspells; + } + sbe = 0; + } + } + + if (spellnospellbook) { + f->spellbook = create_spellbook(0); + } + spellbook_add(f->spellbook, sbe->sp, sbe->level); + commonspells[spellno] = commonspells[--numspells]; + } + } + } +} + +/* ------------------------------------------------------------- */ +/* Erzeugen eines neuen Magiers */ +sc_mage *create_mage(unit * u, magic_t mtyp) +{ + sc_mage *mage; + attrib *a; + + a = a_find(u->attribs, &at_mage); + if (a != NULL) { + a_remove(&u->attribs, a); + } + a = a_add(&u->attribs, a_new(&at_mage)); + mage = a->data.v; + + mage->magietyp = mtyp; + return mage; +} + +/* ------------------------------------------------------------- */ +/* Funktionen für die Bearbeitung der List-of-known-spells */ + +int u_hasspell(const unit *u, const struct spell *sp) +{ + spellbook * book = unit_get_spellbook(u); + spellbook_entry * sbe = book ? spellbook_get(book, sp) : 0; + if (sbe) { + return sbe->level<=effskill(u, SK_MAGIC); + } + return 0; +} + +/* ------------------------------------------------------------- */ +/* Eingestellte Kampfzauberstufe ermitteln */ + +int get_combatspelllevel(const unit * u, int nr) +{ + sc_mage *m = get_mage(u); + + assert(nr < MAXCOMBATSPELLS); + if (m) { + int level = eff_skill(u, SK_MAGIC, u->region); + return MIN(m->combatspells[nr].level, level); + } + return -1; +} + +/* ------------------------------------------------------------- */ +/* Kampfzauber ermitteln, setzen oder löschen */ + +const spell *get_combatspell(const unit * u, int nr) +{ + sc_mage *m; + + assert(nr < MAXCOMBATSPELLS); + m = get_mage(u); + if (m) { + return m->combatspells[nr].sp; + } else if (u_race(u)->precombatspell != NULL) { + return u_race(u)->precombatspell; + } + + return NULL; +} + +void set_combatspell(unit * u, spell * sp, struct order *ord, int level) +{ + sc_mage *mage = get_mage(u); + int i = -1; + + assert(mage || !"trying to set a combat spell for non-mage"); + + /* knowsspell prüft auf ist_magier, ist_spruch, kennt_spruch */ + if (!knowsspell(u->region, u, sp)) { + /* Fehler 'Spell not found' */ + cmistake(u, ord, 173, MSG_MAGIC); + return; + } + if (!u_hasspell(u, sp)) { + /* Diesen Zauber kennt die Einheit nicht */ + cmistake(u, ord, 169, MSG_MAGIC); + return; + } + if (!(sp->sptyp & ISCOMBATSPELL)) { + /* Diesen Kampfzauber gibt es nicht */ + cmistake(u, ord, 171, MSG_MAGIC); + return; + } + + if (sp->sptyp & PRECOMBATSPELL) + i = 0; + else if (sp->sptyp & COMBATSPELL) + i = 1; + else if (sp->sptyp & POSTCOMBATSPELL) + i = 2; + assert(i >= 0); + mage->combatspells[i].sp = sp; + mage->combatspells[i].level = level; + return; +} + +void unset_combatspell(unit * u, spell * sp) +{ + sc_mage *m; + int nr = 0; + int i; + + m = get_mage(u); + if (!m) + return; + + if (!sp) { + for (i = 0; i < MAXCOMBATSPELLS; i++) { + m->combatspells[i].sp = NULL; + } + } else if (sp->sptyp & PRECOMBATSPELL) { + if (sp != get_combatspell(u, 0)) + return; + } else if (sp->sptyp & COMBATSPELL) { + if (sp != get_combatspell(u, 1)) { + return; + } + nr = 1; + } else if (sp->sptyp & POSTCOMBATSPELL) { + if (sp != get_combatspell(u, 2)) { + return; + } + nr = 2; + } + m->combatspells[nr].sp = NULL; + m->combatspells[nr].level = 0; + return; +} + +/* ------------------------------------------------------------- */ +/* Gibt die aktuelle Anzahl der Magiepunkte der Einheit zurück */ +int get_spellpoints(const unit * u) +{ + sc_mage *m; + + m = get_mage(u); + if (!m) + return 0; + + return m->spellpoints; +} + +void set_spellpoints(unit * u, int sp) +{ + sc_mage *m; + + m = get_mage(u); + if (!m) + return; + + m->spellpoints = sp; + + return; +} + +/* + * verändert die Anzahl der Magiepunkte der Einheit um +mp + */ +int change_spellpoints(unit * u, int mp) +{ + sc_mage *m; + int sp; + + m = get_mage(u); + if (!m) { + return 0; + } + + /* verhindere negative Magiepunkte */ + sp = MAX(m->spellpoints + mp, 0); + m->spellpoints = sp; + + return sp; +} + +/* bietet die Möglichkeit, die maximale Anzahl der Magiepunkte mit + * Regionszaubern oder Attributen zu beinflussen + */ +static int get_spchange(const unit * u) +{ + sc_mage *m; + + m = get_mage(u); + if (!m) + return 0; + + return m->spchange; +} + +/* ein Magier kann normalerweise maximal Stufe^2.1/1.2+1 Magiepunkte + * haben. + * Manche Rassen haben einen zusätzlichen Multiplikator + * Durch Talentverlust (zB Insekten im Berg) können negative Werte + * entstehen + */ + +/* Artefakt der Stärke + * Ermöglicht dem Magier mehr Magiepunkte zu 'speichern' + */ +/** TODO: at_skillmod daraus machen */ +static int use_item_aura(const region * r, const unit * u) +{ + int sk, n; + + sk = eff_skill(u, SK_MAGIC, r); + n = (int)(sk * sk * u_race(u)->maxaura / 4); + + return n; +} + +int max_spellpoints(const region * r, const unit * u) +{ + int sk; + double n, msp; + double potenz = 2.1; + double divisor = 1.2; + + sk = eff_skill(u, SK_MAGIC, r); + msp = u_race(u)->maxaura * (pow(sk, potenz) / divisor + 1) + get_spchange(u); + + if (get_item(u, I_AURAKULUM) > 0) { + msp += use_item_aura(r, u); + } + n = get_curseeffect(u->attribs, C_AURA, 0); + if (n > 0) + msp = (msp * n) / 100; + + return MAX((int)msp, 0); +} + +int change_maxspellpoints(unit * u, int csp) +{ + sc_mage *m; + + m = get_mage(u); + if (!m) + return 0; + + m->spchange += csp; + return max_spellpoints(u->region, u); +} + +/* ------------------------------------------------------------- */ +/* Counter für die bereits gezauberte Anzahl Sprüche pro Runde. + * Um nur die Zahl der bereits gezauberten Sprüche zu ermitteln mit + * step = 0 aufrufen. + */ +int countspells(unit * u, int step) +{ + sc_mage *m; + int count; + + m = get_mage(u); + if (!m) + return 0; + + if (step == 0) + return m->spellcount; + + count = m->spellcount + step; + + /* negative Werte abfangen. */ + m->spellcount = MAX(0, count); + + return m->spellcount; +} + +/* ------------------------------------------------------------- */ +/* Die für den Spruch benötigte Aura pro Stufe. + * Die Grundkosten pro Stufe werden hier um 2^count erhöht. Der + * Parameter count ist dabei die Anzahl der bereits gezauberten Sprüche + */ +int spellcost(unit * u, const spell * sp) +{ + int k, aura = 0; + int count = countspells(u, 0); + + for (k = 0; sp->components[k].type; k++) { + if (sp->components[k].type == r_aura) { + aura = sp->components[k].amount; + } + } + aura *= (1 << count); + return aura; +} + +/* ------------------------------------------------------------- */ +/* SPC_LINEAR ist am höchstwertigen, dann müssen Komponenten für die + * Stufe des Magiers vorhanden sein. + * SPC_LINEAR hat die gewünschte Stufe als multiplikator, + * nur SPC_FIX muss nur einmal vorhanden sein, ist also am + * niedrigstwertigen und sollte von den beiden anderen Typen + * überschrieben werden */ +static int spl_costtyp(const spell * sp) +{ + int k; + int costtyp = SPC_FIX; + + for (k = 0; sp->components[k].type; k++) { + if (costtyp == SPC_LINEAR) + return SPC_LINEAR; + + if (sp->components[k].cost == SPC_LINEAR) { + return SPC_LINEAR; + } + + /* wenn keine Fixkosten, Typ übernehmen */ + if (sp->components[k].cost != SPC_FIX) { + costtyp = sp->components[k].cost; + } + } + return costtyp; +} + +/* ------------------------------------------------------------- */ +/* durch Komponenten und cast_level begrenzter maximal möglicher + * Level + * Da die Funktion nicht alle Komponenten durchprobiert sondern beim + * ersten Fehler abbricht, muss die Fehlermeldung später mit cancast() + * generiert werden. + * */ +int eff_spelllevel(unit * u, const spell * sp, int cast_level, int range) +{ + int k, maxlevel, needplevel; + int costtyp = SPC_FIX; + + for (k = 0; sp->components[k].type; k++) { + if (cast_level == 0) + return 0; + + if (sp->components[k].amount > 0) { + /* Die Kosten für Aura sind auch von der Zahl der bereits + * gezauberten Sprüche abhängig */ + if (sp->components[k].type == r_aura) { + needplevel = spellcost(u, sp) * range; + } else { + needplevel = sp->components[k].amount * range; + } + maxlevel = + get_pooled(u, sp->components[k].type, GET_DEFAULT, + needplevel * cast_level) / needplevel; + + /* sind die Kosten fix, so muss die Komponente nur einmal vorhanden + * sein und der cast_level ändert sich nicht */ + if (sp->components[k].cost == SPC_FIX) { + if (maxlevel < 1) + cast_level = 0; + /* ansonsten wird das Minimum aus maximal möglicher Stufe und der + * gewünschten gebildet */ + } else if (sp->components[k].cost == SPC_LEVEL) { + costtyp = SPC_LEVEL; + cast_level = MIN(cast_level, maxlevel); + /* bei Typ Linear müssen die Kosten in Höhe der Stufe vorhanden + * sein, ansonsten schlägt der Spruch fehl */ + } else if (sp->components[k].cost == SPC_LINEAR) { + costtyp = SPC_LINEAR; + if (maxlevel < cast_level) + cast_level = 0; + } + } + } + /* Ein Spruch mit Fixkosten wird immer mit der Stufe des Spruchs und + * nicht auf der Stufe des Magiers gezaubert */ + if (costtyp == SPC_FIX) { + spellbook * spells = unit_get_spellbook(u); + if (spells) { + spellbook_entry * sbe = spellbook_get(spells, sp); + if (sbe) { + return MIN(cast_level, sbe->level); + } + } + log_error("spell %s is not in the spellbook for %s\n", sp->sname, unitname(u)); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Die Spruchgrundkosten werden mit der Entfernung (Farcasting) + * multipliziert, wobei die Aurakosten ein Sonderfall sind, da sie sich + * auch durch die Menge der bereits gezauberten Sprüche erhöht. + * Je nach Kostenart werden dann die Komponenten noch mit cast_level + * multipliziert. + */ +void pay_spell(unit * u, const spell * sp, int cast_level, int range) +{ + int k; + int resuse; + + assert(cast_level>0); + for (k = 0; sp->components[k].type; k++) { + if (sp->components[k].type == r_aura) { + resuse = spellcost(u, sp) * range; + } else { + resuse = sp->components[k].amount * range; + } + + if (sp->components[k].cost == SPC_LINEAR + || sp->components[k].cost == SPC_LEVEL) { + resuse *= cast_level; + } + + use_pooled(u, sp->components[k].type, GET_DEFAULT, resuse); + } +} + +/* ------------------------------------------------------------- */ +/* Ein Magier kennt den Spruch und kann sich die Beschreibung anzeigen + * lassen, wenn diese in seiner Spruchliste steht. Zaubern muss er ihn + * aber dann immer noch nicht können, vieleicht ist seine Stufe derzeit + * nicht ausreichend oder die Komponenten fehlen. + */ +bool knowsspell(const region * r, const unit * u, const spell * sp) +{ + /* Ist überhaupt ein gültiger Spruch angegeben? */ + if (!sp || sp->id == 0) { + return false; + } + /* steht der Spruch in der Spruchliste? */ + return u_hasspell(u, sp)!=0; +} + +/* Um einen Spruch zu beherrschen, muss der Magier die Stufe des + * Spruchs besitzen, nicht nur wissen, das es ihn gibt (also den Spruch + * in seiner Spruchliste haben). + * Kosten für einen Spruch können Magiepunkte, Silber, Kraeuter + * und sonstige Gegenstaende sein. + */ + +bool +cancast(unit * u, const spell * sp, int level, int range, struct order * ord) +{ + int k; + int itemanz; + resource *reslist = NULL; + + if (!knowsspell(u->region, u, sp)) { + /* Diesen Zauber kennt die Einheit nicht */ + cmistake(u, ord, 173, MSG_MAGIC); + return false; + } + /* reicht die Stufe aus? */ + if (eff_skill(u, SK_MAGIC, u->region) < level) { + /* die Einheit ist nicht erfahren genug für diesen Zauber */ + cmistake(u, ord, 169, MSG_MAGIC); + return false; + } + + for (k = 0; sp->components[k].type; ++k) { + if (sp->components[k].amount > 0) { + const resource_type *rtype = sp->components[k].type; + int itemhave; + + /* Die Kosten für Aura sind auch von der Zahl der bereits + * gezauberten Sprüche abhängig */ + if (rtype == r_aura) { + itemanz = spellcost(u, sp) * range; + } else { + itemanz = sp->components[k].amount * range; + } + + /* sind die Kosten stufenabhängig, so muss itemanz noch mit dem + * level multipliziert werden */ + switch (sp->components[k].cost) { + case SPC_LEVEL: + case SPC_LINEAR: + itemanz *= level; + break; + case SPC_FIX: + default: + break; + } + + itemhave = get_pooled(u, rtype, GET_DEFAULT, itemanz); + if (itemhave < itemanz) { + resource *res = malloc(sizeof(resource)); + res->number = itemanz - itemhave; + res->type = rtype; + res->next = reslist; + reslist = res; + } + } + } + if (reslist != NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "missing_components_list", + "list", reslist)); + return false; + } + return true; +} + +/* ------------------------------------------------------------- */ +/* generische Spruchstaerke, + * + * setzt sich derzeit aus der Stufe des Magiers, Magieturmeffekt, + * Spruchitems und Antimagiefeldern zusammen. Es koennen noch die + * Stufe des Spruchs und Magiekosten mit einfliessen. + * + * Die effektive Spruchstärke und ihre Auswirkungen werden in der + * Spruchfunktionsroutine ermittelt. + */ + +double +spellpower(region * r, unit * u, const spell * sp, int cast_level, + struct order *ord) +{ + curse *c; + double force = cast_level; + int elf_power = -1; + + if (sp == NULL) { + return 0; + } else { + /* Bonus durch Magieturm und gesegneten Steinkreis */ + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + if (btype && btype->flags & BTF_MAGIC) + ++force; + } + + if (get_item(u, I_RING_OF_POWER) > 0) + ++force; + if (elf_power < 0) { + elf_power = get_param_int(global.parameters, "rules.magic.elfpower", 0); + } + if (elf_power && u_race(u) == new_race[RC_ELF] && r_isforest(r)) { + ++force; + } + + /* Antimagie in der Zielregion */ + c = get_curse(r->attribs, ct_find("antimagiczone")); + if (curse_active(c)) { + unit *mage = c->magician; + force -= curse_geteffect(c); + curse_changevigour(&r->attribs, c, (float)-cast_level); + cmistake(u, ord, 185, MSG_MAGIC); + if (mage != NULL && mage->faction != NULL) { + if (force > 0) { + ADDMSG(&mage->faction->msgs, msg_message("reduce_spell", + "self mage region", mage, u, r)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("block_spell", + "self mage region", mage, u, r)); + } + } + } + + /* Patzerfluch-Effekt: */ + c = get_curse(r->attribs, ct_find("fumble")); + if (curse_active(c)) { + unit *mage = c->magician; + force -= curse_geteffect(c); + curse_changevigour(&u->attribs, c, -1); + cmistake(u, ord, 185, MSG_MAGIC); + if (mage != NULL && mage->faction != NULL) { + if (force > 0) { + ADDMSG(&mage->faction->msgs, msg_message("reduce_spell", + "self mage region", mage, u, r)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("block_spell", + "self mage region", mage, u, r)); + } + } + } + + force = force * MagicPower(); + + return MAX(force, 0); +} + +/* ------------------------------------------------------------- */ +/* farcasting() == 1 -> gleiche Region, da man mit Null nicht vernünfigt + * rechnen kann */ +static int farcasting(unit * magician, region * r) +{ + int dist; + int mult; + + if (!r) { + return INT_MAX; + } + + dist = koor_distance(r->x, r->y, magician->region->x, magician->region->y); + + if (dist > 24) + return INT_MAX; + + mult = 1 << dist; + if (dist > 1) { + if (!path_exists(magician->region, r, dist * 2, allowed_fly)) + mult = INT_MAX; + } + + return mult; +} + +/* ------------------------------------------------------------- */ +/* Antimagie - Magieresistenz */ +/* ------------------------------------------------------------- */ + +/* allgemeine Magieresistenz einer Einheit, + * reduziert magischen Schaden */ +double magic_resistance(unit * target) +{ + attrib *a; + curse *c; + int n; + const curse_type * ct_goodresist = 0, * ct_badresist = 0; + + /* Bonus durch Rassenmagieresistenz */ + double probability = u_race(target)->magres; + assert(target->number > 0); + + /* Magier haben einen Resistenzbonus vom Magietalent * 5% */ + probability += effskill(target, SK_MAGIC) * 0.05; + + /* Auswirkungen von Zaubern auf der Einheit */ + c = get_curse(target->attribs, ct_find("magicresistance")); + if (c) { + probability += 0.01 * curse_geteffect(c) * get_cursedmen(target, c); + } + + /* Unicorn +10 */ + n = get_item(target, I_ELVENHORSE); + if (n) + probability += n * 0.1 / target->number; + + /* Auswirkungen von Zaubern auf der Region */ + a = a_find(target->region->attribs, &at_curse); + if (a) { + ct_badresist = ct_find("badmagicresistancezone"); + ct_goodresist = ct_find("goodmagicresistancezone"); + } + while (a && a->type == &at_curse) { + curse *c = (curse *) a->data.v; + unit *mage = c->magician; + + if (mage != NULL) { + if (ct_goodresist && c->type == ct_goodresist) { + if (alliedunit(mage, target->faction, HELP_GUARD)) { + probability += curse_geteffect(c) * 0.01; + ct_goodresist = 0; /* only one effect per region */ + } + } else if (ct_badresist && c->type == ct_badresist) { + if (!alliedunit(mage, target->faction, HELP_GUARD)) { + probability -= curse_geteffect(c) * 0.01; + ct_badresist = 0; /* only one effect per region */ + } + } + } + a = a->next; + } + /* Bonus durch Artefakte */ + /* TODO (noch gibs keine) */ + + /* Bonus durch Gebäude */ + { + struct building *b = inside_building(target); + const struct building_type *btype = b ? b->type : NULL; + + /* gesegneter Steinkreis gibt 30% dazu */ + if (btype) + probability += btype->magresbonus * 0.01; + } + return probability; +} + +/* ------------------------------------------------------------- */ +/* Prüft, ob das Objekt dem Zauber widerstehen kann. + * Objekte können Regionen, Units, Gebäude oder Schiffe sein. + * TYP_UNIT: + * Das höchste Talent des Ziels ist sein 'Magieresistenz-Talent', Magier + * bekommen einen Bonus. Grundchance ist 50%, für jede Stufe + * Unterschied gibt es 5%, minimalchance ist 5% für jeden (5-95%) + * Scheitert der Spruch an der Magieresistenz, so gibt die Funktion + * true zurück + */ + +bool +target_resists_magic(unit * magician, void *obj, int objtyp, int t_bonus) +{ + double probability = 0.0; + + if (magician == NULL) + return true; + if (obj == NULL) + return true; + + switch (objtyp) { + case TYP_UNIT: + { + int at, pa = 0; + skill *sv; + unit *u = (unit *) obj; + + at = effskill(magician, SK_MAGIC); + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + int sk = effskill(u, sv->id); + if (pa < sk) + pa = sk; + } + + /* Contest */ + probability = 0.05 * (10 + pa - at); + + probability += magic_resistance((unit *) obj); + break; + } + + case TYP_REGION: + /* Bonus durch Zauber */ + probability += + 0.01 * get_curseeffect(((region *) obj)->attribs, C_RESIST_MAGIC, 0); + break; + + case TYP_BUILDING: + /* Bonus durch Zauber */ + probability += + 0.01 * get_curseeffect(((building *) obj)->attribs, C_RESIST_MAGIC, 0); + + /* Bonus durch Typ */ + probability += 0.01 * ((building *) obj)->type->magres; + + break; + + case TYP_SHIP: + /* Bonus durch Zauber */ + probability += + 0.01 * get_curseeffect(((ship *) obj)->attribs, C_RESIST_MAGIC, 0); + break; + } + + probability = MAX(0.02, probability + t_bonus * 0.01); + probability = MIN(0.98, probability); + + /* gibt true, wenn die Zufallszahl kleiner als die chance ist und + * false, wenn sie gleich oder größer ist, dh je größer die + * Magieresistenz (chance) desto eher gibt die Funktion true zurück */ + return chance(probability); +} + +/* ------------------------------------------------------------- */ + +bool is_magic_resistant(unit * magician, unit * target, int resist_bonus) +{ + return (bool) target_resists_magic(magician, target, TYP_UNIT, + resist_bonus); +} + +/* ------------------------------------------------------------- */ +/* Patzer */ +/* ------------------------------------------------------------- */ +/* allgemeine Zauberpatzer: + * Treten auf, wenn die Stufe des Magieres zu niedrig ist. + * Abhaengig von Staerke des Spruchs. + * + * Die Warscheinlichkeit reicht von 20% (beherrscht den Spruch gerade + * eben) bis zu etwa 1% bei doppelt so gut wie notwendig + */ + +bool fumble(region * r, unit * u, const spell * sp, int cast_grade) +{ +/* X ergibt Zahl zwischen 1 und 0, je kleiner, desto besser der Magier. + * 0,5*40-20=0, dh wenn der Magier doppelt so gut ist, wie der Spruch + * benötigt, gelingt er immer, ist er gleich gut, gelingt der Spruch mit + * 20% Warscheinlichkeit nicht + * */ + + int rnd = 0; + double x = (double)cast_grade / (double)eff_skill(u, SK_MAGIC, r); + int patzer = (int)(((double)x * 40.0) - 20.0); + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + int fumble_enabled = get_param_int(global.parameters, "magic.fumble.enable", 1); + sc_mage * mage; + + if (!fumble_enabled) { + return false; + } + if (btype) + patzer -= btype->fumblebonus; + + /* CHAOSPATZERCHANCE 10 : +10% Chance zu Patzern */ + mage = get_mage(u); + if (mage->magietyp == M_DRAIG) { + patzer += CHAOSPATZERCHANCE; + } + if (is_cursed(u->attribs, C_MBOOST, 0)) { + patzer += CHAOSPATZERCHANCE; + } + if (is_cursed(u->attribs, C_FUMBLE, 0)) { + patzer += CHAOSPATZERCHANCE; + } + + /* wenn die Chance kleiner als 0 ist, können wir gleich false + * zurückgeben */ + if (patzer <= 0) { + return false; + } + rnd = rng_int() % 100; + + if (rnd > patzer) { + /* Glück gehabt, kein Patzer */ + return false; + } + return true; +} + +/* ------------------------------------------------------------- */ +/* Dummy-Zauberpatzer, Platzhalter für speziel auf die Sprüche +* zugeschnittene Patzer */ +static void patzer(castorder * co) +{ + unit *mage = co->magician.u; + + cmistake(mage, co->order, 180, MSG_MAGIC); + + return; +} + +/* Die normalen Spruchkosten müssen immer bezahlt werden, hier noch + * alle weiteren Folgen eines Patzers + */ + +static void do_fumble(castorder * co) +{ + curse *c; + region *r = co_get_region(co); + unit *u = co->magician.u; + const spell *sp = co->sp; + int level = co->level; + int duration; + double effect; + + ADDMSG(&u->faction->msgs, msg_message("patzer", "unit region spell", + u, r, sp)); + switch (rng_int() % 10) { + case 0: + /* wenn vorhanden spezieller Patzer, ansonsten nix */ + if (sp->patzer) + sp->patzer(co); + else + patzer(co); + break; + + case 1: + /* Kröte */ + { + /* one or two things will happen: the toad changes her race back, + * and may or may not get toadslime. + * The list of things to happen are attached to a timeout + * trigger and that's added to the triggerlit of the mage gone toad. + */ + trigger *trestore = trigger_changerace(u, u_race(u), u->irace); + + if (chance(0.7)) { + const item_type *it_toadslime = it_find("toadslime"); + if (it_toadslime != NULL) { + t_add(&trestore, trigger_giveitem(u, it_toadslime, 1)); + } + } + + duration = rng_int() % level / 2; + if (duration < 2) + duration = 2; + add_trigger(&u->attribs, "timer", trigger_timeout(duration, trestore)); + u_setrace(u, new_race[RC_TOAD]); + u->irace = NULL; + ADDMSG(&r->msgs, msg_message("patzer6", "unit region spell", u, r, sp)); + break; + } + /* fall-through is intentional! */ + + case 2: + /* temporärer Stufenverlust */ + duration = MAX(rng_int() % level / 2, 2); + effect = -0.5 * level; + c = + create_curse(u, &u->attribs, ct_find("skillmod"), (float)level, + duration, effect, 1); + c->data.i = SK_MAGIC; + ADDMSG(&u->faction->msgs, msg_message("patzer2", "unit region", u, r)); + break; + case 3: + case 4: + /* Spruch schlägt fehl, alle Magiepunkte weg */ + set_spellpoints(u, 0); + ADDMSG(&u->faction->msgs, msg_message("patzer3", "unit region spell", + u, r, sp)); + break; + + case 5: + case 6: + /* Spruch gelingt, aber alle Magiepunkte weg */ + co->level = sp->cast(co); + set_spellpoints(u, 0); + ADDMSG(&u->faction->msgs, msg_message("patzer4", "unit region spell", + u, r, sp)); + break; + + case 7: + case 8: + case 9: + default: + /* Spruch gelingt, alle nachfolgenden Sprüche werden 2^4 so teuer */ + co->level = sp->cast(co); + ADDMSG(&u->faction->msgs, msg_message("patzer5", "unit region spell", + u, r, sp)); + countspells(u, 3); + } + + return; +} + +/* ------------------------------------------------------------- */ +/* Regeneration von Aura */ +/* ------------------------------------------------------------- */ + +/* Ein Magier regeneriert pro Woche W(Stufe^1.5/2+1), mindestens 1 + * Zwerge nur die Hälfte + */ +static double regeneration(unit * u) +{ + int sk; + double aura, d; + double potenz = 1.5; + double divisor = 2.0; + + sk = effskill(u, SK_MAGIC); + /* Rassenbonus/-malus */ + d = pow(sk, potenz) * u_race(u)->regaura / divisor; + d++; + + /* Einfluss von Artefakten */ + /* TODO (noch gibs keine) */ + + /* Würfeln */ + aura = (rng_double() * d + rng_double() * d) / 2 + 1; + + aura *= MagicRegeneration(); + + return aura; +} + +void regenerate_aura(void) +{ + region *r; + unit *u; + int aura, auramax; + double reg_aura; + int regen; + double mod; + int regen_enabled = get_param_int(global.parameters, "magic.regeneration.enable", 1); + + if (!regen_enabled) return; + + for (r = regions; r; r = r->next) { + for (u = r->units; u; u = u->next) { + if (u->number && is_mage(u)) { + aura = get_spellpoints(u); + auramax = max_spellpoints(r, u); + if (aura < auramax) { + struct building *b = inside_building(u); + const struct building_type *btype = b ? b->type : NULL; + reg_aura = regeneration(u); + + /* Magierturm erhöht die Regeneration um 75% */ + /* Steinkreis erhöht die Regeneration um 50% */ + if (btype) + reg_aura *= btype->auraregen; + + /* Bonus/Malus durch Zauber */ + mod = get_curseeffect(u->attribs, C_AURA, 0); + if (mod > 0) { + reg_aura = (reg_aura * mod) / 100.0; + } + + /* Einfluss von Artefakten */ + /* TODO (noch gibs keine) */ + + /* maximal Differenz bis Maximale-Aura regenerieren + * mindestens 1 Aura pro Monat */ + regen = (int)reg_aura; + reg_aura -= regen; + if (chance(reg_aura)) + ++regen; + regen = MAX(1, regen); + regen = MIN((auramax - aura), regen); + + aura += regen; + ADDMSG(&u->faction->msgs, msg_message("regenaura", + "unit region amount", u, r, regen)); + } + set_spellpoints(u, MIN(aura, auramax)); + } + } + } +} + +static bool +verify_ship(region * r, unit * mage, const spell * sp, spllprm * spobj, + order * ord) +{ + ship *sh = findship(spobj->data.i); + + if (sh != NULL && sh->region != r && (sp->sptyp & SEARCHLOCAL)) { + /* Burg muss in gleicher Region sein */ + sh = NULL; + } + + if (sh == NULL) { + /* Burg nicht gefunden */ + spobj->flag = TARGET_NOTFOUND; + ADDMSG(&mage->faction->msgs, msg_message("spellshipnotfound", + "unit region command id", mage, mage->region, ord, spobj->data.i)); + return false; + } + spobj->flag = 0; + spobj->data.sh = sh; + return true; +} + +static bool +verify_building(region * r, unit * mage, const spell * sp, spllprm * spobj, + order * ord) +{ + building *b = findbuilding(spobj->data.i); + + if (b != NULL && b->region != r && (sp->sptyp & SEARCHLOCAL)) { + /* Burg muss in gleicher Region sein */ + b = NULL; + } + + if (b == NULL) { + /* Burg nicht gefunden */ + spobj->flag = TARGET_NOTFOUND; + ADDMSG(&mage->faction->msgs, msg_message("spellbuildingnotfound", + "unit region command id", mage, mage->region, ord, spobj->data.i)); + return false; + } + spobj->flag = 0; + spobj->data.b = b; + return true; +} + +message *msg_unitnotfound(const struct unit * mage, struct order * ord, + const struct spllprm * spobj) +{ + /* Einheit nicht gefunden */ + char tbuf[20]; + const char *uid; + + if (spobj->typ == SPP_UNIT) { + uid = itoa36(spobj->data.i); + } else { + sprintf(tbuf, "%s %s", LOC(mage->faction->locale, + parameters[P_TEMP]), itoa36(spobj->data.i)); + uid = tbuf; + } + return msg_message("unitnotfound_id", + "unit region command id", mage, mage->region, ord, uid); +} + +static bool +verify_unit(region * r, unit * mage, const spell * sp, spllprm * spobj, + order * ord) +{ + unit *u = NULL; + switch (spobj->typ) { + case SPP_UNIT: + u = findunit(spobj->data.i); + break; + case SPP_TEMP: + u = findnewunit(r, mage->faction, spobj->data.i); + if (u == NULL) + u = findnewunit(mage->region, mage->faction, spobj->data.i); + break; + default: + assert(!"shouldn't happen, this"); + } + if (u != NULL && (sp->sptyp & SEARCHLOCAL)) { + if (u->region != r) + u = NULL; + else if (sp->sptyp & TESTCANSEE) { + if (!cansee(mage->faction, r, u, 0) && !ucontact(u, mage)) { + u = NULL; + } + } + } + + if (u == NULL) { + /* Einheit nicht gefunden */ + spobj->flag = TARGET_NOTFOUND; + ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, ord, spobj)); + return false; + } + /* Einheit wurde gefunden, pointer setzen */ + spobj->flag = 0; + spobj->data.u = u; + return true; +} + +/* ------------------------------------------------------------- */ +/* Zuerst wird versucht alle noch nicht gefundenen Objekte zu finden + * oder zu prüfen, ob das gefundene Objekt wirklich hätte gefunden + * werden dürfen (nicht alle Zauber wirken global). Dabei zählen wir die + * Misserfolge (failed). + * Dann folgen die Tests der gefundenen Objekte auf Magieresistenz und + * Sichtbarkeit. Dabei zählen wir die magieresistenten (resists) + * Objekte. Alle anderen werten wir als Erfolge (success) */ + +/* gibt bei Misserfolg 0 zurück, bei Magieresistenz zumindeste eines + * Objektes 1 und bei Erfolg auf ganzer Linie 2 */ +static void +verify_targets(castorder * co, int *invalid, int *resist, int *success) +{ + unit *mage = co->magician.u; + const spell *sp = co->sp; + region *target_r = co_get_region(co); + spellparameter *sa = co->par; + int i; + + *invalid = 0; + *resist = 0; + *success = 0; + + if (sa && sa->length) { + /* zuerst versuchen wir vorher nicht gefundene Objekte zu finden. + * Wurde ein Objekt durch globalsuche gefunden, obwohl der Zauber + * gar nicht global hätte suchen dürften, setzen wir das Objekt + * zurück. */ + for (i = 0; i < sa->length; i++) { + spllprm *spobj = sa->param[i]; + + switch (spobj->typ) { + case SPP_TEMP: + case SPP_UNIT: + if (!verify_unit(target_r, mage, sp, spobj, co->order)) + ++ * invalid; + break; + case SPP_BUILDING: + if (!verify_building(target_r, mage, sp, spobj, co->order)) + ++ * invalid; + break; + case SPP_SHIP: + if (!verify_ship(target_r, mage, sp, spobj, co->order)) + ++ * invalid; + break; + default: + break; + } + } + + /* Nun folgen die Tests auf cansee und Magieresistenz */ + for (i = 0; i < sa->length; i++) { + spllprm *spobj = sa->param[i]; + unit *u; + building *b; + ship *sh; + region *tr; + + if (spobj->flag == TARGET_NOTFOUND) + continue; + switch (spobj->typ) { + case SPP_TEMP: + case SPP_UNIT: + u = spobj->data.u; + + if ((sp->sptyp & TESTRESISTANCE) + && target_resists_magic(mage, u, TYP_UNIT, 0)) { + /* Fehlermeldung */ + spobj->data.i = u->no; + spobj->flag = TARGET_RESISTS; + ++*resist; + ADDMSG(&mage->faction->msgs, msg_message("spellunitresists", + "unit region command target", mage, mage->region, co->order, u)); + break; + } + + /* TODO: Test auf Parteieigenschaft Magieresistsenz */ + ++*success; + break; + case SPP_BUILDING: + b = spobj->data.b; + + if ((sp->sptyp & TESTRESISTANCE) + && target_resists_magic(mage, b, TYP_BUILDING, 0)) { /* Fehlermeldung */ + spobj->data.i = b->no; + spobj->flag = TARGET_RESISTS; + ++*resist; + ADDMSG(&mage->faction->msgs, msg_message("spellbuildingresists", + "unit region command id", + mage, mage->region, co->order, spobj->data.i)); + break; + } + ++*success; + break; + case SPP_SHIP: + sh = spobj->data.sh; + + if ((sp->sptyp & TESTRESISTANCE) + && target_resists_magic(mage, sh, TYP_SHIP, 0)) { /* Fehlermeldung */ + spobj->data.i = sh->no; + spobj->flag = TARGET_RESISTS; + ++*resist; + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellshipresists", "ship", sh)); + break; + } + ++*success; + break; + + case SPP_REGION: + /* haben wir ein Regionsobjekt, dann wird auch dieses und + nicht target_r überprüft. */ + tr = spobj->data.r; + + if ((sp->sptyp & TESTRESISTANCE) + && target_resists_magic(mage, tr, TYP_REGION, 0)) { /* Fehlermeldung */ + spobj->flag = TARGET_RESISTS; + ++*resist; + ADDMSG(&mage->faction->msgs, msg_message("spellregionresists", + "unit region command", mage, mage->region, co->order)); + break; + } + ++*success; + break; + case SPP_INT: + case SPP_STRING: + ++*success; + break; + + default: + break; + } + } + } else { + /* der Zauber hat keine expliziten Parameter/Ziele, es kann sich + * aber um einen Regionszauber handeln. Wenn notwendig hier die + * Magieresistenz der Region prüfen. */ + if ((sp->sptyp & REGIONSPELL)) { + /* Zielobjekt Region anlegen */ + spllprm *spobj = (spllprm *)malloc(sizeof(spllprm)); + spobj->flag = 0; + spobj->typ = SPP_REGION; + spobj->data.r = target_r; + + sa = calloc(1, sizeof(spellparameter)); + sa->length = 1; + sa->param = calloc(sa->length, sizeof(spllprm *)); + sa->param[0] = spobj; + co->par = sa; + + if ((sp->sptyp & TESTRESISTANCE)) { + if (target_resists_magic(mage, target_r, TYP_REGION, 0)) { + /* Fehlermeldung */ + ADDMSG(&mage->faction->msgs, msg_message("spellregionresists", + "unit region command", mage, mage->region, co->order)); + spobj->flag = TARGET_RESISTS; + ++*resist; + } else { + ++*success; + } + } else { + ++*success; + } + } else { + ++*success; + } + } +} + +/* ------------------------------------------------------------- */ +/* Hilfsstrukturen für ZAUBERE */ +/* ------------------------------------------------------------- */ + +static void free_spellparameter(spellparameter * pa) +{ + int i; + + /* Elemente free'en */ + for (i = 0; i < pa->length; i++) { + + switch (pa->param[i]->typ) { + case SPP_STRING: + free(pa->param[i]->data.s); + break; + default: + break; + } + free(pa->param[i]); + } + + if (pa->param) + free(pa->param); + /* struct free'en */ + free(pa); +} + +static int addparam_string(const char *const param[], spllprm ** spobjp) +{ + spllprm *spobj = *spobjp = malloc(sizeof(spllprm)); + assert(param[0]); + + spobj->flag = 0; + spobj->typ = SPP_STRING; + spobj->data.xs = strdup(param[0]); + return 1; +} + +static int addparam_int(const char *const param[], spllprm ** spobjp) +{ + spllprm *spobj = *spobjp = malloc(sizeof(spllprm)); + assert(param[0]); + + spobj->flag = 0; + spobj->typ = SPP_INT; + spobj->data.i = atoi((char *)param[0]); + return 1; +} + +static int addparam_ship(const char *const param[], spllprm ** spobjp) +{ + spllprm *spobj = *spobjp = malloc(sizeof(spllprm)); + int id = atoi36((const char *)param[0]); + + spobj->flag = 0; + spobj->typ = SPP_SHIP; + spobj->data.i = id; + return 1; +} + +static int addparam_building(const char *const param[], spllprm ** spobjp) +{ + spllprm *spobj = *spobjp = malloc(sizeof(spllprm)); + int id = atoi36((const char *)param[0]); + + spobj->flag = 0; + spobj->typ = SPP_BUILDING; + spobj->data.i = id; + return 1; +} + +static int +addparam_region(const char *const param[], spllprm ** spobjp, const unit * u, + order * ord, plane * pl) +{ + assert(param[0]); + if (param[1] == 0) { + /* Fehler: Zielregion vergessen */ + cmistake(u, ord, 194, MSG_MAGIC); + return -1; + } else { + int tx = atoi((const char *)param[0]), ty = atoi((const char *)param[1]); + int x = rel_to_abs(pl, u->faction, tx, 0); + int y = rel_to_abs(pl, u->faction, ty, 1); + region *rt; + + pnormalize(&x, &y, pl); + rt = findregion(x, y); + + if (rt != NULL) { + spllprm *spobj = *spobjp = (spllprm *)malloc(sizeof(spllprm)); + + spobj->flag = 0; + spobj->typ = SPP_REGION; + spobj->data.r = rt; + } else { + /* Fehler: Zielregion vergessen */ + cmistake(u, ord, 194, MSG_MAGIC); + return -1; + } + return 2; + } +} + +static int +addparam_unit(const char *const param[], spllprm ** spobjp, const unit * u, + order * ord) +{ + spllprm *spobj; + int i = 0; + sppobj_t otype = SPP_UNIT; + + *spobjp = NULL; + if (isparam(param[0], u->faction->locale, P_TEMP)) { + if (param[1] == NULL) { + /* Fehler: Ziel vergessen */ + cmistake(u, ord, 203, MSG_MAGIC); + return -1; + } + ++i; + otype = SPP_TEMP; + } + + spobj = *spobjp = malloc(sizeof(spllprm)); + spobj->flag = 0; + spobj->typ = otype; + spobj->data.i = atoi36((const char *)param[i]); + + return i + 1; +} + +static spellparameter *add_spellparameter(region * target_r, unit * u, + const char *syntax, const char *const param[], int size, struct order *ord) +{ + bool fail = false; + int i = 0; + int p = 0; + const char *c; + spellparameter *par; + int minlen = 0; + + for (c = syntax; *c != 0; ++c) { + /* this makes sure that: + * minlen("kc?") = 0 + * minlen("kc+") = 1 + * minlen("cccc+c?") = 4 + */ + if (*c == '?') + --minlen; + else if (*c != '+' && *c != 'k') + ++minlen; + } + c = syntax; + + /* mindestens ein Ziel (Ziellose Zauber werden nicht + * geparst) */ + if (minlen && size == 0) { + /* Fehler: Ziel vergessen */ + cmistake(u, ord, 203, MSG_MAGIC); + return 0; + } + + par = malloc(sizeof(spellparameter)); + par->length = size; + if (!size) { + par->param = NULL; + return par; + } + par->param = malloc(size * sizeof(spllprm *)); + + while (!fail && *c && i < size && param[i] != NULL) { + spllprm *spobj = NULL; + int j = -1; + switch (*c) { + case '?': + /* tja. das sollte moeglichst nur am Ende passieren, + * weil sonst die kacke dampft. */ + j = 0; + ++c; + assert(*c == 0); + break; + case '+': + /* das vorhergehende Element kommt ein oder mehrmals vor, wir + * springen zum key zurück */ + j = 0; + --c; + break; + case 'u': + /* Parameter ist eine Einheit, evtl. TEMP */ + j = addparam_unit(param + i, &spobj, u, ord); + ++c; + break; + case 'r': + /* Parameter sind zwei Regionskoordinaten */ + /* this silly thing only works in the normal plane! */ + j = addparam_region(param + i, &spobj, u, ord, get_normalplane()); + ++c; + break; + case 'b': + /* Parameter ist eine Burgnummer */ + j = addparam_building(param + i, &spobj); + ++c; + break; + case 's': + j = addparam_ship(param + i, &spobj); + ++c; + break; + case 'c': + /* Text, wird im Spruch ausgewertet */ + j = addparam_string(param + i, &spobj); + ++c; + break; + case 'i': /* Zahl */ + j = addparam_int(param + i, &spobj); + ++c; + break; + case 'k': + ++c; + switch (findparam_ex(param[i++], u->faction->locale)) { + case P_REGION: + spobj = (spllprm *)malloc(sizeof(spllprm)); + spobj->flag = 0; + spobj->typ = SPP_REGION; + spobj->data.r = target_r; + j = 0; + ++c; + break; + case P_UNIT: + if (i < size) { + j = addparam_unit(param + i, &spobj, u, ord); + ++c; + } + break; + case P_BUILDING: + case P_GEBAEUDE: + if (i < size) { + j = addparam_building(param + i, &spobj); + ++c; + } + break; + case P_SHIP: + if (i < size) { + j = addparam_ship(param + i, &spobj); + ++c; + } + break; + default: + j = -1; + break; + } + break; + default: + j = -1; + break; + } + if (j < 0) + fail = true; + else { + if (spobj != NULL) + par->param[p++] = spobj; + i += j; + } + } + + /* im Endeffekt waren es evtl. nur p parameter (wegen TEMP) */ + par->length = p; + if (fail || par->length < minlen) { + cmistake(u, ord, 209, MSG_MAGIC); + free_spellparameter(par); + return NULL; + } + + return par; +} + +struct unit * co_get_caster(struct castorder * co) { + return co->_familiar ? co->_familiar : co->magician.u; +} + +struct region * co_get_region(struct castorder * co) { + return co->_rtarget; +} + +castorder *create_castorder(castorder * co, unit *caster, unit * familiar, const spell * sp, region * r, + int lev, double force, int range, struct order * ord, spellparameter * p) +{ + if (!co) co = (castorder*)calloc(1, sizeof(castorder)); + + co->magician.u = caster; + co->_familiar = familiar; + co->sp = sp; + co->level = lev; + co->force = force; + co->_rtarget = r ? r : (familiar ? familiar->region : (caster ? caster->region : 0)); + co->distance = range; + co->order = copy_order(ord); + co->par = p; + + return co; +} + +void free_castorder(struct castorder *co) +{ + if (co->par) free_spellparameter(co->par); + if (co->order) free_order(co->order); +} + +/* Hänge c-order co an die letze c-order von cll an */ +void add_castorder(spellrank * cll, castorder * co) +{ + if (cll->begin == NULL) { + cll->end = &cll->begin; + } + + *cll->end = co; + cll->end = &co->next; + + return; +} + +void free_castorders(castorder * co) +{ + castorder *co2; + + while (co) { + co2 = co; + co = co->next; + free_castorder(co2); + free(co2); + } + return; +} + +/* ------------------------------------------------------------- */ +/*** + ** at_familiarmage + **/ + +typedef struct familiar_data { + unit *mage; + unit *familiar; +} famililar_data; + +bool is_familiar(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_familiarmage); + return i2b(a != NULL); +} + +static void +a_write_unit(const attrib * a, const void *owner, struct storage *store) +{ + unit *u = (unit *) a->data.v; + write_unit_reference(u, store); +} + +static int sm_familiar(const unit * u, const region * r, skill_t sk, int value) +{ /* skillmod */ + if (sk == SK_MAGIC) + return value; + else { + int mod; + unit *familiar = get_familiar(u); + if (familiar == NULL) { + /* the familiar is dead */ + return value; + } + mod = eff_skill(familiar, sk, r) / 2; + if (r != familiar->region) { + mod /= distance(r, familiar->region); + } + return value + mod; + } +} + +static void set_familiar(unit * mage, unit * familiar) +{ + /* if the skill modifier for the mage does not yet exist, add it */ + attrib *a = a_find(mage->attribs, &at_skillmod); + while (a && a->type == &at_skillmod) { + skillmod_data *smd = (skillmod_data *) a->data.v; + if (smd->special == sm_familiar) + break; + a = a->next; + } + if (a == NULL) { + attrib *an = a_add(&mage->attribs, a_new(&at_skillmod)); + skillmod_data *smd = (skillmod_data *) an->data.v; + smd->special = sm_familiar; + smd->skill = NOSKILL; + } + + a = a_find(mage->attribs, &at_familiar); + if (a == NULL) { + a = a_add(&mage->attribs, a_new(&at_familiar)); + a->data.v = familiar; + } else + assert(!a->data.v || a->data.v == familiar); + /* TODO: Diese Attribute beim Tod des Familiars entfernen: */ + + a = a_find(familiar->attribs, &at_familiarmage); + if (a == NULL) { + a = a_add(&familiar->attribs, a_new(&at_familiarmage)); + a->data.v = mage; + } else + assert(!a->data.v || a->data.v == mage); +} + +void remove_familiar(unit * mage) +{ + attrib *a = a_find(mage->attribs, &at_familiar); + attrib *an; + skillmod_data *smd; + + if (a != NULL) { + a_remove(&mage->attribs, a); + } + a = a_find(mage->attribs, &at_skillmod); + while (a && a->type == &at_skillmod) { + an = a->next; + smd = (skillmod_data *) a->data.v; + if (smd->special == sm_familiar) + a_remove(&mage->attribs, a); + a = an; + } +} + +bool create_newfamiliar(unit * mage, unit * familiar) +{ + /* if the skill modifier for the mage does not yet exist, add it */ + attrib *a; + attrib *afam = a_find(mage->attribs, &at_familiar); + attrib *amage = a_find(familiar->attribs, &at_familiarmage); + + if (afam == NULL) { + afam = a_add(&mage->attribs, a_new(&at_familiar)); + } + afam->data.v = familiar; + if (amage == NULL) { + amage = a_add(&familiar->attribs, a_new(&at_familiarmage)); + } + amage->data.v = mage; + + /* TODO: Diese Attribute beim Tod des Familiars entfernen: */ + /* Wenn der Magier stirbt, dann auch der Vertraute */ + add_trigger(&mage->attribs, "destroy", trigger_killunit(familiar)); + /* Wenn der Vertraute stirbt, dann bekommt der Magier einen Schock */ + add_trigger(&familiar->attribs, "destroy", trigger_shock(mage)); + + a = a_find(mage->attribs, &at_skillmod); + while (a && a->type == &at_skillmod) { + skillmod_data *smd = (skillmod_data *) a->data.v; + if (smd->special == sm_familiar) + break; + a = a->next; + } + if (a == NULL) { + attrib *an = a_add(&mage->attribs, a_new(&at_skillmod)); + skillmod_data *smd = (skillmod_data *) an->data.v; + smd->special = sm_familiar; + smd->skill = NOSKILL; + } + return true; +} + +static int resolve_familiar(variant data, void *addr) +{ + unit *familiar; + int result = resolve_unit(data, &familiar); + if (result == 0 && familiar) { + attrib *a = a_find(familiar->attribs, &at_familiarmage); + if (a != NULL && a->data.v) { + unit *mage = (unit *) a->data.v; + set_familiar(mage, familiar); + } + } + *(unit **) addr = familiar; + return result; +} + +static int read_familiar(attrib * a, void *owner, struct storage *store) +{ + int result = + read_reference(&a->data.v, store, read_unit_reference, resolve_familiar); + if (result == 0 && a->data.v == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +/* clones */ + +void create_newclone(unit * mage, unit * clone) +{ + attrib *a; + + a = a_find(mage->attribs, &at_clone); + if (a == NULL) { + a = a_add(&mage->attribs, a_new(&at_clone)); + a->data.v = clone; + } else + assert(!a->data.v || a->data.v == clone); + /* TODO: Diese Attribute beim Tod des Klons entfernen: */ + + a = a_find(clone->attribs, &at_clonemage); + if (a == NULL) { + a = a_add(&clone->attribs, a_new(&at_clonemage)); + a->data.v = mage; + } else + assert(!a->data.v || a->data.v == mage); + + /* Wenn der Magier stirbt, wird das in destroy_unit abgefangen. + * Kein Trigger, zu kompliziert. */ + + /* Wenn der Klon stirbt, dann bekommt der Magier einen Schock */ + add_trigger(&clone->attribs, "destroy", trigger_clonedied(mage)); +} + +static void set_clone(unit * mage, unit * clone) +{ + attrib *a; + + a = a_find(mage->attribs, &at_clone); + if (a == NULL) { + a = a_add(&mage->attribs, a_new(&at_clone)); + a->data.v = clone; + } else + assert(!a->data.v || a->data.v == clone); + + a = a_find(clone->attribs, &at_clonemage); + if (a == NULL) { + a = a_add(&clone->attribs, a_new(&at_clonemage)); + a->data.v = mage; + } else + assert(!a->data.v || a->data.v == mage); +} + +unit *has_clone(unit * mage) +{ + attrib *a = a_find(mage->attribs, &at_clone); + if (a) + return (unit *) a->data.v; + return NULL; +} + +static int resolve_clone(variant data, void *addr) +{ + unit *clone; + int result = resolve_unit(data, &clone); + if (result == 0 && clone) { + attrib *a = a_find(clone->attribs, &at_clonemage); + if (a != NULL && a->data.v) { + unit *mage = (unit *) a->data.v; + set_clone(mage, clone); + } + } + *(unit **) addr = clone; + return result; +} + +static int read_clone(attrib * a, void *owner, struct storage *store) +{ + int result = + read_reference(&a->data.v, store, read_unit_reference, resolve_clone); + if (result == 0 && a->data.v == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +/* mages */ + +static int resolve_mage(variant data, void *addr) +{ + unit *mage; + int result = resolve_unit(data, &mage); + if (result == 0 && mage) { + attrib *a = a_find(mage->attribs, &at_familiar); + if (a != NULL && a->data.v) { + unit *familiar = (unit *) a->data.v; + set_familiar(mage, familiar); + } + } + *(unit **) addr = mage; + return result; +} + +static int read_magician(attrib * a, void *owner, struct storage *store) +{ + int result = + read_reference(&a->data.v, store, read_unit_reference, resolve_mage); + if (result == 0 && a->data.v == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +static int age_unit(attrib * a) +/* if unit is gone or dead, remove the attribute */ +{ + unit *u = (unit *) a->data.v; + return (u != NULL && u->number > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +attrib_type at_familiarmage = { + "familiarmage", + NULL, + NULL, + age_unit, + a_write_unit, + read_magician, + ATF_UNIQUE +}; + +attrib_type at_familiar = { + "familiar", + NULL, + NULL, + age_unit, + a_write_unit, + read_familiar, + ATF_UNIQUE +}; + +attrib_type at_clonemage = { + "clonemage", + NULL, + NULL, + age_unit, + a_write_unit, + read_magician, + ATF_UNIQUE +}; + +attrib_type at_clone = { + "clone", + NULL, + NULL, + age_unit, + a_write_unit, + read_clone, + ATF_UNIQUE +}; + +unit *get_familiar(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_familiar); + if (a != NULL) { + unit *u = (unit *) a->data.v; + if (u->number > 0) + return u; + } + return NULL; +} + +unit *get_familiar_mage(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_familiarmage); + if (a != NULL) { + unit *u = (unit *) a->data.v; + if (u->number > 0) + return u; + } + return NULL; +} + +unit *get_clone(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_clone); + if (a != NULL) { + unit *u = (unit *) a->data.v; + if (u->number > 0) + return u; + } + return NULL; +} + +unit *get_clone_mage(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_clonemage); + if (a != NULL) { + unit *u = (unit *) a->data.v; + if (u->number > 0) + return u; + } + return NULL; +} + +static bool is_moving_ship(const region * r, ship * sh) +{ + const unit *u = ship_owner(sh); + + if (u) + switch (get_keyword(u->thisorder)) { + case K_ROUTE: + case K_MOVE: + case K_FOLLOW: + return true; + default: + return false; + } + return false; +} + +static castorder *cast_cmd(unit * u, order * ord) +{ + region *r = u->region; + region *target_r = r; + int level, range; + unit *familiar = NULL; + const char *s; + spell *sp = 0; + plane *pl; + spellparameter *args = NULL; + unit * caster = u; + param_t param; + + if (LongHunger(u)) { + cmistake(u, ord, 224, MSG_MAGIC); + return 0; + } + pl = rplane(r); + if (pl && fval(pl, PFL_NOMAGIC)) { + cmistake(u, ord, 269, MSG_MAGIC); + return 0; + } + level = eff_skill(u, SK_MAGIC, r); + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + param = findparam(s, u->faction->locale); + /* für Syntax ' STUFE x REGION y z ' */ + if (param == P_LEVEL) { + int p = getint(); + level = MIN(p, level); + if (level < 1) { + /* Fehler "Das macht wenig Sinn" */ + cmistake(u, ord, 10, MSG_MAGIC); + return 0; + } + s = getstrtoken(); + param = findparam(s, u->faction->locale); + } + if (param == P_REGION) { + int t_x = getint(); + int t_y = getint(); + plane *pl = getplane(u->region); + t_x = rel_to_abs(pl, u->faction, t_x, 0); + t_y = rel_to_abs(pl, u->faction, t_y, 1); + pnormalize(&t_x, &t_y, pl); + target_r = findregion(t_x, t_y); + if (!target_r) { + /* Fehler "Die Region konnte nicht verzaubert werden" */ + ADDMSG(&u->faction->msgs, msg_message("spellregionresists", + "unit region command", u, u->region, ord)); + return 0; + } + s = getstrtoken(); + param = findparam(s, u->faction->locale); + } + /* für Syntax ' REGION x y STUFE z ' + * hier nach REGION nochmal auf STUFE prüfen */ + if (param == P_LEVEL) { + int p = getint(); + level = MIN(p, level); + if (level < 1) { + /* Fehler "Das macht wenig Sinn" */ + cmistake(u, ord, 10, MSG_MAGIC); + return 0; + } + s = getstrtoken(); + } + if (!s[0] || strlen(s) == 0) { + /* Fehler "Es wurde kein Zauber angegeben" */ + cmistake(u, ord, 172, MSG_MAGIC); + return 0; + } + + sp = unit_getspell(u, s, u->faction->locale); + + /* Vertraute können auch Zauber sprechen, die sie selbst nicht + * können. unit_getspell findet aber nur jene Sprüche, die + * die Einheit beherrscht. */ + if (!sp && is_familiar(u)) { + caster = get_familiar_mage(u); + if (caster) { + familiar = u; + sp = unit_getspell(caster, s, caster->faction->locale); + } else { + /* somehow, this familiar has no mage! */ + log_error("cast_cmd: familiar %s is without a mage?\n", unitname(u)); + caster = u; + } + } + + if (!sp) { + /* Fehler 'Spell not found' */ + cmistake(u, ord, 173, MSG_MAGIC); + return 0; + } + /* um testen auf spruchnamen zu unterbinden sollte vor allen + * fehlermeldungen die anzeigen das der magier diesen Spruch + * nur in diese Situation nicht anwenden kann, noch eine + * einfache Sicherheitsprüfung kommen */ + if (!knowsspell(r, u, sp)) { + /* vorsicht! u kann der familiar sein */ + if (!familiar) { + cmistake(u, ord, 173, MSG_MAGIC); + return 0; + } + } + if (sp->sptyp & ISCOMBATSPELL) { + /* Fehler: "Dieser Zauber ist nur im Kampf sinnvoll" */ + cmistake(u, ord, 174, MSG_MAGIC); + return 0; + } + /* Auf dem Ozean Zaubern als quasi-langer Befehl können + * normalerweise nur Meermenschen, ausgenommen explizit als + * OCEANCASTABLE deklarierte Sprüche */ + if (fval(r->terrain, SEA_REGION)) { + if (u_race(u) != new_race[RC_AQUARIAN] + && !fval(u_race(u), RCF_SWIM) + && !(sp->sptyp & OCEANCASTABLE)) { + /* Fehlermeldung */ + ADDMSG(&u->faction->msgs, msg_message("spellfail_onocean", + "unit region command", u, u->region, ord)); + return 0; + } + /* Auf bewegenden Schiffen kann man nur explizit als + * ONSHIPCAST deklarierte Zauber sprechen */ + } else if (u->ship) { + if (is_moving_ship(r, u->ship)) { + if (!(sp->sptyp & ONSHIPCAST)) { + /* Fehler: "Diesen Spruch kann man nicht auf einem sich + * bewegenden Schiff stehend zaubern" */ + cmistake(u, ord, 175, MSG_MAGIC); + return 0; + } + } + } + /* Farcasting bei nicht farcastbaren Sprüchen abfangen */ + range = farcasting(u, target_r); + if (range > 1) { + if (!(sp->sptyp & FARCASTING)) { + /* Fehler "Diesen Spruch kann man nicht in die Ferne + * richten" */ + cmistake(u, ord, 176, MSG_MAGIC); + return 0; + } + if (range > 1024) { /* (2^10) weiter als 10 Regionen entfernt */ + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "spellfail::nocontact", + "target", target_r)); + return 0; + } + } + /* Stufenangabe bei nicht Stufenvariierbaren Sprüchen abfangen */ + if (!(sp->sptyp & SPELLLEVEL)) { + int ilevel = eff_skill(u, SK_MAGIC, u->region); + if (ilevel != level) { + level = ilevel; + ADDMSG(&u->faction->msgs, msg_message("spellfail::nolevel", + "mage region command", u, u->region, ord)); + } + } + /* Vertrautenmagie */ + /* Kennt der Vertraute den Spruch, so zaubert er ganz normal. + * Ansonsten zaubert der Magier durch seinen Vertrauten, dh + * zahlt Komponenten und Aura. Dabei ist die maximale Stufe + * die des Vertrauten! + * Der Spruch wirkt dann auf die Region des Vertrauten und + * gilt nicht als Farcasting. */ + if (familiar) { + if ((sp->sptyp & NOTFAMILIARCAST)) { + /* Fehler: "Diesen Spruch kann der Vertraute nicht zaubern" */ + cmistake(u, ord, 177, MSG_MAGIC); + return 0; + } + if (caster != familiar) { /* Magier zaubert durch Vertrauten */ + if (range > 1) { /* Fehler! Versucht zu Farcasten */ + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "familiar_farcast", + "mage", caster)); + return 0; + } + if (distance(caster->region, r) > eff_skill(caster, SK_MAGIC, caster->region)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "familiar_toofar", + "mage", caster)); + return 0; + } + /* mage auf magier setzen, level anpassen, range für Erhöhung + * der Spruchkosten nutzen, langen Befehl des Magiers + * löschen, zaubern kann er noch */ + range *= 2; + set_order(&caster->thisorder, NULL); + level = MIN(level, eff_skill(caster, SK_MAGIC, caster->region) / 2); + } + } + /* Weitere Argumente zusammenbasteln */ + if (sp->parameter) { + char **params = (char**)malloc(2 * sizeof(char *)); + int p = 0, size = 2; + for (;;) { + s = getstrtoken(); + if (*s == 0) + break; + if (p + 1 >= size) { + size *= 2; + params = (char**)realloc(params, sizeof(char *) * size); + } + params[p++] = strdup(s); + } + params[p] = 0; + args = + add_spellparameter(target_r, caster, sp->parameter, + (const char *const *)params, p, ord); + for (p = 0; params[p]; ++p) + free(params[p]); + free(params); + if (args == NULL) { + /* Syntax war falsch */ + return 0; + } + } + return create_castorder(0, caster, familiar, sp, target_r, level, 0, range, ord, + args); +} + +/* ------------------------------------------------------------- */ +/* Damit man keine Rituale in fremden Gebiet machen kann, diese vor + * Bewegung zaubern. Magier sind also in einem fremden Gebiet eine Runde + * lang verletzlich, da sie es betreten, und angegriffen werden können, + * bevor sie ein Ritual machen können. + * + * Syntax: ZAUBER [REGION X Y] [STUFE ] "Spruchname" [Einheit-1 + * Einheit-2 ..] + * + * Nach Priorität geordnet die Zauber global auswerten. + * + * Die Kosten für Farcasting multiplizieren sich mit der Entfernung, + * cast_level gibt die virtuelle Stufe an, die den durch das Farcasten + * entstandenen Spruchkosten entspricht. Sind die Spruchkosten nicht + * levelabhängig, so sind die Kosten nur von der Entfernung bestimmt, + * die Stärke/Level durch den realen Skill des Magiers + */ + +void magic(void) +{ + region *r; + int rank; + castorder *co; + spellrank spellranks[MAX_SPELLRANK]; + + memset(spellranks, 0, sizeof(spellranks)); + + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + order *ord; + + if (u->number <= 0 || u_race(u) == new_race[RC_SPELL]) + continue; + + if (u_race(u) == new_race[RC_INSECT] && r_insectstalled(r) && + !is_cursed(u->attribs, C_KAELTESCHUTZ, 0)) + continue; + + if (fval(u, UFL_WERE | UFL_LONGACTION)) { + continue; + } + + if (u->thisorder != NULL) { + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == K_CAST) { + castorder *co = cast_cmd(u, ord); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + if (co) { + const spell *sp = co->sp; + add_castorder(&spellranks[sp->rank], co); + } + } + } + } + } + } + + /* Da sich die Aura und Komponenten in der Zwischenzeit verändert + * haben können und sich durch vorherige Sprüche das Zaubern + * erschwert haben kann, muss beim zaubern erneut geprüft werden, ob der + * Spruch überhaupt gezaubert werden kann. + * (level) die effektive Stärke des Spruchs (= Stufe, auf der der + * Spruch gezaubert wird) */ + + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + for (co = spellranks[rank].begin; co; co = co->next) { + order *ord = co->order; + int invalid, resist, success, cast_level = co->level; + bool fumbled = false; + unit *u = co->magician.u; + const spell *sp = co->sp; + region *target_r = co_get_region(co); + + /* reichen die Komponenten nicht, wird der Level reduziert. */ + co->level = eff_spelllevel(u, sp, cast_level, co->distance); + + if (co->level < 1) { + /* Fehlermeldung mit Komponenten generieren */ + cancast(u, sp, cast_level, co->distance, ord); + continue; + } + + if (cast_level > co->level) { + /* Sprüche mit Fixkosten werden immer auf Stufe des Spruchs + * gezaubert, co->level ist aber defaultmäßig Stufe des Magiers */ + if (spl_costtyp(sp) != SPC_FIX) { + ADDMSG(&u->faction->msgs, msg_message("missing_components", + "unit spell level", u, sp, cast_level)); + } + } + + /* Prüfen, ob die realen Kosten für die gewünschten Stufe bezahlt + * werden können */ + if (!cancast(u, sp, co->level, co->distance, ord)) { + /* die Fehlermeldung wird in cancast generiert */ + continue; + } + + co->force = spellpower(target_r, u, sp, co->level, ord); + /* die Stärke kann durch Antimagie auf 0 sinken */ + if (co->force <= 0) { + co->force = 0; + ADDMSG(&u->faction->msgs, msg_message("missing_force", + "unit spell level", u, sp, co->level)); + } + + /* Ziele auf Existenz prüfen und Magieresistenz feststellen. Wurde + * kein Ziel gefunden, so ist verify_targets=0. Scheitert der + * Spruch an der Magieresistenz, so ist verify_targets = 1, bei + * Erfolg auf ganzer Linie ist verify_targets= 2 + */ + verify_targets(co, &invalid, &resist, &success); + if (success + resist == 0) { + /* kein Ziel gefunden, Fehlermeldungen sind in verify_targets */ + /* keine kosten für den zauber */ + continue; /* äußere Schleife, nächster Zauberer */ + } else if (co->force > 0 && resist > 0) { + /* einige oder alle Ziele waren magieresistent */ + if (success == 0) { + co->force = 0; + /* zwar wurde mindestens ein Ziel gefunden, das widerstand + * jedoch dem Zauber. Kosten abziehen und abbrechen. */ + ADDMSG(&u->faction->msgs, msg_message("spell_resist", + "unit region spell", u, r, sp)); + } + } + + /* Auch für Patzer gibt es Erfahrung, müssen die Spruchkosten + * bezahlt werden und die nachfolgenden Sprüche werden teurer */ + if (co->force > 0) { + if (fumble(target_r, u, sp, co->level)) { + /* zuerst bezahlen, dann evt in do_fumble alle Aura verlieren */ + fumbled = true; + } else { + co->level = sp->cast(co); + if (co->level <= 0) { + /* Kosten nur für real benötige Stufe berechnen */ + continue; + } + } + } + /* erst bezahlen, dann Kostenzähler erhöhen */ + if (fumbled) { + do_fumble(co); + } + if (co->level>0) { + pay_spell(u, sp, co->level, co->distance); + } + countspells(u, 1); + } + } + + /* Sind alle Zauber gesprochen gibts Erfahrung */ + for (r = regions; r; r = r->next) { + unit *u; + for (u = r->units; u; u = u->next) { + if (is_mage(u) && countspells(u, 0) > 0) { + produceexp(u, SK_MAGIC, u->number); + /* Spruchlistenaktualiesierung ist in Regeneration */ + } + } + } + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + free_castorders(spellranks[rank].begin); + } + remove_empty_units(); +} + +const char *spell_info(const spell * sp, const struct locale *lang) +{ + return LOC(lang, mkname("spellinfo", sp->sname)); +} + +const char *spell_name(const spell * sp, const struct locale *lang) +{ + return LOC(lang, mkname("spell", sp->sname)); +} + +const char *curse_name(const curse_type * ctype, const struct locale *lang) +{ + return LOC(lang, mkname("spell", ctype->cname)); +} + +spell *unit_getspell(struct unit *u, const char *name, const struct locale * lang) +{ + sc_mage * mage = get_mage(u); + if (mage) { + variant token; + void * tokens = 0; + spellbook *sb = unit_get_spellbook(u); + + if (sb) { + quicklist * ql; + int qi; + + for (qi = 0, ql = sb->spells; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry *sbe = (spellbook_entry *)ql_get(ql, qi); + spell *sp = sbe->sp; + const char *n = spell_name(sp, lang); + if (!n) { + log_error("no translation in locale %s for spell %s\n", locale_name(lang), sp->sname); + } else { + token.v = sp; + addtoken(&tokens, n, token); + } + } + } + + if (tokens) { + if (findtoken(tokens, name, &token) != E_TOK_NOMATCH) { + freetokens(tokens); + return (spell *) token.v; + } + freetokens(tokens); + } + } + return 0; +} + +static critbit_tree cb_spellbooks; + +spellbook * get_spellbook(const char * name) +{ + char buffer[64]; + spellbook * result; + const void * match; + + if (cb_find_prefix(&cb_spellbooks, name, strlen(name), &match, 1, 0)) { + cb_get_kv(match, &result, sizeof(result)); + } else { + size_t len = strlen(name); + result = create_spellbook(name); + assert(strlen(name)+sizeof(result) + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_MAGIC +#define H_KRNL_MAGIC +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------------------------------------------------- */ + +#define MAXCOMBATSPELLS 3 /* PRECOMBAT COMBAT POSTCOMBAT */ +#define MAX_SPELLRANK 9 /* Standard-Rank 5 */ +#define MAXINGREDIENT 5 /* bis zu 5 Komponenten pro Zauber */ +#define CHAOSPATZERCHANCE 10 /* +10% Chance zu Patzern */ + +/* ------------------------------------------------------------- */ + +#define IRONGOLEM_CRUMBLE 15 /* monatlich Chance zu zerfallen */ +#define STONEGOLEM_CRUMBLE 10 /* monatlich Chance zu zerfallen */ + +/* ------------------------------------------------------------- */ +/* Spruchparameter + * Wir suchen beim Parsen des Befehls erstmal nach lokalen Objekten, + * erst in verify_targets wird dann global gesucht, da in den meisten + * Fällen das Zielobjekt lokal sein dürfte */ + +/* siehe auch objtype_t in objtypes.h */ + typedef enum { + SPP_REGION, /* "r" : findregion(x,y) -> *region */ + SPP_UNIT, /* - : atoi36() -> int */ + SPP_TEMP, /* - : temp einheit */ + SPP_BUILDING, /* - : atoi() -> int */ + SPP_SHIP, /* - : atoi() -> int */ + SPP_STRING, /* "c" */ + SPP_INT /* "i" : atoi() -> int */ + } sppobj_t; + + typedef struct spllprm { + sppobj_t typ; + int flag; + union { + struct region *r; + struct unit *u; + struct building *b; + struct ship *sh; + char *s; + char *xs; + int i; + } data; + } spllprm; + + typedef struct spellparameter { + int length; /* Anzahl der Elemente */ + struct spllprm **param; + } spellparameter; + + typedef struct strarray { + int length; /* Anzahl der Elemente */ + char **strings; + } strarray; + +#define TARGET_RESISTS (1<<0) +#define TARGET_NOTFOUND (1<<1) + +/* ------------------------------------------------------------- */ +/* Magierichtungen */ + + extern const char *magic_school[MAXMAGIETYP]; + +/* ------------------------------------------------------------- */ +/* Magier: + * - Magierichtung + * - Magiepunkte derzeit + * - Malus (neg. Wert)/ Bonus (pos. Wert) auf maximale Magiepunkte + * (können sich durch Questen absolut verändern und durch Gegenstände + * temporär). Auch für Artefakt benötigt man permanente MP + * - Anzahl bereits gezauberte Sprüche diese Runde + * - Kampfzauber (3) (vor/während/nach) + * - Spruchliste + */ + +typedef struct combatspell { + int level; + const struct spell *sp; +} combatspell; + +typedef struct spell_names { + struct spell_names *next; + const struct locale *lang; + void * tokens; +} spell_names; + +typedef struct sc_mage { + magic_t magietyp; + int spellpoints; + int spchange; + int spellcount; + combatspell combatspells[MAXCOMBATSPELLS]; + struct spellbook *spellbook; +} sc_mage; + +/* ------------------------------------------------------------- */ +/* Zauberliste */ + + typedef struct castorder { + struct castorder *next; + union { + struct unit *u; + struct fighter *fig; + } magician; /* Magier (kann vom Typ struct unit oder fighter sein) */ + struct unit *_familiar; /* Vertrauter, gesetzt, wenn der Spruch durch + den Vertrauten gezaubert wird */ + const struct spell *sp; /* Spruch */ + int level; /* gewünschte Stufe oder Stufe des Magiers */ + double force; /* Stärke des Zaubers */ + struct region *_rtarget; /* Zielregion des Spruchs */ + int distance; /* Entfernung zur Zielregion */ + struct order *order; /* Befehl */ + struct spellparameter *par; /* für weitere Parameter */ + } castorder; + + struct unit * co_get_caster(struct castorder * co); + struct region * co_get_region(struct castorder * co); + + typedef struct spell_component { + const struct resource_type *type; + int amount; + int cost; + } spell_component; + +/* ------------------------------------------------------------- */ + +/* besondere Spruchtypen */ +#define FARCASTING (1<<0) /* ZAUBER [struct region x y] */ +#define SPELLLEVEL (1<<1) /* ZAUBER [STUFE x] */ + +/* ID's können zu drei unterschiedlichen Entitäten gehören: Einheiten, + * Gebäuden und Schiffen. */ +#define UNITSPELL (1<<2) /* ZAUBER .. [ ..] */ +#define SHIPSPELL (1<<3) /* ZAUBER .. [ ..] */ +#define BUILDINGSPELL (1<<4) /* ZAUBER .. [ ..] */ +#define REGIONSPELL (1<<5) /* wirkt auf struct region */ + +#define PRECOMBATSPELL (1<<7) /* PRÄKAMPFZAUBER .. */ +#define COMBATSPELL (1<<8) /* KAMPFZAUBER .. */ +#define POSTCOMBATSPELL (1<<9) /* POSTKAMPFZAUBER .. */ +#define ISCOMBATSPELL (PRECOMBATSPELL|COMBATSPELL|POSTCOMBATSPELL) + +#define OCEANCASTABLE (1<<10) /* Können auch nicht-Meermenschen auf + hoher See zaubern */ +#define ONSHIPCAST (1<<11) /* kann auch auf von Land ablegenden + Schiffen stehend gezaubert werden */ +/* */ +#define NOTFAMILIARCAST (1<<12) +#define TESTRESISTANCE (1<<13) /* alle Zielobjekte (u, s, b, r) auf + Magieresistenz prüfen */ +#define SEARCHLOCAL (1<<14) /* Ziel muss in der target_region sein */ +#define TESTCANSEE (1<<15) /* alle Zielunits auf cansee prüfen */ +#define ANYTARGET (UNITSPELL|REGIONSPELL|BUILDINGSPELL|SHIPSPELL) /* wirkt auf alle objekttypen (unit, ship, building, region) */ + +/* Flag Spruchkostenberechnung: */ + enum { + SPC_FIX, /* Fixkosten */ + SPC_LEVEL, /* Komponenten pro Level */ + SPC_LINEAR /* Komponenten pro Level und müssen vorhanden sein */ + }; + + enum { + RS_DUMMY, + RS_FARVISION, + MAX_REGIONSPELLS + }; + +/* ------------------------------------------------------------- */ +/* Prototypen */ + + void magic(void); + + void regenerate_aura(void); + + extern struct attrib_type at_seenspell; + extern struct attrib_type at_mage; + extern struct attrib_type at_familiarmage; + extern struct attrib_type at_familiar; + extern struct attrib_type at_clonemage; + extern struct attrib_type at_clone; + extern struct attrib_type at_reportspell; + extern struct attrib_type at_icastle; + + typedef struct icastle_data { + const struct building_type *type; + struct building *building; /* reverse pointer to dissolve the object */ + int time; + } icastle_data; + +/* ------------------------------------------------------------- */ +/* Kommentare: + * + * Spruchzauberrei und Gegenstandszauberrei werden getrennt behandelt. + * Das macht u.a. bestimmte Fehlermeldungen einfacher, das + * identifizieren der Komponennten über den Missversuch ist nicht + * möglich + * Spruchzauberrei: 'ZAUBER [struct region x y] [STUFE a] "Spruchname" [Ziel]' + * Gegenstandszauberrei: 'BENUTZE "Gegenstand" [Ziel]' + * + * Die Funktionen: + */ + +/* Magier */ + sc_mage *create_mage(struct unit *u, magic_t mtyp); + /* macht die struct unit zu einem neuen Magier: legt die struct u->mage an + * und initialisiert den Magiertypus mit mtyp. */ + sc_mage *get_mage(const struct unit *u); + /* gibt u->mage zurück, bei nicht-Magiern *NULL */ + bool is_mage(const struct unit *u); + /* gibt true, wenn u->mage gesetzt. */ + bool is_familiar(const struct unit *u); + /* gibt true, wenn eine Familiar-Relation besteht. */ + +/* Sprüche */ + int get_combatspelllevel(const struct unit *u, int nr); + /* versucht, eine eingestellte maximale Kampfzauberstufe + * zurückzugeben. 0 = Maximum, -1 u ist kein Magier. */ + const struct spell *get_combatspell(const struct unit *u, int nr); + /* gibt den Kampfzauber nr [pre/kampf/post] oder NULL zurück */ + void set_combatspell(struct unit *u, struct spell * sp, struct order *ord, + int level); + /* setzt Kampfzauber */ + void unset_combatspell(struct unit *u, struct spell * sp); + /* löscht Kampfzauber */ + /* fügt den Spruch mit der Id spellid der Spruchliste der Einheit hinzu. */ + int u_hasspell(const struct unit *u, const struct spell *sp); + /* prüft, ob der Spruch in der Spruchliste der Einheit steht. */ + void pick_random_spells(struct faction *f, int level, struct spellbook * book, int num_spells); + void show_new_spells(struct faction * f, int level, const struct spellbook *book); + void updatespelllist(struct unit *u); + /* fügt alle Zauber des Magiegebietes der Einheit, deren Stufe kleiner + * als das aktuelle Magietalent ist, in die Spruchliste der Einheit + * ein */ + bool knowsspell(const struct region *r, const struct unit *u, + const struct spell * sp); + /* prüft, ob die Einheit diesen Spruch gerade beherrscht, dh + * mindestens die erforderliche Stufe hat. Hier können auch Abfragen + * auf spezielle Antimagiezauber auf Regionen oder Einheiten eingefügt + * werden + */ + +/* Magiepunkte */ + int get_spellpoints(const struct unit *u); + /* Gibt die aktuelle Anzahl der Magiepunkte der Einheit zurück */ + void set_spellpoints(struct unit *u, int sp); + /* setzt die Magiepunkte auf sp */ + int change_spellpoints(struct unit *u, int mp); + /* verändert die Anzahl der Magiepunkte der Einheit um +mp */ + int max_spellpoints(const struct region *r, const struct unit *u); + /* gibt die aktuell maximal möglichen Magiepunkte der Einheit zurück */ + int change_maxspellpoints(struct unit *u, int csp); + /* verändert die maximalen Magiepunkte einer Einheit */ + +/* Zaubern */ + extern double spellpower(struct region *r, struct unit *u, const struct spell * sp, + int cast_level, struct order *ord); + /* ermittelt die Stärke eines Spruchs */ + bool fumble(struct region *r, struct unit *u, const struct spell * sp, + int cast_level); + /* true, wenn der Zauber misslingt, bei false gelingt der Zauber */ + + typedef struct spellrank { + struct castorder *begin; + struct castorder **end; + } spellrank; + + struct castorder *create_castorder(struct castorder * co, struct unit *caster, + struct unit * familiar, const struct spell * sp, struct region * r, + int lev, double force, int range, struct order * ord, struct spellparameter * p); + void free_castorder(struct castorder *co); + /* Zwischenspreicher für Zauberbefehle, notwendig für Prioritäten */ + void add_castorder(struct spellrank *cll, struct castorder *co); + /* Hänge c-order co an die letze c-order von cll an */ + void free_castorders(struct castorder *co); + /* Speicher wieder freigeben */ + +/* Prüfroutinen für Zaubern */ + int countspells(struct unit *u, int step); + /* erhöht den Counter für Zaubersprüche um 'step' und gibt die neue + * Anzahl der gezauberten Sprüche zurück. */ + int spellcost(struct unit *u, const struct spell * sp); + /* gibt die für diesen Spruch derzeit notwendigen Magiepunkte auf der + * geringstmöglichen Stufe zurück, schon um den Faktor der bereits + * zuvor gezauberten Sprüche erhöht */ + bool cancast(struct unit *u, const struct spell * spruch, int eff_stufe, + int distance, struct order *ord); + /* true, wenn Einheit alle Komponenten des Zaubers (incl. MP) für die + * geringstmögliche Stufe hat und den Spruch beherrscht */ + void pay_spell(struct unit *u, const struct spell * sp, int eff_stufe, int distance); + /* zieht die Komponenten des Zaubers aus dem Inventory der Einheit + * ab. Die effektive Stufe des gezauberten Spruchs ist wichtig für + * die korrekte Bestimmung der Magiepunktkosten */ + int eff_spelllevel(struct unit *u, const struct spell * sp, int cast_level, + int distance); + /* ermittelt die effektive Stufe des Zaubers. Dabei ist cast_level + * die gewünschte maximale Stufe (im Normalfall Stufe des Magiers, + * bei Farcasting Stufe*2^Entfernung) */ + bool is_magic_resistant(struct unit *magician, struct unit *target, int + resist_bonus); + /* Mapperfunktion für target_resists_magic() vom Typ struct unit. */ + extern double magic_resistance(struct unit *target); + /* gibt die Chance an, mit der einem Zauber widerstanden wird. Je + * größer, desto resistenter ist da Opfer */ + bool target_resists_magic(struct unit *magician, void *obj, int objtyp, + int resist_bonus); + /* gibt false zurück, wenn der Zauber gelingt, true, wenn das Ziel + * widersteht */ + extern struct spell * unit_getspell(struct unit *u, const char *s, + const struct locale *lang); + + /* Sprüche in der struct region */ + /* (sind in curse) */ + extern struct unit *get_familiar(const struct unit *u); + extern struct unit *get_familiar_mage(const struct unit *u); + extern struct unit *get_clone(const struct unit *u); + extern struct unit *get_clone_mage(const struct unit *u); + extern struct attrib_type at_familiar; + extern struct attrib_type at_familiarmage; + extern void remove_familiar(struct unit *mage); + extern bool create_newfamiliar(struct unit *mage, struct unit *familiar); + extern void create_newclone(struct unit *mage, struct unit *familiar); + extern struct unit *has_clone(struct unit *mage); + + extern const char *spell_info(const struct spell *sp, + const struct locale *lang); + extern const char *spell_name(const struct spell *sp, + const struct locale *lang); + extern const char *curse_name(const struct curse_type *ctype, + const struct locale *lang); + + extern struct message *msg_unitnotfound(const struct unit *mage, + struct order *ord, const struct spllprm *spobj); + extern int FactionSpells(void); + + extern void write_spells(struct quicklist *slist, struct storage *store); + extern void read_spells(struct quicklist **slistp, magic_t mtype, + struct storage *store); + extern double MagicPower(void); + + extern struct spellbook * get_spellbook(const char * name); + extern void free_spellbooks(void); +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/magic_test.c b/core/src/kernel/magic_test.c new file mode 100644 index 000000000..8a4a1ef69 --- /dev/null +++ b/core/src/kernel/magic_test.c @@ -0,0 +1,389 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +void test_updatespells(CuTest * tc) +{ + faction * f; + spell * sp; + spellbook *book = 0; + + test_cleanup(); + + f = test_create_faction(0); + sp = create_spell("testspell", 0); + CuAssertPtrNotNull(tc, sp); + + book = create_spellbook("spells"); + CuAssertPtrNotNull(tc, book); + spellbook_add(book, sp, 1); + + CuAssertPtrEquals(tc, 0, f->spellbook); + pick_random_spells(f, 1, book, 1); + CuAssertPtrNotNull(tc, f->spellbook); + CuAssertIntEquals(tc, 1, ql_length(f->spellbook->spells)); + CuAssertPtrNotNull(tc, spellbook_get(f->spellbook, sp)); +} + +void test_spellbooks(CuTest * tc) +{ + spell *sp; + spellbook *herp, *derp; + spellbook_entry *entry; + const char * sname = "herpderp"; + test_cleanup(); + + herp = get_spellbook("herp"); + derp = get_spellbook("derp"); + CuAssertPtrNotNull(tc, herp); + CuAssertPtrNotNull(tc, derp); + CuAssertTrue(tc, derp!=herp); + CuAssertStrEquals(tc, "herp", herp->name); + CuAssertStrEquals(tc, "derp", derp->name); + + sp = create_spell(sname, 0); + spellbook_add(herp, sp, 1); + CuAssertPtrNotNull(tc, sp); + entry = spellbook_get(herp, sp); + CuAssertPtrNotNull(tc, entry); + CuAssertPtrEquals(tc, sp, entry->sp); + /* CuAssertPtrEquals(tc, 0, spellbook_get(derp, sname)); */ + + test_cleanup(); + herp = get_spellbook("herp"); + CuAssertPtrNotNull(tc, herp); + /* CuAssertPtrEquals(tc, 0, spellbook_get(herp, sname)); */ +} + +static spell * test_magic_create_spell(void) +{ + spell *sp; + sp = create_spell("testspell", 0); + + sp->components = (spell_component *) calloc(4, sizeof(spell_component)); + sp->components[0].amount = 1; + sp->components[0].type = rt_find("money"); + sp->components[0].cost = SPC_FIX; + sp->components[1].amount = 1; + sp->components[1].type = rt_find("aura"); + sp->components[1].cost = SPC_LEVEL; + sp->components[2].amount = 1; + sp->components[2].type = rt_find("horse"); + sp->components[2].cost = SPC_LINEAR; + return sp; +} + +void test_pay_spell(CuTest * tc) +{ + spell *sp; + unit * u; + faction * f; + region * r; + int level; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + + sp = test_magic_create_spell(); + CuAssertPtrNotNull(tc, sp); + + set_level(u, SK_MAGIC, 5); + unit_add_spell(u, 0, sp, 1); + + change_resource(u, rt_find("money"), 1); + change_resource(u, rt_find("aura"), 3); + change_resource(u, rt_find("horse"), 3); + + level = eff_spelllevel(u, sp, 3, 1); + CuAssertIntEquals(tc, 3, level); + pay_spell(u, sp, level, 1); + CuAssertIntEquals(tc, 0, get_resource(u, rt_find("money"))); + CuAssertIntEquals(tc, 0, get_resource(u, rt_find("aura"))); + CuAssertIntEquals(tc, 0, get_resource(u, rt_find("horse"))); +} + +void test_pay_spell_failure(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + int level; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + + sp = test_magic_create_spell(); + CuAssertPtrNotNull(tc, sp); + + set_level(u, SK_MAGIC, 5); + unit_add_spell(u, 0, sp, 1); + + CuAssertIntEquals(tc, 1, change_resource(u, rt_find("money"), 1)); + CuAssertIntEquals(tc, 2, change_resource(u, rt_find("aura"), 2)); + CuAssertIntEquals(tc, 3, change_resource(u, rt_find("horse"), 3)); + + level = eff_spelllevel(u, sp, 3, 1); + CuAssertIntEquals(tc, 2, level); + pay_spell(u, sp, level, 1); + CuAssertIntEquals(tc, 1, change_resource(u, rt_find("money"), 1)); + CuAssertIntEquals(tc, 3, change_resource(u, rt_find("aura"), 3)); + CuAssertIntEquals(tc, 2, change_resource(u, rt_find("horse"), 1)); + + CuAssertIntEquals(tc, 0, eff_spelllevel(u, sp, 3, 1)); + CuAssertIntEquals(tc, 0, change_resource(u, rt_find("money"), -1)); + CuAssertIntEquals(tc, 0, eff_spelllevel(u, sp, 2, 1)); +} + +void test_getspell_unit(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + struct locale * lang; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + u = test_create_unit(f, r); + create_mage(u, M_GRAY); + skill_enabled[SK_MAGIC] = 1; + + set_level(u, SK_MAGIC, 1); + + lang = find_locale("de"); + sp = create_spell("testspell", 0); + locale_setstring(lang, mkname("spell", sp->sname), "Herp-a-derp"); + + CuAssertPtrEquals(tc, 0, unit_getspell(u, "Herp-a-derp", lang)); + + unit_add_spell(u, 0, sp, 1); + CuAssertPtrNotNull(tc, unit_getspell(u, "Herp-a-derp", lang)); +} + +void test_getspell_faction(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + struct locale * lang; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + f->magiegebiet = M_TYBIED; + u = test_create_unit(f, r); + create_mage(u, f->magiegebiet); + skill_enabled[SK_MAGIC] = 1; + + set_level(u, SK_MAGIC, 1); + + lang = find_locale("de"); + sp = create_spell("testspell", 0); + locale_setstring(lang, mkname("spell", sp->sname), "Herp-a-derp"); + + CuAssertPtrEquals(tc, 0, unit_getspell(u, "Herp-a-derp", lang)); + + f->spellbook = create_spellbook(0); + spellbook_add(f->spellbook, sp, 1); + CuAssertPtrEquals(tc, sp, unit_getspell(u, "Herp-a-derp", lang)); +} + +void test_getspell_school(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + struct locale * lang; + struct spellbook * book; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + f->magiegebiet = M_TYBIED; + u = test_create_unit(f, r); + create_mage(u, f->magiegebiet); + skill_enabled[SK_MAGIC] = 1; + set_level(u, SK_MAGIC, 1); + + lang = find_locale("de"); + sp = create_spell("testspell", 0); + locale_setstring(lang, mkname("spell", sp->sname), "Herp-a-derp"); + + CuAssertPtrEquals(tc, 0, unit_getspell(u, "Herp-a-derp", lang)); + + book = faction_get_spellbook(f); + CuAssertPtrNotNull(tc, book); + spellbook_add(book, sp, 1); + CuAssertPtrEquals(tc, sp, unit_getspell(u, "Herp-a-derp", lang)); +} + +void test_set_pre_combatspell(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + const int index = 0; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + f->magiegebiet = M_TYBIED; + u = test_create_unit(f, r); + skill_enabled[SK_MAGIC] = 1; + set_level(u, SK_MAGIC, 1); + sp = create_spell("testspell", 0); + sp->sptyp |= PRECOMBATSPELL; + + unit_add_spell(u, 0, sp, 1); + + set_combatspell(u, sp, 0, 2); + CuAssertPtrEquals(tc, sp, (spell *)get_combatspell(u, index)); + set_level(u, SK_MAGIC, 2); + CuAssertIntEquals(tc, 2, get_combatspelllevel(u, index)); + set_level(u, SK_MAGIC, 1); + CuAssertIntEquals(tc, 1, get_combatspelllevel(u, index)); + unset_combatspell(u, sp); + CuAssertIntEquals(tc, 0, get_combatspelllevel(u, index)); + CuAssertPtrEquals(tc, 0, (spell *)get_combatspell(u, index)); +} + +void test_set_main_combatspell(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + const int index = 1; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + f->magiegebiet = M_TYBIED; + u = test_create_unit(f, r); + skill_enabled[SK_MAGIC] = 1; + set_level(u, SK_MAGIC, 1); + sp = create_spell("testspell", 0); + sp->sptyp |= COMBATSPELL; + + unit_add_spell(u, 0, sp, 1); + + set_combatspell(u, sp, 0, 2); + CuAssertPtrEquals(tc, sp, (spell *)get_combatspell(u, index)); + set_level(u, SK_MAGIC, 2); + CuAssertIntEquals(tc, 2, get_combatspelllevel(u, index)); + set_level(u, SK_MAGIC, 1); + CuAssertIntEquals(tc, 1, get_combatspelllevel(u, index)); + unset_combatspell(u, sp); + CuAssertIntEquals(tc, 0, get_combatspelllevel(u, index)); + CuAssertPtrEquals(tc, 0, (spell *)get_combatspell(u, index)); +} + +void test_set_post_combatspell(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + const int index = 2; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + f->magiegebiet = M_TYBIED; + u = test_create_unit(f, r); + skill_enabled[SK_MAGIC] = 1; + set_level(u, SK_MAGIC, 1); + sp = create_spell("testspell", 0); + sp->sptyp |= POSTCOMBATSPELL; + + unit_add_spell(u, 0, sp, 1); + + set_combatspell(u, sp, 0, 2); + CuAssertPtrEquals(tc, sp, (spell *)get_combatspell(u, index)); + set_level(u, SK_MAGIC, 2); + CuAssertIntEquals(tc, 2, get_combatspelllevel(u, index)); + set_level(u, SK_MAGIC, 1); + CuAssertIntEquals(tc, 1, get_combatspelllevel(u, index)); + unset_combatspell(u, sp); + CuAssertIntEquals(tc, 0, get_combatspelllevel(u, index)); + CuAssertPtrEquals(tc, 0, (spell *)get_combatspell(u, index)); +} + +void test_hasspell(CuTest * tc) +{ + spell *sp; + struct unit * u; + struct faction * f; + struct region * r; + + test_cleanup(); + test_create_world(); + r = findregion(0, 0); + f = test_create_faction(0); + f->magiegebiet = M_TYBIED; + u = test_create_unit(f, r); + skill_enabled[SK_MAGIC] = 1; + sp = create_spell("testspell", 0); + sp->sptyp |= POSTCOMBATSPELL; + + unit_add_spell(u, 0, sp, 2); + + set_level(u, SK_MAGIC, 1); + CuAssertTrue(tc, !u_hasspell(u, sp)); + + set_level(u, SK_MAGIC, 2); + CuAssertTrue(tc, u_hasspell(u, sp)); + + set_level(u, SK_MAGIC, 1); + CuAssertTrue(tc, !u_hasspell(u, sp)); +} + +CuSuite *get_magic_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_updatespells); + SUITE_ADD_TEST(suite, test_spellbooks); + SUITE_ADD_TEST(suite, test_pay_spell); + SUITE_ADD_TEST(suite, test_pay_spell_failure); + SUITE_ADD_TEST(suite, test_getspell_unit); + SUITE_ADD_TEST(suite, test_getspell_faction); + SUITE_ADD_TEST(suite, test_getspell_school); + SUITE_ADD_TEST(suite, test_set_pre_combatspell); + SUITE_ADD_TEST(suite, test_set_main_combatspell); + SUITE_ADD_TEST(suite, test_set_post_combatspell); + SUITE_ADD_TEST(suite, test_hasspell); + return suite; +} diff --git a/core/src/kernel/message.c b/core/src/kernel/message.c new file mode 100644 index 000000000..001e04004 --- /dev/null +++ b/core/src/kernel/message.c @@ -0,0 +1,296 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "message.h" + +/* kernel includes */ +#include "building.h" +#include "faction.h" +#include "item.h" +#include "order.h" +#include "plane.h" +#include "region.h" +#include "unit.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +typedef struct msg_setting { + struct msg_setting *next; + const struct message_type *type; + int level; +} msg_setting; + +/************ Compatibility function *************/ +#define MAXSTRLEN (4*DISPLAYSIZE+3) +#include "region.h" +#include + +static void +arg_set(variant args[], const message_type * mtype, const char *buffer, + variant v) +{ + int i; + for (i = 0; i != mtype->nparameters; ++i) { + if (!strcmp(buffer, mtype->pnames[i])) + break; + } + if (i != mtype->nparameters) { + args[i] = v; + } else { + fprintf(stderr, "invalid parameter %s for message type %s\n", buffer, + mtype->name); + assert(!"program aborted."); + } +} + +struct message *msg_feedback(const struct unit *u, struct order *ord, + const char *name, const char *sig, ...) +{ + va_list marker; + const message_type *mtype = mt_find(name); + char paramname[64]; + const char *ic = sig; + variant args[16]; + variant var; + memset(args, 0, sizeof(args)); + + if (ord == NULL) + ord = u->thisorder; + + if (!mtype) { + log_error("trying to create message of unknown type \"%s\"\n", name); + return msg_message("missing_feedback", "unit region command name", u, + u->region, ord, name); + } + + var.v = (void *)u; + arg_set(args, mtype, "unit", var); + var.v = (void *)u->region; + arg_set(args, mtype, "region", var); + var.v = (void *)ord; + arg_set(args, mtype, "command", var); + + va_start(marker, sig); + while (*ic && !isalnum(*ic)) + ic++; + while (*ic) { + char *oc = paramname; + int i; + + while (isalnum(*ic)) + *oc++ = *ic++; + *oc = '\0'; + + for (i = 0; i != mtype->nparameters; ++i) { + if (!strcmp(paramname, mtype->pnames[i])) + break; + } + if (i != mtype->nparameters) { + if (mtype->types[i]->vtype == VAR_VOIDPTR) { + args[i].v = va_arg(marker, void *); + } else if (mtype->types[i]->vtype == VAR_INT) { + args[i].i = va_arg(marker, int); + } else { + assert(!"unknown variant type"); + } + } else { + log_error("invalid parameter %s for message type %s\n", paramname, mtype->name); + assert(!"program aborted."); + } + while (*ic && !isalnum(*ic)) + ic++; + } + va_end(marker); + + return msg_create(mtype, args); +} + +message *msg_message(const char *name, const char *sig, ...) + /* msg_message("oops_error", "unit region command", u, r, cmd) */ +{ + va_list marker; + const message_type *mtype = mt_find(name); + char paramname[64]; + const char *ic = sig; + variant args[16]; + memset(args, 0, sizeof(args)); + + if (!mtype) { + log_warning("trying to create message of unknown type \"%s\"\n", name); + if (strcmp(name, "missing_message") != 0) { + return msg_message("missing_message", "name", name); + } + return NULL; + } + + va_start(marker, sig); + while (*ic && !isalnum(*ic)) + ic++; + while (*ic) { + char *oc = paramname; + int i; + + while (isalnum(*ic)) + *oc++ = *ic++; + *oc = '\0'; + + for (i = 0; i != mtype->nparameters; ++i) { + if (!strcmp(paramname, mtype->pnames[i])) + break; + } + if (i != mtype->nparameters) { + if (mtype->types[i]->vtype == VAR_VOIDPTR) { + args[i].v = va_arg(marker, void *); + } else if (mtype->types[i]->vtype == VAR_INT) { + args[i].i = va_arg(marker, int); + } else { + assert(!"unknown variant type"); + } + } else { + log_error("invalid parameter %s for message type %s\n", paramname, mtype->name); + assert(!"program aborted."); + } + while (*ic && !isalnum(*ic)) + ic++; + } + va_end(marker); + + return msg_create(mtype, args); +} + +static void +caddmessage(region * r, faction * f, const char *s, msg_t mtype, int level) +{ + message *m = NULL; + +#define LOG_ENGLISH +#ifdef LOG_ENGLISH + if (f && f->locale != default_locale) { + log_warning("message for locale \"%s\": %s\n", locale_name(f->locale), s); + } +#endif + unused(level); + switch (mtype) { + case MSG_INCOME: + assert(f); + m = add_message(&f->msgs, msg_message("msg_economy", "string", s)); + break; + case MSG_BATTLE: + assert(0 || !"battle-meldungen nicht über addmessage machen"); + break; + case MSG_MOVE: + assert(f); + m = add_message(&f->msgs, msg_message("msg_movement", "string", s)); + break; + case MSG_COMMERCE: + assert(f); + m = add_message(&f->msgs, msg_message("msg_economy", "string", s)); + break; + case MSG_PRODUCE: + assert(f); + m = add_message(&f->msgs, msg_message("msg_production", "string", s)); + break; + case MSG_MAGIC: + case MSG_COMMENT: + case MSG_MESSAGE: + case MSG_ORCVERMEHRUNG: + case MSG_EVENT: + /* Botschaften an REGION oder einzelne PARTEI */ + m = msg_message("msg_event", "string", s); + if (!r) { + assert(f); + m = add_message(&f->msgs, m); + } else { + if (f == NULL) + add_message(&r->msgs, m); + else + r_addmessage(r, f, m); + } + break; + default: + assert(!"Ungültige Msg-Klasse!"); + } + if (m) + msg_release(m); +} + +void addmessage(region * r, faction * f, const char *s, msg_t mtype, int level) +{ + caddmessage(r, f, s, mtype, level); +} + +message * cmistake(const unit * u, struct order *ord, int mno, int mtype) +{ + message * result; + static char msgname[20]; + unused(mtype); + + if (is_monsters(u->faction)) + return 0; + sprintf(msgname, "error%d", mno); + result = msg_feedback(u, ord, msgname, ""); + ADDMSG(&u->faction->msgs, result); + return result; +} + +extern unsigned int new_hashstring(const char *s); + +void free_messagelist(message_list * msgs) +{ + struct mlist **mlistptr = &msgs->begin; + while (*mlistptr) { + struct mlist *ml = *mlistptr; + *mlistptr = ml->next; + msg_release(ml->msg); + free(ml); + } + free(msgs); +} + +message *add_message(message_list ** pm, message * m) +{ + if (!lomem && m != NULL) { + struct mlist *mnew = malloc(sizeof(struct mlist)); + if (*pm == NULL) { + *pm = malloc(sizeof(message_list)); + (*pm)->end = &(*pm)->begin; + } + mnew->msg = msg_addref(m); + mnew->next = NULL; + *((*pm)->end) = mnew; + (*pm)->end = &mnew->next; + } + return m; +} diff --git a/core/src/kernel/message.h b/core/src/kernel/message.h new file mode 100644 index 000000000..9820950be --- /dev/null +++ b/core/src/kernel/message.h @@ -0,0 +1,66 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_MESSAGE +#define H_KRNL_MESSAGE +#ifdef __cplusplus +extern "C" { +#endif + +#include + + struct message; + struct faction; + struct msglevel; + + struct message_type; + + typedef struct mlist { + struct mlist *next; + struct message *msg; + } mlist; + + typedef struct message_list { + struct mlist *begin, **end; + } message_list; + + extern void free_messagelist(message_list * msgs); + + typedef struct msglevel { + /* used to set specialized msg-levels */ + struct msglevel *next; + const struct message_type *type; + int level; + } msglevel; + + extern struct message *msg_message(const char *name, const char *sig, ...); + extern struct message *msg_feedback(const struct unit *, struct order *cmd, + const char *name, const char *sig, ...); + extern struct message *add_message(struct message_list **pm, + struct message *m); + void addmessage(struct region *r, struct faction *f, const char *s, + msg_t mtype, int level); + +#define ADDMSG(msgs, mcreate) { message * m = mcreate; if (m) { assert(m->refcount>=1); add_message(msgs, m); msg_release(m); } } + + extern struct message * cmistake(const struct unit *u, struct order *ord, int mno, + int mtype); +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/move.c b/core/src/kernel/move.c new file mode 100644 index 000000000..8f595dda5 --- /dev/null +++ b/core/src/kernel/move.c @@ -0,0 +1,2710 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "move.h" + +#include "alchemy.h" +#include "connection.h" +#include "build.h" +#include "building.h" +#include "calendar.h" +#include "curse.h" +#include "faction.h" +#include "item.h" +#include "magic.h" +#include "message.h" +#include "order.h" +#include "plane.h" +#include "race.h" +#include "region.h" +#include "render.h" +#include "reports.h" +#include "save.h" +#include "ship.h" +#include "skill.h" +#include "terrain.h" +#include "teleport.h" +#include "unit.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* attributes includes */ +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +int *storms; + +typedef struct traveldir { + int no; + direction_t dir; + int age; +} traveldir; + +static attrib_type at_traveldir = { + "traveldir", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, /* Weil normales Aging an ungünstiger Stelle */ + a_writechars, + a_readchars +}; + +typedef struct follower { + struct follower *next; + unit *uf; + unit *ut; + const region_list *route_end; +} follower; + +static void +get_followers(unit * target, region * r, const region_list * route_end, + follower ** followers) +{ + unit *uf; + for (uf = r->units; uf; uf = uf->next) { + if (fval(uf, UFL_FOLLOWING) && !fval(uf, UFL_NOTMOVING)) { + const attrib *a = a_findc(uf->attribs, &at_follow); + if (a && a->data.v == target) { + follower *fnew = (follower *)malloc(sizeof(follower)); + fnew->uf = uf; + fnew->ut = target; + fnew->route_end = route_end; + fnew->next = *followers; + *followers = fnew; + } + } + } +} + +static void shiptrail_init(attrib * a) +{ + a->data.v = calloc(1, sizeof(traveldir)); +} + +static void shiptrail_finalize(attrib * a) +{ + free(a->data.v); +} + +static int shiptrail_age(attrib * a) +{ + traveldir *t = (traveldir *) (a->data.v); + + t->age--; + return (t->age > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +static int shiptrail_read(attrib * a, void *owner, struct storage *store) +{ + traveldir *t = (traveldir *) (a->data.v); + + t->no = store->r_int(store); + t->dir = (direction_t) store->r_int(store); + t->age = store->r_int(store); + return AT_READ_OK; +} + +static void +shiptrail_write(const attrib * a, const void *owner, struct storage *store) +{ + traveldir *t = (traveldir *) (a->data.v); + store->w_int(store, t->no); + store->w_int(store, t->dir); + store->w_int(store, t->age); +} + +attrib_type at_shiptrail = { + "traveldir_new", + shiptrail_init, + shiptrail_finalize, + shiptrail_age, + shiptrail_write, + shiptrail_read +}; + +static int age_speedup(attrib * a) +{ + if (a->data.sa[0] > 0) { + a->data.sa[0] = a->data.sa[0] - a->data.sa[1]; + } + return (a->data.sa[0] > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +attrib_type at_speedup = { + "speedup", + NULL, NULL, + age_speedup, + a_writeint, + a_readint +}; + +/* ------------------------------------------------------------- */ + +static attrib_type at_driveweight = { + "driveweight", NULL, NULL, NULL, NULL, NULL +}; + +static bool entrance_allowed(const struct unit *u, const struct region *r) +{ +#ifdef REGIONOWNERS + faction *owner = region_get_owner(r); + if (owner == NULL || u->faction == owner) + return true; + if (alliedfaction(rplane(r), owner, u->faction, HELP_TRAVEL)) + return true; + return false; +#else + return true; +#endif +} + +int personcapacity(const unit * u) +{ + int cap = u_race(u)->weight + u_race(u)->capacity; + return cap; +} + +static int eff_weight(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_driveweight); + + if (a) + return weight(u) + a->data.i; + + return weight(u); +} + +static void +get_transporters(const item * itm, int *p_animals, int *p_acap, int *p_vehicles, + int *p_vcap) +{ + int vehicles = 0, vcap = 0; + int animals = 0, acap = 0; + + for (; itm != NULL; itm = itm->next) { + const item_type *itype = itm->type; + if (itype->capacity > 0) { + if (itype->flags & ITF_ANIMAL) { + animals += itm->number; + if (acap == 0) + acap = itype->capacity; + assert(acap == itype->capacity + || !"animals with different capacity not supported"); + } + if (itype->flags & ITF_VEHICLE) { + vehicles += itm->number; + if (vcap == 0) + vcap = itype->capacity; + assert(vcap == itype->capacity + || !"vehicles with different capacity not supported"); + } + } + } + *p_vehicles = vehicles; + *p_animals = animals; + *p_vcap = vcap; + *p_acap = acap; +} + +static int ridingcapacity(unit * u) +{ + int vehicles = 0, vcap = 0; + int animals = 0, acap = 0; + + get_transporters(u->items, &animals, &acap, &vehicles, &vcap); + + /* Man trägt sein eigenes Gewicht plus seine Kapazität! Die Menschen + ** tragen nichts (siehe walkingcapacity). Ein Wagen zählt nur, wenn er + ** von zwei Pferden gezogen wird */ + + animals = MIN(animals, effskill(u, SK_RIDING) * u->number * 2); + if (fval(u_race(u), RCF_HORSE)) + animals += u->number; + + /* maximal diese Pferde können zum Ziehen benutzt werden */ + vehicles = MIN(animals / HORSESNEEDED, vehicles); + + return vehicles * vcap + animals * acap; +} + +int walkingcapacity(const struct unit *u) +{ + int n, tmp, people, pferde_fuer_wagen; + int wagen_ohne_pferde, wagen_mit_pferden, wagen_mit_trollen; + int vehicles = 0, vcap = 0; + int animals = 0, acap = 0; + + get_transporters(u->items, &animals, &acap, &vehicles, &vcap); + + /* Das Gewicht, welches die Pferde tragen, plus das Gewicht, welches + * die Leute tragen */ + + pferde_fuer_wagen = MIN(animals, effskill(u, SK_RIDING) * u->number * 4); + if (fval(u_race(u), RCF_HORSE)) { + animals += u->number; + people = 0; + } else { + people = u->number; + } + + /* maximal diese Pferde können zum Ziehen benutzt werden */ + wagen_mit_pferden = MIN(vehicles, pferde_fuer_wagen / HORSESNEEDED); + + n = wagen_mit_pferden * vcap; + + if (u_race(u) == new_race[RC_TROLL]) { + /* 4 Trolle ziehen einen Wagen. */ + /* Unbesetzte Wagen feststellen */ + wagen_ohne_pferde = vehicles - wagen_mit_pferden; + + /* Genug Trolle, um die Restwagen zu ziehen? */ + wagen_mit_trollen = MIN(u->number / 4, wagen_ohne_pferde); + + /* Wagenkapazität hinzuzählen */ + n += wagen_mit_trollen * vcap; + wagen_ohne_pferde -= wagen_mit_trollen; + } + + n += animals * acap; + n += people * personcapacity(u); + /* Goliathwasser */ + tmp = get_effect(u, oldpotiontype[P_STRONG]); + if (tmp > 0) { + int horsecap = olditemtype[I_HORSE]->capacity; + if (tmp > people) + tmp = people; + n += tmp * (horsecap - personcapacity(u)); + } + /* change_effect wird in ageing gemacht */ + tmp = get_item(u, I_TROLLBELT); + n += MIN(people, tmp) * (STRENGTHMULTIPLIER - 1) * personcapacity(u); + + return n; +} + +enum { + E_CANWALK_OK = 0, + E_CANWALK_TOOMANYHORSES, + E_CANWALK_TOOMANYCARTS, + E_CANWALK_TOOHEAVY +}; + +static int canwalk(unit * u) +{ + int maxwagen, maxpferde; + int vehicles = 0, vcap = 0; + int animals = 0, acap = 0; + + /* workaround: monsters are too stupid to drop items, therefore they have + * infinite carrying capacity */ + + if (is_monsters(u->faction)) + return E_CANWALK_OK; + + get_transporters(u->items, &animals, &acap, &vehicles, &vcap); + + maxwagen = effskill(u, SK_RIDING) * u->number * 2; + if (u_race(u) == new_race[RC_TROLL]) { + maxwagen = MAX(maxwagen, u->number / 4); + } + maxpferde = effskill(u, SK_RIDING) * u->number * 4 + u->number; + + if (animals > maxpferde) + return E_CANWALK_TOOMANYHORSES; + + if (walkingcapacity(u) - eff_weight(u) >= 0) + return E_CANWALK_OK; + + /* Stimmt das Gewicht, impliziert dies hier, daß alle Wagen ohne + * Zugpferde/-trolle als Fracht aufgeladen wurden: zu viele Pferde hat + * die Einheit nicht zum Ziehen benutzt, also nicht mehr Wagen gezogen + * als erlaubt. */ + + if (vehicles > maxwagen) + return E_CANWALK_TOOMANYCARTS; + /* Es muß nicht zwingend an den Wagen liegen, aber egal... (man + * könnte z.B. auch 8 Eisen abladen, damit ein weiterer Wagen als + * Fracht draufpaßt) */ + + return E_CANWALK_TOOHEAVY; +} + +bool canfly(unit * u) +{ + if (get_item(u, I_PEGASUS) >= u->number && effskill(u, SK_RIDING) >= 4) + return true; + + if (fval(u_race(u), RCF_FLY)) + return true; + + if (get_movement(&u->attribs, MV_FLY)) + return true; + + return false; +} + +bool canswim(unit * u) +{ + if (get_item(u, I_DOLPHIN) >= u->number && effskill(u, SK_RIDING) >= 4) + return true; + + if (u_race(u)->flags & RCF_FLY) + return true; + + if (u_race(u)->flags & RCF_SWIM) + return true; + + if (get_movement(&u->attribs, MV_FLY)) + return true; + + if (get_movement(&u->attribs, MV_SWIM)) + return true; + + return false; +} + +static int canride(unit * u) +{ + int horses = 0, maxhorses, unicorns = 0, maxunicorns; + int skill = effskill(u, SK_RIDING); + item *itm; + static const item_type *it_horse = 0; + static const item_type *it_elvenhorse = 0; + static const item_type *it_charger = 0; + + if (it_horse == 0) { + it_horse = it_find("horse"); + it_elvenhorse = it_find("elvenhorse"); + it_charger = it_find("charger"); + } + + for (itm = u->items; itm; itm = itm->next) { + if (itm->type == it_horse || itm->type == it_charger) { + horses += itm->number; + } else if (itm->type == it_elvenhorse) { + unicorns += itm->number; + } + } + + maxunicorns = (skill / 5) * u->number; + maxhorses = skill * u->number * 2; + + if (!(u_race(u)->flags & RCF_HORSE) + && ((horses == 0 && unicorns == 0) + || horses > maxhorses || unicorns > maxunicorns)) { + return 0; + } + + if (ridingcapacity(u) - eff_weight(u) >= 0) { + if (horses == 0 && unicorns >= u->number && !(u_race(u)->flags & RCF_HORSE)) { + return 2; + } + return 1; + } + + return 0; +} + +static bool cansail(const region * r, ship * sh) +{ + /* sonst ist construction:: size nicht ship_type::maxsize */ + assert(!sh->type->construction + || sh->type->construction->improvement == NULL); + + if (sh->type->construction && sh->size != sh->type->construction->maxsize) { + return false; + } else { + int n = 0, p = 0; + int mweight = shipcapacity(sh); + int mcabins = sh->type->cabins; + + getshipweight(sh, &n, &p); + + if (n > mweight) + return false; + if (mcabins && p > mcabins) + return false; + } + return true; +} + +int enoughsailors(const ship * sh, const region * r) +{ + int n; + unit *u; + + n = 0; + + for (u = r->units; u; u = u->next) { + if (u->ship == sh) + n += eff_skill(u, SK_SAILING, r) * u->number; + } + return n >= sh->type->sumskill; +} + +/* ------------------------------------------------------------- */ + +static ship *do_maelstrom(region * r, unit * u) +{ + int damage; + ship *sh = u->ship; + + damage = rng_int() % 75 + rng_int() % 75 - eff_skill(u, SK_SAILING, r) * 4; + + if (damage <= 0) { + return sh; + } + + damage_ship(u->ship, 0.01 * damage); + + if (sh->damage >= sh->size * DAMAGE_SCALE) { + ADDMSG(&u->faction->msgs, msg_message("entermaelstrom", + "region ship damage sink", r, sh, damage, 1)); + remove_ship(&sh->region->ships, sh); + return NULL; + } + ADDMSG(&u->faction->msgs, msg_message("entermaelstrom", + "region ship damage sink", r, sh, damage, 0)); + return u->ship; +} + +/** sets a marker in the region telling that the unit has travelled through it + * this is used for two distinctly different purposes: + * - to report that a unit has travelled through. the report function + * makes sure to only report the ships of travellers, not the travellers + * themselves + * - to report the region to the traveller + */ +void travelthru(const unit * u, region * r) +{ + attrib *ru = a_add(&r->attribs, a_new(&at_travelunit)); + + fset(r, RF_TRAVELUNIT); + + ru->data.v = (void *)u; + + /* the first and last region of the faction gets reset, because travelthrough + * could be in regions that are located before the [first, last] interval, + * and recalculation is needed */ +#ifdef SMART_INTERVALS + update_interval(u->faction, r); +#endif +} + +static void leave_trail(ship * sh, region * from, region_list * route) +{ + region *r = from; + + while (route != NULL) { + region *rn = route->data; + direction_t dir = reldirection(r, rn); + + /* TODO: we cannot leave a trail into special directions + * if we use this kind of direction-attribute */ + if (dir < MAXDIRECTIONS && dir >= 0) { + traveldir *td = NULL; + attrib *a = a_find(r->attribs, &at_shiptrail); + + while (a != NULL && a->type == &at_shiptrail) { + td = (traveldir *) a->data.v; + if (td->no == sh->no) + break; + a = a->next; + } + + if (a == NULL || a->type != &at_shiptrail) { + a = a_add(&(r->attribs), a_new(&at_shiptrail)); + td = (traveldir *) a->data.v; + td->no = sh->no; + } + td->dir = dir; + td->age = 2; + } + route = route->next; + r = rn; + } +} + +static void +mark_travelthru(const unit * u, region * r, const region_list * route, + const region_list * route_end) +{ + /* kein travelthru in der letzten region! */ + while (route != route_end) { + travelthru(u, r); + r = route->data; + route = route->next; + } +} + +ship *move_ship(ship * sh, region * from, region * to, region_list * route) +{ + unit **iunit = &from->units; + unit **ulist = &to->units; + bool trail = (route == NULL); + + if (from != to) { + translist(&from->ships, &to->ships, sh); + sh->region = to; + } + if (!trail) { + leave_trail(sh, from, route); + trail = true; + } + + while (*iunit != NULL) { + unit *u = *iunit; + assert(u->region == from); + + if (u->ship == sh) { + if (route != NULL) + mark_travelthru(u, from, route, NULL); + if (from != to) { + u->ship = 0; /* temporary trick -- do not use u_set_ship here */ + move_unit(u, to, ulist); + ulist = &u->next; + u->ship = sh; /* undo the trick -- do not use u_set_ship here */ + } + if (route && eff_skill(u, SK_SAILING, from) >= 1) { + produceexp(u, SK_SAILING, u->number); + } + } + if (*iunit == u) + iunit = &u->next; + } + + return sh; +} + +static bool is_freezing(const unit * u) +{ + if (u_race(u) != new_race[RC_INSECT]) + return false; + if (is_cursed(u->attribs, C_KAELTESCHUTZ, 0)) + return false; + return true; +} + +int check_ship_allowed(struct ship *sh, const region * r) +{ + int c = 0; + static const building_type *bt_harbour = NULL; + + if (bt_harbour == NULL) + bt_harbour = bt_find("harbour"); + + if (sh->region && r_insectstalled(r)) { + /* insekten dürfen nicht hier rein. haben wir welche? */ + unit *u; + + for (u = sh->region->units; u != NULL; u = u->next) { + if (u->ship != sh) + continue; + + if (is_freezing(u)) { + unit *captain = ship_owner(sh); + if (captain) { + ADDMSG(&captain->faction->msgs, msg_message("detectforbidden", + "unit region", u, r)); + } + + return SA_NO_INSECT; + } + } + } + + if (bt_harbour && buildingtype_exists(r, bt_harbour, true)) + return SA_HARBOUR; + for (c = 0; sh->type->coasts[c] != NULL; ++c) { + if (sh->type->coasts[c] == r->terrain) + return SA_COAST; + } + + return SA_NO_COAST; +} + +static bool flying_ship(const ship * sh) +{ + if (sh->type->flags & SFL_FLY) + return true; + if (sh->flags & SF_FLYING) + return true; + return false; +} + +static void set_coast(ship * sh, region * r, region * rnext) +{ + if (sh->type->flags & SFL_NOCOAST) { + sh->coast = NODIRECTION; + } else if (!fval(rnext->terrain, SEA_REGION) && !flying_ship(sh)) { + sh->coast = reldirection(rnext, r); + assert(fval(r->terrain, SEA_REGION)); + } else { + sh->coast = NODIRECTION; + } +} + +static float damage_drift(void) +{ + static float value = -1.0F; + if (value < 0) { + value = get_param_flt(global.parameters, "rules.ship.damage_drift", 0.02F); + } + return value; +} + +static void drifting_ships(region * r) +{ + direction_t d; + bool drift = get_param_int(global.parameters, "rules.ship.drifting", 1)!=0; + + if (fval(r->terrain, SEA_REGION)) { + ship **shp = &r->ships; + while (*shp) { + ship *sh = *shp; + region *rnext = NULL; + region_list *route = NULL; + unit *firstu = NULL, *captain; + int d_offset; + direction_t dir = 0; + + if (sh->type->fishing > 0) { + sh->flags |= SF_FISHING; + } + + /* Schiff schon abgetrieben oder durch Zauber geschützt? */ + if (!drift || fval(sh, SF_DRIFTED) || is_cursed(sh->attribs, C_SHIP_NODRIFT, 0)) { + shp = &sh->next; + continue; + } + + /* Kapitän bestimmen */ + for (captain = r->units; captain; captain = captain->next) { + if (captain->ship != sh) + continue; + if (firstu == NULL) + firstu = captain; + if (eff_skill(captain, SK_SAILING, r) >= sh->type->cptskill) { + break; + } + } + /* Kapitän da? Beschädigt? Genügend Matrosen? + * Genügend leicht? Dann ist alles OK. */ + + assert(sh->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ + if (captain && sh->size == sh->type->construction->maxsize + && enoughsailors(sh, r) && cansail(r, sh)) { + shp = &sh->next; + continue; + } + + /* Auswahl einer Richtung: Zuerst auf Land, dann + * zufällig. Falls unmögliches Resultat: vergiß es. */ + d_offset = rng_int() % MAXDIRECTIONS; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn; + dir = (direction_t) ((d + d_offset) % MAXDIRECTIONS); + rn = rconnect(r, dir); + if (rn != NULL && fval(rn->terrain, SAIL_INTO) && check_ship_allowed(sh, rn) > 0) { + rnext = rn; + if (!fval(rnext->terrain, SEA_REGION)) + break; + } + } + + if (rnext == NULL) { + shp = &sh->next; + continue; + } + + /* Das Schiff und alle Einheiten darin werden nun von r + * nach rnext verschoben. Danach eine Meldung. */ + add_regionlist(&route, rnext); + + set_coast(sh, r, rnext); + sh = move_ship(sh, r, rnext, route); + free_regionlist(route); + + if (firstu != NULL) { + unit *u, *lastu = NULL; + message *msg = msg_message("ship_drift", "ship dir", sh, dir); + for (u = firstu; u; u = u->next) { + if (u->ship == sh && !fval(u->faction, FFL_MARK)) { + fset(u->faction, FFL_MARK); + add_message(&u->faction->msgs, msg); + lastu = u->next; + } + } + for (u = firstu; u != lastu; u = u->next) { + freset(u->faction, FFL_MARK); + } + msg_release(msg); + } + + if (sh != NULL) { + fset(sh, SF_DRIFTED); + + damage_ship(sh, damage_drift()); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + remove_ship(&sh->region->ships, sh); + } + } + + if (*shp == sh) + shp = &sh->next; + } + } +} + +static bool present(region * r, unit * u) +{ + return (bool) (u && u->region == r); +} + +static void caught_target(region * r, unit * u) +{ + attrib *a = a_find(u->attribs, &at_follow); + + /* Verfolgungen melden */ + /* Misserfolgsmeldung, oder bei erfolgreichem Verfolgen unter + * Umstaenden eine Warnung. */ + + if (a) { + unit *target = (unit *) a->data.v; + + if (!present(r, target)) { + ADDMSG(&u->faction->msgs, msg_message("followfail", "unit follower", + target, u)); + } else if (!alliedunit(target, u->faction, HELP_ALL) + && cansee(target->faction, r, u, 0)) { + ADDMSG(&target->faction->msgs, msg_message("followdetect", + "unit follower", target, u)); + } + } +} + +/* TODO: Unsichtbarkeit bedenken ! */ + +static unit *bewegung_blockiert_von(unit * reisender, region * r) +{ + unit *u; + int perception = 0; + bool contact = false; + unit *guard = NULL; + + if (fval(u_race(reisender), RCF_ILLUSIONARY)) + return NULL; + for (u = r->units; u && !contact; u = u->next) { + if (is_guard(u, GUARD_TRAVELTHRU)) { + int sk = eff_skill(u, SK_PERCEPTION, r); + if (invisible(reisender, u) >= reisender->number) + continue; + if (u->faction == reisender->faction) + contact = true; + else if (ucontact(u, reisender)) + contact = true; + else if (alliedunit(u, reisender->faction, HELP_GUARD)) + contact = true; + else if (sk >= perception) { + perception = sk; + guard = u; + } + } + } + if (!contact && guard) { + double prob = 0.3; /* 30% base chance */ + prob += 0.1 * (perception - eff_stealth(reisender, r)); + prob += 0.1 * MIN(guard->number, get_item(guard, I_AMULET_OF_TRUE_SEEING)); + + if (chance(prob)) { + return guard; + } + } + return NULL; +} + +static bool is_guardian_u(const unit * guard, unit * u, unsigned int mask) +{ + if (guard->faction == u->faction) + return false; + if (is_guard(guard, mask) == 0) + return false; + if (alliedunit(guard, u->faction, HELP_GUARD)) + return false; + if (ucontact(guard, u)) + return false; + if (!cansee(guard->faction, u->region, u, 0)) + return false; + + return true; +} + +static bool is_guardian_r(const unit * guard) +{ + if (guard->number == 0) + return false; + if (besieged(guard)) + return false; + + /* if region_owners exist then they may be guardians: */ + if (guard->building && rule_region_owners() && guard==building_owner(guard->building)) { + faction *owner = region_get_owner(guard->region); + if (owner == guard->faction) { + building *bowner = largestbuilding(guard->region, &cmp_taxes, false); + if (bowner == guard->building) { + return true; + } + } + } + + if ((guard->flags & UFL_GUARD) == 0) + return false; + if (!armedmen(guard, true) && !fval(u_race(guard), RCF_UNARMEDGUARD)) + return false; + return true; +} + +bool is_guard(const struct unit * u, int mask) +{ + return is_guardian_r(u) && (getguard(u) & mask) != 0; +} + +#define MAXGUARDCACHE 16 +/** returns the guard which prevents 'u' from doing 'mask' actions in 'r'. +*/ +unit *is_guarded(region * r, unit * u, unsigned int mask) +{ + unit *u2 = NULL; + int i, noguards = 1; + static unit *guardcache[MAXGUARDCACHE], *lastguard; /* STATIC_XCALL: used across calls */ + static int gamecookie = -1; + + if (!fval(r, RF_GUARDED)) { + return NULL; + } + + if (gamecookie != global.cookie) { + if (gamecookie >= 0) { + /* clear the previous turn's cache */ + memset(guardcache, 0, sizeof(guardcache)); + lastguard = NULL; + } + gamecookie = global.cookie; + } + + if (lastguard && lastguard->region == r) { + if (is_guardian_u(lastguard, u, mask)) { + return lastguard; + } + } + + for (i = 0; i != MAXGUARDCACHE; ++i) { + unit *guard = guardcache[i]; + if (guard && guard != lastguard && guard->region == r) { + noguards = 0; + if (is_guardian_u(guard, u, mask)) { + lastguard = guard; + return guard; + } + if (u2 == guard) { + /* same guard twice signals we've tested everyone */ + return NULL; + } + u2 = guard; + } else { + /* exhausted all the guards in the cache, but maybe we'll find one later? */ + break; + } + } + + /* at this point, u2 is the last unit we tested to + * be a guard (and failed), or NULL + * i is the position of the first free slot in the cache */ + + for (u2 = (u2 ? u2->next : r->units); u2; u2 = u2->next) { + if (is_guardian_r(u2)) { + noguards = 0; + /* u2 is a guard, so worth remembering */ + if (i < MAXGUARDCACHE) + guardcache[i++] = u2; + if (is_guardian_u(u2, u, mask)) { + /* u2 is our guard. stop processing (we might have to go further next time) */ + lastguard = u2; + return u2; + } + } + } + /* there are no more guards. we signal this by duplicating the last one. + * i is still the position of the first free slot in the cache */ + if (i > 0 && i < MAXGUARDCACHE) { + guardcache[i] = guardcache[i - 1]; + } + + if (noguards) { + /* you are mistaken, sir. there are no guards in these lands */ + freset(r, RF_GUARDED); + } + return NULL; +} + +static const char *shortdirections[MAXDIRECTIONS] = { + "dir_nw", + "dir_ne", + "dir_east", + "dir_se", + "dir_sw", + "dir_west" +}; + +static void cycle_route(order * ord, unit * u, int gereist) +{ + int bytes, cm = 0; + char tail[1024], *bufp = tail; + char neworder[2048]; + const char *token; + direction_t d = NODIRECTION; + bool paused = false; + bool pause; + order *norder; + size_t size = sizeof(tail) - 1; + + if (get_keyword(ord) != K_ROUTE) + return; + tail[0] = '\0'; + + init_tokens(ord); + skip_token(); + + neworder[0] = 0; + for (cm = 0;; ++cm) { + const struct locale *lang = u->faction->locale; + pause = false; + token = getstrtoken(); + if (token && *token) { + d = finddirection(token, lang); + if (d == D_PAUSE) { + pause = true; + } else if (d == NODIRECTION) { + break; + } + } else { + break; + } + if (cm < gereist) { + /* hier sollte keine PAUSE auftreten */ + assert(!pause); + if (!pause) { + const char *loc = LOC(lang, shortdirections[d]); + if (bufp != tail) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, loc, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } else if (strlen(neworder) > sizeof(neworder) / 2) + break; + else if (cm == gereist && !paused && pause) { + const char *loc = LOC(lang, parameters[P_PAUSE]); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, loc, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + paused = true; + } else if (pause) { + /* da PAUSE nicht in ein shortdirections[d] umgesetzt wird (ist + * hier keine normale direction), muss jede PAUSE einzeln + * herausgefiltert und explizit gesetzt werden */ + if (neworder[0]) + strcat(neworder, " "); + strcat(neworder, LOC(lang, parameters[P_PAUSE])); + } else { + if (neworder[0]) + strcat(neworder, " "); + strcat(neworder, LOC(lang, shortdirections[d])); + } + } + + if (neworder[0]) { + norder = create_order(K_ROUTE, u->faction->locale, "%s %s", neworder, tail); + } else { + norder = create_order(K_ROUTE, u->faction->locale, "%s", tail); + } + replace_order(&u->orders, ord, norder); + free_order(norder); +} + +static bool transport(unit * ut, unit * u) +{ + order *ord; + + if (LongHunger(u) || fval(ut->region->terrain, SEA_REGION)) { + return false; + } + + for (ord = ut->orders; ord; ord = ord->next) { + if (get_keyword(ord) == K_TRANSPORT) { + init_tokens(ord); + skip_token(); + if (getunit(ut->region, ut->faction) == u) { + return true; + } + } + } + return false; +} + +static bool can_move(const unit * u) +{ + if (u_race(u)->flags & RCF_CANNOTMOVE) + return false; + if (get_movement(&u->attribs, MV_CANNOTMOVE)) + return false; + return true; +} + +static void init_transportation(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + unit *u; + + /* This is just a simple check for non-corresponding K_TRANSPORT/ + * K_DRIVE. This is time consuming for an error check, but there + * doesn't seem to be an easy way to speed this up. */ + for (u = r->units; u; u = u->next) { + if (get_keyword(u->thisorder) == K_DRIVE && can_move(u) + && !fval(u, UFL_NOTMOVING) && !LongHunger(u)) { + unit *ut; + + init_tokens(u->thisorder); + skip_token(); + ut = getunit(r, u->faction); + if (ut == NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "feedback_unit_not_found", "")); + continue; + } + if (!transport(ut, u)) { + if (cansee(u->faction, r, ut, 0)) { + cmistake(u, u->thisorder, 286, MSG_MOVE); + } else { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "feedback_unit_not_found", "")); + } + } + } + } + + /* This calculates the weights of all transported units and + * adds them to an internal counter which is used by travel () to + * calculate effective weight and movement. */ + + if (!fval(r->terrain, SEA_REGION)) { + for (u = r->units; u; u = u->next) { + order *ord; + int w = 0; + + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == K_TRANSPORT) { + init_tokens(ord); + skip_token(); + for (;;) { + unit *ut = getunit(r, u->faction); + + if (ut == NULL) + break; + if (get_keyword(ut->thisorder) == K_DRIVE && can_move(ut) + && !fval(ut, UFL_NOTMOVING) && !LongHunger(ut)) { + init_tokens(ut->thisorder); + skip_token(); + if (getunit(r, ut->faction) == u) { + w += weight(ut); + } + } + } + } + } + if (w > 0) + a_add(&u->attribs, a_new(&at_driveweight))->data.i = w; + } + } + } +} + +static bool roadto(const region * r, direction_t dir) +{ + /* wenn es hier genug strassen gibt, und verbunden ist, und es dort + * genug strassen gibt, dann existiert eine strasse in diese richtung */ + region *r2; + static const curse_type *roads_ct = NULL; + + if (dir >= MAXDIRECTIONS || dir < 0) + return false; + r2 = rconnect(r, dir); + if (r == NULL || r2 == NULL) + return false; + + if (roads_ct == NULL) + roads_ct = ct_find("magicstreet"); + if (roads_ct != NULL) { + if (get_curse(r->attribs, roads_ct) != NULL) + return true; + if (get_curse(r2->attribs, roads_ct) != NULL) + return true; + } + + if (r->terrain->max_road <= 0) + return false; + if (r2->terrain->max_road <= 0) + return false; + if (rroad(r, dir) < r->terrain->max_road) + return false; + if (rroad(r2, dir_invert(dir)) < r2->terrain->max_road) + return false; + return true; +} + +static const region_list *cap_route(region * r, const region_list * route, + const region_list * route_end, int speed) +{ + region *current = r; + int moves = speed; + const region_list *iroute = route; + while (iroute != route_end) { + region *next = iroute->data; + direction_t reldir = reldirection(current, next); + + /* adjust the range of the unit */ + if (roadto(current, reldir)) + moves -= BP_ROAD; + else + moves -= BP_NORMAL; + if (moves < 0) + break; + iroute = iroute->next; + current = next; + } + return iroute; +} + +static region *next_region(unit * u, region * current, region * next) +{ + connection *b; + + b = get_borders(current, next); + while (b != NULL) { + if (b->type->move) { + region *rto = b->type->move(b, u, current, next, true); + if (rto != next) { + /* the target region was changed (wisps, for example). check the + * new target region for borders */ + next = rto; + b = get_borders(current, next); + continue; + } + } + b = b->next; + } + return next; +} + +static const region_list *reroute(unit * u, const region_list * route, + const region_list * route_end) +{ + region *current = u->region; + while (route != route_end) { + region *next = next_region(u, current, route->data); + if (next != route->data) + break; + route = route->next; + } + return route; +} + +static void make_route(unit * u, order * ord, region_list ** routep) +{ + region_list **iroute = routep; + region *current = u->region; + region *next = NULL; + const char *token = getstrtoken(); + int error = movewhere(u, token, current, &next); + + if (error != E_MOVE_OK) { + message *msg = movement_error(u, token, ord, error); + if (msg != NULL) { + add_message(&u->faction->msgs, msg); + msg_release(msg); + } + next = NULL; + } + + while (next != NULL) { + if (current == next) { + /* PAUSE */ + break; + } + next = next_region(u, current, next); + + add_regionlist(iroute, next); + iroute = &(*iroute)->next; + + current = next; + token = getstrtoken(); + error = movewhere(u, token, current, &next); + if (error) { + message *msg = movement_error(u, token, ord, error); + if (msg != NULL) { + add_message(&u->faction->msgs, msg); + msg_release(msg); + } + next = NULL; + } + } +} + +/** calculate the speed of a unit + * + * zu Fuß reist man 1 Region, zu Pferd 2 Regionen. Mit Straßen reist + * man zu Fuß 2, mit Pferden 3 weit. + * + * Berechnet wird das mit BPs. Zu Fuß hat man 4 BPs, zu Pferd 6. + * Normalerweise verliert man 3 BP pro Region, bei Straßen nur 2 BP. + * Außerdem: Wenn Einheit transportiert, nur halbe BP + */ +static int movement_speed(unit * u) +{ + int mp; + static const curse_type *speed_ct; + static bool init = false; + double dk = u_race(u)->speed; + + assert(u->number); + /* dragons have a fixed speed, and no other effects work on them: */ + switch (old_race(u_race(u))) { + case RC_DRAGON: + case RC_WYRM: + case RC_FIREDRAGON: + case RC_BIRTHDAYDRAGON: + case RC_SONGDRAGON: + return BP_DRAGON; + default: + break; + } + + if (!init) { + init = true; + speed_ct = ct_find("speed"); + } + if (speed_ct) { + curse *c = get_curse(u->attribs, speed_ct); + if (c != NULL) { + int men = get_cursedmen(u, c); + dk *= 1.0 + (double)men / (double)u->number; + } + } + + switch (canride(u)) { + + case 1: /* Pferd */ + mp = BP_RIDING; + break; + + case 2: /* Einhorn */ + mp = BP_UNICORN; + break; + + default: + mp = BP_WALKING; + + /* Siebenmeilentee */ + if (get_effect(u, oldpotiontype[P_FAST]) >= u->number) { + mp *= 2; + change_effect(u, oldpotiontype[P_FAST], -u->number); + } + + /* unicorn in inventory */ + if (u->number <= get_item(u, I_FEENSTIEFEL)) { + mp *= 2; + } + + /* Im Astralraum sind Tyb und Ill-Magier doppelt so schnell. + * Nicht kumulativ mit anderen Beschleunigungen! */ + if (mp * dk <= BP_WALKING * u_race(u)->speed && is_astral(u->region) + && is_mage(u)) { + sc_mage *mage = get_mage(u); + if (mage->magietyp == M_TYBIED || mage->magietyp == M_ILLAUN) { + mp *= 2; + } + } + break; + } + return (int)(dk * mp); +} + +enum { + TRAVEL_NORMAL, + TRAVEL_FOLLOWING, + TRAVEL_TRANSPORTED, + TRAVEL_RUNNING +}; + +static arg_regions *var_copy_regions(const region_list * begin, int size) +{ + const region_list *rsrc; + + if (size > 0) { + int i = 0; + arg_regions *dst = + (arg_regions *) malloc(sizeof(arg_regions) + sizeof(region *) * size); + dst->nregions = size; + dst->regions = (region **) (dst + 1); + for (rsrc = begin; i != size; rsrc = rsrc->next) { + dst->regions[i++] = rsrc->data; + } + return dst; + } + return NULL; +} + +static const region_list *travel_route(unit * u, + const region_list * route_begin, const region_list * route_end, order * ord, + int mode) +{ + region *r = u->region; + region *current = u->region; + const region_list *iroute = route_begin; + int steps = 0; + bool landing = false; /* aquarians have landed */ + + while (iroute && iroute != route_end) { + region *next = iroute->data; + direction_t reldir = reldirection(current, next); + connection *b = get_borders(current, next); + + /* check if we are caught by guarding units */ + if (iroute != route_begin && mode != TRAVEL_RUNNING + && mode != TRAVEL_TRANSPORTED) { + unit *wache = bewegung_blockiert_von(u, current); + if (wache != NULL) { + ADDMSG(&u->faction->msgs, msg_message("moveblockedbyguard", + "unit region guard", u, current, wache)); + break; + } + } + + /* check movement from/to oceans. + * aquarian special, flying units, horses, the works */ + if ((u_race(u)->flags & RCF_FLY) == 0) { + if (!fval(next->terrain, SEA_REGION)) { + /* next region is land */ + if (fval(current->terrain, SEA_REGION)) { + int moving = u_race(u)->flags & (RCF_SWIM | RCF_WALK | RCF_COASTAL); + /* Die Einheit kann nicht fliegen, ist im Ozean, und will an Land */ + if (moving != (RCF_SWIM | RCF_WALK) && (moving & RCF_COASTAL) == 0) { + /* can't swim+walk and isn't allowed to enter coast from sea */ + if (ord != NULL) + cmistake(u, ord, 44, MSG_MOVE); + break; + } + landing = true; + } else if ((u_race(u)->flags & RCF_WALK) == 0) { + /* Spezialeinheiten, die nicht laufen können. */ + ADDMSG(&u->faction->msgs, msg_message("detectocean", + "unit region", u, next)); + break; + } else if (landing) { + /* wir sind diese woche angelandet */ + ADDMSG(&u->faction->msgs, msg_message("detectocean", + "unit region", u, next)); + break; + } + } else { + /* Ozeanfelder können nur von Einheiten mit Schwimmen und ohne + * Pferde betreten werden. */ + if (!(canswim(u) || canfly(u))) { + ADDMSG(&u->faction->msgs, msg_message("detectocean", + "unit region", u, next)); + break; + } + } + + if (fval(current->terrain, SEA_REGION) || fval(next->terrain, SEA_REGION)) { + /* trying to enter or exit ocean with horses, are we? */ + if (has_horses(u)) { + /* tries to do it with horses */ + if (ord != NULL) + cmistake(u, ord, 67, MSG_MOVE); + break; + } + } + + } + + /* movement blocked by a wall */ + if (reldir >= 0 && move_blocked(u, current, next)) { + ADDMSG(&u->faction->msgs, msg_message("leavefail", + "unit region", u, next)); + break; + } + + /* region ownership only: region owned by enemies */ + if (!entrance_allowed(u, next)) { + ADDMSG(&u->faction->msgs, msg_message("regionowned", + "unit region target", u, current, next)); + break; + } + + /* illusionary units disappear in antimagic zones */ + if (fval(u_race(u), RCF_ILLUSIONARY)) { + curse *c = get_curse(next->attribs, ct_find("antimagiczone")); + if (curse_active(c)) { + curse_changevigour(&next->attribs, c, (float)-u->number); + ADDMSG(&u->faction->msgs, msg_message("illusionantimagic", "unit", u)); + set_number(u, 0); + break; + } + } + + /* terrain is marked as forbidden (curse, etc) */ + if (fval(next, RF_BLOCKED) || fval(next->terrain, FORBIDDEN_REGION)) { + ADDMSG(&u->faction->msgs, msg_message("detectforbidden", + "unit region", u, next)); + break; + } + + /* unit is an insect and cannot move into a glacier */ + if (u_race(u) == new_race[RC_INSECT]) { + if (r_insectstalled(next) && is_freezing(u)) { + ADDMSG(&u->faction->msgs, msg_message("detectforbidden", + "unit region", u, next)); + break; + } + } + + /* effect of borders */ + while (b != NULL) { + if (b->type->move) { + b->type->move(b, u, current, next, false); + } + b = b->next; + } + + current = next; + iroute = iroute->next; + ++steps; + if (u->number == 0) + break; + } + + if (iroute != route_begin) { + /* the unit has moved at least one region */ + int walkmode; + + setguard(u, GUARD_NONE); + cycle_route(ord, u, steps); + + if (mode == TRAVEL_RUNNING) { + walkmode = 0; + } + if (canride(u)) { + walkmode = 1; + produceexp(u, SK_RIDING, u->number); + } else { + walkmode = 2; + } + + /* Berichte über Durchreiseregionen */ + + if (mode != TRAVEL_TRANSPORTED) { + arg_regions *ar = var_copy_regions(route_begin, steps - 1); + ADDMSG(&u->faction->msgs, msg_message("travel", + "unit mode start end regions", u, walkmode, r, current, ar)); + } + + mark_travelthru(u, r, route_begin, iroute); + move_unit(u, current, NULL); + + /* make orders for the followers */ + } + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + setguard(u, GUARD_NONE); + assert(u->region == current); + return iroute; +} + +static bool ship_ready(const region * r, unit * u) +{ + if (!u->ship || u!=ship_owner(u->ship)) { + cmistake(u, u->thisorder, 146, MSG_MOVE); + return false; + } + if (eff_skill(u, SK_SAILING, r) < u->ship->type->cptskill) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "error_captain_skill_low", "value ship", u->ship->type->cptskill, + u->ship)); + return false; + } + assert(u->ship->type->construction->improvement == NULL); /* sonst ist construction::size nicht ship_type::maxsize */ + if (u->ship->size != u->ship->type->construction->maxsize) { + cmistake(u, u->thisorder, 15, MSG_MOVE); + return false; + } + if (!enoughsailors(u->ship, r)) { + cmistake(u, u->thisorder, 1, MSG_MOVE); +/* mistake(u, u->thisorder, + "Auf dem Schiff befinden sich zuwenig erfahrene Seeleute.", MSG_MOVE); */ + return false; + } + if (!cansail(r, u->ship)) { + cmistake(u, u->thisorder, 18, MSG_MOVE); + return false; + } + return true; +} + +unit *owner_buildingtyp(const region * r, const building_type * bt) +{ + building *b; + unit *owner; + + for (b = rbuildings(r); b; b = b->next) { + owner = building_owner(b); + if (b->type == bt && owner != NULL) { + if (b->size >= bt->maxsize) { + return owner; + } + } + } + + return NULL; +} + +bool +buildingtype_exists(const region * r, const building_type * bt, bool working) +{ + building *b; + + for (b = rbuildings(r); b; b = b->next) { + if (b->type == bt && b->size >= bt->maxsize && (!working || fval(b, BLD_WORKING))) + return true; + } + + return false; +} + +/* Prüft, ob Ablegen von einer Küste in eine der erlaubten Richtungen erfolgt. */ + +static bool check_takeoff(ship * sh, region * from, region * to) +{ + if (!fval(from->terrain, SEA_REGION) && sh->coast != NODIRECTION) { + direction_t coast = sh->coast; + direction_t dir = reldirection(from, to); + direction_t coastr = (direction_t) ((coast + 1) % MAXDIRECTIONS); + direction_t coastl = + (direction_t) ((coast + MAXDIRECTIONS - 1) % MAXDIRECTIONS); + + if (dir != coast && dir != coastl && dir != coastr + && !buildingtype_exists(from, bt_find("harbour"), true)) { + return false; + } + } + + return true; +} + +static void +sail(unit * u, order * ord, bool move_on_land, region_list ** routep) +{ + region *starting_point = u->region; + region *current_point, *last_point; + int k, step = 0; + region_list **iroute = routep; + ship *sh = u->ship; + faction *f = u->faction; + region *next_point = NULL; + int error; + const char *token = getstrtoken(); + + if (routep) + *routep = NULL; + + error = movewhere(u, token, starting_point, &next_point); + if (error) { + message *msg = movement_error(u, token, ord, error); + if (msg != NULL) { + add_message(&u->faction->msgs, msg); + msg_release(msg); + } + return; + } + + if (!ship_ready(starting_point, u)) + return; + + /* Wir suchen so lange nach neuen Richtungen, wie es geht. Diese werden + * dann nacheinander ausgeführt. */ + + k = shipspeed(sh, u); + + last_point = starting_point; + current_point = starting_point; + + /* die nächste Region, in die man segelt, wird durch movewhere () aus der + * letzten Region bestimmt. + * + * Anfangen tun wir bei starting_point. next_point ist beim ersten + * Durchlauf schon gesetzt (Parameter!). current_point ist die letzte gültige, + * befahrene Region. */ + + while (next_point && current_point != next_point && step < k) { + const char *token; + int error; + const terrain_type *tthis = current_point->terrain; + /* these values need to be updated if next_point changes (due to storms): */ + const terrain_type *tnext = next_point->terrain; + + assert(sh == u->ship || !"ship has sunk, but we didn't notice it"); + + if (fval(next_point->terrain, FORBIDDEN_REGION)||fval(next_point,RF_BLOCKED)) { + ADDMSG(&f->msgs, msg_message("sailforbidden", + "ship region", sh, next_point)); + break; + } + + if (!flying_ship(sh)) { + int stormchance; + static int stormyness; + static int gamecookie = -1; + int reason; + + if (gamecookie != global.cookie) { + bool storms_enabled = get_param_int(global.parameters, "rules.ship.storms", 1)!=0; + if (storms_enabled) { + gamedate date; + get_gamedate(turn, &date); + stormyness = storms[date.month] * 5; + } + gamecookie = global.cookie; + } + + /* storms should be the first thing we do. */ + stormchance = stormyness / shipspeed(sh, u); + if (check_leuchtturm(next_point, NULL)) + stormchance /= 3; + + if (rng_int() % 10000 < stormchance * sh->type->storm + && fval(current_point->terrain, SEA_REGION)) { + if (!is_cursed(sh->attribs, C_SHIP_NODRIFT, 0)) { + region *rnext = NULL; + bool storm = true; + int d_offset = rng_int() % MAXDIRECTIONS; + direction_t d; + /* Sturm nur, wenn nächste Region Hochsee ist. */ + for (d = 0; d != MAXDIRECTIONS; ++d) { + direction_t dnext = (direction_t) ((d + d_offset) % MAXDIRECTIONS); + region *rn = rconnect(current_point, dnext); + + if (rn != NULL) { + if (fval(rn->terrain, FORBIDDEN_REGION)) + continue; + if (!fval(rn->terrain, SEA_REGION)) { + storm = false; + break; + } + if (rn != next_point) + rnext = rn; + } + } + if (storm && rnext != NULL) { + ADDMSG(&f->msgs, msg_message("storm", "ship region sink", + sh, current_point, sh->damage >= sh->size * DAMAGE_SCALE)); + + /* damage the ship. we handle destruction in the end */ + damage_ship(sh, damage_drift()); + if (sh->damage >= sh->size * DAMAGE_SCALE) + break; + + next_point = rnext; + /* these values need to be updated if next_point changes (due to storms): */ + tnext = next_point->terrain; + } + } + } + + if (!fval(tthis, SEA_REGION)) { + if (!fval(tnext, SEA_REGION)) { + if (!move_on_land) { + /* check that you're not traveling from one land region to another. */ + ADDMSG(&u->faction->msgs, msg_message("shipnoshore", + "ship region", sh, next_point)); + break; + } + } else { + if (!check_takeoff(sh, current_point, next_point)) { + /* Schiff kann nicht ablegen */ + cmistake(u, ord, 182, MSG_MOVE); + break; + } + } + } else if (fval(tnext, SEA_REGION)) { + /* target region is an ocean, and we're not leaving a shore */ + if (!(sh->type->flags & SFL_OPENSEA)) { + /* ship can only stay close to shore */ + direction_t d; + + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rc = rconnect(next_point, d); + if (rc == NULL || !fval(rc->terrain, SEA_REGION)) + break; + } + if (d == MAXDIRECTIONS) { + /* Schiff kann nicht aufs offene Meer */ + cmistake(u, ord, 249, MSG_MOVE); + break; + } + } + } + + reason = check_ship_allowed(sh, next_point); + if (reason<0) { + /* for some reason or another, we aren't allowed in there.. */ + if (check_leuchtturm(current_point, NULL) || reason == SA_NO_INSECT) { + ADDMSG(&f->msgs, msg_message("sailnolandingstorm", "ship region", sh, next_point)); + } else { + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.nolanding", + 0.10F); + ADDMSG(&f->msgs, msg_message("sailnolanding", "ship region", sh, + next_point)); + damage_ship(sh, dmg); + /* we handle destruction at the end */ + } + break; + } + + if (curse_active(get_curse(next_point->attribs, ct_find("maelstrom")))) { + if (do_maelstrom(next_point, u) == NULL) + break; + } + + } + + /* !flying_ship */ + /* Falls Blockade, endet die Seglerei hier */ + if (move_blocked(u, current_point, next_point)) { + ADDMSG(&u->faction->msgs, msg_message("sailfail", "ship region", sh, + current_point)); + break; + } + + /* Falls kein Problem, eines weiter ziehen */ + fset(sh, SF_MOVED); + if (iroute) { + add_regionlist(iroute, next_point); + iroute = &(*iroute)->next; + } + step++; + + last_point = current_point; + current_point = next_point; + + if (!fval(current_point->terrain, SEA_REGION) + && !is_cursed(sh->attribs, C_SHIP_FLYING, 0)) + break; + token = getstrtoken(); + error = movewhere(u, token, current_point, &next_point); + if (error || next_point == NULL) { + message *msg = movement_error(u, token, ord, error); + if (msg != NULL) { + add_message(&u->faction->msgs, msg); + msg_release(msg); + } + next_point = current_point; + break; + } + } + + if (sh->damage >= sh->size * DAMAGE_SCALE) { + if (sh->region) { + ADDMSG(&f->msgs, msg_message("shipsink", "ship", sh)); + remove_ship(&sh->region->ships, sh); + } + sh = NULL; + } + + /* Nun enthält current_point die Region, in der das Schiff seine Runde + * beendet hat. Wir generieren hier ein Ereignis für den Spieler, das + * ihm sagt, bis wohin er gesegelt ist, falls er überhaupt vom Fleck + * gekommen ist. Das ist nicht der Fall, wenn er von der Küste ins + * Inland zu segeln versuchte */ + + if (sh != NULL && fval(sh, SF_MOVED)) { + unit *hafenmeister; + /* nachdem alle Richtungen abgearbeitet wurden, und alle Einheiten + * transferiert wurden, kann der aktuelle Befehl gelöscht werden. */ + cycle_route(ord, u, step); + set_order(&u->thisorder, NULL); + if (!move_on_land) { + set_coast(sh, last_point, current_point); + } + + if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) { + ADDMSG(&f->msgs, msg_message("shipfly", "ship from to", sh, + starting_point, current_point)); + } else { + ADDMSG(&f->msgs, msg_message("shipsail", "ship from to", sh, + starting_point, current_point)); + } + + /* Das Schiff und alle Einheiten darin werden nun von + * starting_point nach current_point verschoben */ + + /* Verfolgungen melden */ + if (fval(u, UFL_FOLLOWING)) + caught_target(current_point, u); + + sh = move_ship(sh, starting_point, current_point, *routep); + + /* Hafengebühren ? */ + + hafenmeister = owner_buildingtyp(current_point, bt_find("harbour")); + if (sh && hafenmeister != NULL) { + item *itm; + unit *u2; + item *trans = NULL; + + for (u2 = current_point->units; u2; u2 = u2->next) { + if (u2->ship == sh && !alliedunit(hafenmeister, u->faction, HELP_GUARD)) { + + if (effskill(hafenmeister, SK_PERCEPTION) > effskill(u2, SK_STEALTH)) { + for (itm = u2->items; itm; itm = itm->next) { + const luxury_type *ltype = resource2luxury(itm->type->rtype); + if (ltype != NULL && itm->number > 0) { + int st = itm->number * effskill(hafenmeister, SK_TRADE) / 50; + st = MIN(itm->number, st); + + if (st > 0) { + i_change(&u2->items, itm->type, -st); + i_change(&hafenmeister->items, itm->type, st); + i_add(&trans, i_new(itm->type, st)); + } + } + } + } + } + } + if (trans) { + message *msg = + msg_message("harbor_trade", "unit items ship", hafenmeister, trans, + u->ship); + add_message(&u->faction->msgs, msg); + add_message(&hafenmeister->faction->msgs, msg); + msg_release(msg); + while (trans) + i_remove(&trans, trans); + } + } + } +} + +unit *get_captain(const ship * sh) +{ + const region *r = sh->region; + unit *u; + + for (u = r->units; u; u = u->next) { + if (u->ship == sh && u->number + && eff_skill(u, SK_SAILING, r) >= sh->type->cptskill) + return u; + } + + return NULL; +} + +/* Segeln, Wandern, Reiten +* when this routine returns a non-zero value, movement for the region needs +* to be done again because of followers that got new MOVE orders. +* Setting FL_LONGACTION will prevent a unit from being handled more than once +* by this routine +* +* the token parser needs to be initialized before calling this function! +*/ + +/** fleeing units use this function +*/ +void run_to(unit * u, region * to) +{ + region_list *route = NULL; + add_regionlist(&route, to); + travel_route(u, route, NULL, NULL, TRAVEL_RUNNING); + free_regionlist(route); + /* weder transport noch follow */ +} + +static const region_list *travel_i(unit * u, const region_list * route_begin, + const region_list * route_end, order * ord, int mode, follower ** followers) +{ + region *r = u->region; + + if (u->building && !can_leave(u)) { + cmistake(u, u->thisorder, 150, MSG_MOVE); + return route_begin; + } + switch (canwalk(u)) { + case E_CANWALK_TOOHEAVY: + cmistake(u, ord, 57, MSG_MOVE); + return route_begin; + case E_CANWALK_TOOMANYHORSES: + cmistake(u, ord, 56, MSG_MOVE); + return route_begin; + case E_CANWALK_TOOMANYCARTS: + cmistake(u, ord, 42, MSG_MOVE); + return route_begin; + } + route_end = cap_route(r, route_begin, route_end, movement_speed(u)); + + route_end = travel_route(u, route_begin, route_end, ord, mode); + get_followers(u, r, route_end, followers); + + /* transportation */ + for (ord = u->orders; ord; ord = ord->next) { + unit *ut; + + if (get_keyword(ord) != K_TRANSPORT) + continue; + + init_tokens(ord); + skip_token(); + ut = getunit(r, u->faction); + if (ut != NULL) { + if (get_keyword(ut->thisorder) == K_DRIVE) { + if (ut->building && !can_leave(ut)) { + cmistake(ut, ut->thisorder, 150, MSG_MOVE); + cmistake(u, ord, 99, MSG_MOVE); + } else if (!can_move(ut)) { + cmistake(u, ord, 99, MSG_MOVE); + } else { + bool found = false; + + if (!fval(ut, UFL_NOTMOVING) && !LongHunger(ut)) { + init_tokens(ut->thisorder); + skip_token(); + if (getunit(u->region, ut->faction) == u) { + const region_list *route_to = + travel_route(ut, route_begin, route_end, ord, + TRAVEL_TRANSPORTED); + + if (route_to != route_begin) { + get_followers(ut, r, route_to, followers); + } + ADDMSG(&ut->faction->msgs, msg_message("transport", + "unit target start end", u, ut, r, ut->region)); + found = true; + } + } + if (!found) { + if (cansee(u->faction, u->region, ut, 0)) { + cmistake(u, ord, 90, MSG_MOVE); + } else { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "feedback_unit_not_found", "")); + } + } + } + } + } else { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + } + } + return route_end; +} + +/** traveling without ships + * walking, flying or riding units use this function + */ +static void travel(unit * u, region_list ** routep) +{ + region *r = u->region; + region_list *route_begin = NULL; + follower *followers = NULL; + + if (routep) + *routep = NULL; + + /* a few pre-checks that need not be done for each step: */ + if (!fval(r->terrain, SEA_REGION)) { + ship *sh = u->ship; + + if (!can_leave(u)) { + cmistake(u, u->thisorder, 150, MSG_MOVE); + return; + } + + /* An Land kein NACH wenn in dieser Runde Schiff VERLASSEN! */ + if (sh == NULL) { + sh = leftship(u); + if (sh && sh->region != u->region) + sh = NULL; + } + if (sh) { + unit *guard = is_guarded(r, u, GUARD_LANDING); + if (guard) { + ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, + "region_guarded", "guard", guard)); + return; + } + } + if (u->ship && u_race(u)->flags & RCF_SWIM) { + cmistake(u, u->thisorder, 143, MSG_MOVE); + return; + } + } else if (u->ship && fval(u->ship, SF_MOVED)) { + /* die Einheit ist auf einem Schiff, das sich bereits bewegt hat */ + cmistake(u, u->thisorder, 13, MSG_MOVE); + return; + } + + make_route(u, u->thisorder, routep); + route_begin = *routep; + + /* und ab die post: */ + travel_i(u, route_begin, NULL, u->thisorder, TRAVEL_NORMAL, &followers); + + /* followers */ + while (followers != NULL) { + follower *fnext = followers->next; + unit *uf = followers->uf; + unit *ut = followers->ut; + const region_list *route_end = followers->route_end; + + free(followers); + followers = fnext; + + if (uf->region == r) { + order *follow_order; + const struct locale *lang = u->faction->locale; + + /* construct an order */ + follow_order = create_order(K_FOLLOW, lang, "%s %i", + LOC(uf->faction->locale, parameters[P_UNIT]), ut->no); + + route_end = reroute(uf, route_begin, route_end); + travel_i(uf, route_begin, route_end, follow_order, TRAVEL_FOLLOWING, + &followers); + caught_target(uf->region, uf); + free_order(follow_order); + } + } + +} + +static void move(unit * u, bool move_on_land) +{ + region_list *route = NULL; + + assert(u->number); + if (u->ship && u==ship_owner(u->ship)) { + sail(u, u->thisorder, move_on_land, &route); + } else { + travel(u, &route); + } + + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + set_order(&u->thisorder, NULL); + + if (route != NULL) + free_regionlist(route); +} + +typedef struct piracy_data { + const struct faction *pirate; + const struct faction *target; + direction_t dir; +} piracy_data; + +static void piracy_init(struct attrib *a) +{ + a->data.v = calloc(1, sizeof(piracy_data)); +} + +static void piracy_done(struct attrib *a) +{ + free(a->data.v); +} + +static attrib_type at_piracy_direction = { + "piracy_direction", + piracy_init, + piracy_done, + DEFAULT_AGE, + NO_WRITE, + NO_READ +}; + +static attrib *mk_piracy(const faction * pirate, const faction * target, + direction_t target_dir) +{ + attrib *a = a_new(&at_piracy_direction); + piracy_data *data = a->data.v; + data->pirate = pirate; + data->target = target; + data->dir = target_dir; + return a; +} + +static void piracy_cmd(unit * u, struct order *ord) +{ + region *r = u->region; + ship *sh = u->ship, *sh2; + direction_t dir, target_dir = NODIRECTION; + struct { + const faction *target; + int value; + } aff[MAXDIRECTIONS]; + int saff = 0; + int *il = NULL; + const char *s; + attrib *a; + + if (!sh) { + cmistake(u, ord, 144, MSG_MOVE); + return; + } + + if (!u->ship || u!=ship_owner(u->ship)) { + cmistake(u, ord, 146, MSG_MOVE); + return; + } + + /* Feststellen, ob schon ein anderer alliierter Pirat ein + * Ziel gefunden hat. */ + + init_tokens(ord); + skip_token(); + s = getstrtoken(); + if (s != NULL && *s) { + il = intlist_init(); + while (s && *s) { + il = intlist_add(il, atoi36(s)); + s = getstrtoken(); + } + } + + for (a = a_find(r->attribs, &at_piracy_direction); + a && a->type == &at_piracy_direction; a = a->next) { + piracy_data *data = a->data.v; + const faction *p = data->pirate; + const faction *t = data->target; + + if (alliedunit(u, p, HELP_FIGHT)) { + if (il == 0 || (t && intlist_find(il, t->no))) { + target_dir = data->dir; + break; + } + } + } + + /* Wenn nicht, sehen wir, ob wir ein Ziel finden. */ + + if (target_dir == NODIRECTION) { + /* Einheit ist also Kapitän. Jetzt gucken, in wievielen + * Nachbarregionen potentielle Opfer sind. */ + + for (dir = 0; dir < MAXDIRECTIONS; dir++) { + region *rc = rconnect(r, dir); + aff[dir].value = 0; + aff[dir].target = 0; + if (rc && fval(rc->terrain, SWIM_INTO) && check_takeoff(sh, r, rc)) { + + for (sh2 = rc->ships; sh2; sh2 = sh2->next) { + unit *cap = ship_owner(sh2); + if (cap) { + faction *f = visible_faction(cap->faction, cap); + if (alliedunit(u, f, HELP_FIGHT)) + continue; + if (il == 0 || intlist_find(il, cap->faction->no)) { + ++aff[dir].value; + if (rng_int() % aff[dir].value == 0) { + aff[dir].target = f; + } + } + } + } + + /* Und aufaddieren. */ + saff += aff[dir].value; + } + } + + if (saff != 0) { + saff = rng_int() % saff; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + if (saff != aff[dir].value) + break; + saff -= aff[dir].value; + } + target_dir = dir; + a = + a_add(&r->attribs, mk_piracy(u->faction, aff[dir].target, target_dir)); + } + } + + free(il); + + /* Wenn kein Ziel gefunden, entsprechende Meldung generieren */ + if (target_dir == NODIRECTION) { + ADDMSG(&u->faction->msgs, msg_message("piratenovictim", + "ship region", sh, r)); + return; + } + + /* Meldung generieren */ + ADDMSG(&u->faction->msgs, msg_message("piratesawvictim", + "ship region dir", sh, r, target_dir)); + + /* Befehl konstruieren */ + set_order(&u->thisorder, create_order(K_MOVE, u->faction->locale, "%s", + LOC(u->faction->locale, directions[target_dir]))); + + /* Bewegung ausführen */ + init_tokens(u->thisorder); + skip_token(); + move(u, true); +} + +static void age_traveldir(region * r) +{ + attrib *a = a_find(r->attribs, &at_traveldir); + + while (a && a->type == &at_traveldir) { + attrib *an = a->next; + a->data.ca[3]--; + if (a->data.ca[3] <= 0) { + a_remove(&r->attribs, a); + } + a = an; + } +} + +static direction_t hunted_dir(attrib * at, int id) +{ + attrib *a = a_find(at, &at_shiptrail); + direction_t d = NODIRECTION; + + while (a != NULL && a->type == &at_shiptrail) { + traveldir *t = (traveldir *) (a->data.v); + if (t->no == id) { + d = t->dir; + /* do not break, because we want the last one for this ship */ + } + a = a->next; + } + + return d; +} + +static int hunt(unit * u, order * ord) +{ + region *rc = u->region; + int bytes, moves, id, speed; + char command[256], *bufp = command; + size_t size = sizeof(command); + direction_t dir; + + if (fval(u, UFL_NOTMOVING)) { + return 0; + } else if (!u->ship) { + cmistake(u, ord, 144, MSG_MOVE); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); /* FOLGE SCHIFF ist immer lang */ + return 0; + } else if (u!=ship_owner(u->ship)) { + cmistake(u, ord, 146, MSG_MOVE); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); /* FOLGE SCHIFF ist immer lang */ + return 0; + } else if (!can_move(u)) { + cmistake(u, ord, 55, MSG_MOVE); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); /* FOLGE SCHIFF ist immer lang */ + return 0; + } + + id = getshipid(); + + if (id <= 0) { + cmistake(u, ord, 20, MSG_MOVE); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); /* FOLGE SCHIFF ist immer lang */ + return 0; + } + + dir = hunted_dir(rc->attribs, id); + + if (dir == NODIRECTION) { + ship *sh = findship(id); + if (sh == NULL || sh->region != rc) { + cmistake(u, ord, 20, MSG_MOVE); + } + fset(u, UFL_LONGACTION | UFL_NOTMOVING); /* FOLGE SCHIFF ist immer lang */ + return 0; + } + + bufp = command; + bytes = slprintf(bufp, size, "%s %s", LOC(u->faction->locale, keywords[K_MOVE]), LOC(u->faction->locale, directions[dir])); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + moves = 1; + + speed = getuint(); + if (speed == 0) { + speed = shipspeed(u->ship, u); + } else { + int maxspeed = shipspeed(u->ship, u); + if (maxspeed < speed) + speed = maxspeed; + } + rc = rconnect(rc, dir); + while (moves < speed && (dir = hunted_dir(rc->attribs, id)) != NODIRECTION) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(u->faction->locale, directions[dir]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + moves++; + rc = rconnect(rc, dir); + } + + /* In command steht jetzt das NACH-Kommando. */ + + /* NACH ignorieren und Parsing initialisieren. */ + igetstrtoken(command); + /* NACH ausführen */ + move(u, false); + return 1; /* true -> Einheitenliste von vorne durchgehen */ +} + +void destroy_damaged_ships(void) +{ + region *r; + ship *sh, *shn; + + for (r = regions; r; r = r->next) { + for (sh = r->ships; sh;) { + shn = sh->next; + if (sh->damage >= sh->size * DAMAGE_SCALE) { + remove_ship(&sh->region->ships, sh); + } + sh = shn; + } + } +} + +/* Bewegung, Verfolgung, Piraterie */ + +/** ships that folow other ships + * Dann generieren die jagenden Einheiten ihre Befehle und + * bewegen sich. + * Following the trails of other ships. + */ +static void move_hunters(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + unit **up = &r->units; + + while (*up != NULL) { + unit *u = *up; + + if (!fval(u, UFL_MOVED | UFL_NOTMOVING)) { + order *ord; + + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == K_FOLLOW) { + param_t p; + + init_tokens(ord); + skip_token(); + p = getparam(u->faction->locale); + if (p != P_SHIP) { + if (p != P_UNIT) { + cmistake(u, ord, 240, MSG_MOVE); + } + break; + } + + /* wir folgen definitiv einem Schiff. */ + + if (fval(u, UFL_NOTMOVING)) { + cmistake(u, ord, 52, MSG_MOVE); + break; + } else if (!can_move(u)) { + cmistake(u, ord, 55, MSG_MOVE); + break; + } + + if (!fval(u, UFL_NOTMOVING) && !LongHunger(u) && hunt(u, ord)) { + up = &r->units; + break; + } + } + } + } + if (*up == u) + up = &u->next; + } + } +} + +/** Piraten and Drift + * + */ +static void move_pirates(void) +{ + region *r; + + for (r = regions; r; r = r->next) { + unit **up = &r->units; + + while (*up) { + unit *u = *up; + + if (!fval(u, UFL_NOTMOVING) && get_keyword(u->thisorder) == K_PIRACY) { + piracy_cmd(u, u->thisorder); + fset(u, UFL_LONGACTION | UFL_NOTMOVING); + } + + if (*up == u) { + /* not moved, use next unit */ + up = &u->next; + } else if (*up && (*up)->region != r) { + /* moved the previous unit along with u (units on same ship) + * must start from the beginning again */ + up = &r->units; + } + /* else *up is already the next unit */ + } + + a_removeall(&r->attribs, &at_piracy_direction); + age_traveldir(r); + } +} + +void movement(void) +{ + int ships; + + /* Initialize the additional encumbrance by transported units */ + init_transportation(); + + /* Move ships in last phase, others first + * This is to make sure you can't land someplace and then get off the ship + * in the same turn. + */ + for (ships = 0; ships <= 1; ++ships) { + region *r = regions; + while (r != NULL) { + unit **up = &r->units; + bool repeat = false; + + while (*up) { + unit *u = *up; + keyword_t kword; + + if (u->ship && fval(u->ship, SF_DRIFTED)) { + up = &u->next; + continue; + } + kword = get_keyword(u->thisorder); + + if (kword == K_ROUTE || kword == K_MOVE) { + /* after moving, the unit has no thisorder. this prevents + * it from moving twice (or getting error messages twice). + * UFL_NOTMOVING is set in combat if the unit is not allowed + * to move because it was involved in a battle. + */ + if (fval(u, UFL_NOTMOVING)) { + if (fval(u, UFL_LONGACTION)) { + cmistake(u, u->thisorder, 52, MSG_MOVE); + set_order(&u->thisorder, NULL); + } else { + cmistake(u, u->thisorder, 319, MSG_MOVE); + set_order(&u->thisorder, NULL); + } + } else if (fval(u, UFL_MOVED)) { + cmistake(u, u->thisorder, 187, MSG_MOVE); + set_order(&u->thisorder, NULL); + } else if (!can_move(u)) { + cmistake(u, u->thisorder, 55, MSG_MOVE); + set_order(&u->thisorder, NULL); + } else { + if (ships) { + if (u->ship && ship_owner(u->ship)==u) { + init_tokens(u->thisorder); + skip_token(); + move(u, false); + } + } else { + if (!u->ship || ship_owner(u->ship)!=u) { + init_tokens(u->thisorder); + skip_token(); + move(u, false); + } + } + } + } + if (u->region == r) { + /* not moved, use next unit */ + up = &u->next; + } else { + if (*up && (*up)->region != r) { + /* moved the upcoming unit along with u (units on ships or followers, + * for example). must start from the beginning again immediately */ + up = &r->units; + repeat = false; + } else { + repeat = true; + } + } + /* else *up is already the next unit */ + } + if (repeat) + continue; + if (ships == 0) { + /* Abtreiben von beschädigten, unterbemannten, überladenen Schiffen */ + drifting_ships(r); + } + r = r->next; + } + } + + move_hunters(); + move_pirates(); +} + +/** Overrides long orders with a FOLLOW order if the target is moving. + * FOLLOW SHIP is a long order, and doesn't need to be treated in here. + * BUGS: http://bugs.eressea.de/view.php?id=1444 (A folgt B folgt C) + */ +void follow_unit(unit * u) +{ + region *r = u->region; + attrib *a = NULL; + order *ord; + + if (fval(u, UFL_NOTMOVING) || LongHunger(u)) + return; + + for (ord = u->orders; ord; ord = ord->next) { + const struct locale *lang = u->faction->locale; + + if (get_keyword(ord) == K_FOLLOW) { + init_tokens(ord); + skip_token(); + if (getparam(lang) == P_UNIT) { + int id = read_unitid(u->faction, r); + + if (a != NULL) { + a = a_find(u->attribs, &at_follow); + } + + if (id > 0) { + unit *uf = findunit(id); + if (!a) { + a = a_add(&u->attribs, make_follow(uf)); + } else { + a->data.v = uf; + } + } else if (a) { + a_remove(&u->attribs, a); + a = NULL; + } + } + } + } + + if (a && !fval(u, UFL_MOVED | UFL_NOTMOVING)) { + unit *u2 = a->data.v; + bool follow = false; + + if (!u2 || u2->region != r || !cansee(u->faction, r, u2, 0)) { + return; + } + + switch (get_keyword(u2->thisorder)) { + case K_MOVE: + case K_ROUTE: + case K_DRIVE: + follow = true; + break; + default: + for (ord = u2->orders; ord; ord = ord->next) { + switch (get_keyword(ord)) { + case K_FOLLOW: + case K_PIRACY: + follow = true; + break; + default: + continue; + } + break; + } + break; + } + if (!follow) { + attrib *a2 = a_find(u2->attribs, &at_follow); + if (a2 != NULL) { + unit *u3 = a2->data.v; + follow = (u3 && u2->region == u3->region); + } + } + if (follow) { + fset(u, UFL_FOLLOWING); + fset(u2, UFL_FOLLOWED); + /* FOLLOW unit on a (potentially) moving unit prevents long orders */ + set_order(&u->thisorder, NULL); + } + } +} diff --git a/core/src/kernel/move.h b/core/src/kernel/move.h new file mode 100644 index 000000000..c76c362c1 --- /dev/null +++ b/core/src/kernel/move.h @@ -0,0 +1,79 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_MOVEMENT +#define H_KRNL_MOVEMENT +#ifdef __cplusplus +extern "C" { +#endif + + struct unit; + struct ship; + struct building_type; + +/* die Zahlen sind genau äquivalent zu den race Flags */ +#define MV_CANNOTMOVE (1<<5) +#define MV_FLY (1<<7) /* kann fliegen */ +#define MV_SWIM (1<<8) /* kann schwimmen */ +#define MV_WALK (1<<9) /* kann über Land gehen */ + +/* Die tragekapaz. ist hardcodiert mit defines, da es bis jetzt sowieso nur 2 +** objekte gibt, die etwas tragen. */ +#define SILVERWEIGHT 1 +#define SCALEWEIGHT 100 /* Faktor, um den die Anzeige von gewichten + * * skaliert wird */ +#define HORSECAPACITY 7000 +#define WAGONCAPACITY 14000 + +#define HORSESNEEDED 2 + +/* ein mensch wiegt 10, traegt also 5, ein pferd wiegt 50, traegt also 20. ein +** wagen wird von zwei pferden gezogen und traegt total 140, davon 40 die +** pferde, macht nur noch 100, aber samt eigenem gewicht (40) macht also 140. */ + + int personcapacity(const struct unit *u); + void movement(void); + void run_to(struct unit *u, struct region *to); + struct unit *is_guarded(struct region *r, struct unit *u, unsigned int mask); + bool is_guard(const struct unit *u, int mask); + int enoughsailors(const struct ship *sh, const struct region *r); + bool canswim(struct unit *u); + bool canfly(struct unit *u); + struct unit *get_captain(const struct ship *sh); + void travelthru(const struct unit *u, struct region *r); + struct ship *move_ship(struct ship *sh, struct region *from, + struct region *to, struct region_list *route); + int walkingcapacity(const struct unit *u); + void follow_unit(struct unit *u); + bool buildingtype_exists(const struct region *r, + const struct building_type *bt, bool working); + struct unit *owner_buildingtyp(const struct region *r, + const struct building_type *bt); + + extern struct attrib_type at_speedup; + +#define SA_HARBOUR 2 +#define SA_COAST 1 +#define SA_NO_INSECT -1 +#define SA_NO_COAST -2 + + extern int check_ship_allowed(struct ship *sh, const struct region * r); +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/move_test.c b/core/src/kernel/move_test.c new file mode 100644 index 000000000..640b9ada8 --- /dev/null +++ b/core/src/kernel/move_test.c @@ -0,0 +1,86 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +static void test_ship_not_allowed_in_coast(CuTest * tc) +{ + region *r; + ship * sh; + terrain_type * ttype; + ship_type * stype; + const char * names[] = { "derp", "derp_p" }; + + test_cleanup(); + test_create_world(); + + ttype = test_create_terrain("glacier", LAND_REGION|ARCTIC_REGION|WALK_INTO|SAIL_INTO); + stype = test_create_shiptype(names); + stype->coasts = (const struct terrain_type **)calloc(2, sizeof(const struct terrain_type *)); + + r = test_create_region(0, 0, ttype); + sh = test_create_ship(0, stype); + + CuAssertIntEquals(tc, SA_NO_COAST, check_ship_allowed(sh, r)); + stype->coasts[0] = ttype; + CuAssertIntEquals(tc, SA_COAST, check_ship_allowed(sh, r)); +} + +static void test_ship_allowed_with_harbor(CuTest * tc) +{ + region *r; + ship * sh; + terrain_type * ttype; + building_type * btype; + + test_cleanup(); + test_create_world(); + + ttype = test_create_terrain("glacier", LAND_REGION|ARCTIC_REGION|WALK_INTO|SAIL_INTO); + btype = test_create_buildingtype("harbour"); + + r = test_create_region(0, 0, ttype); + sh = test_create_ship(0, 0); + + test_create_building(r, btype); + CuAssertIntEquals(tc, SA_HARBOUR, check_ship_allowed(sh, r)); +} + +static void test_building_type_exists(CuTest * tc) +{ + region *r; + building *b; + building_type *btype; + building_type *btype2 = (building_type *)calloc(1, sizeof(building_type)); + + test_cleanup(); + test_create_world(); + + btype = bt_find("castle"); + + r = findregion(-1, 0); + b = new_building(btype, r, default_locale); + + CuAssertPtrNotNull(tc, b); + CuAssertTrue(tc, !buildingtype_exists(r, NULL, true)); + CuAssertTrue(tc, buildingtype_exists(r, btype, true)); + CuAssertTrue(tc, !buildingtype_exists(r, btype2, true)); +} + +CuSuite *get_move_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_building_type_exists); + SUITE_ADD_TEST(suite, test_ship_not_allowed_in_coast); + SUITE_ADD_TEST(suite, test_ship_allowed_with_harbor); + return suite; +} diff --git a/core/src/kernel/names.c b/core/src/kernel/names.c new file mode 100644 index 000000000..a0089829b --- /dev/null +++ b/core/src/kernel/names.c @@ -0,0 +1,480 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "names.h" + +/* kernel includes */ +#include "unit.h" +#include "region.h" +#include "faction.h" +#include "magic.h" +#include "race.h" +#include "terrain.h" +#include "terrainid.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +const char *describe_braineater(unit * u, const struct locale *lang) +{ + return LOC(lang, "describe_braineater"); +} + +static const char *make_names(const char *monster, int *num_postfix, + int pprefix, int *num_name, int *num_prefix, int ppostfix) +{ + int uv, uu, un; + static char name[NAMESIZE + 1]; + char zText[32]; + const char *str; + + if (*num_prefix == 0) { + + for (*num_prefix = 0;; ++*num_prefix) { + sprintf(zText, "%s_prefix_%d", monster, *num_prefix); + str = locale_getstring(default_locale, zText); + if (str == NULL) + break; + } + + for (*num_name = 0;; ++*num_name) { + sprintf(zText, "%s_name_%d", monster, *num_name); + str = locale_getstring(default_locale, zText); + if (str == NULL) + break; + } + + for (*num_postfix = 0;; ++*num_postfix) { + sprintf(zText, "%s_postfix_%d", monster, *num_postfix); + str = locale_getstring(default_locale, zText); + if (str == NULL) + break; + } + } + + if (*num_name == 0) { + return NULL; + } + + /* nur 50% aller Namen haben "Vor-Teil" */ + uv = rng_int() % (*num_prefix * pprefix); + + uu = rng_int() % *num_name; + + /* nur 50% aller Namen haben "Nach-Teil", wenn kein Vor-Teil */ + if (uv >= *num_prefix) { + un = rng_int() % *num_postfix; + } else { + un = rng_int() % (*num_postfix * ppostfix); + } + + name[0] = 0; + if (uv < *num_prefix) { + sprintf(zText, "%s_prefix_%d", monster, uv); + str = locale_getstring(default_locale, zText); + if (str) { + strcat(name, (const char *)str); + strcat(name, " "); + } + } + + sprintf(zText, "%s_name_%d", monster, uu); + str = locale_getstring(default_locale, zText); + if (str) + strcat(name, (const char *)str); + + if (un < *num_postfix) { + sprintf(zText, "%s_postfix_%d", monster, un); + str = locale_getstring(default_locale, zText); + if (str) { + strcat(name, " "); + strcat(name, (const char *)str); + } + } + return name; +} + +const char *undead_name(const unit * u) +{ + static int num_postfix, num_name, num_prefix; + return make_names("undead", &num_postfix, 2, &num_name, &num_prefix, 2); +} + +const char *skeleton_name(const unit * u) +{ + static int num_postfix, num_name, num_prefix; + return make_names("skeleton", &num_postfix, 5, &num_name, &num_prefix, 2); +} + +const char *zombie_name(const unit * u) +{ + static int num_postfix, num_name, num_prefix; + return make_names("zombie", &num_postfix, 5, &num_name, &num_prefix, 2); +} + +const char *ghoul_name(const unit * u) +{ + static int num_postfix, num_name, num_prefix; + return make_names("ghoul", &num_postfix, 5, &num_name, &num_prefix, 4); +} + +/* Drachen */ + +#define SIL1 15 + +const char *silbe1[SIL1] = { + "Tar", + "Ter", + "Tor", + "Pan", + "Par", + "Per", + "Nim", + "Nan", + "Nun", + "Gor", + "For", + "Fer", + "Kar", + "Kur", + "Pen", +}; + +#define SIL2 19 + +const char *silbe2[SIL2] = { + "da", + "do", + "dil", + "di", + "dor", + "dar", + "ra", + "ran", + "ras", + "ro", + "rum", + "rin", + "ten", + "tan", + "ta", + "tor", + "gur", + "ga", + "gas", +}; + +#define SIL3 14 + +const char *silbe3[SIL3] = { + "gul", + "gol", + "dol", + "tan", + "tar", + "tur", + "sur", + "sin", + "kur", + "kor", + "kar", + "dul", + "dol", + "bus", +}; + +const char *generic_name(const unit * u) +{ + if (u->no == 1) { + return LOC(u->faction->locale, mkname("race", u_race(u)->_name[0])); + } + return LOC(u->faction->locale, mkname("race", u_race(u)->_name[1])); +} + +const char *dragon_name(const unit * u) +{ + static char name[NAMESIZE + 1]; + int rnd, ter = 0; + int anzahl = 1; + static int num_postfix; + char zText[32]; + const char *str; + + if (num_postfix == 0) { + for (num_postfix = 0;; ++num_postfix) { + sprintf(zText, "dragon_postfix_%d", num_postfix); + str = locale_getstring(default_locale, zText); + if (str == NULL) + break; + } + if (num_postfix == 0) + num_postfix = -1; + } + if (num_postfix <= 0) { + return NULL; + } + + if (u) { + region *r = u->region; + anzahl = u->number; + switch (rterrain(r)) { + case T_PLAIN: + ter = 1; + break; + case T_MOUNTAIN: + ter = 2; + break; + case T_DESERT: + ter = 3; + break; + case T_SWAMP: + ter = 4; + break; + case T_GLACIER: + ter = 5; + break; + } + } + + rnd = num_postfix / 6; + rnd = (rng_int() % rnd) + ter * rnd; + + sprintf(zText, "dragon_postfix_%d", rnd); + + str = locale_getstring(default_locale, zText); + assert(str != NULL); + + if (anzahl > 1) { + const char *no_article = strchr((const char *)str, ' '); + assert(no_article); + /* TODO: GERMAN */ + sprintf(name, "Die %sn von %s", no_article+1, rname(u->region, + default_locale)); + } else { + char n[32]; + + strcpy(n, silbe1[rng_int() % SIL1]); + strcat(n, silbe2[rng_int() % SIL2]); + strcat(n, silbe3[rng_int() % SIL3]); + if (rng_int() % 5 > 2) { + sprintf(name, "%s, %s", n, str); /* "Name, der Titel" */ + } else { + strcpy(name, (const char *)str); /* "Der Titel Name" */ + name[0] = (char)toupper(name[0]); /* TODO: UNICODE - should use towupper() */ + strcat(name, " "); + strcat(name, n); + } + if (u && (rng_int() % 3 == 0)) { + strcat(name, " von "); + strcat(name, (const char *)rname(u->region, default_locale)); + } + } + + return name; +} + +/* Dracoide */ + +#define DRAC_PRE 13 +static const char *drac_pre[DRAC_PRE] = { + "Siss", + "Xxaa", + "Shht", + "X'xixi", + "Xar", + "X'zish", + "X", + "Sh", + "R", + "Z", + "Y", + "L", + "Ck", +}; + +#define DRAC_MID 12 +static const char *drac_mid[DRAC_MID] = { + "siss", + "xxaa", + "shht", + "xxi", + "xar", + "x'zish", + "x", + "sh", + "r", + "z'ck", + "y", + "rl" +}; + +#define DRAC_SUF 10 +static const char *drac_suf[DRAC_SUF] = { + "xil", + "shh", + "s", + "x", + "arr", + "lll", + "lll", + "shack", + "ck", + "k" +}; + +const char *dracoid_name(const unit * u) +{ + static char name[NAMESIZE + 1]; + int mid_syllabels; + + u = u; + /* Wieviele Mittelteile? */ + + mid_syllabels = rng_int() % 4; + + strcpy(name, drac_pre[rng_int() % DRAC_PRE]); + while (mid_syllabels > 0) { + mid_syllabels--; + if (rng_int() % 10 < 4) + strcat(name, "'"); + strcat(name, drac_mid[rng_int() % DRAC_MID]); + } + strcat(name, drac_suf[rng_int() % DRAC_SUF]); + return name; +} + +/** returns an abbreviation of a string. + * TODO: buflen is being ignored */ + +const char *abkz(const char *s, char *buf, size_t buflen, size_t maxchars) +{ + const char *p = s; + char *bufp; + unsigned int c = 0; + size_t bpt, i; + ucs4_t ucs; + size_t size; + int result; + + /* Prüfen, ob Kurz genug */ + + if (strlen(s) <= maxchars) { + return s; + } + /* Anzahl der Wörter feststellen */ + + while (*p != 0) { + + result = unicode_utf8_to_ucs4(&ucs, p, &size); + assert(result == 0 || "damnit, we're not handling invalid input here!"); + + /* Leerzeichen überspringen */ + while (*p != 0 && !iswalnum((wint_t) ucs)) { + p += size; + result = unicode_utf8_to_ucs4(&ucs, p, &size); + assert(result == 0 || "damnit, we're not handling invalid input here!"); + } + + /* Counter erhöhen */ + if (*p != 0) + ++c; + + /* alnums überspringen */ + while (*p != 0 && iswalnum((wint_t) ucs)) { + p += size; + result = unicode_utf8_to_ucs4(&ucs, p, &size); + assert(result == 0 || "damnit, we're not handling invalid input here!"); + } + } + + /* Buchstaben pro Teilkürzel = MAX(1,max/AnzWort) */ + + bpt = MAX(1, maxchars / c); + + /* Einzelne Wörter anspringen und jeweils die ersten BpT kopieren */ + + p = s; + c = 0; + bufp = buf; + + result = unicode_utf8_to_ucs4(&ucs, p, &size); + assert(result == 0 || "damnit, we're not handling invalid input here!"); + + while (*p != 0 && c < maxchars) { + /* Leerzeichen überspringen */ + + while (*p != 0 && !iswalnum((wint_t) ucs)) { + p += size; + result = unicode_utf8_to_ucs4(&ucs, p, &size); + assert(result == 0 || "damnit, we're not handling invalid input here!"); + } + + /* alnums übertragen */ + + for (i = 0; i < bpt && *p != 0 && iswalnum((wint_t) ucs); ++i) { + memcpy(bufp, p, size); + p += size; + bufp += size; + ++c; + + result = unicode_utf8_to_ucs4(&ucs, p, &size); + assert(result == 0 || "damnit, we're not handling invalid input here!"); + } + + /* Bis zum nächsten Leerzeichen */ + + while (c < maxchars && *p != 0 && iswalnum((wint_t) ucs)) { + p += size; + result = unicode_utf8_to_ucs4(&ucs, p, &size); + assert(result == 0 || "damnit, we're not handling invalid input here!"); + } + } + + *bufp = 0; + + return buf; +} + +void register_names(void) +{ + register_function((pf_generic) describe_braineater, "describe_braineater"); + /* function name + * generate a name for a nonplayerunit + * race->generate_name() */ + register_function((pf_generic) undead_name, "nameundead"); + register_function((pf_generic) skeleton_name, "nameskeleton"); + register_function((pf_generic) zombie_name, "namezombie"); + register_function((pf_generic) ghoul_name, "nameghoul"); + register_function((pf_generic) dragon_name, "namedragon"); + register_function((pf_generic) dracoid_name, "namedracoid"); + register_function((pf_generic) generic_name, "namegeneric"); +} diff --git a/core/src/kernel/names.h b/core/src/kernel/names.h new file mode 100644 index 000000000..03637d097 --- /dev/null +++ b/core/src/kernel/names.h @@ -0,0 +1,37 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_NAMES +#define H_KRNL_NAMES +#ifdef __cplusplus +extern "C" { +#endif + extern void register_names(void); + const char *undead_name(const struct unit *u); + const char *skeleton_name(const struct unit *u); + const char *zombie_name(const struct unit *u); + const char *ghoul_name(const struct unit *u); + const char *dragon_name(const struct unit *u); + const char *dracoid_name(const struct unit *u); + const char *generic_name(const struct unit *u); + const char *abkz(const char *s, char *buf, size_t size, size_t maxchars); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/objtypes.h b/core/src/kernel/objtypes.h new file mode 100644 index 000000000..293158ecb --- /dev/null +++ b/core/src/kernel/objtypes.h @@ -0,0 +1,39 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_OBJTYPES +#define H_KRNL_OBJTYPES +#ifdef __cplusplus +extern "C" { +#endif + + typedef enum { + TYP_UNIT, + TYP_REGION, + TYP_BUILDING, + TYP_SHIP, + TYP_FACTION, + TYP_ACTION, + TYP_TRIGGER, + TYP_TIMEOUT + } objtype_t; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/order.c b/core/src/kernel/order.c new file mode 100644 index 000000000..13cd7dc6d --- /dev/null +++ b/core/src/kernel/order.c @@ -0,0 +1,609 @@ +/* vi: set ts=2: + +-------------------+ + | | Christian Schlittchen + | Eressea PBEM host | Enno Rehling + | (c) 1998 - 2004 | Katja Zedel + | | + +-------------------+ + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "order.h" + +#include "skill.h" + +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +# define ORD_KEYWORD(ord) (ord)->data->_keyword +# define ORD_LOCALE(ord) locale_array[(ord)->data->_lindex]->lang +# define ORD_STRING(ord) (ord)->data->_str + +typedef struct locale_data { + struct order_data *short_orders[MAXKEYWORDS]; + struct order_data *study_orders[MAXSKILLS]; + const struct locale *lang; +} locale_data; + +static struct locale_data *locale_array[16]; +static int nlocales = 0; + +typedef struct order_data { + char *_str; + int _refcount:20; + int _lindex:4; + keyword_t _keyword; +} order_data; + +static void release_data(order_data * data) +{ + if (data) { + if (--data->_refcount == 0) { + if (data->_str) + free(data->_str); + free(data); + } + } +} + +void replace_order(order ** dlist, order * orig, const order * src) +{ + while (*dlist != NULL) { + order *dst = *dlist; + if (dst->data == orig->data) { + order *cpy = copy_order(src); + *dlist = cpy; + cpy->next = dst->next; + dst->next = 0; + free_order(dst); + } + dlist = &(*dlist)->next; + } +} + +keyword_t get_keyword(const order * ord) +{ + if (ord == NULL) { + return NOKEYWORD; + } + return ORD_KEYWORD(ord); +} + +/** returns a plain-text representation of the order. + * This is the inverse function to the parse_order command. Note that + * keywords are expanded to their full length. + */ +static char *get_command(const order * ord, char *sbuffer, size_t size) +{ + char *bufp = sbuffer; + const char *text = ORD_STRING(ord); + keyword_t kwd = ORD_KEYWORD(ord); + int bytes; + + if (ord->_persistent) { + if (size > 0) { + *bufp++ = '@'; + --size; + } else { + WARN_STATIC_BUFFER(); + } + } + if (kwd != NOKEYWORD) { + const struct locale *lang = ORD_LOCALE(ord); + if (size > 0) { + if (text) + --size; + bytes = (int)strlcpy(bufp, (const char *)LOC(lang, keywords[kwd]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (text) + *bufp++ = ' '; + } else { + WARN_STATIC_BUFFER(); + } + } + if (text) { + bytes = (int)strlcpy(bufp, (const char *)text, size); + if (wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + if (bufp - sbuffer >= 6) { + bufp -= 6; + while (bufp > sbuffer && (*bufp & 0x80) != 0) { + ++size; + --bufp; + } + memcpy(bufp, "[...]", 6); /* TODO: make sure this only happens in eval_command */ + bufp += 6; + } + } + } + if (size > 0) + *bufp = 0; + return sbuffer; +} + +char *getcommand(const order * ord) +{ + char sbuffer[DISPLAYSIZE * 2]; + return strdup(get_command(ord, sbuffer, sizeof(sbuffer))); +} + +void free_order(order * ord) +{ + if (ord != NULL) { + assert(ord->next == 0); + + release_data(ord->data); + free(ord); + } +} + +order *copy_order(const order * src) +{ + if (src != NULL) { + order *ord = (order *) malloc(sizeof(order)); + ord->next = NULL; + ord->_persistent = src->_persistent; + ord->data = src->data; + ++ord->data->_refcount; + return ord; + } + return NULL; +} + +void set_order(struct order **destp, struct order *src) +{ + if (*destp == src) + return; + free_order(*destp); + *destp = src; +} + +void free_orders(order ** olist) +{ + while (*olist) { + order *ord = *olist; + *olist = ord->next; + ord->next = NULL; + free_order(ord); + } +} + +static order_data *create_data(keyword_t kwd, const char *sptr, int lindex) +{ + const char *s = sptr; + order_data *data; + const struct locale *lang = locale_array[lindex]->lang; + + if (kwd != NOKEYWORD) + s = (*sptr) ? sptr : NULL; + + /* learning, only one order_data per skill required */ + if (kwd == K_STUDY) { + skill_t sk = findskill(parse_token(&sptr), lang); + switch (sk) { + case NOSKILL: /* fehler */ + break; + case SK_MAGIC: /* kann parameter haben */ + if (*sptr != 0) + break; + default: /* nur skill als Parameter, keine extras */ + data = locale_array[lindex]->study_orders[sk]; + if (data == NULL) { + const char *skname = skillname(sk, lang); + data = (order_data *) malloc(sizeof(order_data)); + locale_array[lindex]->study_orders[sk] = data; + data->_keyword = kwd; + data->_lindex = lindex; + if (strchr(skname, ' ') != NULL) { + size_t len = strlen(skname); + data->_str = malloc(len + 3); + data->_str[0] = '\"'; + memcpy(data->_str + 1, skname, len); + data->_str[len + 1] = '\"'; + data->_str[len + 2] = '\0'; + } else { + data->_str = strdup(skname); + } + data->_refcount = 1; + } + ++data->_refcount; + return data; + } + } + + /* orders with no parameter, only one order_data per order required */ + else if (kwd != NOKEYWORD && *sptr == 0) { + data = locale_array[lindex]->short_orders[kwd]; + if (data == NULL) { + data = (order_data *) malloc(sizeof(order_data)); + locale_array[lindex]->short_orders[kwd] = data; + data->_keyword = kwd; + data->_lindex = lindex; + data->_str = NULL; + data->_refcount = 1; + } + ++data->_refcount; + return data; + } + data = (order_data *) malloc(sizeof(order_data)); + data->_keyword = kwd; + data->_lindex = lindex; + data->_str = s ? strdup(s) : NULL; + data->_refcount = 1; + return data; +} + +static order *create_order_i(keyword_t kwd, const char *sptr, int persistent, + const struct locale *lang) +{ + order *ord = NULL; + int lindex; + + /* if this is just nonsense, then we skip it. */ + if (lomem) { + switch (kwd) { + case K_KOMMENTAR: + case NOKEYWORD: + return NULL; + default: + break; + } + } + + for (lindex = 0; lindex != nlocales; ++lindex) { + if (locale_array[lindex]->lang == lang) + break; + } + if (lindex == nlocales) { + locale_array[nlocales] = (locale_data *) calloc(1, sizeof(locale_data)); + locale_array[nlocales]->lang = lang; + ++nlocales; + } + + ord = (order *) malloc(sizeof(order)); + ord->_persistent = persistent; + ord->next = NULL; + + ord->data = create_data(kwd, sptr, lindex); + + return ord; +} + +order *create_order(keyword_t kwd, const struct locale * lang, + const char *params, ...) +{ + char zBuffer[DISPLAYSIZE]; + if (params) { + char *bufp = zBuffer; + int bytes; + size_t size = sizeof(zBuffer) - 1; + va_list marker; + + va_start(marker, params); + while (*params) { + if (*params == '%') { + int i; + const char *s; + ++params; + switch (*params) { + case 's': + s = va_arg(marker, const char *); + bytes = (int)strlcpy(bufp, s, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + break; + case 'd': + i = va_arg(marker, int); + bytes = (int)strlcpy(bufp, itoa10(i), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + break; + case 'i': + i = va_arg(marker, int); + bytes = (int)strlcpy(bufp, itoa36(i), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + break; + default: + assert(!"unknown format-character in create_order"); + } + } else if (size > 0) { + *bufp++ = *params; + --size; + } + ++params; + } + va_end(marker); + *bufp = 0; + } else { + zBuffer[0] = 0; + } + return create_order_i(kwd, zBuffer, 0, lang); +} + +order *parse_order(const char *s, const struct locale * lang) +{ + while (*s && !isalnum(*(unsigned char *)s) && !ispunct(*(unsigned char *)s)) + ++s; + if (*s != 0) { + keyword_t kwd; + const char *sptr; + int persistent = 0; + + while (*s == '@') { + persistent = 1; + ++s; + } + sptr = s; + kwd = findkeyword(parse_token(&sptr), lang); + if (kwd != NOKEYWORD) { + while (isxspace(*(unsigned char *)sptr)) + ++sptr; + s = sptr; + } + return create_order_i(kwd, s, persistent, lang); + } + return NULL; +} + +/** + * Returns true if the order qualifies as "repeated". An order is repeated if it will overwrite the + * old default order. K_BUY is in this category, but not K_MOVE. + * + * \param ord An order. + * \return true if the order is long + * \sa is_exclusive(), is_repeated(), is_persistent() + */ +bool is_repeated(const order * ord) +{ + keyword_t kwd = ORD_KEYWORD(ord); + const struct locale *lang = ORD_LOCALE(ord); + const char * s; + int result = 0; + + switch (kwd) { + case K_CAST: + case K_BUY: + case K_SELL: + case K_ROUTE: + case K_DRIVE: + case K_WORK: + case K_BESIEGE: + case K_ENTERTAIN: + case K_TAX: + case K_RESEARCH: + case K_SPY: + case K_STEAL: + case K_SABOTAGE: + case K_STUDY: + case K_TEACH: + case K_BREED: + case K_PIRACY: + case K_PLANT: + result = 1; + break; + + case K_FOLLOW: + /* FOLLOW is only a long order if we are following a ship. */ + parser_pushstate(); + init_tokens(ord); + skip_token(); + s = getstrtoken(); + result = isparam(s, lang, P_SHIP); + parser_popstate(); + break; + + case K_MAKE: + /* Falls wir MACHE TEMP haben, ignorieren wir es. Alle anderen + * Arten von MACHE zaehlen aber als neue defaults und werden + * behandelt wie die anderen (deswegen kein break nach case + * K_MAKE) - und in thisorder (der aktuelle 30-Tage Befehl) + * abgespeichert). */ + parser_pushstate(); + init_tokens(ord); /* initialize token-parser */ + skip_token(); + s = getstrtoken(); + result = !isparam(s, lang, P_TEMP); + parser_popstate(); + break; + default: + result = 0; + } + return result; +} + +/** + * Returns true if the order qualifies as "exclusive". An order is exclusive if it makes all other + * long orders illegal. K_MOVE is in this category, but not K_BUY. + * + * \param ord An order. + * \return true if the order is long + * \sa is_exclusive(), is_repeated(), is_persistent() + */ +bool is_exclusive(const order * ord) +{ + keyword_t kwd = ORD_KEYWORD(ord); + const struct locale *lang = ORD_LOCALE(ord); + int result = 0; + + switch (kwd) { + case K_MOVE: + case K_ROUTE: + case K_DRIVE: + case K_WORK: + case K_BESIEGE: + case K_ENTERTAIN: + case K_TAX: + case K_RESEARCH: + case K_SPY: + case K_STEAL: + case K_SABOTAGE: + case K_STUDY: + case K_TEACH: + case K_BREED: + case K_PIRACY: + case K_PLANT: + result = 1; + break; + + case K_FOLLOW: + /* FOLLOW is only a long order if we are following a ship. */ + parser_pushstate(); + init_tokens(ord); + skip_token(); + result = isparam(getstrtoken(), lang, P_SHIP); + parser_popstate(); + break; + + case K_MAKE: + /* Falls wir MACHE TEMP haben, ignorieren wir es. Alle anderen + * Arten von MACHE zaehlen aber als neue defaults und werden + * behandelt wie die anderen (deswegen kein break nach case + * K_MAKE) - und in thisorder (der aktuelle 30-Tage Befehl) + * abgespeichert). */ + parser_pushstate(); + init_tokens(ord); /* initialize token-parser */ + skip_token(); + result = !isparam(getstrtoken(), lang, P_TEMP); + parser_popstate(); + break; + default: + result = 0; + } + return result; +} + +/** + * Returns true if the order qualifies as "long". An order is long if it excludes most other long + * orders. + * + * \param ord An order. + * \return true if the order is long + * \sa is_exclusive(), is_repeated(), is_persistent() + */ +bool is_long(const order * ord) +{ + keyword_t kwd = ORD_KEYWORD(ord); + const struct locale *lang = ORD_LOCALE(ord); + bool result = false; + + switch (kwd) { + case K_CAST: + case K_BUY: + case K_SELL: + case K_MOVE: + case K_ROUTE: + case K_DRIVE: + case K_WORK: + case K_BESIEGE: + case K_ENTERTAIN: + case K_TAX: + case K_RESEARCH: + case K_SPY: + case K_STEAL: + case K_SABOTAGE: + case K_STUDY: + case K_TEACH: + case K_BREED: + case K_PIRACY: + case K_PLANT: + return true; + + case K_FOLLOW: + /* FOLLOW is only a long order if we are following a ship. */ + parser_pushstate(); + init_tokens(ord); + skip_token(); + result = isparam(getstrtoken(), lang, P_SHIP); + parser_popstate(); + break; + + case K_MAKE: + /* Falls wir MACHE TEMP haben, ignorieren wir es. Alle anderen + * Arten von MACHE zaehlen aber als neue defaults und werden + * behandelt wie die anderen (deswegen kein break nach case + * K_MAKE) - und in thisorder (der aktuelle 30-Tage Befehl) + * abgespeichert). */ + parser_pushstate(); + init_tokens(ord); /* initialize token-parser */ + skip_token(); + result = !isparam(getstrtoken(), lang, P_TEMP); + parser_popstate(); + break; + default: + result = false; + } + return result; +} + +/** + * Returns true if the order qualifies as "persistent". An order is persistent if it will be + * included in the template orders. @-orders, comments and most long orders are in this category, + * but not K_MOVE. + * + * \param ord An order. + * \return true if the order is persistent + * \sa is_exclusive(), is_repeated(), is_persistent() + */ +bool is_persistent(const order * ord) +{ + keyword_t kwd = ORD_KEYWORD(ord); + int persist = ord->_persistent != 0; + switch (kwd) { + case K_MOVE: + case NOKEYWORD: + /* lang, aber niemals persistent! */ + return false; + + case K_KOMMENTAR: + return true; + + default: + return persist || is_repeated(ord); + } + +} + +char *write_order(const order * ord, char *buffer, size_t size) +{ + if (ord == 0) { + buffer[0] = 0; + } else { + keyword_t kwd = ORD_KEYWORD(ord); + if (kwd == NOKEYWORD) { + const char *text = ORD_STRING(ord); + strlcpy(buffer, (const char *)text, size); + } else { + get_command(ord, buffer, size); + } + } + return buffer; +} + +void push_order(order ** ordp, order * ord) +{ + while (*ordp) + ordp = &(*ordp)->next; + *ordp = ord; +} + +void init_tokens(const struct order *ord) +{ + char *cmd = getcommand(ord); + init_tokens_str(cmd, cmd); +} diff --git a/core/src/kernel/order.h b/core/src/kernel/order.h new file mode 100644 index 000000000..d62f6b2ad --- /dev/null +++ b/core/src/kernel/order.h @@ -0,0 +1,65 @@ +/* vi: set ts=2: + +-------------------+ + | | Christian Schlittchen + | Eressea PBEM host | Enno Rehling + | (c) 1998 - 2004 | Katja Zedel + | | + +-------------------+ + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef KRNL_ORDER_H +#define KRNL_ORDER_H +#ifdef __cplusplus +extern "C" { +#endif + +/* Encapsulation of an order + * + * This structure contains one order given by a unit. These used to be + * stored in string lists, but by storing them in order-structures, + * it is possible to use reference-counting on them, reduce string copies, + * and reduce overall memory usage by sharing strings between orders (not + * implemented yet) saving approx. 50% of all string-related memory. + */ + + struct order_data; + + typedef struct order { + struct order *next; + /* do not access this data: */ + struct order_data *data; + int _persistent:1; + } order; + +/* constructor */ + extern order *create_order(keyword_t kwd, const struct locale *lang, + const char *params, ...); + extern order *parse_order(const char *s, const struct locale *lang); + extern void replace_order(order ** dst, order * orig, const order * src); + +/* reference counted copies of orders: */ + extern order *copy_order(const order * ord); + extern void free_order(order * ord); + extern void free_orders(order ** olist); + + extern void push_order(struct order **olist, struct order *ord); + +/* access functions for orders */ + extern keyword_t get_keyword(const order * ord); + extern void set_order(order ** destp, order * src); + extern char *getcommand(const order * ord); + extern bool is_persistent(const order * ord); + extern bool is_exclusive(const order * ord); + extern bool is_repeated(const order * ord); + extern bool is_long(const order * ord); + + extern char *write_order(const order * ord, char *buffer, size_t size); + extern void init_tokens(const struct order *ord); /* initialize token parsing */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/pathfinder.c b/core/src/kernel/pathfinder.c new file mode 100644 index 000000000..875211deb --- /dev/null +++ b/core/src/kernel/pathfinder.c @@ -0,0 +1,212 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include +#include "pathfinder.h" + +#include "region.h" +#include "terrain.h" + +#include +#include +#include + +bool allowed_swim(const region * src, const region * r) +{ + if (fval(r->terrain, SWIM_INTO)) + return true; + return false; +} + +bool allowed_walk(const region * src, const region * r) +{ + if (fval(r->terrain, WALK_INTO)) + return true; + return false; +} + +bool allowed_fly(const region * src, const region * r) +{ + if (fval(r->terrain, FLY_INTO)) + return true; + return false; +} + +typedef struct node { + struct node *next; + region *r; + struct node *prev; + int distance; +} node; + +static node *node_garbage; + +void pathfinder_cleanup(void) +{ + while (node_garbage) { + node *n = node_garbage; + node_garbage = n->next; + free(n); + } +} + +static node *new_node(region * r, int distance, node * prev) +{ + node *n; + if (node_garbage != NULL) { + n = node_garbage; + node_garbage = n->next; + } else + n = malloc(sizeof(node)); + n->next = NULL; + n->prev = prev; + n->r = r; + n->distance = distance; + return n; +} + +static node *free_node(node * n) +{ + node *s = n->next; + n->next = node_garbage; + node_garbage = n; + return s; +} + +static void free_nodes(node * root) +{ + while (root != NULL) { + region *r = root->r; + freset(r, RF_MARK); + root = free_node(root); + } +} + +struct quicklist *regions_in_range(struct region *start, int maxdist, + bool(*allowed) (const struct region *, const struct region *)) +{ + quicklist * rlist = NULL; + node *root = new_node(start, 0, NULL); + node **end = &root->next; + node *n = root; + + while (n != NULL) { + region *r = n->r; + int depth = n->distance + 1; + int d; + + if (n->distance >= maxdist) + break; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn == NULL) + continue; + if (fval(rn, RF_MARK)) + continue; /* already been there */ + if (allowed && !allowed(r, rn)) + continue; /* can't go there */ + + /* add the region to the list of available ones. */ + ql_push(&rlist, rn); + + /* make sure we don't go here again, and put the region into the set for + further BFS'ing */ + fset(rn, RF_MARK); + *end = new_node(rn, depth, n); + end = &(*end)->next; + } + n = n->next; + } + free_nodes(root); + + return rlist; +} + +static region **internal_path_find(region * start, const region * target, + int maxlen, bool(*allowed) (const region *, const region *)) +{ + static region *path[MAXDEPTH + 2]; /* STATIC_RETURN: used for return, not across calls */ + direction_t d; + node *root = new_node(start, 0, NULL); + node **end = &root->next; + node *n = root; + bool found = false; + assert(maxlen <= MAXDEPTH); + fset(start, RF_MARK); + + while (n != NULL) { + region *r = n->r; + int depth = n->distance + 1; + if (n->distance >= maxlen) + break; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn == NULL) + continue; + if (fval(rn, RF_MARK)) + continue; /* already been there */ + if (!allowed(r, rn)) + continue; /* can't go there */ + if (rn == target) { + int i = depth; + path[i + 1] = NULL; + path[i] = rn; + while (n) { + path[--i] = n->r; + n = n->prev; + } + found = true; + break; + } else { + fset(rn, RF_MARK); + *end = new_node(rn, depth, n); + end = &(*end)->next; + } + } + if (found) + break; + n = n->next; + } + free_nodes(root); + if (found) + return path; + return NULL; +} + +bool +path_exists(region * start, const region * target, int maxlen, + bool(*allowed) (const region *, const region *)) +{ + assert((!fval(start, RF_MARK) && !fval(target, RF_MARK)) + || !"Some Algorithm did not clear its RF_MARKs!"); + if (start == target) + return true; + if (internal_path_find(start, target, maxlen, allowed) != NULL) + return true; + return false; +} + +region **path_find(region * start, const region * target, int maxlen, + bool(*allowed) (const region *, const region *)) +{ + assert((!fval(start, RF_MARK) && !fval(target, RF_MARK)) + || !"Did you call path_init()?"); + return internal_path_find(start, target, maxlen, allowed); +} diff --git a/core/src/kernel/pathfinder.h b/core/src/kernel/pathfinder.h new file mode 100644 index 000000000..6c9edb94e --- /dev/null +++ b/core/src/kernel/pathfinder.h @@ -0,0 +1,50 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_PATHFINDER +#define H_KRNL_PATHFINDER +#ifdef __cplusplus +extern "C" { +#endif + +#define MAXDEPTH 1024 + + extern int search[MAXDEPTH][2]; + extern int search_len; + + extern struct region **path_find(struct region *start, + const struct region *target, int maxlen, + bool(*allowed) (const struct region *, const struct region *)); + extern bool path_exists(struct region *start, const struct region *target, + int maxlen, bool(*allowed) (const struct region *, + const struct region *)); + extern bool allowed_swim(const struct region *src, + const struct region *target); + extern bool allowed_fly(const struct region *src, + const struct region *target); + extern bool allowed_walk(const struct region *src, + const struct region *target); + extern struct quicklist *regions_in_range(struct region *src, int maxdist, + bool(*allowed) (const struct region *, const struct region *)); + + extern void pathfinder_cleanup(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/plane.c b/core/src/kernel/plane.c new file mode 100644 index 000000000..13ea38ce9 --- /dev/null +++ b/core/src/kernel/plane.c @@ -0,0 +1,321 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "plane.h" + +/* kernel includes */ +#include "region.h" +#include "faction.h" + +/* util includes */ +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +struct plane *planes; + +int plane_width(const plane * pl) +{ + if (pl) { + return pl->maxx - pl->minx + 1; + } + return 0; +} + +int plane_height(const plane * pl) +{ + if (pl) { + return pl->maxy - pl->miny + 1; + } + return 0; +} + +plane *get_homeplane(void) +{ + return getplanebyid(0); +} + +plane *getplane(const region * r) +{ + if (r) { + return r->_plane; + } + return get_homeplane(); +} + +plane *getplanebyid(int id) +{ + plane *p; + + for (p = planes; p; p = p->next) { + if (p->id == id) { + return p; + } + } + return NULL; +} + +plane *getplanebyname(const char *name) +{ + plane *p; + + for (p = planes; p; p = p->next) + if (p->name && !strcmp(p->name, name)) + return p; + return NULL; +} + +plane *findplane(int x, int y) +{ + plane *pl; + + for (pl = planes; pl; pl = pl->next) { + if (x >= pl->minx && x <= pl->maxx && y >= pl->miny && y <= pl->maxy) { + return pl; + } + } + return NULL; +} + +int getplaneid(const region * r) +{ + if (r) { + plane *pl = getplane(r); + if (pl) + return pl->id; + + for (pl = planes; pl; pl = pl->next) { + if (r->x >= pl->minx && r->x <= pl->maxx + && r->y >= pl->miny && r->y <= pl->maxy) { + return pl->id; + } + } + } + return 0; +} + +static int +ursprung_x(const faction * f, const plane * pl, const region * rdefault) +{ + ursprung *ur; + int id = 0; + + if (!f) + return 0; + + if (pl) + id = pl->id; + + for (ur = f->ursprung; ur; ur = ur->next) { + if (ur->id == id) + return ur->x; + } + if (!rdefault) + return 0; + set_ursprung((faction *) f, id, rdefault->x - plane_center_x(pl), + rdefault->y - plane_center_y(pl)); + return rdefault->x - plane_center_x(pl); +} + +static int +ursprung_y(const faction * f, const plane * pl, const region * rdefault) +{ + ursprung *ur; + int id = 0; + + if (!f) + return 0; + + if (pl) + id = pl->id; + + for (ur = f->ursprung; ur; ur = ur->next) { + if (ur->id == id) + return ur->y; + } + if (!rdefault) + return 0; + set_ursprung((faction *) f, id, rdefault->x - plane_center_x(pl), + rdefault->y - plane_center_y(pl)); + return rdefault->y - plane_center_y(pl); +} + +int plane_center_x(const plane * pl) +{ + if (pl == NULL) + return 0; + + return (pl->minx + pl->maxx) / 2; +} + +int plane_center_y(const plane * pl) +{ + if (pl == NULL) + return 0; + + return (pl->miny + pl->maxy) / 2; +} + +void +adjust_coordinates(const faction * f, int *x, int *y, const plane * pl, + const region * r) +{ + int nx = *x; + int ny = *y; + if (f) { + nx -= ursprung_x(f, pl, r); + ny -= ursprung_y(f, pl, r); + } + if (pl) { + int plx = plane_center_x(pl); + int ply = plane_center_y(pl); + int width = plane_width(pl); + int height = plane_height(pl); + int width_2 = width / 2; + int height_2 = height / 2; + + nx -= plx; + ny -= ply; + + if (nx < 0) + nx = (width - (-nx) % width); + if (nx > width_2) + nx -= width; + if (ny < 0) + ny = (height - (-ny) % height); + if (ny > height_2) + ny -= height; + + assert(nx <= pl->maxx - plx); + assert(nx >= pl->minx - plx); + assert(ny <= pl->maxy - ply); + assert(ny >= pl->miny - ply); + + } + + *x = nx; + *y = ny; +} + +void set_ursprung(faction * f, int id, int x, int y) +{ + ursprung *ur; + assert(f != NULL); + for (ur = f->ursprung; ur; ur = ur->next) { + if (ur->id == id) { + ur->x = ur->x + x; + ur->y = ur->y + y; + return; + } + } + + ur = calloc(1, sizeof(ursprung)); + ur->id = id; + ur->x = x; + ur->y = y; + + addlist(&f->ursprung, ur); +} + +plane *create_new_plane(int id, const char *name, int minx, int maxx, int miny, + int maxy, int flags) +{ + plane *pl = getplanebyid(id); + + if (pl) + return pl; + pl = calloc(1, sizeof(plane)); + + pl->next = NULL; + pl->id = id; + if (name) + pl->name = strdup(name); + pl->minx = minx; + pl->maxx = maxx; + pl->miny = miny; + pl->maxy = maxy; + pl->flags = flags; + + addlist(&planes, pl); + return pl; +} + +/* Umrechnung Relative-Absolute-Koordinaten */ +int +rel_to_abs(const struct plane *pl, const struct faction *f, int rel, + unsigned char index) +{ + assert(index == 0 || index == 1); + + if (index == 0) + return (rel + ursprung_x(f, pl, NULL) + plane_center_x(pl)); + + return (rel + ursprung_y(f, pl, NULL) + plane_center_y(pl)); +} + +int resolve_plane(variant id, void *addr) +{ + int result = 0; + plane *pl = NULL; + if (id.i != 0) { + pl = getplanebyid(id.i); + if (pl == NULL) { + result = -1; + } + } + *(plane **) addr = pl; + return result; +} + +void write_plane_reference(const plane * u, struct storage *store) +{ + store->w_int(store, u ? (u->id) : 0); +} + +int read_plane_reference(plane ** pp, struct storage *store) +{ + variant id; + id.i = store->r_int(store); + if (id.i == 0) { + *pp = NULL; + return AT_READ_FAIL; + } + *pp = getplanebyid(id.i); + if (*pp == NULL) + ur_add(id, pp, resolve_plane); + return AT_READ_OK; +} + +bool is_watcher(const struct plane * p, const struct faction * f) +{ + struct watcher *w; + if (!p) + return false; + w = p->watchers; + while (w && w->faction != f) + w = w->next; + return (w != NULL); +} diff --git a/core/src/kernel/plane.h b/core/src/kernel/plane.h new file mode 100644 index 000000000..c573c096b --- /dev/null +++ b/core/src/kernel/plane.h @@ -0,0 +1,87 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_PLANES +#define H_KRNL_PLANES +#ifdef __cplusplus +extern "C" { +#endif + +#define PFL_NOCOORDS 1 /* not in use */ +#define PFL_NORECRUITS 2 +#define PFL_NOALLIANCES 4 +#define PFL_LOWSTEALING 8 +#define PFL_NOGIVE 16 /* Übergaben sind unmöglich */ +#define PFL_NOATTACK 32 /* Angriffe und Diebstähle sind unmöglich */ +#define PFL_NOTERRAIN 64 /* Terraintyp wird nicht angezeigt TODO? */ +#define PFL_NOMAGIC 128 /* Zaubern ist unmöglich */ +#define PFL_NOSTEALTH 256 /* Tarnung außer Betrieb */ +#define PFL_NOTEACH 512 /* Lehre außer Betrieb */ +#define PFL_NOBUILD 1024 /* Bauen außer Betrieb */ +#define PFL_NOFEED 2048 /* Kein Unterhalt nötig */ +#define PFL_FRIENDLY 4096 /* everyone is your ally */ +#define PFL_NOORCGROWTH 8192 /* orcs don't grow */ +#define PFL_NOMONSTERS 16384 /* no monster randenc */ +#define PFL_SEESPECIAL 32768 /* far seeing */ + + typedef struct watcher { + struct watcher *next; + struct faction *faction; + unsigned char mode; + } watcher; + + typedef struct plane { + struct plane *next; + struct watcher *watchers; + int id; + char *name; + int minx, maxx, miny, maxy; + unsigned int flags; + struct attrib *attribs; + } plane; + +#define plane_id(pl) ( (pl) ? (pl)->id : 0 ) + + extern struct plane *planes; + + struct plane *getplane(const struct region *r); + struct plane *findplane(int x, int y); + void init_planes(void); + int getplaneid(const struct region *r); + struct plane *getplanebyid(int id); + int plane_center_x(const struct plane *pl); + int plane_center_y(const struct plane *pl); + void set_ursprung(struct faction *f, int id, int x, int y); + struct plane *create_new_plane(int id, const char *name, int minx, int maxx, + int miny, int maxy, int flags); + struct plane *getplanebyname(const char *); + struct plane *get_homeplane(void); + extern int rel_to_abs(const struct plane *pl, const struct faction *f, + int rel, unsigned char index); + extern bool is_watcher(const struct plane *p, const struct faction *f); + extern int resolve_plane(variant data, void *addr); + extern void write_plane_reference(const plane * p, struct storage *store); + extern int read_plane_reference(plane ** pp, struct storage *store); + extern int plane_width(const plane * pl); + extern int plane_height(const plane * pl); + void adjust_coordinates(const struct faction *f, int *x, int *y, + const struct plane *pl, const struct region *r); +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/player.c b/core/src/kernel/player.c new file mode 100644 index 000000000..f914580c9 --- /dev/null +++ b/core/src/kernel/player.c @@ -0,0 +1,102 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ +#include +#include "player.h" + +#include +#include + +#include +#include + +#define PMAXHASH 1021 + +typedef struct player_hash { + struct player *entries; +} player_hash; + +static player_hash *players[PMAXHASH]; + +player *make_player(const struct faction *f) +{ + player *p = calloc(sizeof(player), 1); + unsigned int hash; + + for (p->id = rng_int();; p->id++) { + /* if there is a hashing conflict, resolve it */ + player *pi = get_player(p->id); + if (pi) + p->id++; + else + break; + } + hash = p->id % PMAXHASH; + p->faction = f; + p->nexthash = players[hash]->entries; + players[hash]->entries = p; + + return p; +} + +player *next_player(player * p) +{ + if (p->nexthash) + return p->nexthash; + else { + unsigned int hash = p->id % PMAXHASH; + p = NULL; + while (++hash != PMAXHASH) { + if (players[hash]->entries != NULL) { + p = players[hash]->entries; + break; + } + } + return p; + } +} + +player *get_player(unsigned int id) +{ + unsigned int hash = id % PMAXHASH; + struct player *p = players[hash]->entries; + + while (p && p->id != id) + p = p->nexthash; + return p; +} + +player *get_players(void) +{ + struct player *p = NULL; + unsigned int hash = 0; + + while (p != NULL && hash != PMAXHASH) { + p = players[hash++]->entries; + } + return p; +} + +void players_done(void) +{ + int i; + for (i = 0; i != PMAXHASH; ++i) { + player *p = players[i]->entries; + players[i]->entries = p->nexthash; + free(p->name); + if (p->email) + free(p->email); + if (p->name) + free(p->name); + free(p); + } +} diff --git a/core/src/kernel/player.h b/core/src/kernel/player.h new file mode 100644 index 000000000..a53a9e75a --- /dev/null +++ b/core/src/kernel/player.h @@ -0,0 +1,43 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ +#ifndef H_KRNL_PLAYER +#define H_KRNL_PLAYER +#ifdef __cplusplus +extern "C" { +#endif + + struct faction; + + typedef struct player { + unsigned int id; + char *name; + char *email; + char *address; + struct vacation { + int weeks; + char *email; + } *vacation; + const struct faction *faction; + + struct player *nexthash; /* don't use! */ + } player; + + extern struct player *get_players(void); + extern struct player *get_player(unsigned int id); + extern struct player *make_player(const struct faction *f); + extern struct player *next_player(struct player *p); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/pool.c b/core/src/kernel/pool.c new file mode 100644 index 000000000..954cdb164 --- /dev/null +++ b/core/src/kernel/pool.c @@ -0,0 +1,255 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "pool.h" + +#include "faction.h" +#include "item.h" +#include "magic.h" +#include "order.h" +#include "region.h" +#include "race.h" +#include "unit.h" + +#include +#include + +#include +#include + +#define TODO_POOL +#undef TODO_RESOURCES + +int get_resource(const unit * u, const resource_type * rtype) +{ + const item_type *itype = resource2item(rtype); + + assert(rtype); + if (rtype->uget) { + /* this resource is probably special */ + int i = rtype->uget(u, rtype); + if (i >= 0) + return i; + } + else if (rtype->uchange) { + /* this resource is probably special */ + int i = rtype->uchange((unit *)u, rtype, 0); + if (i >= 0) + return i; + } + if (itype != NULL) { + if (itype == olditemtype[R_STONE] && (u_race(u)->flags & RCF_STONEGOLEM)) { + return u->number * GOLEM_STONE; + } else if (itype == olditemtype[R_IRON] && (u_race(u)->flags & RCF_IRONGOLEM)) { + return u->number * GOLEM_IRON; + } else { + const item *i = *i_findc(&u->items, itype); + if (i) + return i->number; + return 0; + } + } + if (rtype == oldresourcetype[R_AURA]) + return get_spellpoints(u); + if (rtype == oldresourcetype[R_PERMAURA]) + return max_spellpoints(u->region, u); + log_error("trying to get unknown resource '%s'.\n", rtype->_name[0]); + return 0; +} + +int change_resource(unit * u, const resource_type * rtype, int change) +{ + int i = 0; + + if (rtype->uchange) + i = rtype->uchange(u, rtype, change); + else if (rtype == oldresourcetype[R_AURA]) + i = change_spellpoints(u, change); + else if (rtype == oldresourcetype[R_PERMAURA]) + i = change_maxspellpoints(u, change); + else + assert(!"undefined resource detected. rtype->uchange not initialized."); + assert(i==get_resource(u, rtype)); + assert(i >= 0); + if (i >= 100000000) { + log_warning("%s has %d %s\n", unitname(u), i, rtype->_name[0]); + } + return i; +} + +int get_reservation(const unit * u, const resource_type * rtype) +{ + reservation *res = u->reservations; + + if (rtype == oldresourcetype[R_STONE] && (u_race(u)->flags & RCF_STONEGOLEM)) + return (u->number * GOLEM_STONE); + if (rtype == oldresourcetype[R_IRON] && (u_race(u)->flags & RCF_IRONGOLEM)) + return (u->number * GOLEM_IRON); + while (res && res->type != rtype) + res = res->next; + if (res) + return res->value; + return 0; +} + +int change_reservation(unit * u, const resource_type * rtype, int value) +{ + reservation *res, **rp = &u->reservations; + + if (!value) + return 0; + + while (*rp && (*rp)->type != rtype) + rp = &(*rp)->next; + res = *rp; + if (!res) { + *rp = res = calloc(sizeof(reservation), 1); + res->type = rtype; + res->value = value; + } else if (res && res->value + value <= 0) { + *rp = res->next; + free(res); + return 0; + } else { + res->value += value; + } + return res->value; +} + +int set_resvalue(unit * u, const resource_type * rtype, int value) +{ + reservation *res, **rp = &u->reservations; + + while (*rp && (*rp)->type != rtype) + rp = &(*rp)->next; + res = *rp; + if (!res) { + if (!value) + return 0; + *rp = res = calloc(sizeof(reservation), 1); + res->type = rtype; + res->value = value; + } else if (res && value <= 0) { + *rp = res->next; + free(res); + return 0; + } else { + res->value = value; + } + return res->value; +} + +int +get_pooled(const unit * u, const resource_type * rtype, unsigned int mode, + int count) +{ + const faction *f = u->faction; + unit *v; + int use = 0; + region *r = u->region; + int have = get_resource(u, rtype); + + if ((u_race(u)->ec_flags & GETITEM) == 0) { + mode &= (GET_SLACK | GET_RESERVE); + } + + if ((mode & GET_SLACK) && (mode & GET_RESERVE)) + use = have; + else { + int reserve = get_reservation(u, rtype); + int slack = MAX(0, have - reserve); + if (mode & GET_RESERVE) + use = have - slack; + else if (mode & GET_SLACK) + use = slack; + } + if (rtype->flags & RTF_POOLED && mode & ~(GET_SLACK | GET_RESERVE)) { + for (v = r->units; v && use < count; v = v->next) + if (u != v) { + int mask; + + if (v->items == NULL && rtype->uget == NULL) + continue; + if ((urace(v)->ec_flags & GIVEITEM) == 0) + continue; + + if (v->faction == f) { + mask = (mode >> 3) & (GET_SLACK | GET_RESERVE); + } else if (alliedunit(v, f, HELP_MONEY)) + mask = (mode >> 6) & (GET_SLACK | GET_RESERVE); + else + continue; + use += get_pooled(v, rtype, mask, count - use); + } + } + return use; +} + +int +use_pooled(unit * u, const resource_type * rtype, unsigned int mode, int count) +{ + const faction *f = u->faction; + unit *v; + int use = count; + region *r = u->region; + int n = 0, have = get_resource(u, rtype); + + if ((u_race(u)->ec_flags & GETITEM) == 0) { + mode &= (GET_SLACK | GET_RESERVE); + } + + if ((mode & GET_SLACK) && (mode & GET_RESERVE)) { + n = MIN(use, have); + } else { + int reserve = get_reservation(u, rtype); + int slack = MAX(0, have - reserve); + if (mode & GET_RESERVE) { + n = have - slack; + n = MIN(use, n); + change_reservation(u, rtype, -n); + } else if (mode & GET_SLACK) { + n = MIN(use, slack); + } + } + if (n > 0) { + change_resource(u, rtype, -n); + use -= n; + } + + if (rtype->flags & RTF_POOLED && mode & ~(GET_SLACK | GET_RESERVE)) { + for (v = r->units; use > 0 && v != NULL; v = v->next) + if (u != v) { + int mask; + if ((urace(v)->ec_flags & GIVEITEM) == 0) + continue; + if (v->items == NULL && rtype->uget == NULL) + continue; + + if (v->faction == f) { + mask = (mode >> 3) & (GET_SLACK | GET_RESERVE); + } else if (alliedunit(v, f, HELP_MONEY)) + mask = (mode >> 6) & (GET_SLACK | GET_RESERVE); + else + continue; + use -= use_pooled(v, rtype, mask, use); + } + } + return count - use; +} diff --git a/core/src/kernel/pool.h b/core/src/kernel/pool.h new file mode 100644 index 000000000..6de15bf3c --- /dev/null +++ b/core/src/kernel/pool.h @@ -0,0 +1,62 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_POOL_H +#define H_KRNL_POOL_H +#ifdef __cplusplus +extern "C" { +#endif + +/* bitfield values for get/use/change operations */ +#define GET_SLACK 0x01 +#define GET_RESERVE 0x02 + +#define GET_POOLED_SLACK 0x08 +#define GET_POOLED_RESERVE 0x10 +#define GET_POOLED_FORCE 0x20 /* ignore f->options pools */ +#define GET_ALLIED_SLACK 0x30 +#define GET_ALLIED_RESERVE 0x40 + +/* for convenience: */ +#define GET_DEFAULT (GET_RESERVE|GET_SLACK|GET_POOLED_SLACK) +#define GET_ALL (GET_SLACK|GET_RESERVE|GET_POOLED_SLACK|GET_POOLED_RESERVE|GET_POOLED_FORCE) + + int get_pooled(const struct unit *u, const struct resource_type *res, + unsigned int mode, int count); + int use_pooled(struct unit *u, const struct resource_type *res, + unsigned int mode, int count); + /** use_pooled + * verbraucht 'count' Objekte der resource 'itm' + * unter zuhilfenahme des Pools der struct region und Aufbrauch des + * von der Einheit reservierten Resourcen + */ + + int get_resource(const struct unit *u, const struct resource_type *res); + int change_resource(struct unit *u, const struct resource_type *res, + int change); + + int get_reservation(const struct unit *u, const struct resource_type *res); + int change_reservation(struct unit *u, const struct resource_type *res, + int value); + + int set_resvalue(struct unit * u, const struct resource_type * rtype, int value); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/pool_test.c b/core/src/kernel/pool_test.c new file mode 100644 index 000000000..3b801a9b9 --- /dev/null +++ b/core/src/kernel/pool_test.c @@ -0,0 +1,47 @@ +#include +#include + +#include "pool.h" +#include "magic.h" +#include "unit.h" +#include "item.h" +#include "region.h" +#include "skill.h" + +#include +#include + +void test_change_resource(CuTest * tc) +{ + struct unit * u; + struct faction * f; + struct region * r; + const char * names[] = { "money", "aura", "permaura", "horse", "hp", 0 }; + int i; + + test_cleanup(); + test_create_world(); + skill_enabled[SK_MAGIC] = 1; + + r = findregion(0, 0); + f = test_create_faction(0); + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + set_level(u, SK_MAGIC, 5); + create_mage(u, M_DRAIG); + + for (i=0;names[i];++i) { + const struct resource_type *rtype = rt_find(names[i]); + int have = get_resource(u, rtype); + CuAssertIntEquals(tc, have+1, change_resource(u, rtype, 1)); + CuAssertIntEquals(tc, have+1, get_resource(u, rtype)); + } +} + +CuSuite *get_pool_suite(void) +{ + CuSuite *suite = CuSuiteNew(); +/* SUITE_ADD_TEST(suite, test_pool); */ + SUITE_ADD_TEST(suite, test_change_resource); + return suite; +} diff --git a/core/src/kernel/race.c b/core/src/kernel/race.c new file mode 100644 index 000000000..95686c56f --- /dev/null +++ b/core/src/kernel/race.c @@ -0,0 +1,294 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "race.h" + +#include "alchemy.h" +#include "build.h" +#include "building.h" +#include "equipment.h" +#include "faction.h" +#include "group.h" +#include "item.h" +#include "magic.h" +#include "names.h" +#include "pathfinder.h" +#include "region.h" +#include "ship.h" +#include "skill.h" +#include "terrain.h" +#include "unit.h" +#include "version.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include + +/* attrib includes */ +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +/** external variables **/ +race *races; +int num_races = 0; + +race_list *get_familiarraces(void) +{ + static int init = 0; + static race_list *familiarraces; + + if (!init) { + race *rc = races; + for (; rc != NULL; rc = rc->next) { + if (rc->init_familiar != NULL) { + racelist_insert(&familiarraces, rc); + } + } + init = false; + } + return familiarraces; +} + +void racelist_clear(struct race_list **rl) +{ + while (*rl) { + race_list *rl2 = (*rl)->next; + free(*rl); + *rl = rl2; + } +} + +void racelist_insert(struct race_list **rl, const struct race *r) +{ + race_list *rl2 = (race_list *) malloc(sizeof(race_list)); + + rl2->data = r; + rl2->next = *rl; + + *rl = rl2; +} + +race *rc_new(const char *zName) +{ + char zBuffer[80]; + race *rc = (race *)calloc(sizeof(race), 1); + + rc->hitpoints = 1; + if (strchr(zName, ' ') != NULL) { + log_error("race '%s' has an invalid name. remove spaces\n", zName); + assert(strchr(zName, ' ') == NULL); + } + strcpy(zBuffer, zName); + rc->_name[0] = strdup(zBuffer); + sprintf(zBuffer, "%s_p", zName); + rc->_name[1] = strdup(zBuffer); + sprintf(zBuffer, "%s_d", zName); + rc->_name[2] = strdup(zBuffer); + sprintf(zBuffer, "%s_x", zName); + rc->_name[3] = strdup(zBuffer); + rc->precombatspell = NULL; + + rc->attack[0].type = AT_COMBATSPELL; + rc->attack[1].type = AT_NONE; + return rc; +} + +race *rc_add(race * rc) +{ + rc->index = num_races++; + rc->next = races; + return races = rc; +} + +static const char *racealias[][2] = { + {"uruk", "orc"}, /* there was a time when the orc race was called uruk (and there were other orcs). That was really confusing */ + {"skeletton lord", "skeleton lord"}, /* we once had a typo here. it is fixed */ + {NULL, NULL} +}; + +race *rc_find(const char *name) +{ + const char *rname = name; + race *rc = races; + int i; + + for (i = 0; racealias[i][0]; ++i) { + if (strcmp(racealias[i][0], name) == 0) { + rname = racealias[i][1]; + break; + } + } + while (rc && !strcmp(rname, rc->_name[0]) == 0) + rc = rc->next; + return rc; +} + +/** dragon movement **/ +bool allowed_dragon(const region * src, const region * target) +{ + if (fval(src->terrain, ARCTIC_REGION) && fval(target->terrain, SEA_REGION)) + return false; + return allowed_fly(src, target); +} + +char **race_prefixes = NULL; + +extern void add_raceprefix(const char *prefix) +{ + static size_t size = 4; + static unsigned int next = 0; + if (race_prefixes == NULL) + race_prefixes = malloc(size * sizeof(char *)); + if (next + 1 == size) { + size *= 2; + race_prefixes = realloc(race_prefixes, size * sizeof(char *)); + } + race_prefixes[next++] = strdup(prefix); + race_prefixes[next] = NULL; +} + +/* Die Bezeichnungen dürfen wegen der Art des Speicherns keine + * Leerzeichen enthalten! */ + +/* "den Zwergen", "Halblingsparteien" */ + +void set_show_item(faction * f, item_t i) +{ + attrib *a = a_add(&f->attribs, a_new(&at_showitem)); + a->data.v = (void *)olditemtype[i]; +} + +bool r_insectstalled(const region * r) +{ + return fval(r->terrain, ARCTIC_REGION); +} + +const char *rc_name(const race * rc, int n) +{ + return rc ? mkname("race", rc->_name[n]) : NULL; +} + +const char *raceprefix(const unit * u) +{ + const attrib *asource = u->faction->attribs; + + if (fval(u, UFL_GROUP)) { + const attrib *agroup = agroup = a_findc(u->attribs, &at_group); + if (agroup != NULL) + asource = ((const group *)(agroup->data.v))->attribs; + } + return get_prefix(asource); +} + +const char *racename(const struct locale *loc, const unit * u, const race * rc) +{ + const char *prefix = raceprefix(u); + + if (prefix != NULL) { + static char lbuf[80]; + char *bufp = lbuf; + size_t size = sizeof(lbuf) - 1; + int ch, bytes; + + bytes = (int)strlcpy(bufp, LOC(loc, mkname("prefix", prefix)), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + bytes = (int)strlcpy(bufp, LOC(loc, rc_name(rc, u->number != 1)), size); + assert(~bufp[0] & 0x80 || !"unicode/not implemented"); + ch = tolower(*(unsigned char *)bufp); + bufp[0] = (char)ch; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + *bufp = 0; + + return lbuf; + } + return LOC(loc, rc_name(rc, u->number != 1)); +} + +int +rc_specialdamage(const race * ar, const race * dr, + const struct weapon_type *wtype) +{ + race_t art = old_race(ar); + int m, modifier = 0; + + if (wtype != NULL && wtype->modifiers != NULL) + for (m = 0; wtype->modifiers[m].value; ++m) { + /* weapon damage for this weapon, possibly by race */ + if (wtype->modifiers[m].flags & WMF_DAMAGE) { + race_list *rlist = wtype->modifiers[m].races; + if (rlist != NULL) { + while (rlist) { + if (rlist->data == ar) + break; + rlist = rlist->next; + } + if (rlist == NULL) + continue; + } + modifier += wtype->modifiers[m].value; + } + } + switch (art) { + case RC_HALFLING: + if (wtype != NULL && dragonrace(dr)) { + modifier += 5; + } + break; + default: + break; + } + return modifier; +} + +void write_race_reference(const race * rc, struct storage *store) +{ + store->w_tok(store, rc ? rc->_name[0] : "none"); +} + +variant read_race_reference(struct storage *store) +{ + variant result; + char zName[20]; + store->r_tok_buf(store, zName, sizeof(zName)); + + if (strcmp(zName, "none") == 0) { + result.v = NULL; + return result; + } else { + result.v = rc_find(zName); + } + assert(result.v != NULL); + return result; +} diff --git a/core/src/kernel/race.h b/core/src/kernel/race.h new file mode 100644 index 000000000..c6f511d44 --- /dev/null +++ b/core/src/kernel/race.h @@ -0,0 +1,199 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_RACE_H +#define H_KRNL_RACE_H +#ifdef __cplusplus +extern "C" { +#endif + +#include "magic.h" /* wegen MAXMAGIETYP */ + +#define AT_NONE 0 +#define AT_STANDARD 1 +#define AT_DRAIN_EXP 2 +#define AT_DRAIN_ST 3 +#define AT_NATURAL 4 +#define AT_DAZZLE 5 +#define AT_SPELL 6 +#define AT_COMBATSPELL 7 +#define AT_STRUCTURAL 8 + +#define GOLEM_IRON 4 /* Anzahl Eisen in einem Eisengolem */ +#define GOLEM_STONE 4 /* Anzahl Steine in einem Steingolem */ + +#define RACESPOILCHANCE 5 /* Chance auf rassentypische Beute */ + + struct param; + struct spell; + + typedef struct att { + int type; + union { + const char *dice; + const struct spell *sp; + } data; + int flags; + int level; + } att; + + extern int num_races; + + typedef struct race { + struct param *parameters; + const char *_name[4]; /* neu: name[4]völker */ + float magres; + float maxaura; /* Faktor auf Maximale Aura */ + float regaura; /* Faktor auf Regeneration */ + float recruit_multi; /* Faktor für Bauernverbrauch */ + int index; + int recruitcost; + int maintenance; + int splitsize; + int weight; + int capacity; + float speed; + float aggression; /* chance that a monster will attack */ + int hitpoints; + const char *def_damage; + char armor; + int at_default; /* Angriffsskill Unbewaffnet (default: -2) */ + int df_default; /* Verteidigungsskill Unbewaffnet (default: -2) */ + int at_bonus; /* Verändert den Angriffsskill (default: 0) */ + int df_bonus; /* Verändert den Verteidigungskill (default: 0) */ + const struct spell *precombatspell; + struct att attack[10]; + char bonus[MAXSKILLS]; + signed char *study_speed; /* study-speed-bonus in points/turn (0=30 Tage) */ + bool __remove_me_nonplayer; + int flags; + int battle_flags; + int ec_flags; + race_t oldfamiliars[MAXMAGIETYP]; + + const char *(*generate_name) (const struct unit *); + const char *(*describe) (const struct unit *, const struct locale *); + void (*age) (struct unit * u); + bool(*move_allowed) (const struct region *, const struct region *); + struct item *(*itemdrop) (const struct race *, int size); + void (*init_familiar) (struct unit *); + + const struct race *familiars[MAXMAGIETYP]; + struct attrib *attribs; + struct race *next; + } race; + + typedef struct race_list { + struct race_list *next; + const struct race *data; + } race_list; + + extern void racelist_clear(struct race_list **rl); + extern void racelist_insert(struct race_list **rl, const struct race *r); + + extern struct race_list *get_familiarraces(void); + extern struct race *races; + + extern struct race *rc_find(const char *); + extern const char *rc_name(const struct race *, int); + extern struct race *rc_add(struct race *); + extern struct race *rc_new(const char *zName); + extern int rc_specialdamage(const race *, const race *, + const struct weapon_type *); + +/* Flags */ +#define RCF_PLAYERRACE (1<<0) /* can be played by a player. */ +#define RCF_KILLPEASANTS (1<<1) /* Töten Bauern. Dämonen werden nicht über dieses Flag, sondern in randenc() behandelt. */ +#define RCF_SCAREPEASANTS (1<<2) +#define RCF_CANSTEAL (1<<3) +#define RCF_MOVERANDOM (1<<4) +#define RCF_CANNOTMOVE (1<<5) +#define RCF_LEARN (1<<6) /* Lernt automatisch wenn struct faction == 0 */ +#define RCF_FLY (1<<7) /* kann fliegen */ +#define RCF_SWIM (1<<8) /* kann schwimmen */ +#define RCF_WALK (1<<9) /* kann über Land gehen */ +#define RCF_NOLEARN (1<<10) /* kann nicht normal lernen */ +#define RCF_NOTEACH (1<<11) /* kann nicht lehren */ +#define RCF_HORSE (1<<12) /* Einheit ist Pferd, sozusagen */ +#define RCF_DESERT (1<<13) /* 5% Chance, das Einheit desertiert */ +#define RCF_ILLUSIONARY (1<<14) /* (Illusion & Spell) Does not drop items. */ +#define RCF_ABSORBPEASANTS (1<<15) /* Tötet und absorbiert Bauern */ +#define RCF_NOHEAL (1<<16) /* Einheit kann nicht geheilt werden */ +#define RCF_NOWEAPONS (1<<17) /* Einheit kann keine Waffen benutzen */ +#define RCF_SHAPESHIFT (1<<18) /* Kann TARNE RASSE benutzen. */ +#define RCF_SHAPESHIFTANY (1<<19) /* Kann TARNE RASSE "string" benutzen. */ +#define RCF_UNDEAD (1<<20) /* Undead. */ +#define RCF_DRAGON (1<<21) /* Drachenart (für Zauber) */ +#define RCF_COASTAL (1<<22) /* kann in Landregionen an der Küste sein */ +#define RCF_UNARMEDGUARD (1<<23) /* kann ohne Waffen bewachen */ +#define RCF_CANSAIL (1<<24) /* Einheit darf Schiffe betreten */ +#define RCF_INVISIBLE (1<<25) /* not visible in any report */ +#define RCF_SHIPSPEED (1<<26) /* race gets +1 on shipspeed */ +#define RCF_STONEGOLEM (1<<27) /* race gets stonegolem properties */ +#define RCF_IRONGOLEM (1<<28) /* race gets irongolem properties */ + +/* Economic flags */ +#define GIVEITEM (1<<1) /* gibt Gegenstände weg */ +#define GIVEPERSON (1<<2) /* übergibt Personen */ +#define GIVEUNIT (1<<3) /* Einheiten an andere Partei übergeben */ +#define GETITEM (1<<4) /* nimmt Gegenstände an */ +#define ECF_REC_HORSES (1<<6) /* Rekrutiert aus Pferden */ +#define ECF_REC_ETHEREAL (1<<7) /* Rekrutiert aus dem Nichts */ +#define ECF_REC_UNLIMITED (1<<8) /* Rekrutiert ohne Limit */ + +/* Battle-Flags */ +#define BF_EQUIPMENT (1<<0) /* Kann Ausrüstung benutzen */ +#define BF_NOBLOCK (1<<1) /* Wird in die Rückzugsberechnung nicht einbezogen */ +#define BF_RES_PIERCE (1<<2) /* Halber Schaden durch PIERCE */ +#define BF_RES_CUT (1<<3) /* Halber Schaden durch CUT */ +#define BF_RES_BASH (1<<4) /* Halber Schaden durch BASH */ +#define BF_INV_NONMAGIC (1<<5) /* Immun gegen nichtmagischen Schaden */ +#define BF_CANATTACK (1<<6) /* Kann keine ATTACKIERE Befehle ausfuehren */ + + extern int unit_old_max_hp(struct unit *u); + extern const char *racename(const struct locale *lang, const struct unit *u, + const race * rc); + +#define omniscient(f) (((f)->race)==new_race[RC_ILLUSION] || ((f)->race)==new_race[RC_TEMPLATE]) + +#define playerrace(rc) (fval((rc), RCF_PLAYERRACE)) +#define dragonrace(rc) ((rc) == new_race[RC_FIREDRAGON] || (rc) == new_race[RC_DRAGON] || (rc) == new_race[RC_WYRM] || (rc) == new_race[RC_BIRTHDAYDRAGON]) +#define humanoidrace(rc) (fval((rc), RCF_UNDEAD) || (rc)==new_race[RC_DRACOID] || playerrace(rc)) +#define illusionaryrace(rc) (fval(rc, RCF_ILLUSIONARY)) + + extern bool allowed_dragon(const struct region *src, + const struct region *target); + + extern bool r_insectstalled(const struct region *r); + + extern void add_raceprefix(const char *); + extern char **race_prefixes; + + extern void write_race_reference(const struct race *rc, + struct storage *store); + extern variant read_race_reference(struct storage *store); + + extern const char *raceprefix(const struct unit *u); + + extern void give_starting_equipment(const struct equipment *eq, + struct unit *u); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/region.c b/core/src/kernel/region.c new file mode 100644 index 000000000..e22e5ef62 --- /dev/null +++ b/core/src/kernel/region.c @@ -0,0 +1,1625 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "region.h" + +/* kernel includes */ +#include "alliance.h" +#include "building.h" +#include "connection.h" +#include "curse.h" +#include "equipment.h" +#include "faction.h" +#include "item.h" +#include "message.h" +#include "plane.h" +#include "region.h" +#include "resources.h" +#include "save.h" +#include "ship.h" +#include "terrain.h" +#include "terrainid.h" +#include "unit.h" +#include "version.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +extern int dice_rand(const char *s); + +region *regions; + +int get_maxluxuries(void) +{ + static int maxluxuries = -1; + if (maxluxuries == -1) { + const luxury_type *ltype; + maxluxuries = 0; + for (ltype = luxurytypes; ltype; ltype = ltype->next) + ++maxluxuries; + } + return maxluxuries; +} + +const int delta_x[MAXDIRECTIONS] = { + -1, 0, 1, 1, 0, -1 +}; + +const int delta_y[MAXDIRECTIONS] = { + 1, 1, 0, -1, -1, 0 +}; + +static const direction_t back[MAXDIRECTIONS] = { + D_SOUTHEAST, + D_SOUTHWEST, + D_WEST, + D_NORTHWEST, + D_NORTHEAST, + D_EAST, +}; + +direction_t dir_invert(direction_t dir) +{ + switch (dir) { + case D_PAUSE: + case D_SPECIAL: + return dir; + break; + default: + if (dir >= 0 && dir < MAXDIRECTIONS) + return back[dir]; + } + assert(!"illegal direction"); + return NODIRECTION; +} + +const char *write_regionname(const region * r, const faction * f, char *buffer, + size_t size) +{ + char *buf = (char *)buffer; + const struct locale *lang = f ? f->locale : 0; + if (r == NULL) { + strlcpy(buf, "(null)", size); + } else { + plane *pl = rplane(r); + int nx = r->x, ny = r->y; + pnormalize(&nx, &ny, pl); + adjust_coordinates(f, &nx, &ny, pl, r); + slprintf(buf, size, "%s (%d,%d)", rname(r, lang), nx, ny); + } + return buffer; +} + +const char *regionname(const region * r, const faction * f) +{ + static int index = 0; + static char buf[2][NAMESIZE]; + index = 1-index; + return write_regionname(r, f, buf[index], sizeof(buf[index])); +} + +int deathcount(const region * r) +{ + attrib *a = a_find(r->attribs, &at_deathcount); + if (!a) + return 0; + return a->data.i; +} + +int chaoscount(const region * r) +{ + attrib *a = a_find(r->attribs, &at_chaoscount); + if (!a) + return 0; + return a->data.i; +} + +void deathcounts(region * r, int fallen) +{ + attrib *a; + static const curse_type *ctype = NULL; + + if (fallen == 0) + return; + if (!ctype) + ctype = ct_find("holyground"); + if (ctype && curse_active(get_curse(r->attribs, ctype))) + return; + + a = a_find(r->attribs, &at_deathcount); + if (!a) + a = a_add(&r->attribs, a_new(&at_deathcount)); + a->data.i += fallen; + + if (a->data.i <= 0) + a_remove(&r->attribs, a); +} + +void chaoscounts(region * r, int fallen) +{ + attrib *a; + + if (fallen == 0) + return; + + a = a_find(r->attribs, &at_chaoscount); + if (!a) + a = a_add(&r->attribs, a_new(&at_chaoscount)); + a->data.i += fallen; + + if (a->data.i <= 0) + a_remove(&r->attribs, a); +} + +/********************/ +/* at_direction */ +/********************/ +static void a_initdirection(attrib * a) +{ + a->data.v = calloc(1, sizeof(spec_direction)); +} + +static void a_freedirection(attrib * a) +{ + free(a->data.v); +} + +static int a_agedirection(attrib * a) +{ + spec_direction *d = (spec_direction *) (a->data.v); + --d->duration; + return (d->duration > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +typedef struct dir_lookup { + char *name; + const char *oldname; + struct dir_lookup *next; +} dir_lookup; + +static dir_lookup *dir_name_lookup; + +void register_special_direction(const char *name) +{ + struct locale *lang; + char *str = strdup(name); + + for (lang = locales; lang; lang = nextlocale(lang)) { + void **tokens = get_translations(lang, UT_SPECDIR); + const char *token = LOC(lang, name); + + if (token) { + variant var; + + var.v = str; + addtoken(tokens, token, var); + + if (lang == default_locale) { + dir_lookup *dl = malloc(sizeof(dir_lookup)); + dl->name = str; + dl->oldname = token; + dl->next = dir_name_lookup; + dir_name_lookup = dl; + } + } else { + log_error("no translation for spec_direction '%s' in locale '%s'\n", name, locale_name(lang)); + } + } +} + +static int a_readdirection(attrib * a, void *owner, struct storage *store) +{ + spec_direction *d = (spec_direction *) (a->data.v); + + d->x = (short)store->r_int(store); + d->y = (short)store->r_int(store); + d->duration = store->r_int(store); + if (store->version < UNICODE_VERSION) { + char lbuf[16]; + dir_lookup *dl = dir_name_lookup; + + store->r_tok_buf(store, NULL, 0); + store->r_tok_buf(store, lbuf, sizeof(lbuf)); + + cstring_i(lbuf); + for (; dl; dl = dl->next) { + if (strcmp(lbuf, dl->oldname) == 0) { + d->keyword = strdup(dl->name); + sprintf(lbuf, "%s_desc", d->keyword); + d->desc = strdup(dl->name); + break; + } + } + if (dl == NULL) { + log_error("unknown spec_direction '%s'\n", lbuf); + assert(!"not implemented"); + } + } else { + d->desc = store->r_tok(store); + d->keyword = store->r_tok(store); + } + d->active = true; + return AT_READ_OK; +} + +static void +a_writedirection(const attrib * a, const void *owner, struct storage *store) +{ + spec_direction *d = (spec_direction *) (a->data.v); + + store->w_int(store, d->x); + store->w_int(store, d->y); + store->w_int(store, d->duration); + store->w_tok(store, d->desc); + store->w_tok(store, d->keyword); +} + +attrib_type at_direction = { + "direction", + a_initdirection, + a_freedirection, + a_agedirection, + a_writedirection, + a_readdirection +}; + +region *find_special_direction(const region * r, const char *token, + const struct locale *lang) +{ + attrib *a; + spec_direction *d; + + if (strlen(token) == 0) + return NULL; + for (a = a_find(r->attribs, &at_direction); a && a->type == &at_direction; + a = a->next) { + d = (spec_direction *) (a->data.v); + + if (d->active) { + void **tokens = get_translations(lang, UT_SPECDIR); + variant var; + if (findtoken(*tokens, token, &var) == E_TOK_SUCCESS) { + if (strcmp((const char *)var.v, d->keyword) == 0) { + return findregion(d->x, d->y); + } + } + } + } + + return NULL; +} + +attrib *create_special_direction(region * r, region * rt, int duration, + const char *desc, const char *keyword) +{ + attrib *a = a_add(&r->attribs, a_new(&at_direction)); + spec_direction *d = (spec_direction *) (a->data.v); + + d->active = false; + d->x = rt->x; + d->y = rt->y; + d->duration = duration; + d->desc = strdup(desc); + d->keyword = strdup(keyword); + + return a; +} + +/* Moveblock wird zur Zeit nicht über Attribute, sondern ein Bitfeld + r->moveblock gemacht. Sollte umgestellt werden, wenn kompliziertere + Dinge gefragt werden. */ + +/********************/ +/* at_moveblock */ +/********************/ +void a_initmoveblock(attrib * a) +{ + a->data.v = calloc(1, sizeof(moveblock)); +} + +int a_readmoveblock(attrib * a, void *owner, struct storage *store) +{ + moveblock *m = (moveblock *) (a->data.v); + int i; + + i = store->r_int(store); + m->dir = (direction_t) i; + return AT_READ_OK; +} + +void +a_writemoveblock(const attrib * a, const void *owner, struct storage *store) +{ + moveblock *m = (moveblock *) (a->data.v); + store->w_int(store, (int)m->dir); +} + +attrib_type at_moveblock = { + "moveblock", a_initmoveblock, NULL, NULL, a_writemoveblock, a_readmoveblock +}; + +#define coor_hashkey(x, y) (unsigned int)((x<<16) + y) +#define RMAXHASH MAXREGIONS +static region *regionhash[RMAXHASH]; +static int dummy_data; +static region *dummy_ptr = (region *) & dummy_data; /* a funny hack */ + +typedef struct uidhashentry { + unsigned int uid; + region *r; +} uidhashentry; +static uidhashentry uidhash[MAXREGIONS]; + +struct region *findregionbyid(unsigned int uid) +{ + int key = uid % MAXREGIONS; + while (uidhash[key].uid != 0 && uidhash[key].uid != uid) + ++key; + return uidhash[key].r; +} + +#define DELMARKER dummy_ptr + +static void unhash_uid(region * r) +{ + int key = r->uid % MAXREGIONS; + assert(r->uid); + while (uidhash[key].uid != 0 && uidhash[key].uid != r->uid) + ++key; + assert(uidhash[key].r == r); + uidhash[key].r = NULL; +} + +static void hash_uid(region * r) +{ + unsigned int uid = r->uid; + for (;;) { + if (uid != 0) { + int key = uid % MAXREGIONS; + while (uidhash[key].uid != 0 && uidhash[key].uid != uid) + ++key; + if (uidhash[key].uid == 0) { + uidhash[key].uid = uid; + uidhash[key].r = r; + break; + } + assert(uidhash[key].r != r || !"duplicate registration"); + } + r->uid = uid = rng_int(); + } +} + +#define HASH_STATISTICS 1 +#if HASH_STATISTICS +static int hash_requests; +static int hash_misses; +#endif + +bool pnormalize(int *x, int *y, const plane * pl) +{ + if (pl) { + if (x) { + int width = pl->maxx - pl->minx + 1; + int nx = *x - pl->minx; + nx = (nx > 0) ? nx : (width - (-nx) % width); + *x = nx % width + pl->minx; + } + if (y) { + int height = pl->maxy - pl->miny + 1; + int ny = *y - pl->miny; + ny = (ny > 0) ? ny : (height - (-ny) % height); + *y = ny % height + pl->miny; + } + } + return false; /* TBD */ +} + +static region *rfindhash(int x, int y) +{ + unsigned int rid = coor_hashkey(x, y); + int key = HASH1(rid, RMAXHASH), gk = HASH2(rid, RMAXHASH); +#if HASH_STATISTICS + ++hash_requests; +#endif + while (regionhash[key] != NULL && (regionhash[key] == DELMARKER + || regionhash[key]->x != x || regionhash[key]->y != y)) { + key = (key + gk) % RMAXHASH; +#if HASH_STATISTICS + ++hash_misses; +#endif + } + return regionhash[key]; +} + +void rhash(region * r) +{ + unsigned int rid = coor_hashkey(r->x, r->y); + int key = HASH1(rid, RMAXHASH), gk = HASH2(rid, RMAXHASH); + while (regionhash[key] != NULL && regionhash[key] != DELMARKER + && regionhash[key] != r) { + key = (key + gk) % RMAXHASH; + } + assert(regionhash[key] != r || !"trying to add the same region twice"); + regionhash[key] = r; +} + +void runhash(region * r) +{ + unsigned int rid = coor_hashkey(r->x, r->y); + int key = HASH1(rid, RMAXHASH), gk = HASH2(rid, RMAXHASH); + +#ifdef FAST_CONNECT + int d, di; + for (d = 0, di = MAXDIRECTIONS / 2; d != MAXDIRECTIONS; ++d, ++di) { + region *rc = r->connect[d]; + if (rc != NULL) { + if (di >= MAXDIRECTIONS) + di -= MAXDIRECTIONS; + rc->connect[di] = NULL; + r->connect[d] = NULL; + } + } +#endif + while (regionhash[key] != NULL && regionhash[key] != r) { + key = (key + gk) % RMAXHASH; + } + assert(regionhash[key] == r || !"trying to remove a unit that is not hashed"); + regionhash[key] = DELMARKER; +} + +region *r_connect(const region * r, direction_t dir) +{ + region *result; + int x, y; +#ifdef FAST_CONNECT + region *rmodify = (region *) r; + assert(dir >= 0 && dir < MAXDIRECTIONS); + if (r->connect[dir]) + return r->connect[dir]; +#endif + assert(dir < MAXDIRECTIONS); + x = r->x + delta_x[dir]; + y = r->y + delta_y[dir]; + pnormalize(&x, &y, rplane(r)); + result = rfindhash(x, y); +#ifdef FAST_CONNECT + if (result) { + rmodify->connect[dir] = result; + result->connect[back[dir]] = rmodify; + } +#endif + return result; +} + +region *findregion(int x, int y) +{ + return rfindhash(x, y); +} + +/* Contributed by Hubert Mackenberg. Thanks. + * x und y Abstand zwischen x1 und x2 berechnen + */ +static int koor_distance_orig(int x1, int y1, int x2, int y2) +{ + int dx = x1 - x2; + int dy = y1 - y2; + + /* Bei negativem dy am Ursprung spiegeln, das veraendert + * den Abstand nicht + */ + if (dy < 0) { + dy = -dy; + dx = -dx; + } + + /* + * dy ist jetzt >=0, fuer dx sind 3 Faelle zu untescheiden + */ + if (dx >= 0) { + int result = dx + dy; + return result; + } else if (-dx >= dy) { + int result = -dx; + return result; + } else { + return dy; + } +} + +static int +koor_distance_wrap_xy(int x1, int y1, int x2, int y2, int width, int height) +{ + int dx = x1 - x2; + int dy = y1 - y2; + int result, dist; + int mindist = MIN(width, height) >> 1; + + /* Bei negativem dy am Ursprung spiegeln, das veraendert + * den Abstand nicht + */ + if (dy < 0) { + dy = -dy; + dx = -dx; + } + if (dx < 0) { + dx = width + dx; + } + /* dx,dy is now pointing northeast */ + result = dx + dy; + if (result <= mindist) + return result; + + dist = (width - dx) + (height - dy); /* southwest */ + if (dist >= 0 && dist < result) { + result = dist; + if (result <= mindist) + return result; + } + dist = MAX(dx, height - dy); + if (dist >= 0 && dist < result) { + result = dist; + if (result <= mindist) + return result; + } + dist = MAX(width - dx, dy); + if (dist >= 0 && dist < result) + result = dist; + return result; +} + +int koor_distance(int x1, int y1, int x2, int y2) +{ + const plane *p1 = findplane(x1, y1); + const plane *p2 = findplane(x2, y2); + if (p1 != p2) + return INT_MAX; + else { + int width = plane_width(p1); + int height = plane_height(p1); + if (width && height) { + return koor_distance_wrap_xy(x1, y1, x2, y2, width, height); + } else { + return koor_distance_orig(x1, y1, x2, y2); + } + } +} + +int distance(const region * r1, const region * r2) +{ + return koor_distance(r1->x, r1->y, r2->x, r2->y); +} + +static direction_t +koor_reldirection(int ax, int ay, int bx, int by, const struct plane *pl) +{ + int dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + int x = ax + delta_x[dir]; + int y = ay + delta_y[dir]; + pnormalize(&x, &y, pl); + if (bx == x && by == y) + return (direction_t)dir; + } + return NODIRECTION; +} + +spec_direction *special_direction(const region * from, const region * to) +{ + const attrib *a = a_findc(from->attribs, &at_direction); + + while (a != NULL && a->type == &at_direction) { + spec_direction *sd = (spec_direction *) a->data.v; + if (sd->x == to->x && sd->y == to->y) + return sd; + a = a->next; + } + return NULL; +} + +direction_t reldirection(const region * from, const region * to) +{ + plane *pl = rplane(from); + if (pl == rplane(to)) { + direction_t dir = koor_reldirection(from->x, from->y, to->x, to->y, pl); + + if (dir == NODIRECTION) { + spec_direction *sd = special_direction(from, to); + if (sd != NULL && sd->active) + return D_SPECIAL; + } + return dir; + } + return NODIRECTION; +} + +void free_regionlist(region_list * rl) +{ + while (rl) { + region_list *rl2 = rl->next; + free(rl); + rl = rl2; + } +} + +void add_regionlist(region_list ** rl, region * r) +{ + region_list *rl2 = (region_list *) malloc(sizeof(region_list)); + + rl2->data = r; + rl2->next = *rl; + + *rl = rl2; +} + +/********************/ +/* at_horseluck */ +/********************/ +attrib_type at_horseluck = { + "horseluck", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ, + ATF_UNIQUE +}; + +/**********************/ +/* at_peasantluck */ +/**********************/ +attrib_type at_peasantluck = { + "peasantluck", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ, + ATF_UNIQUE +}; + +/*********************/ +/* at_chaoscount */ +/*********************/ +attrib_type at_chaoscount = { + "chaoscount", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + a_writeint, + a_readint, + ATF_UNIQUE +}; + +/*********************/ +/* at_deathcount */ +/*********************/ +attrib_type at_deathcount = { + "deathcount", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + a_writeint, + a_readint, + ATF_UNIQUE +}; + +/*********************/ +/* at_woodcount */ +/*********************/ +attrib_type at_woodcount = { + "woodcount", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + a_readint, + ATF_UNIQUE +}; + +/*********************/ +/* at_travelunit */ +/*********************/ +attrib_type at_travelunit = { + "travelunit", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ +}; + +void rsetroad(region * r, direction_t d, short val) +{ + connection *b; + region *r2 = rconnect(r, d); + + if (!r2) { + return; + } + b = get_borders(r, r2); + while (b && b->type != &bt_road) { + b = b->next; + } + if (!b) { + if (!val) return; + b = new_border(&bt_road, r, r2); + } + if (r == b->from) { + b->data.sa[0] = val; + } else { + b->data.sa[1] = val; + } +} + +short rroad(const region * r, direction_t d) +{ + connection *b; + region *r2 = rconnect(r, d); + + if (!r2) { + return 0; + } + b = get_borders(r, r2); + while (b && b->type != &bt_road) { + b = b->next; + } + if (!b) { + return 0; + } + + return (r == b->from) ? b->data.sa[0] : b->data.sa[1]; +} + +bool r_isforest(const region * r) +{ + if (fval(r->terrain, FOREST_REGION)) { + /* needs to be covered with at leas 48% trees */ + int mincover = (int)(r->terrain->size * 0.48); + int trees = rtrees(r, 2) + rtrees(r, 1); + return (trees * TREESIZE >= mincover); + } + return false; +} + +bool is_coastregion(region * r) +{ + direction_t i; + int res = 0; + + for (i = 0; !res && i < MAXDIRECTIONS; i++) { + region *rn = rconnect(r, i); + if (rn && fval(rn->terrain, SEA_REGION)) + res++; + } + return res!=0; +} + +int rpeasants(const region * r) +{ + return ((r)->land ? (r)->land->peasants : 0); +} + +void rsetpeasants(region * r, int value) +{ + ((r)->land ? ((r)->land->peasants = + (value)) : (assert((value) >= 0), (value)), 0); +} + +int rmoney(const region * r) +{ + return ((r)->land ? (r)->land->money : 0); +} + +void rsethorses(const region * r, int value) +{ + assert(value >= 0); + if (r->land) + r->land->horses = value; +} + +int rhorses(const region * r) +{ + return r->land ? r->land->horses : 0; +} + +void rsetmoney(region * r, int value) +{ + ((r)->land ? ((r)->land->money = + (value)) : (assert((value) >= 0), (value)), 0); +} + +void r_setdemand(region * r, const luxury_type * ltype, int value) +{ + struct demand *d, **dp = &r->land->demands; + + if (ltype == NULL) + return; + + while (*dp && (*dp)->type != ltype) + dp = &(*dp)->next; + d = *dp; + if (!d) { + d = *dp = malloc(sizeof(struct demand)); + d->next = NULL; + d->type = ltype; + } + d->value = value; +} + +const item_type *r_luxury(region * r) +{ + struct demand *dmd; + if (r->land) { + if (!r->land->demands) { + fix_demand(r); + } + for (dmd = r->land->demands; dmd; dmd = dmd->next) { + if (dmd->value == 0) + return dmd->type->itype; + } + } + return NULL; +} + +int r_demand(const region * r, const luxury_type * ltype) +{ + struct demand *d = r->land->demands; + while (d && d->type != ltype) + d = d->next; + if (!d) + return -1; + return d->value; +} + +const char *rname(const region * r, const struct locale *lang) +{ + if (r->land && r->land->name) { + return r->land->name; + } + return LOC(lang, terrain_name(r)); +} + +int rtrees(const region * r, int ageclass) +{ + return ((r)->land ? (r)->land->trees[ageclass] : 0); +} + +int rsettrees(const region * r, int ageclass, int value) +{ + if (!r->land) + assert(value == 0); + else { + assert(value >= 0); + return r->land->trees[ageclass] = value; + } + return 0; +} + +static region *last; + +static unsigned int max_index = 0; + +region *new_region(int x, int y, struct plane *pl, unsigned int uid) +{ + region *r; + + pnormalize(&x, &y, pl); + r = rfindhash(x, y); + + if (r) { + log_error("duplicate region discovered: %s(%d,%d)\n", regionname(r, NULL), x, y); + if (r->units) + log_error("duplicate region contains units\n"); + return r; + } + r = calloc(1, sizeof(region)); + r->x = x; + r->y = y; + r->uid = uid; + r->age = 1; + r->_plane = pl; + rhash(r); + hash_uid(r); + if (last) + addlist(&last, r); + else + addlist(®ions, r); + last = r; + assert(r->next == NULL); + r->index = ++max_index; + return r; +} + +static region *deleted_regions; + +void remove_region(region ** rlist, region * r) +{ + + while (r->units) { + unit *u = r->units; + i_freeall(&u->items); + remove_unit(&r->units, u); + } + + runhash(r); + unhash_uid(r); + while (*rlist && *rlist != r) + rlist = &(*rlist)->next; + assert(*rlist == r); + *rlist = r->next; + r->next = deleted_regions; + deleted_regions = r; +} + +static void freeland(land_region * lr) +{ + while (lr->demands) { + struct demand *d = lr->demands; + lr->demands = d->next; + free(d); + } + if (lr->name) + free(lr->name); + free(lr); +} + +void region_setresource(region * r, const resource_type * rtype, int value) +{ + rawmaterial *rm = r->resources; + while (rm) { + if (rm->type->rtype == rtype) { + rm->amount = value; + break; + } + rm = rm->next; + } + if (!rm) { + if (rtype == rt_find("money")) + rsetmoney(r, value); + else if (rtype == rt_find("peasant")) + rsetpeasants(r, value); + else if (rtype == rt_find("horse")) + rsethorses(r, value); + else { + int i; + for (i = 0; r->terrain->production[i].type; ++i) { + const terrain_production *production = r->terrain->production + i; + if (production->type==rtype) { + add_resource(r, 1, value, dice_rand(production->divisor), rtype); + break; + } + } + } + } +} + +int region_getresource(const region * r, const resource_type * rtype) +{ + const rawmaterial *rm; + for (rm = r->resources; rm; rm = rm->next) { + if (rm->type->rtype == rtype) { + return rm->amount; + } + } + if (rtype == rt_find("money")) + return rmoney(r); + if (rtype == rt_find("horse")) + return rhorses(r); + if (rtype == rt_find("peasant")) + return rpeasants(r); + return 0; +} + +void free_region(region * r) +{ + if (last == r) + last = NULL; + free(r->display); + if (r->land) + freeland(r->land); + + if (r->msgs) { + free_messagelist(r->msgs); + r->msgs = 0; + } + + while (r->individual_messages) { + struct individual_message *msg = r->individual_messages; + r->individual_messages = msg->next; + if (msg->msgs) + free_messagelist(msg->msgs); + free(msg); + } + + while (r->attribs) + a_remove(&r->attribs, r->attribs); + while (r->resources) { + rawmaterial *res = r->resources; + r->resources = res->next; + free(res); + } + + while (r->donations) { + donation *don = r->donations; + r->donations = don->next; + free(don); + } + + while (r->units) { + unit *u = r->units; + r->units = u->next; + uunhash(u); + free_unit(u); + free(u); + } + + while (r->buildings) { + building *b = r->buildings; + assert(b->region == r); + r->buildings = b->next; + bunhash(b); /* must be done here, because remove_building does it, and wasn't called */ + free_building(b); + } + + while (r->ships) { + ship *s = r->ships; + assert(s->region == r); + r->ships = s->next; + sunhash(s); + free_ship(s); + } + + free(r); +} + +void free_regions(void) +{ + memset(uidhash, 0, sizeof(uidhash)); + while (deleted_regions) { + region *r = deleted_regions; + deleted_regions = r->next; + free_region(r); + } + while (regions) { + region *r = regions; + regions = r->next; + runhash(r); + free_region(r); + } + max_index = 0; + last = NULL; +} + +/** creates a name for a region + * TODO: Make vowels XML-configurable and allow non-ascii characters again. + * - that will probably require a wchar_t * string to pick from. + */ +static char *makename(void) +{ + int s, v, k, e, p = 0, x = 0; + size_t nk, ne, nv, ns; + static char name[16]; + const char *kons = "bcdfghklmnprstvwz", + *start = "bcdgtskpvfr", + *end = "nlrdst", + *vowels = "aaaaaaaaaaaeeeeeeeeeeeeiiiiiiiiiiioooooooooooouuuuuuuuuuyy"; + + /* const char * vowels_latin1 = "aaaaaaaaaàâeeeeeeeeeéèêiiiiiiiiiíîoooooooooóòôuuuuuuuuuúyy"; */ + + nk = strlen(kons); + ne = strlen(end); + nv = strlen(vowels); + ns = strlen(start); + + for (s = rng_int() % 3 + 2; s > 0; s--) { + if (x > 0) { + k = rng_int() % (int)nk; + name[p] = kons[k]; + p++; + } else { + k = rng_int() % (int)ns; + name[p] = start[k]; + p++; + } + v = rng_int() % (int)nv; + name[p] = vowels[v]; + p++; + if (rng_int() % 3 == 2 || s == 1) { + e = rng_int() % (int)ne; + name[p] = end[e]; + p++; + x = 1; + } else + x = 0; + } + name[p] = '\0'; + name[0] = (char)toupper(name[0]); + return name; +} + +void setluxuries(region * r, const luxury_type * sale) +{ + const luxury_type *ltype; + + assert(r->land); + + if (r->land->demands) + freelist(r->land->demands); + + for (ltype = luxurytypes; ltype; ltype = ltype->next) { + struct demand *dmd = malloc(sizeof(struct demand)); + dmd->type = ltype; + if (ltype != sale) + dmd->value = 1 + rng_int() % 5; + else + dmd->value = 0; + dmd->next = r->land->demands; + r->land->demands = dmd; + } +} + +void terraform_region(region * r, const terrain_type * terrain) +{ + /* Resourcen, die nicht mehr vorkommen können, löschen */ + const terrain_type *oldterrain = r->terrain; + rawmaterial **lrm = &r->resources; + + assert(terrain); + + while (*lrm) { + rawmaterial *rm = *lrm; + const resource_type *rtype = NULL; + + if (terrain->production != NULL) { + int i; + for (i = 0; terrain->production[i].type; ++i) { + if (rm->type->rtype == terrain->production[i].type) { + rtype = rm->type->rtype; + break; + } + } + } + if (rtype == NULL) { + *lrm = rm->next; + free(rm); + } else { + lrm = &rm->next; + } + } + + r->terrain = terrain; + terraform_resources(r); + + if (!fval(terrain, LAND_REGION)) { + region_setinfo(r, NULL); + if (r->land != NULL) { + i_freeall(&r->land->items); + freeland(r->land); + r->land = NULL; + } + rsettrees(r, 0, 0); + rsettrees(r, 1, 0); + rsettrees(r, 2, 0); + rsethorses(r, 0); + rsetpeasants(r, 0); + rsetmoney(r, 0); + freset(r, RF_ENCOUNTER); + freset(r, RF_MALLORN); + /* Beschreibung und Namen löschen */ + return; + } + + if (r->land) { + int d; + for (d=0;d!=MAXDIRECTIONS;++d) { + rsetroad(r, d, 0); + } + i_freeall(&r->land->items); + } else { + static struct surround { + struct surround *next; + const luxury_type *type; + int value; + } *trash = NULL, *nb = NULL; + const luxury_type *ltype = NULL; + direction_t d; + int mnr = 0; + + r->land = calloc(1, sizeof(land_region)); + r->land->ownership = NULL; + region_set_morale(r, MORALE_DEFAULT, -1); + region_setname(r, makename()); + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *nr = rconnect(r, d); + if (nr && nr->land) { + struct demand *sale = r->land->demands; + while (sale && sale->value != 0) + sale = sale->next; + if (sale) { + struct surround *sr = nb; + while (sr && sr->type != sale->type) + sr = sr->next; + if (!sr) { + if (trash) { + sr = trash; + trash = trash->next; + } else { + sr = calloc(1, sizeof(struct surround)); + } + sr->next = nb; + sr->type = sale->type; + sr->value = 1; + nb = sr; + } else + sr->value++; + ++mnr; + } + } + } + if (!nb) { + int i = get_maxluxuries(); + if (i > 0) { + i = rng_int() % i; + ltype = luxurytypes; + while (i--) + ltype = ltype->next; + } + } else { + int i = rng_int() % mnr; + struct surround *srd = nb; + while (i > srd->value) { + i -= srd->value; + srd = srd->next; + } + if (srd->type) + setluxuries(r, srd->type); + while (srd->next != NULL) + srd = srd->next; + srd->next = trash; + trash = nb; + nb = NULL; + } + } + + if (fval(terrain, LAND_REGION)) { + const item_type *itype = NULL; + char equip_hash[64]; + + /* TODO: put the equipment in struct terrain, faster */ + sprintf(equip_hash, "terrain_%s", terrain->_name); + equip_items(&r->land->items, get_equipment(equip_hash)); + + if (r->terrain->herbs) { + int len = 0; + while (r->terrain->herbs[len]) + ++len; + if (len) + itype = r->terrain->herbs[rng_int() % len]; + } + if (itype != NULL) { + rsetherbtype(r, itype); + rsetherbs(r, (short)(50 + rng_int() % 31)); + } else { + rsetherbtype(r, NULL); + } + if (oldterrain == NULL || !fval(oldterrain, LAND_REGION)) { + if (rng_int() % 100 < 3) + fset(r, RF_MALLORN); + else + freset(r, RF_MALLORN); + if (rng_int() % 100 < ENCCHANCE) { + fset(r, RF_ENCOUNTER); + } + } + } + + if (oldterrain == NULL || terrain->size != oldterrain->size) { + if (terrain == newterrain(T_PLAIN)) { + rsethorses(r, rng_int() % (terrain->size / 50)); + if (rng_int() % 100 < 40) { + rsettrees(r, 2, terrain->size * (30 + rng_int() % 40) / 1000); + } + } else if (chance(0.2)) { + rsettrees(r, 2, terrain->size * (30 + rng_int() % 40) / 1000); + } else { + rsettrees(r, 2, 0); + } + rsettrees(r, 1, rtrees(r, 2) / 4); + rsettrees(r, 0, rtrees(r, 2) / 8); + + if (!fval(r, RF_CHAOTIC)) { + int peasants; + peasants = (maxworkingpeasants(r) * (20 + dice_rand("6d10"))) / 100; + rsetpeasants(r, MAX(100, peasants)); + rsetmoney(r, rpeasants(r) * ((wage(r, NULL, NULL, + INT_MAX) + 1) + rng_int() % 5)); + } + } +} + +/** ENNO: + * ich denke, das das hier nicht sein sollte. + * statt dessen sollte ein attribut an der region sein, das das erledigt, + * egal ob durch den spell oder anderes angelegt. + **/ +#include "curse.h" +int production(const region * r) +{ + /* muß rterrain(r) sein, nicht rterrain() wegen rekursion */ + int p = r->terrain->size / MAXPEASANTS_PER_AREA; + if (curse_active(get_curse(r->attribs, ct_find("drought")))) + p /= 2; + + return p; +} + +int resolve_region_coor(variant id, void *address) +{ + region *r = findregion(id.sa[0], id.sa[1]); + if (r) { + *(region **) address = r; + return 0; + } + *(region **) address = NULL; + return -1; +} + +int resolve_region_id(variant id, void *address) +{ + region *r = NULL; + if (id.i != 0) { + r = findregionbyid((unsigned int)id.i); + if (r == NULL) { + *(region **) address = NULL; + return -1; + } + } + *(region **) address = r; + return 0; +} + +variant read_region_reference(struct storage * store) +{ + variant result; + if (store->version < UIDHASH_VERSION) { + result.sa[0] = (short)store->r_int(store); + result.sa[1] = (short)store->r_int(store); + } else { + result.i = store->r_int(store); + } + return result; +} + +void write_region_reference(const region * r, struct storage *store) +{ + if (r) { + store->w_int(store, r->uid); + } else { + store->w_int(store, 0); + } +} + +struct message_list *r_getmessages(const struct region *r, + const struct faction *viewer) +{ + struct individual_message *imsg = r->individual_messages; + while (imsg && (imsg)->viewer != viewer) + imsg = imsg->next; + if (imsg) + return imsg->msgs; + return NULL; +} + +struct message *r_addmessage(struct region *r, const struct faction *viewer, + struct message *msg) +{ + assert(r); + if (viewer) { + struct individual_message *imsg; + imsg = r->individual_messages; + while (imsg && imsg->viewer != viewer) + imsg = imsg->next; + if (imsg == NULL) { + imsg = malloc(sizeof(struct individual_message)); + imsg->next = r->individual_messages; + imsg->msgs = NULL; + r->individual_messages = imsg; + imsg->viewer = viewer; + } + return add_message(&imsg->msgs, msg); + } + return add_message(&r->msgs, msg); +} + +struct faction *region_get_owner(const struct region *r) +{ + assert(rule_region_owners()); + if (r->land && r->land->ownership) { + return r->land->ownership->owner; + } + return NULL; +} + +struct alliance *region_get_alliance(const struct region *r) +{ + assert(rule_region_owners()); + if (r->land && r->land->ownership) { + region_owner *own = r->land->ownership; + return own->owner ? own->owner->alliance : own->alliance; + } + return NULL; +} + +void region_set_owner(struct region *r, struct faction *owner, int turn) +{ + assert(rule_region_owners()); + if (r->land) { + if (!r->land->ownership) { + r->land->ownership = malloc(sizeof(region_owner)); + assert(region_get_morale(r) == MORALE_DEFAULT); + r->land->ownership->owner = NULL; + r->land->ownership->alliance = NULL; + r->land->ownership->flags = 0; + } + r->land->ownership->since_turn = turn; + r->land->ownership->morale_turn = turn; + assert(r->land->ownership->owner != owner); + r->land->ownership->owner = owner; + if (owner) { + r->land->ownership->alliance = owner->alliance; + } + } +} + +faction *update_owners(region * r) +{ + faction *f = NULL; + assert(rule_region_owners()); + if (r->land) { + building *bowner = largestbuilding(r, &cmp_current_owner, false); + building *blargest = largestbuilding(r, &cmp_taxes, false); + if (blargest) { + if (!bowner || bowner->size < blargest->size) { + /* region owners update? */ + unit *u = building_owner(blargest); + f = region_get_owner(r); + if (u == NULL) { + if (f) { + region_set_owner(r, NULL, turn); + r->land->ownership->flags |= OWNER_MOURNING; + f = NULL; + } + } else if (u->faction != f) { + if (!r->land->ownership) { + /* there has never been a prior owner */ + region_set_morale(r, MORALE_DEFAULT, turn); + } else { + alliance *al = region_get_alliance(r); + if (al && u->faction->alliance == al) { + int morale = MAX(0, r->land->morale - MORALE_TRANSFER); + region_set_morale(r, morale, turn); + } else { + region_set_morale(r, MORALE_TAKEOVER, turn); + if (f) { + r->land->ownership->flags |= OWNER_MOURNING; + } + } + } + region_set_owner(r, u->faction, turn); + f = u->faction; + } + } + } else if (r->land->ownership && r->land->ownership->owner) { + r->land->ownership->flags |= OWNER_MOURNING; + region_set_owner(r, NULL, turn); + f = NULL; + } + } + return f; +} + +void region_setinfo(struct region *r, const char *info) +{ + free(r->display); + r->display = info ? strdup(info) : 0; +} + +const char *region_getinfo(const region * r) +{ + return r->display ? r->display : ""; +} + +void region_setname(struct region *r, const char *name) +{ + if (r->land) { + free(r->land->name); + r->land->name = name ? strdup(name) : 0; + } +} + +const char *region_getname(const region * r) +{ + if (r->land && r->land->name) { + return r->land->name; + } + return ""; +} + +int region_get_morale(const region * r) +{ + if (r->land) { + assert(r->land->morale >= 0 && r->land->morale <= MORALE_MAX); + return r->land->morale; + } + return -1; +} + +void region_set_morale(region * r, int morale, int turn) +{ + if (r->land) { + r->land->morale = (short)morale; + if (turn >= 0 && r->land->ownership) { + r->land->ownership->morale_turn = turn; + } + assert(r->land->morale >= 0 && r->land->morale <= MORALE_MAX); + } +} + +void get_neighbours(const region * r, region ** list) +{ + int dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + list[dir] = rconnect(r, (direction_t)dir); + } +} + +int owner_change(const region * r) +{ + if (r->land && r->land->ownership) { + return r->land->ownership->since_turn; + } + return INT_MIN; +} + +bool is_mourning(const region * r, int in_turn) +{ + int change = owner_change(r); + return (change == in_turn - 1 + && (r->land->ownership->flags & OWNER_MOURNING)); +} diff --git a/core/src/kernel/region.h b/core/src/kernel/region.h new file mode 100644 index 000000000..2cdd3dd95 --- /dev/null +++ b/core/src/kernel/region.h @@ -0,0 +1,307 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_REGION +#define H_KRNL_REGION +#ifdef __cplusplus +extern "C" { +#endif + +#include "types.h" + +/* FAST_CONNECT: regions are directly connected to neighbours, saves doing + a hash-access each time a neighbour is needed */ +#define FAST_CONNECT + +#define RF_CHAOTIC (1<<0) +#define RF_MALLORN (1<<1) +#define RF_BLOCKED (1<<2) + +#define RF_BLOCK_NORTHWEST (1<<3) +#define RF_BLOCK_NORTHEAST (1<<4) +#define RF_BLOCK_EAST (1<<5) +#define RF_BLOCK_SOUTHEAST (1<<6) +#define RF_BLOCK_SOUTHWEST (1<<7) +#define RF_BLOCK_WEST (1<<8) + +#define RF_ENCOUNTER (1<<9) +#define RF_MIGRATION (1<<10) +#define RF_UNUSED_1 (1<<11) +#define RF_ORCIFIED (1<<12) +#define RF_CURSED (1<<13) + + /* debug flags */ +#define RF_COMBATDEBUG (1<<14) +#define RF_MAPPER_HIGHLIGHT (1<<14) /* only used by mapper, not stored */ +#define RF_LIGHTHOUSE (1<<15) /* this region may contain a lighthouse */ + +#define RF_SELECT (1<<17) +#define RF_MARK (1<<18) + +/* flags that speed up attribute access: */ +#define RF_TRAVELUNIT (1<<19) +#define RF_GUARDED (1<<20) + +#define RF_ALL 0xFFFFFF + +#define RF_SAVEMASK (RF_GUARDED|RF_CHAOTIC|RF_MALLORN|RF_BLOCKED|RF_BLOCK_NORTHWEST|RF_BLOCK_NORTHEAST|RF_BLOCK_EAST|RF_BLOCK_SOUTHEAST|RF_BLOCK_SOUTHWEST|RF_BLOCK_WEST|RF_ENCOUNTER|RF_ORCIFIED) + struct message; + struct message_list; + struct rawmaterial; + struct donation; + struct item; + +#define MORALE_TAX_FACTOR 0.005 /* 0.5% tax per point of morale */ +#define MORALE_MAX 10 /* Maximum morale allowed */ +#define MORALE_DEFAULT 1 /* Morale of peasants when they are conquered for the first time */ +#define MORALE_TAKEOVER 0 /* Morale of peasants after they lose their lord */ +#define MORALE_COOLDOWN 2 /* minimum cooldown before a morale change occurs */ +#define MORALE_AVERAGE 6 /* default average time for morale to change */ +#define MORALE_TRANSFER 2 /* points of morale lost when GIVE COMMAND */ + +#define OWNER_MOURNING 0x01 + typedef struct region_owner { + struct faction *owner; + struct alliance *alliance; + int since_turn; /* turn the region changed owners */ + int morale_turn; /* turn when morale has changed most recently */ + unsigned int flags; + } region_owner; + + typedef struct demand { + struct demand *next; + const struct luxury_type *type; + int value; + } demand; + + typedef struct land_region { + char *name; + /* TODO: demand kann nach Konvertierung entfernt werden. */ + demand *demands; + const struct item_type *herbtype; + short herbs; + short morale; + int trees[3]; /* 0 -> seeds, 1 -> shoots, 2 -> trees */ + int horses; + int peasants; + int newpeasants; + int money; + struct item *items; /* items that can be claimed */ + struct region_owner *ownership; + } land_region; + + typedef struct donation { + struct donation *next; + struct faction *f1, *f2; + int amount; + } donation; + + typedef struct region { + struct region *next; + struct land_region *land; + struct unit *units; + struct ship *ships; + struct building *buildings; + unsigned int index; + /* an ascending number, to improve the speed of determining the interval in + which a faction has its units. See the implementations of firstregion + and lastregion */ + unsigned int uid; /* a unique id */ + int x, y; + struct plane *_plane; /* to access, use rplane(r) */ + char *display; + unsigned int flags; + unsigned short age; + struct message_list *msgs; + struct individual_message { + struct individual_message *next; + const struct faction *viewer; + struct message_list *msgs; + } *individual_messages; + struct attrib *attribs; + struct donation *donations; + const struct terrain_type *terrain; + struct rawmaterial *resources; +#ifdef FAST_CONNECT + struct region *connect[MAXDIRECTIONS]; /* use rconnect(r, dir) to access */ +#endif + } region; + + extern struct region *regions; + + typedef struct region_list { + struct region_list *next; + struct region *data; + } region_list; + + struct message_list *r_getmessages(const struct region *r, + const struct faction *viewer); + struct message *r_addmessage(struct region *r, const struct faction *viewer, + struct message *msg); + + typedef struct spec_direction { + int x, y; + int duration; + bool active; + char *desc; + char *keyword; + } spec_direction; + + typedef struct { + direction_t dir; + } moveblock; + +#define reg_hashkey(r) (r->index) + + int distance(const struct region *, const struct region *); + int koor_distance(int ax, int ay, int bx, int by); + direction_t reldirection(const struct region *from, const struct region *to); + struct region *findregion(int x, int y); + struct region *findregionbyid(unsigned int uid); + + extern struct attrib_type at_direction; + extern struct attrib_type at_moveblock; + extern struct attrib_type at_peasantluck; + extern struct attrib_type at_horseluck; + extern struct attrib_type at_chaoscount; + extern struct attrib_type at_woodcount; + extern struct attrib_type at_deathcount; + extern struct attrib_type at_travelunit; + + void initrhash(void); + void rhash(struct region *r); + void runhash(struct region *r); + + void free_regionlist(region_list * rl); + void add_regionlist(region_list ** rl, struct region *r); + + struct region *find_special_direction(const struct region *r, + const char *token, const struct locale *lang); + void register_special_direction(const char *name); + struct spec_direction *special_direction(const region * from, + const region * to); + struct attrib *create_special_direction(struct region *r, struct region *rt, + int duration, const char *desc, const char *keyword); + + int deathcount(const struct region *r); + int chaoscount(const struct region *r); + + void deathcounts(struct region *r, int delta); + void chaoscounts(struct region *r, int delta); + + void setluxuries(struct region *r, const struct luxury_type *sale); + int get_maxluxuries(void); + + short rroad(const struct region *r, direction_t d); + void rsetroad(struct region *r, direction_t d, short value); + + bool is_coastregion(struct region *r); + + int rtrees(const struct region *r, int ageclass); + enum { + TREE_SEED = 0, + TREE_SAPLING = 1, + TREE_TREE = 2 + }; + + int rsettrees(const struct region *r, int ageclass, int value); + + int rpeasants(const struct region *r); + void rsetpeasants(struct region *r, int value); + int rmoney(const struct region *r); + void rsetmoney(struct region *r, int value); + int rhorses(const struct region *r); + void rsethorses(const struct region *r, int value); + +#define rbuildings(r) ((r)->buildings) + +#define rherbtype(r) ((r)->land?(r)->land->herbtype:0) +#define rsetherbtype(r, value) if ((r)->land) (r)->land->herbtype=(value) + +#define rherbs(r) ((r)->land?(r)->land->herbs:0) +#define rsetherbs(r, value) if ((r)->land) ((r)->land->herbs=(short)(value)) + + bool r_isforest(const struct region *r); + +#define rterrain(r) (oldterrain((r)->terrain)) +#define rsetterrain(r, t) ((r)->terrain = newterrain(t)) + + const char *rname(const struct region *r, const struct locale *lang); + +#define rplane(r) getplane(r) + + void r_setdemand(struct region *r, const struct luxury_type *ltype, + int value); + int r_demand(const struct region *r, const struct luxury_type *ltype); + + const char *write_regionname(const struct region *r, const struct faction *f, + char *buffer, size_t size); + + struct region *new_region(int x, int y, struct plane *pl, unsigned int uid); + void remove_region(region ** rlist, region * r); + void terraform_region(struct region *r, const struct terrain_type *terrain); + bool pnormalize(int *x, int *y, const struct plane *pl); + + extern const int delta_x[MAXDIRECTIONS]; + extern const int delta_y[MAXDIRECTIONS]; + direction_t dir_invert(direction_t dir); + int production(const struct region *r); + + void region_set_owner(struct region *r, struct faction *owner, int turn); + struct faction *region_get_owner(const struct region *r); + struct alliance *region_get_alliance(const struct region *r); + + struct region *r_connect(const struct region *, direction_t dir); +#ifdef FAST_CONNECT +# define rconnect(r, dir) ((r)->connect[dir]?(r)->connect[dir]:r_connect(r, (direction_t)dir)) +#else +# define rconnect(r, dir) r_connect(r, (direction_t)dir) +#endif + + void free_regions(void); + + int region_get_morale(const region * r); + void region_set_morale(region * r, int morale, int turn); + + void write_region_reference(const struct region *r, struct storage *store); + variant read_region_reference(struct storage *store); + int resolve_region_coor(variant id, void *address); + int resolve_region_id(variant id, void *address); +#define RESOLVE_REGION(version) ((version + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include + +struct message; + +/* TODO: this could be nicer and faster + * call with MSG(("msg_name", "param", p), buf, faction). */ +#define MSG(makemsg, buf, size, loc, ud) { struct message * m = msg_message makemsg; nr_render(m, loc, buf, size, ud); msg_release(m); } +#define RENDER(f, buf, size, mcreate) { struct message * m = msg_message mcreate; nr_render(m, f->locale, buf, size, f); msg_release(m); } diff --git a/core/src/kernel/reports.c b/core/src/kernel/reports.c new file mode 100644 index 000000000..38c6fddae --- /dev/null +++ b/core/src/kernel/reports.c @@ -0,0 +1,2410 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "reports.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include + +/* attributes includes */ +#include +#include +#include + +bool nocr = false; +bool nonr = false; +bool noreports = false; + +const char *visibility[] = { + "none", + "neighbour", + "lighthouse", + "travel", + "far", + "unit", + "battle" +}; + +const char *coasts[MAXDIRECTIONS] = { + "coast::nw", + "coast::ne", + "coast::e", + "coast::se", + "coast::sw", + "coast::w" +}; + +static char *groupid(const struct group *g, const struct faction *f) +{ + typedef char name[OBJECTIDSIZE + 1]; + static name idbuf[8]; + static int nextbuf = 0; + char *buf = idbuf[(++nextbuf) % 8]; + sprintf(buf, "%s (%s)", g->name, factionid(f)); + return buf; +} + +const char *combatstatus[] = { + "status_aggressive", "status_front", + "status_rear", "status_defensive", + "status_avoid", "status_flee" +}; + +const char *report_kampfstatus(const unit * u, const struct locale *lang) +{ + static char fsbuf[64]; + + strlcpy(fsbuf, LOC(lang, combatstatus[u->status]), sizeof(fsbuf)); + if (fval(u, UFL_NOAID)) { + strcat(fsbuf, ", "); + strcat(fsbuf, LOC(lang, "status_noaid")); + } + + return fsbuf; +} + +const char *hp_status(const unit * u) +{ + double p = (double)((double)u->hp / (double)(u->number * unit_max_hp(u))); + + if (p > 2.00) + return mkname("damage", "critical"); + if (p > 1.50) + return mkname("damage", "heavily"); + if (p < 0.50) + return mkname("damage", "badly"); + if (p < 0.75) + return mkname("damage", "wounded"); + if (p < 0.99) + return mkname("damage", "exhausted"); + + return NULL; +} + +void +report_item(const unit * owner, const item * i, const faction * viewer, + const char **name, const char **basename, int *number, bool singular) +{ + assert(!owner || owner->number); + if (owner && owner->faction == viewer) { + if (name) + *name = + locale_string(viewer->locale, resourcename(i->type->rtype, + ((i->number != 1 && !singular) ? GR_PLURAL : 0))); + if (basename) + *basename = resourcename(i->type->rtype, 0); + if (number) + *number = i->number; + } else if (owner && i->type->rtype == r_silver) { + int pp = i->number / owner->number; + if (number) + *number = 1; + if (pp > 50000 && dragonrace(u_race(owner))) { + if (name) + *name = locale_string(viewer->locale, "dragonhoard"); + if (basename) + *basename = "dragonhoard"; + } else if (pp > 5000) { + if (name) + *name = locale_string(viewer->locale, "moneychest"); + if (basename) + *basename = "moneychest"; + } else if (pp > 500) { + if (name) + *name = locale_string(viewer->locale, "moneybag"); + if (basename) + *basename = "moneybag"; + } else { + if (number) + *number = 0; + if (name) + *name = NULL; + if (basename) + *basename = NULL; + } + } else { + if (name) + *name = + locale_string(viewer->locale, resourcename(i->type->rtype, + NMF_APPEARANCE | ((i->number != 1 && !singular) ? GR_PLURAL : 0))); + if (basename) + *basename = resourcename(i->type->rtype, NMF_APPEARANCE); + if (number) { + if (fval(i->type, ITF_HERB)) + *number = 1; + else + *number = i->number; + } + } +} + +int *nmrs = NULL; + +int update_nmrs(void) +{ + int i, newplayers = 0; + faction *f; + int turn = global.data_turn; + + if (nmrs == NULL) + nmrs = malloc(sizeof(int) * (NMRTimeout() + 1)); + for (i = 0; i <= NMRTimeout(); ++i) { + nmrs[i] = 0; + } + + for (f = factions; f; f = f->next) { + if (fval(f, FFL_ISNEW)) { + ++newplayers; + } else if (!is_monsters(f) && f->alive) { + int nmr = turn - f->lastorders + 1; + if (nmr < 0 || nmr > NMRTimeout()) { + log_error("faction %s has %d NMRS\n", factionid(f), nmr); + nmr = MAX(0, nmr); + nmr = MIN(nmr, NMRTimeout()); + } + ++nmrs[nmr]; + } + } + return newplayers; +} + +#define ORDERS_IN_NR 1 +static size_t buforder(char *bufp, size_t size, const order * ord, int mode) +{ + size_t tsize = 0; + int bytes; + + bytes = (int)strlcpy(bufp, ", \"", size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (mode < ORDERS_IN_NR) { + char *cmd = getcommand(ord); + bytes = (int)strlcpy(bufp, cmd, size); + free(cmd); + } else { + bytes = (int)strlcpy(bufp, "...", size); + } + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (size > 1) { + *bufp++ = '\"'; + --size; + } else { + WARN_STATIC_BUFFER(); + } + ++tsize; + + return tsize; +} + +/** create a report of a list of items to a non-owner. + * \param result: an array of size items. + * \param size: maximum number of items to return + * \param owner: the owner of the items, or NULL for faction::items etc. + * \param viewer: the faction looking at the items + */ +int +report_items(const item * items, item * result, int size, const unit * owner, + const faction * viewer) +{ + const item *itm; + int n = 0; /* number of results */ + + assert(owner == NULL || viewer != owner->faction + || !"not required for owner=viewer!"); + assert(size); + + for (itm = items; itm; itm = itm->next) { + item *ishow; + const char *ic; + + report_item(owner, itm, viewer, NULL, &ic, NULL, false); + if (ic && *ic) { + for (ishow = result; ishow != result + n; ++ishow) { + const char *sc; + + if (ishow->type == itm->type) + sc = ic; + else + report_item(owner, ishow, viewer, NULL, &sc, NULL, false); + if (sc == ic || strcmp(sc, ic) == 0) { + ishow->number += itm->number; + break; + } + } + if (ishow == result + n) { + if (n == size) { + log_error("too many items to report, increase buffer size.\n"); + return -1; + } + result[n].number = itm->number; + result[n].type = itm->type; + result[n].next = (n + 1 == size) ? NULL : result + n + 1; + ++n; + } + } + } + if (n > 0) + result[n - 1].next = NULL; + return n; +} + +static void +report_resource(resource_report * result, const char *name, int number, + int level) +{ + result->name = name; + result->number = number; + result->level = level; +} + +void report_race(const struct unit *u, const char **name, const char **illusion) +{ + if (illusion) { + const race *irace = u_irace(u); + if (irace && irace != u_race(u)) { + *illusion = irace->_name[0]; + } else { + *illusion = NULL; + } + } + if (name) { + *name = u_race(u)->_name[0]; + if (fval(u_race(u), RCF_SHAPESHIFTANY)) { + const char *str = get_racename(u->attribs); + if (str) + *name = str; + } + } +} + +void +report_building(const struct building *b, const char **name, + const char **illusion) +{ + static int init; + static const struct building_type *bt_illusion; + + if (name) { + *name = buildingtype(b->type, b, b->size); + } + if (illusion) { + *illusion = NULL; + + if (!init) { + bt_illusion = bt_find("illusioncastle"); + init = 1; + } + if (bt_illusion && b->type == bt_illusion) { + const attrib *a = a_findc(b->attribs, &at_icastle); + if (a != NULL) { + icastle_data *icastle = (icastle_data *) a->data.v; + *illusion = buildingtype(icastle->type, b, b->size); + } + } + } +} + +int +report_resources(const seen_region * sr, resource_report * result, int size, + const faction * viewer) +{ + const region *r = sr->r; + int n = 0; + + if (r->land) { + int peasants = rpeasants(r); + int money = rmoney(r); + int horses = rhorses(r); + int trees = rtrees(r, 2); + int saplings = rtrees(r, 1); + bool mallorn = fval(r, RF_MALLORN) != 0; + + if (money) { + if (n >= size) + return -1; + report_resource(result + n, "rm_money", money, -1); + ++n; + } + if (peasants) { + if (n >= size) + return -1; + report_resource(result + n, "rm_peasant", peasants, -1); + ++n; + } + if (horses) { + if (n >= size) + return -1; + report_resource(result + n, "rm_horse", horses, -1); + ++n; + } + if (saplings) { + if (n >= size) + return -1; + report_resource(result + n, mallorn ? "rm_mallornsapling" : "rm_sapling", + saplings, -1); + ++n; + } + if (trees) { + if (n >= size) + return -1; + report_resource(result + n, mallorn ? "rm_mallorn" : "rm_tree", trees, + -1); + ++n; + } + } + + if (sr->mode >= see_unit) { + rawmaterial *res = r->resources; + while (res) { + int maxskill = 0; + const item_type *itype = resource2item(res->type->rtype); + int level = res->level + itype->construction->minskill - 1; + int visible = -1; + if (res->type->visible == NULL) { + visible = res->amount; + level = res->level + itype->construction->minskill - 1; + } else { + const unit *u; + for (u = r->units; visible != res->amount && u != NULL; u = u->next) { + if (u->faction == viewer) { + int s = eff_skill(u, itype->construction->skill, r); + if (s > maxskill) { + maxskill = s; + visible = res->type->visible(res, maxskill); + } + } + } + } + if (level >= 0 && visible >= 0) { + if (n >= size) + return -1; + report_resource(result + n, res->type->name, visible, level); + n++; + } + res = res->next; + } + } + return n; +} + +int +bufunit(const faction * f, const unit * u, int indent, int mode, char *buf, + size_t size) +{ + int i, dh; + int getarnt = fval(u, UFL_ANON_FACTION); + const char *pzTmp, *str; + building *b; + bool isbattle = (bool) (mode == see_battle); + int telepath_see = 0; + attrib *a_fshidden = NULL; + item *itm; + item *show; + faction *fv = visible_faction(f, u); + char *bufp = buf; + bool itemcloak = false; + static const curse_type *itemcloak_ct = 0; + static bool init = false; + int bytes; + item result[MAX_INVENTORY]; + + if (!init) { + init = true; + itemcloak_ct = ct_find("itemcloak"); + } + if (itemcloak_ct != NULL) { + itemcloak = curse_active(get_curse(u->attribs, itemcloak_ct)); + } + + bytes = (int)strlcpy(bufp, unitname(u), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (!isbattle) { + attrib *a_otherfaction = a_find(u->attribs, &at_otherfaction); + if (u->faction == f) { + if (fval(u, UFL_GROUP)) { + attrib *a = a_find(u->attribs, &at_group); + if (a) { + group *g = (group *) a->data.v; + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, groupid(g, f), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + if (getarnt) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "anonymous"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else if (a_otherfaction) { + faction *otherfaction = get_otherfaction(a_otherfaction); + if (otherfaction) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, factionname(otherfaction), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + } else { + if (getarnt) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "anonymous"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else { + if (a_otherfaction && alliedunit(u, f, HELP_FSTEALTH)) { + faction *f = get_otherfaction(a_otherfaction); + bytes = + snprintf(bufp, size, ", %s (%s)", factionname(f), + factionname(u->faction)); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, factionname(fv), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + } + } + + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (u->faction != f && a_fshidden && a_fshidden->data.ca[0] == 1 + && effskill(u, SK_STEALTH) >= 6) { + bytes = (int)strlcpy(bufp, "? ", size); + } else { + bytes = snprintf(bufp, size, "%d ", u->number); + } + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + pzTmp = get_racename(u->attribs); + if (pzTmp) { + bytes = (int)strlcpy(bufp, pzTmp, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (u->faction == f && fval(u_race(u), RCF_SHAPESHIFTANY)) { + bytes = (int)strlcpy(bufp, " (", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, racename(f->locale, u, u_race(u)), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (size > 1) { + strcpy(bufp++, ")"); + --size; + } + } + } else { + const race *irace = u_irace(u); + bytes = (int)strlcpy(bufp, racename(f->locale, u, irace), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (u->faction == f && irace != u_race(u)) { + bytes = (int)strlcpy(bufp, " (", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, racename(f->locale, u, u_race(u)), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (size > 1) { + strcpy(bufp++, ")"); + --size; + } + } + } + + if (fval(u, UFL_HERO) && (u->faction == f || omniscient(f))) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "hero"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + /* status */ + + if (u->number && (u->faction == f || telepath_see || isbattle)) { + const char *c = locale_string(f->locale, hp_status(u)); + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, report_kampfstatus(u, f->locale), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (c || fval(u, UFL_HUNGER)) { + bytes = (int)strlcpy(bufp, " (", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + if (c) { + bytes = (int)strlcpy(bufp, c, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (fval(u, UFL_HUNGER)) { + if (c) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, LOC(f->locale, "unit_hungers"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (size > 1) { + strcpy(bufp++, ")"); + --size; + } + } + } + if (is_guard(u, GUARD_ALL) != 0) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "unit_guards"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + if ((b = usiege(u)) != NULL) { + bytes = (int)strlcpy(bufp, ", belagert ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, buildingname(b), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + dh = 0; + if (u->faction == f || telepath_see) { + skill *sv; + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + bytes = (int)spskill(bufp, size, f->locale, u, sv, &dh, 1); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + + dh = 0; + if (f == u->faction || telepath_see || omniscient(f)) { + show = u->items; + } else if (!itemcloak && mode >= see_unit && !(a_fshidden + && a_fshidden->data.ca[1] == 1 && effskill(u, SK_STEALTH) >= 3)) { + int n = report_items(u->items, result, MAX_INVENTORY, u, f); + assert(n >= 0); + if (n > 0) + show = result; + else + show = NULL; + } else { + show = NULL; + } + for (itm = show; itm; itm = itm->next) { + const char *ic; + int in, bytes; + report_item(u, itm, f, &ic, NULL, &in, false); + if (in == 0 || ic == NULL) + continue; + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (!dh) { + bytes = snprintf(bufp, size, "%s: ", LOC(f->locale, "nr_inventory")); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + dh = 1; + } + if (in == 1) { + bytes = (int)strlcpy(bufp, ic, size); + } else { + bytes = snprintf(bufp, size, "%d %s", in, ic); + } + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + if (u->faction == f || telepath_see) { + spellbook *book = unit_get_spellbook(u); + + if (book) { + quicklist *ql = book->spells; + int qi, header, maxlevel = effskill(u, SK_MAGIC); + int bytes = snprintf(bufp, size, ". Aura %d/%d", get_spellpoints(u), max_spellpoints(u->region, u)); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + + for (header = 0, qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry * sbe = (spellbook_entry *) ql_get(ql, qi); + if (sbe->level <= maxlevel) { + if (!header) { + bytes = snprintf(bufp, size, ", %s: ", LOC(f->locale, "nr_spells")); + header = 1; + } else { + bytes = (int)strlcpy(bufp, ", ", size); + } + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + bytes = (int)strlcpy(bufp, spell_name(sbe->sp, f->locale), size); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + } + } + + for (i = 0; i != MAXCOMBATSPELLS; ++i) { + if (get_combatspell(u, i)) + break; + } + if (i != MAXCOMBATSPELLS) { + bytes = + snprintf(bufp, size, ", %s: ", LOC(f->locale, "nr_combatspells")); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + dh = 0; + for (i = 0; i < MAXCOMBATSPELLS; i++) { + const spell *sp; + if (!dh) { + dh = 1; + } else { + bytes = (int)strlcpy(bufp, ", ", size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + } + sp = get_combatspell(u, i); + if (sp) { + int sl = get_combatspelllevel(u, i); + bytes = + (int)strlcpy(bufp, spell_name(sp, u->faction->locale), size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) { + WARN_STATIC_BUFFER(); + } + + if (sl > 0) { + bytes = snprintf(bufp, size, " (%d)", sl); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } else { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_nospells"), size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + } + } + if (!isbattle) { + bool printed = 0; + order *ord;; + for (ord = u->old_orders; ord; ord = ord->next) { + if (is_repeated(ord)) { + if (printed < ORDERS_IN_NR) { + bytes = (int)buforder(bufp, size, ord, printed++); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else + break; + } + } + if (printed < ORDERS_IN_NR) + for (ord = u->orders; ord; ord = ord->next) { + if (is_repeated(ord)) { + if (printed < ORDERS_IN_NR) { + bytes = (int)buforder(bufp, size, ord, printed++); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } else + break; + } + } + } + } + i = 0; + + str = u_description(u, f->locale); + if (str) { + bytes = (int)strlcpy(bufp, "; ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + bytes = (int)strlcpy(bufp, str, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + i = str[strlen(str) - 1]; + } + if (i != '!' && i != '?' && i != '.') { + if (size > 1) { + strcpy(bufp++, "."); + --size; + } + } + pzTmp = uprivate(u); + if (u->faction == f && pzTmp) { + bytes = (int)strlcpy(bufp, " (Bem: ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, pzTmp, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, ")", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + dh = 0; + if (!getarnt && f) { + if (alliedfaction(rplane(u->region), f, fv, HELP_ALL)) { + dh = 1; + } + } + if (size <= 1) { + log_warning("bufunit ran out of space after writing %u bytes.\n", (bufp - buf)); + } + return dh; +} + +/* TODO: telepath_see wird nicht berücksichtigt: Parteien mit + * telepath_see sollten immer einzelne Einheiten zu sehen + * bekommen, alles andere ist darstellungsteschnisch kompliziert. + */ + +size_t +spskill(char *buffer, size_t size, const struct locale * lang, + const struct unit * u, struct skill * sv, int *dh, int days) +{ + char *bufp = buffer; + int i, effsk; + int bytes; + size_t tsize = 0; + + if (!u->number) + return 0; + if (sv->level <= 0) { + if (sv->old <= 0 || (u->faction->options & want(O_SHOWSKCHANGE)) == 0) { + return 0; + } + } + + bytes = (int)strlcpy(bufp, ", ", size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (!*dh) { + bytes = (int)strlcpy(bufp, LOC(lang, "nr_skills"), size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + bytes = (int)strlcpy(bufp, ": ", size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + *dh = 1; + } + bytes = (int)strlcpy(bufp, skillname(sv->id, lang), size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + bytes = (int)strlcpy(bufp, " ", size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (sv->id == SK_MAGIC) { + sc_mage *mage = get_mage(u); + if (mage && mage->magietyp != M_GRAY) { + bytes = + (int)strlcpy(bufp, LOC(lang, mkname("school", + magic_school[mage->magietyp])), size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + bytes = (int)strlcpy(bufp, " ", size); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + + if (sv->id == SK_STEALTH && fval(u, UFL_STEALTH)) { + i = u_geteffstealth(u); + if (i >= 0) { + bytes = slprintf(bufp, size, "%d/", i); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + + effsk = effskill(u, sv->id); + bytes = slprintf(bufp, size, "%d", effsk); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (u->faction->options & want(O_SHOWSKCHANGE)) { + int oldeff = 0; + int diff; + + if (sv->old > 0) { + oldeff = sv->old + get_modifier(u, sv->id, sv->old, u->region, false); + } + + oldeff = MAX(0, oldeff); + diff = effsk - oldeff; + + if (diff != 0) { + bytes = slprintf(bufp, size, " (%s%d)", (diff > 0) ? "+" : "", diff); + tsize += bytes; + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + return tsize; +} + +void lparagraph(struct strlist **SP, char *s, int indent, char mark) +{ + + /* Die Liste SP wird mit dem String s aufgefuellt, mit indent und einer + * mark, falls angegeben. SP wurde also auf 0 gesetzt vor dem Aufruf. + * Vgl. spunit (). */ + + char *buflocal = calloc(strlen(s) + indent + 1, sizeof(char)); + + if (indent) { + memset(buflocal, ' ', indent); + if (mark) + buflocal[indent - 2] = mark; + } + strcpy(buflocal + indent, s); + addstrlist(SP, buflocal); + free(buflocal); +} + +void +spunit(struct strlist **SP, const struct faction *f, const unit * u, int indent, + int mode) +{ + char buf[DISPLAYSIZE]; + int dh = bufunit(f, u, indent, mode, buf, sizeof(buf)); + lparagraph(SP, buf, indent, + (char)((u->faction == f) ? '*' : (dh ? '+' : '-'))); +} + +struct message *msg_curse(const struct curse *c, const void *obj, objtype_t typ, + int self) +{ + if (c->type->curseinfo) { + /* if curseinfo returns NULL, then we don't want to tell the viewer anything. */ + return c->type->curseinfo(obj, typ, c, self); + } else { + message *msg = cinfo_simple(obj, typ, c, self); + if (msg == NULL) { + const char *unknown[] = + { "unit_unknown", "region_unknown", "building_unknown", + "ship_unknown" }; + msg = msg_message(mkname("curseinfo", unknown[typ]), "id", c->no); + log_error("no curseinfo function for %s and no fallback either.\n", c->type->cname); + } else { + log_error("no curseinfo function for %s, using cinfo_simple fallback.\n", c->type->cname); + } + return msg; + } +} + +const struct unit *ucansee(const struct faction *f, const struct unit *u, + const struct unit *x) +{ + if (cansee(f, u->region, u, 0)) + return u; + return x; +} + +int stealth_modifier(int seen_mode) +{ + switch (seen_mode) { + case see_unit: + return 0; + case see_far: + case see_lighthouse: + return -2; + case see_travel: + return -1; + default: + return INT_MIN; + } +} + +void transfer_seen(quicklist ** dst, quicklist ** src) +{ + assert(!*dst); + *dst = *src; + *src = NULL; +} + +static void get_addresses(report_context * ctx) +{ +/* "TODO: travelthru" */ + seen_region *sr = NULL; + region *r; + const faction *lastf = NULL; + quicklist *flist = 0; + + transfer_seen(&flist, &ctx->f->seen_factions); + + ctx->f->seen_factions = NULL; /* do not delete it twice */ + ql_push(&flist, ctx->f); + + if (f_get_alliance(ctx->f)) { + quicklist *ql = ctx->f->alliance->members; + int qi; + for (qi = 0; ql; ql_advance(&ql, &qi, 1)) { + ql_set_insert(&flist, ql_get(ql, qi)); + } + } + + /* find the first region that this faction can see */ + for (r = ctx->first; sr == NULL && r != ctx->last; r = r->next) { + sr = find_seen(ctx->seen, r); + } + + for (; sr != NULL; sr = sr->next) { + int stealthmod = stealth_modifier(sr->mode); + r = sr->r; + if (sr->mode == see_lighthouse) { + unit *u = r->units; + for (; u; u = u->next) { + faction *sf = visible_faction(ctx->f, u); + if (lastf != sf) { + if (u->building || u->ship || (stealthmod > INT_MIN + && cansee(ctx->f, r, u, stealthmod))) { + ql_set_insert(&flist, sf); + lastf = sf; + } + } + } + } else if (sr->mode == see_travel) { + unit *u = r->units; + while (u) { + faction *sf = visible_faction(ctx->f, u); + assert(u->faction != ctx->f); /* if this is see_travel only, then I shouldn't be here. */ + if (lastf != sf) { + attrib *a = a_find(r->attribs, &at_travelunit); + while (a && a->type == &at_travelunit) { + unit *u2 = (unit *) a->data.v; + if (u2->faction == ctx->f) { + if (cansee_unit(u2, u, stealthmod)) { + ql_set_insert(&flist, sf); + lastf = sf; + break; + } + } + a = a->next; + } + } + u = u->next; + } + } else if (sr->mode > see_travel) { + const unit *u = r->units; + while (u != NULL) { + if (u->faction != ctx->f) { + faction *sf = visible_faction(ctx->f, u); + bool ballied = sf && sf != ctx->f && sf != lastf + && !fval(u, UFL_ANON_FACTION) && cansee(ctx->f, r, u, stealthmod); + if (ballied || ALLIED(ctx->f, sf)) { + ql_set_insert(&flist, sf); + lastf = sf; + } + } + u = u->next; + } + } + } + + if (f_get_alliance(ctx->f)) { + faction *f2; + for (f2 = factions; f2; f2 = f2->next) { + if (f2->alliance == ctx->f->alliance) { + ql_set_insert(&flist, f2); + } + } + } + ctx->addresses = flist; +} + +#define MAXSEEHASH 0x1000 +seen_region *reuse; + +seen_region **seen_init(void) +{ + return (seen_region **) calloc(MAXSEEHASH, sizeof(seen_region *)); +} + +void seen_done(seen_region * seehash[]) +{ + int i; + for (i = 0; i != MAXSEEHASH; ++i) { + seen_region *sd = seehash[i]; + if (sd == NULL) + continue; + while (sd->nextHash != NULL) + sd = sd->nextHash; + sd->nextHash = reuse; + reuse = seehash[i]; + seehash[i] = NULL; + } + /* free(seehash); */ +} + +void free_seen(void) +{ + while (reuse) { + seen_region *r = reuse; + reuse = reuse->nextHash; + free(r); + } +} + +void +link_seen(seen_region * seehash[], const region * first, const region * last) +{ + const region *r = first; + seen_region *sr = NULL; + + if (first == last) + return; + + do { + sr = find_seen(seehash, r); + r = r->next; + } while (sr == NULL && r != last); + + while (r != last) { + seen_region *sn = find_seen(seehash, r); + if (sn != NULL) { + sr->next = sn; + sr = sn; + } + r = r->next; + } + sr->next = 0; +} + +seen_region *find_seen(struct seen_region *seehash[], const region * r) +{ + unsigned int index = reg_hashkey(r) & (MAXSEEHASH - 1); + seen_region *find = seehash[index]; + while (find) { + if (find->r == r) + return find; + find = find->nextHash; + } + return NULL; +} + +static void get_seen_interval(report_context * ctx) +{ + /* this is required to find the neighbour regions of the ones we are in, + * which may well be outside of [firstregion, lastregion) */ + int i; + for (i = 0; i != MAXSEEHASH; ++i) { + seen_region *sr = ctx->seen[i]; + while (sr != NULL) { + if (ctx->first == NULL || sr->r->index < ctx->first->index) { + ctx->first = sr->r; + } + if (ctx->last != NULL && sr->r->index >= ctx->last->index) { + ctx->last = sr->r->next; + } + sr = sr->nextHash; + } + } + link_seen(ctx->seen, ctx->first, ctx->last); +} + +bool +add_seen(struct seen_region *seehash[], struct region *r, unsigned char mode, + bool dis) +{ + seen_region *find = find_seen(seehash, r); + if (find == NULL) { + unsigned int index = reg_hashkey(r) & (MAXSEEHASH - 1); + if (!reuse) + reuse = (seen_region *) calloc(1, sizeof(struct seen_region)); + find = reuse; + reuse = reuse->nextHash; + find->nextHash = seehash[index]; + seehash[index] = find; + find->r = r; + } else if (find->mode >= mode) { + return false; + } + find->mode = mode; + find->disbelieves |= dis; + return true; +} + +typedef struct report_type { + struct report_type *next; + report_fun write; + const char *extension; + int flag; +} report_type; + +static report_type *report_types; + +void register_reporttype(const char *extension, report_fun write, int flag) +{ + report_type *type = (report_type *)malloc(sizeof(report_type)); + type->extension = extension; + type->write = write; + type->flag = flag; + type->next = report_types; + report_types = type; +} + +static quicklist *get_regions_distance(region * root, int radius) +{ + quicklist *ql, *rlist = NULL; + int qi = 0; + + ql_push(&rlist, root); + fset(root, RF_MARK); + ql = rlist; + + while (ql) { + region *r = (region *)ql_get(ql, qi); + region * next[MAXDIRECTIONS]; + int d; + get_neighbours(r, next); + + for (d = 0; d != MAXDIRECTIONS; ++d) { + if (next[d] && !fval(next[d], RF_MARK) && distance(next[d], root) <= radius) { + ql_push(&rlist, next[d]); + fset(next[d], RF_MARK); + } + } + ql_advance(&ql, &qi, 1); + } + for (ql=rlist,qi=0;ql;ql_advance(&ql, &qi, 1)) { + region *r = (region *)ql_get(ql, qi); + freset(r, RF_MARK); + } + return rlist; +} + +static void view_default(struct seen_region **seen, region * r, faction * f) +{ + int dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *r2 = rconnect(r, dir); + if (r2) { + connection *b = get_borders(r, r2); + while (b) { + if (!b->type->transparent(b, f)) + break; + b = b->next; + } + if (!b) + add_seen(seen, r2, see_neighbour, false); + } + } +} + +static void view_neighbours(struct seen_region **seen, region * r, faction * f) +{ + int d; + region * nb[MAXDIRECTIONS]; + + get_neighbours(r, nb); + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *r2 = nb[d]; + if (r2) { + connection *b = get_borders(r, r2); + while (b) { + if (!b->type->transparent(b, f)) + break; + b = b->next; + } + if (!b) { + if (add_seen(seen, r2, see_far, false)) { + if (!(fval(r2->terrain, FORBIDDEN_REGION))) { + int dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *r3 = rconnect(r2, dir); + if (r3) { + connection *b = get_borders(r2, r3); + while (b) { + if (!b->type->transparent(b, f)) + break; + b = b->next; + } + if (!b) + add_seen(seen, r3, see_neighbour, false); + } + } + } + } + } + } + } +} + +static void +recurse_regatta(struct seen_region **seen, region * center, region * r, + faction * f, int maxdist) +{ + int d; + int dist = distance(center, r); + region * nb[MAXDIRECTIONS]; + + get_neighbours(r, nb); + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *r2 = nb[d]; + if (r2) { + int ndist = distance(center, r2); + if (ndist > dist && fval(r2->terrain, SEA_REGION)) { + connection *b = get_borders(r, r2); + while (b) { + if (!b->type->transparent(b, f)) + break; + b = b->next; + } + if (!b) { + if (ndist < maxdist) { + if (add_seen(seen, r2, see_far, false)) { + recurse_regatta(seen, center, r2, f, maxdist); + } + } else + add_seen(seen, r2, see_neighbour, false); + } + } + } + } +} + +static void view_regatta(struct seen_region **seen, region * r, faction * f) +{ + unit *u; + int skill = 0; + for (u = r->units; u; u = u->next) { + if (u->faction == f) { + int es = effskill(u, SK_PERCEPTION); + if (es > skill) + skill = es; + } + } + recurse_regatta(seen, r, r, f, skill / 2); +} + +static void prepare_lighthouse(building * b, faction * f) +{ + int range = lighthouse_range(b, f); + quicklist *ql, *rlist = get_regions_distance(b->region, range); + int qi; + + for (ql=rlist,qi=0;ql;ql_advance(&ql, &qi,1)) { + region *rl = (region *)ql_get(ql, qi); + if (!fval(rl->terrain, FORBIDDEN_REGION)) { + region * next[MAXDIRECTIONS]; + int d; + + get_neighbours(rl, next); + add_seen(f->seen, rl, see_lighthouse, false); + for (d = 0; d != MAXDIRECTIONS; ++d) { + if (next[d]) { + add_seen(f->seen, next[d], see_neighbour, false); + } + } + } + } + ql_free(rlist); +} + +void reorder_units(region * r) +{ + unit **unext = &r->units; + + if (r->buildings) { + building *b = r->buildings; + while (*unext && b) { + unit **ufirst = unext; /* where the first unit in the building should go */ + unit **umove = unext; /* a unit we consider moving */ + unit *owner = building_owner(b); + while (owner && *umove) { + unit *u = *umove; + if (u->building == b) { + unit **uinsert = unext; + if (u==owner) { + uinsert = ufirst; + } + if (umove != uinsert) { + *umove = u->next; + u->next = *uinsert; + *uinsert = u; + } else { + /* no need to move, skip ahead */ + umove = &u->next; + } + if (unext == uinsert) { + /* we have a new well-placed unit. jump over it */ + unext = &u->next; + } + } else { + umove = &u->next; + } + } + b = b->next; + } + } + + if (r->ships) { + ship *sh = r->ships; + /* first, move all units up that are not on ships */ + unit **umove = unext; /* a unit we consider moving */ + while (*umove) { + unit *u = *umove; + if (u->number && !u->ship) { + if (umove != unext) { + *umove = u->next; + u->next = *unext; + *unext = u; + } else { + /* no need to move, skip ahead */ + umove = &u->next; + } + /* we have a new well-placed unit. jump over it */ + unext = &u->next; + } else { + umove = &u->next; + } + } + + while (*unext && sh) { + unit **ufirst = unext; /* where the first unit in the building should go */ + unit **umove = unext; /* a unit we consider moving */ + unit *owner = ship_owner(sh); + while (owner && *umove) { + unit *u = *umove; + if (u->number && u->ship == sh) { + unit **uinsert = unext; + if (u==owner) { + uinsert = ufirst; + owner = u; + } + if (umove != uinsert) { + *umove = u->next; + u->next = *uinsert; + *uinsert = u; + } else { + /* no need to move, skip ahead */ + umove = &u->next; + } + if (unext == uinsert) { + /* we have a new well-placed unit. jump over it */ + unext = &u->next; + } + } else { + umove = &u->next; + } + } + sh = sh->next; + } + } +} + +static void prepare_reports(void) +{ + region *r; + faction *f; + static const struct building_type *bt_lighthouse = NULL; + if (bt_lighthouse == NULL) { + bt_lighthouse = bt_find("lighthouse"); + } + for (f = factions; f; f = f->next) { + if (f->seen) + seen_done(f->seen); + f->seen = seen_init(); + } + + for (r = regions; r; r = r->next) { + attrib *ru; + unit *u; + plane *p = rplane(r); + + reorder_units(r); + + if (p) { + watcher *w = p->watchers; + for (; w; w = w->next) { + add_seen(w->faction->seen, r, w->mode, false); +#ifdef SMART_INTERVALS + update_interval(w->faction, r); +#endif + } + } + + for (u = r->units; u; u = u->next) { + if (u->building && u->building->type == bt_lighthouse) { + /* we are in a lighthouse. add the regions we can see from here! */ + prepare_lighthouse(u->building, u->faction); + } + + if (u_race(u) != new_race[RC_SPELL] || u->number == RS_FARVISION) { + if (fval(u, UFL_DISBELIEVES)) { + add_seen(u->faction->seen, r, see_unit, true); + } else { + add_seen(u->faction->seen, r, see_unit, false); + } + } + } + + if (fval(r, RF_TRAVELUNIT)) { + for (ru = a_find(r->attribs, &at_travelunit); + ru && ru->type == &at_travelunit; ru = ru->next) { + unit *u = (unit *) ru->data.v; + + /* make sure the faction has not been removed this turn: */ + if (u->faction) { + add_seen(u->faction->seen, r, see_travel, false); + } + } + } + } +} + +static seen_region **prepare_report(faction * f) +{ + struct seen_region *sr; + region *r = firstregion(f); + region *last = lastregion(f); + + link_seen(f->seen, r, last); + + for (sr = NULL; sr == NULL && r != last; r = r->next) { + sr = find_seen(f->seen, r); + } + + for (; sr != NULL; sr = sr->next) { + if (sr->mode > see_neighbour) { + region *r = sr->r; + plane *p = rplane(r); + void (*view) (struct seen_region **, region *, faction *) = view_default; + + if (p && fval(p, PFL_SEESPECIAL)) { + /* TODO: this is not very customizable */ + view = (strcmp(p->name, "Regatta")==0) ? view_regatta : view_neighbours; + } + view(f->seen, r, f); + } + } + return f->seen; +} + +int write_reports(faction * f, time_t ltime) +{ + int backup = 1, maxbackup = 128; + bool gotit = false; + struct report_context ctx; + const char *encoding = "UTF-8"; + + if (noreports) { + return false; + } + ctx.f = f; + ctx.report_time = time(NULL); + ctx.seen = prepare_report(f); + ctx.first = firstregion(f); + ctx.last = lastregion(f); + ctx.addresses = NULL; + ctx.userdata = NULL; + get_seen_interval(&ctx); + get_addresses(&ctx); + + do { + report_type *rtype = report_types; + + errno = 0; + if (verbosity >= 2) { + log_printf(stdout, "Reports for %s:", factionname(f)); + } + for (; rtype != NULL; rtype = rtype->next) { + if (f->options & rtype->flag) { + char filename[MAX_PATH]; + sprintf(filename, "%s/%d-%s.%s", reportpath(), turn, factionid(f), + rtype->extension); + if (rtype->write(filename, &ctx, encoding) == 0) { + gotit = true; + } + } + } + + if (errno) { + char zText[64]; + puts(" ERROR"); + sprintf(zText, "Waiting %u seconds before retry", backup); + perror(zText); + sleep(backup); + if (backup < maxbackup) { + backup *= 2; + } + } else if (verbosity >= 2) { + puts(" DONE"); + } + } while (errno); + if (!gotit) { + log_warning("No report for faction %s!\n", factionid(f)); + } + ql_free(ctx.addresses); + seen_done(ctx.seen); + return 0; +} + +static void nmr_warnings(void) +{ + faction *f, *fa; +#define FRIEND (HELP_GUARD|HELP_MONEY) + for (f = factions; f; f = f->next) { + if (!is_monsters(f) && (turn - f->lastorders) >= 2) { + message *msg = NULL; + for (fa = factions; fa; fa = fa->next) { + int warn = 0; + if (get_param_int(global.parameters, "rules.alliances", 0) != 0) { + if (f->alliance && f->alliance == fa->alliance) { + warn = 1; + } + } else if (alliedfaction(NULL, f, fa, FRIEND) + && alliedfaction(NULL, fa, f, FRIEND)) { + warn = 1; + } + if (warn) { + if (msg == NULL) { + msg = + msg_message("warn_dropout", "faction turns", f, + turn - f->lastorders); + } + add_message(&fa->msgs, msg); + } + } + if (msg != NULL) + msg_release(msg); + } + } +} + +static void report_donations(void) +{ + region *r; + for (r = regions; r; r = r->next) { + while (r->donations) { + donation *sp = r->donations; + if (sp->amount > 0) { + struct message *msg = msg_message("donation", + "from to amount", sp->f1, sp->f2, sp->amount); + r_addmessage(r, sp->f1, msg); + r_addmessage(r, sp->f2, msg); + msg_release(msg); + } + r->donations = sp->next; + free(sp); + } + } +} + +static void write_script(FILE * F, const faction * f) +{ + report_type *rtype; + char buf[1024]; + + fprintf(F, "faction=%s:email=%s:lang=%s", factionid(f), f->email, + locale_name(f->locale)); + if (f->options & (1 << O_BZIP2)) + fputs(":compression=bz2", F); + else + fputs(":compression=zip", F); + + fputs(":reports=", F); + buf[0] = 0; + for (rtype = report_types; rtype != NULL; rtype = rtype->next) { + if (f->options & rtype->flag) { + if (buf[0]) + strcat(buf, ","); + strcat(buf, rtype->extension); + } + } + fputs(buf, F); + fputc('\n', F); +} + +int init_reports(void) +{ + prepare_reports(); +#ifdef HAVE_STAT + { + stat_type st; + if (stat(reportpath(), &st) == 0) + return 0; + } +#endif + if (os_mkdir(reportpath(), 0700) != 0) { + if (errno != EEXIST) { + perror("could not create reportpath"); + return -1; + } + } + return 0; +} + +int reports(void) +{ + faction *f; + FILE *mailit; + time_t ltime = time(NULL); + int retval = 0; + char path[MAX_PATH]; + + if (verbosity >= 1) { + log_printf(stdout, "Writing reports for turn %d:", turn); + } + nmr_warnings(); + report_donations(); + remove_empty_units(); + + sprintf(path, "%s/reports.txt", reportpath()); + mailit = fopen(path, "w"); + if (mailit == NULL) { + log_error("%s could not be opened!\n", path); + } + + for (f = factions; f; f = f->next) { + int error = write_reports(f, ltime); + if (error) + retval = error; + if (mailit) + write_script(mailit, f); + } + if (mailit) + fclose(mailit); + free_seen(); +#ifdef GLOBAL_REPORT + { + const char *str = get_param(global.parameters, "globalreport"); + if (str != NULL) { + sprintf(path, "%s/%s.%u.cr", reportpath(), str, turn); + global_report(path); + } + } +#endif + return retval; +} + +static variant var_copy_string(variant x) +{ + x.v = x.v ? strdup((const char *)x.v) : 0; + return x; +} + +static void var_free_string(variant x) +{ + free(x.v); +} + +static variant var_copy_order(variant x) +{ + x.v = copy_order((order *) x.v); + return x; +} + +static void var_free_order(variant x) +{ + free_order(x.v); +} + +static variant var_copy_items(variant x) +{ + item *isrc; + resource *rdst = NULL, **rptr = &rdst; + + for (isrc = (item *) x.v; isrc != NULL; isrc = isrc->next) { + resource *res = malloc(sizeof(resource)); + res->number = isrc->number; + res->type = isrc->type->rtype; + *rptr = res; + rptr = &res->next; + } + *rptr = NULL; + x.v = rdst; + return x; +} + +static void var_free_resources(variant x) +{ + resource *rsrc = (resource *) x.v; + while (rsrc) { + resource *res = rsrc->next; + free(rsrc); + rsrc = res; + } + x.v = 0; +} + +static void var_free_regions(variant x) +{ + free(x.v); +} + +const char *trailinto(const region * r, const struct locale *lang) +{ + char ref[32]; + const char *s; + if (r) { + const char *tname = terrain_name(r); + strcat(strcpy(ref, tname), "_trail"); + s = locale_string(lang, ref); + if (s && *s) { + if (strstr(s, "%s")) + return s; + } + } + return "%s"; +} + +size_t +f_regionid(const region * r, const faction * f, char *buffer, size_t size) +{ + size_t len; + if (!r) { + len = strlcpy(buffer, "(Chaos)", size); + } else { + plane *pl = rplane(r); + const char *name = pl ? pl->name : 0; + int nx = r->x, ny = r->y; + int named = (name && name[0]); + pnormalize(&nx, &ny, pl); + adjust_coordinates(f, &nx, &ny, pl, r); + len = strlcpy(buffer, rname(r, f?f->locale:0), size); + snprintf(buffer + len, size-len, " (%d,%d%s%s)", nx, ny, named ? "," : "", (named) ? name : ""); + buffer[size-1] = 0; + len=strlen(buffer); + } + return len; +} + +static char *f_regionid_s(const region * r, const faction * f) +{ + static int i = 0; + static char bufs[4][NAMESIZE + 20]; + char *buf = bufs[(++i) % 4]; + + f_regionid(r, f, buf, NAMESIZE + 20); + return buf; +} + +/*** BEGIN MESSAGE RENDERING ***/ +static void eval_localize(struct opstack **stack, const void *userdata) +{ /* (string, locale) -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct locale *lang = f ? f->locale : default_locale; + const char *c = (const char *)opop_v(stack); + c = locale_string(lang, c); + opush_v(stack, strcpy(balloc(strlen(c) + 1), c)); +} + +static void eval_trailto(struct opstack **stack, const void *userdata) +{ /* (int, int) -> int */ + const struct faction *f = (const struct faction *)userdata; + const struct locale *lang = f ? f->locale : default_locale; + const struct region *r = (const struct region *)opop(stack).v; + const char *trail = trailinto(r, lang); + const char *rn = f_regionid_s(r, f); + variant var; + char *x = var.v = balloc(strlen(trail) + strlen(rn)); + sprintf(x, trail, rn); + opush(stack, var); +} + +static void eval_unit(struct opstack **stack, const void *userdata) +{ /* unit -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct unit *u = (const struct unit *)opop(stack).v; + const char *c = u ? unitname(u) : LOC(f->locale, "an_unknown_unit"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_unit_dative(struct opstack **stack, const void *userdata) +{ /* unit -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct unit *u = (const struct unit *)opop(stack).v; + const char *c = u ? unitname(u) : LOC(f->locale, "unknown_unit_dative"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_spell(struct opstack **stack, const void *userdata) +{ /* unit -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct spell *sp = (const struct spell *)opop(stack).v; + const char *c = + sp ? spell_name(sp, f->locale) : LOC(f->locale, "an_unknown_spell"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_curse(struct opstack **stack, const void *userdata) +{ /* unit -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct curse_type *sp = (const struct curse_type *)opop(stack).v; + const char *c = + sp ? curse_name(sp, f->locale) : LOC(f->locale, "an_unknown_curse"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_unitname(struct opstack **stack, const void *userdata) +{ /* unit -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct unit *u = (const struct unit *)opop(stack).v; + const char *c = u ? u->name : LOC(f->locale, "an_unknown_unit"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_unitid(struct opstack **stack, const void *userdata) +{ /* unit -> int */ + const struct faction *f = (const struct faction *)userdata; + const struct unit *u = (const struct unit *)opop(stack).v; + const char *c = u ? u->name : LOC(f->locale, "an_unknown_unit"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_unitsize(struct opstack **stack, const void *userdata) +{ /* unit -> int */ + const struct unit *u = (const struct unit *)opop(stack).v; + variant var; + + var.i = u->number; + opush(stack, var); +} + +static void eval_faction(struct opstack **stack, const void *userdata) +{ /* faction -> string */ + const struct faction *f = (const struct faction *)opop(stack).v; + const char *c = factionname(f); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_alliance(struct opstack **stack, const void *userdata) +{ /* faction -> string */ + const struct alliance *al = (const struct alliance *)opop(stack).v; + const char *c = alliancename(al); + variant var; + if (c != NULL) { + size_t len = strlen(c); + var.v = strcpy(balloc(len + 1), c); + } else + var.v = NULL; + opush(stack, var); +} + +static void eval_region(struct opstack **stack, const void *userdata) +{ /* region -> string */ + char name[NAMESIZE + 32]; + const struct faction *f = (const struct faction *)userdata; + const struct region *r = (const struct region *)opop(stack).v; + const char *c = write_regionname(r, f, name, sizeof(name)); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_terrain(struct opstack **stack, const void *userdata) +{ /* region -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct region *r = (const struct region *)opop(stack).v; + const char *c = LOC(f->locale, terrain_name(r)); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_ship(struct opstack **stack, const void *userdata) +{ /* ship -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct ship *u = (const struct ship *)opop(stack).v; + const char *c = u ? shipname(u) : LOC(f->locale, "an_unknown_ship"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_building(struct opstack **stack, const void *userdata) +{ /* building -> string */ + const struct faction *f = (const struct faction *)userdata; + const struct building *u = (const struct building *)opop(stack).v; + const char *c = u ? buildingname(u) : LOC(f->locale, "an_unknown_building"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_weight(struct opstack **stack, const void *userdata) +{ /* region -> string */ + char buffer[32]; + const struct faction *f = (const struct faction *)userdata; + const struct locale *lang = f->locale; + int weight = opop_i(stack); + variant var; + + if (weight % SCALEWEIGHT == 0) { + if (weight == SCALEWEIGHT) { + sprintf(buffer, "1 %s", LOC(lang, "weight_unit")); + } else { + sprintf(buffer, "%u %s", weight / SCALEWEIGHT, LOC(lang, + "weight_unit_p")); + } + } else { + if (weight == 1) { + sprintf(buffer, "1 %s %u", LOC(lang, "weight_per"), SCALEWEIGHT); + } else { + sprintf(buffer, "%u %s %u", weight, LOC(lang, "weight_per_p"), + SCALEWEIGHT); + } + } + + var.v = strcpy(balloc(strlen(buffer) + 1), buffer); + opush(stack, var); +} + +static void eval_resource(struct opstack **stack, const void *userdata) +{ + const faction *report = (const faction *)userdata; + const struct locale *lang = report ? report->locale : default_locale; + int j = opop(stack).i; + const struct resource_type *res = (const struct resource_type *)opop(stack).v; + const char *c = LOC(lang, resourcename(res, j != 1)); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_race(struct opstack **stack, const void *userdata) +{ + const faction *report = (const faction *)userdata; + const struct locale *lang = report ? report->locale : default_locale; + int j = opop(stack).i; + const race *r = (const race *)opop(stack).v; + const char *c = LOC(lang, rc_name(r, j != 1)); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_order(struct opstack **stack, const void *userdata) +{ /* order -> string */ + const struct order *ord = (const struct order *)opop(stack).v; + static char buf[512]; + size_t len; + variant var; + + unused(userdata); + write_order(ord, buf, sizeof(buf)); + len = strlen(buf); + var.v = strcpy(balloc(len + 1), buf); + opush(stack, var); +} + +static void eval_resources(struct opstack **stack, const void *userdata) +{ /* order -> string */ + const faction *report = (const faction *)userdata; + const struct locale *lang = report ? report->locale : default_locale; + const struct resource *res = (const struct resource *)opop(stack).v; + static char buf[1024]; /* but we only use about half of this */ + size_t size = sizeof(buf) - 1; + variant var; + + char *bufp = buf; + while (res != NULL && size > 4) { + const char *rname = + resourcename(res->type, (res->number != 1) ? NMF_PLURAL : 0); + int bytes = snprintf(bufp, size, "%d %s", res->number, LOC(lang, rname)); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0 || size < sizeof(buf) / 2) { + WARN_STATIC_BUFFER(); + break; + } + + res = res->next; + if (res != NULL && size > 2) { + strcat(bufp, ", "); + bufp += 2; + size -= 2; + } + } + *bufp = 0; + var.v = strcpy(balloc(bufp - buf + 1), buf); + opush(stack, var); +} + +static void eval_regions(struct opstack **stack, const void *userdata) +{ /* order -> string */ + const faction *report = (const faction *)userdata; + int i = opop(stack).i; + int end, begin = opop(stack).i; + const arg_regions *regions = (const arg_regions *)opop(stack).v; + static char buf[256]; + size_t size = sizeof(buf) - 1; + variant var; + char *bufp = buf; + + if (regions == NULL) { + end = begin; + } else { + if (i >= 0) + end = begin + i; + else + end = regions->nregions + i; + } + for (i = begin; i < end; ++i) { + const char *rname = (const char *)regionname(regions->regions[i], report); + int bytes = (int)strlcpy(bufp, rname, size); + if (bytes && wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (i + 1 < end && size > 2) { + strcat(bufp, ", "); + bufp += 2; + size -= 2; + } + } + *bufp = 0; + var.v = strcpy(balloc(bufp - buf + 1), buf); + opush(stack, var); +} + +static void eval_trail(struct opstack **stack, const void *userdata) +{ /* order -> string */ + const faction *report = (const faction *)userdata; + const struct locale *lang = report ? report->locale : default_locale; + int i, end = 0, begin = 0; + const arg_regions *regions = (const arg_regions *)opop(stack).v; + static char buf[512]; + size_t size = sizeof(buf) - 1; + variant var; + char *bufp = buf; +#ifdef _SECURECRT_ERRCODE_VALUES_DEFINED + /* stupid MS broke snprintf */ + int eold = errno; +#endif + + if (regions != NULL) { + end = regions->nregions; + for (i = begin; i < end; ++i) { + region *r = regions->regions[i]; + const char *trail = trailinto(r, lang); + const char *rn = f_regionid_s(r, report); + int bytes = snprintf(bufp, size, trail, rn); + if (bytes < 0 || wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + if (i + 2 < end) { + bytes = (int)strlcpy(bufp, ", ", size); + } else if (i + 1 < end) { + bytes = (int)strlcpy(bufp, LOC(lang, "list_and"), size); + } else + bytes = 0; + + if (bytes && wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + *bufp = 0; + var.v = strcpy(balloc(bufp - buf + 1), buf); + opush(stack, var); +#ifdef _SECURECRT_ERRCODE_VALUES_DEFINED + if (errno == ERANGE) { + errno = eold; + } +#endif +} + +static void eval_direction(struct opstack **stack, const void *userdata) +{ + const faction *report = (const faction *)userdata; + const struct locale *lang = report ? report->locale : default_locale; + int i = opop(stack).i; + const char *c = LOC(lang, (i >= 0) ? directions[i] : "unknown_direction"); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_skill(struct opstack **stack, const void *userdata) +{ + const faction *report = (const faction *)userdata; + const struct locale *lang = report ? report->locale : default_locale; + skill_t sk = (skill_t) opop(stack).i; + const char *c = skillname(sk, lang); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +static void eval_int36(struct opstack **stack, const void *userdata) +{ + int i = opop(stack).i; + const char *c = itoa36(i); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); + unused(userdata); +} + +/*** END MESSAGE RENDERING ***/ + +#include + +static void log_orders(const struct message *msg) +{ + faction *f = get_monsters(); + char buffer[4096]; + int i; + + for (i = 0; i != msg->type->nparameters; ++i) { + if (msg->type->types[i]->copy == &var_copy_order) { + const char *section = nr_section(msg); + nr_render(msg, f ? f->locale : default_locale, buffer, sizeof(buffer), f); + log_debug("MESSAGE [%s]: %s\n", section, buffer); + break; + } + } +} + +int report_action(region * r, unit * actor, message * msg, int flags) +{ + int result = 0; + unit *u; + int view = flags & (ACTION_CANSEE | ACTION_CANNOTSEE); + + /* melden, 1x pro Partei */ + if (flags & ACTION_RESET) { + freset(actor->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + } + if (view) { + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + bool show = u->faction == actor->faction; + fset(u->faction, FFL_SELECT); + if (view == ACTION_CANSEE) { + /* Bei Fernzaubern sieht nur die eigene Partei den Magier */ + show = show || (r == actor->region + && cansee(u->faction, r, actor, 0)); + } else if (view == ACTION_CANNOTSEE) { + show = !show && !(r == actor->region + && cansee(u->faction, r, actor, 0)); + } else { + /* the unliely (or lazy) case */ + show = true; + } + + if (show) { + r_addmessage(r, u->faction, msg); + } else { /* Partei des Magiers, sieht diesen immer */ + result = 1; + } + } + } + /* Ist niemand von der Partei des Magiers in der Region, dem Magier + * nochmal gesondert melden */ + if ((flags & ACTION_CANSEE) && !fval(actor->faction, FFL_SELECT)) { + add_message(&actor->faction->msgs, msg); + } + } + return result; +} + +void register_reports(void) +{ + /* register datatypes for the different message objects */ + register_argtype("alliance", NULL, NULL, VAR_VOIDPTR); + register_argtype("building", NULL, NULL, VAR_VOIDPTR); + register_argtype("direction", NULL, NULL, VAR_INT); + register_argtype("faction", NULL, NULL, VAR_VOIDPTR); + register_argtype("race", NULL, NULL, VAR_VOIDPTR); + register_argtype("region", NULL, NULL, VAR_VOIDPTR); + register_argtype("resource", NULL, NULL, VAR_VOIDPTR); + register_argtype("ship", NULL, NULL, VAR_VOIDPTR); + register_argtype("skill", NULL, NULL, VAR_VOIDPTR); + register_argtype("spell", NULL, NULL, VAR_VOIDPTR); + register_argtype("curse", NULL, NULL, VAR_VOIDPTR); + register_argtype("unit", NULL, NULL, VAR_VOIDPTR); + register_argtype("int", NULL, NULL, VAR_INT); + register_argtype("string", var_free_string, var_copy_string, VAR_VOIDPTR); + register_argtype("order", var_free_order, var_copy_order, VAR_VOIDPTR); + register_argtype("resources", var_free_resources, NULL, VAR_VOIDPTR); + register_argtype("items", var_free_resources, var_copy_items, VAR_VOIDPTR); + register_argtype("regions", var_free_regions, NULL, VAR_VOIDPTR); + + msg_log_create = &log_orders; + + /* register functions that turn message contents to readable strings */ + add_function("alliance", &eval_alliance); + add_function("region", &eval_region); + add_function("terrain", &eval_terrain); + add_function("weight", &eval_weight); + add_function("resource", &eval_resource); + add_function("race", &eval_race); + add_function("faction", &eval_faction); + add_function("ship", &eval_ship); + add_function("unit", &eval_unit); + add_function("unit.dative", &eval_unit_dative); + add_function("unit.name", &eval_unitname); + add_function("unit.id", &eval_unitid); + add_function("unit.size", &eval_unitsize); + add_function("building", &eval_building); + add_function("skill", &eval_skill); + add_function("order", &eval_order); + add_function("direction", &eval_direction); + add_function("int36", &eval_int36); + add_function("trailto", &eval_trailto); + add_function("localize", &eval_localize); + add_function("spell", &eval_spell); + add_function("curse", &eval_curse); + add_function("resources", &eval_resources); + add_function("regions", &eval_regions); + add_function("trail", &eval_trail); + + /* register alternative visibility functions */ + register_function((pf_generic) view_neighbours, "view_neighbours"); + register_function((pf_generic) view_regatta, "view_regatta"); +} diff --git a/core/src/kernel/reports.h b/core/src/kernel/reports.h new file mode 100644 index 000000000..ffd4ee42c --- /dev/null +++ b/core/src/kernel/reports.h @@ -0,0 +1,163 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_REPORTS +#define H_KRNL_REPORTS + +#include +#include "objtypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Alter, ab dem der Score angezeigt werden soll: */ +#define DISPLAYSCORE 12 +/* Breite einer Reportzeile: */ +#define REPORTWIDTH 78 + + extern const char *directions[]; + extern const char *coasts[]; + extern bool nonr; + extern bool nocr; + extern bool noreports; + +/* kann_finden speedups */ + extern bool kann_finden(struct faction *f1, struct faction *f2); + extern struct unit *can_find(struct faction *, struct faction *); + +/* funktionen zum schreiben eines reports */ + void sparagraph(struct strlist **SP, const char *s, int indent, char mark); + void lparagraph(struct strlist **SP, char *s, int indent, char mark); + const char *hp_status(const struct unit *u); + extern size_t spskill(char *pbuf, size_t siz, const struct locale *lang, const struct unit *u, struct skill *sv, int *dh, int days); /* mapper */ + extern void spunit(struct strlist **SP, const struct faction *f, + const struct unit *u, int indent, int mode); + + extern int reports(void); + extern int write_reports(struct faction *f, time_t ltime); + extern int init_reports(void); + extern void reorder_units(struct region * r); + + extern const struct unit *ucansee(const struct faction *f, + const struct unit *u, const struct unit *x); + + int hat_in_region(item_t itm, struct region *r, struct faction *f); + +/* für fast_region und neuen CR: */ + + enum { + see_none, + see_neighbour, + see_lighthouse, + see_travel, + see_far, + see_unit, + see_battle + }; + extern int stealth_modifier(int seen_mode); + + typedef struct seen_region { + struct seen_region *nextHash; + struct seen_region *next; + struct region *r; + unsigned char mode; + bool disbelieves; + } seen_region; + + extern struct seen_region *find_seen(struct seen_region *seehash[], + const struct region *r); + extern bool add_seen(struct seen_region *seehash[], struct region *r, + unsigned char mode, bool dis); + extern struct seen_region **seen_init(void); + extern void seen_done(struct seen_region *seehash[]); + extern void free_seen(void); + extern void link_seen(seen_region * seehash[], const struct region *first, + const struct region *last); + extern const char *visibility[]; + + typedef struct report_context { + struct faction *f; + struct quicklist *addresses; + struct seen_region **seen; + struct region *first, *last; + void *userdata; + time_t report_time; + } report_context; + + typedef int (*report_fun) (const char *filename, report_context * ctx, + const char *charset); + extern void register_reporttype(const char *extension, report_fun write, + int flag); + + extern int bufunit(const struct faction *f, const struct unit *u, int indent, + int mode, char *buf, size_t size); + + extern const char *trailinto(const struct region *r, + const struct locale *lang); + extern const char *report_kampfstatus(const struct unit *u, + const struct locale *lang); + + extern void register_reports(void); + + extern int update_nmrs(void); + extern int *nmrs; + + extern struct message *msg_curse(const struct curse *c, const void *obj, + objtype_t typ, int slef); + + typedef struct arg_regions { + int nregions; + struct region **regions; + } arg_regions; + + typedef struct resource_report { + const char *name; + int number; + int level; + } resource_report; + int report_resources(const struct seen_region *sr, + struct resource_report *result, int size, const struct faction *viewer); + int report_items(const struct item *items, struct item *result, int size, + const struct unit *owner, const struct faction *viewer); + void report_item(const struct unit *owner, const struct item *i, + const struct faction *viewer, const char **name, const char **basename, + int *number, bool singular); + void report_building(const struct building *b, const char **btype, + const char **billusion); + void report_race(const struct unit *u, const char **rcname, + const char **rcillusion); + +#define ACTION_RESET 0x01 /* reset the one-time-flag FFL_SELECT (on first pass) */ +#define ACTION_CANSEE 0x02 /* to people who can see the actor */ +#define ACTION_CANNOTSEE 0x04 /* to people who can not see the actor */ + extern int report_action(struct region *r, struct unit *actor, + struct message *msg, int flags); + + extern size_t f_regionid(const struct region *r, const struct faction *f, + char *buffer, size_t size); + + extern const char *combatstatus[]; +#define GR_PLURAL 0x01 /* grammar: plural */ +#define MAX_INVENTORY 128 /* maimum number of different items in an inventory */ +#define MAX_RAWMATERIALS 8 /* maximum kinds of raw materials in a regions */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/reports_test.c b/core/src/kernel/reports_test.c new file mode 100644 index 000000000..0bfa47e34 --- /dev/null +++ b/core/src/kernel/reports_test.c @@ -0,0 +1,83 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +static void test_reorder_units(CuTest * tc) +{ + region *r; + building *b; + ship * s; + unit *u0, *u1, *u2, *u3, *u4; + struct faction * f; + const building_type *btype; + const ship_type *stype; + + test_cleanup(); + test_create_world(); + + btype = bt_find("castle"); + stype = st_find("boat"); + + r = findregion(-1, 0); + b = test_create_building(r, btype); + s = test_create_ship(r, stype); + f = test_create_faction(0); + + u0 = test_create_unit(f, r); + u_set_ship(u0, s); + u1 = test_create_unit(f, r); + u_set_ship(u1, s); + ship_set_owner(u1); + u2 = test_create_unit(f, r); + u3 = test_create_unit(f, r); + u_set_building(u3, b); + u4 = test_create_unit(f, r); + u_set_building(u4, b); + building_set_owner(u4); + + reorder_units(r); + + CuAssertPtrEquals(tc, u4, r->units); + CuAssertPtrEquals(tc, u3, u4->next); + CuAssertPtrEquals(tc, u2, u3->next); + CuAssertPtrEquals(tc, u1, u2->next); + CuAssertPtrEquals(tc, u0, u1->next); + CuAssertPtrEquals(tc, 0, u0->next); +} + +static void test_regionid(CuTest * tc) { + size_t len; + const struct terrain_type * plain; + struct region * r; + char buffer[64]; + + test_cleanup(); + plain = test_create_terrain("plain", 0); + r = test_create_region(0, 0, plain); + + memset(buffer, -2, sizeof(buffer)); + len = f_regionid(r, 0, buffer, sizeof(buffer)); + CuAssertIntEquals(tc, 11, len); + CuAssertStrEquals(tc, "plain (0,0)", buffer); + + memset(buffer, -2, sizeof(buffer)); + len = f_regionid(r, 0, buffer, 11); + CuAssertIntEquals(tc, 10, len); + CuAssertStrEquals(tc, "plain (0,0", buffer); + CuAssertIntEquals(tc, (char)-2, buffer[11]); +} + +CuSuite *get_reports_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_reorder_units); + SUITE_ADD_TEST(suite, test_regionid); + return suite; +} diff --git a/core/src/kernel/resources.c b/core/src/kernel/resources.c new file mode 100644 index 000000000..5f3c14757 --- /dev/null +++ b/core/src/kernel/resources.c @@ -0,0 +1,224 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "resources.h" + +/* kernel includes */ +#include "build.h" +#include "item.h" +#include "region.h" +#include "terrain.h" + +#include +#include + +#include +#include +#include + +static double ResourceFactor(void) +{ + static double value = -1.0; + if (value < 0) { + const char *str = get_param(global.parameters, "resource.factor"); + value = str ? atof(str) : 1.0; + } + return value; +} + +void update_resources(region * r) +{ + struct rawmaterial *res = r->resources; + while (res) { + if (res->type->update) + res->type->update(res, r); + res = res->next; + } +} + +extern int dice_rand(const char *s); + +static void update_resource(struct rawmaterial *res, double modifier) +{ + double amount = 1 + (res->level - res->startlevel) * res->divisor / 100.0; + amount = ResourceFactor() * res->base * amount * modifier; + if (amount < 1.0) + res->amount = 1; + else + res->amount = (int)amount; + assert(res->amount > 0); +} + +void +add_resource(region * r, int level, int base, int divisor, + const resource_type * rtype) +{ + struct rawmaterial *rm = calloc(sizeof(struct rawmaterial), 1); + + rm->next = r->resources; + r->resources = rm; + rm->level = level; + rm->startlevel = level; + rm->base = base; + rm->divisor = divisor; + rm->flags = 0; + rm->type = rmt_get(rtype); + update_resource(rm, 1.0); + rm->type->terraform(rm, r); +} + +void terraform_resources(region * r) +{ + int i; + const terrain_type *terrain = r->terrain; + static int terraform_all = -1; + if (terraform_all<0) { + terraform_all = get_param_int(global.parameters, "rules.terraform.all", 0); + } + + if (terrain->production == NULL) + return; + for (i = 0; terrain->production[i].type; ++i) { + rawmaterial *rm; + const terrain_production *production = terrain->production + i; + const resource_type *rtype = production->type; + + for (rm = r->resources; rm; rm = rm->next) { + if (rm->type->rtype == rtype) + break; + } + if (rm) { + continue; + } + + if (terraform_all || chance(production->chance)) { + add_resource(r, dice_rand(production->startlevel), + dice_rand(production->base), dice_rand(production->divisor), + production->type); + } + } +} + +static void terraform_default(struct rawmaterial *res, const region * r) +{ +#define SHIFT 70 + double modifier = + 1.0 + ((rng_int() % (SHIFT * 2 + 1)) - SHIFT) * ((rng_int() % (SHIFT * 2 + + 1)) - SHIFT) / 10000.0; + res->amount = (int)(res->amount * modifier); /* random adjustment, +/- 91% */ + if (res->amount < 1) + res->amount = 1; + unused(r); +} + +#ifdef RANDOM_CHANGE +static void resource_random_change(int *pvalue, bool used) +{ + int split = 5; + int rnd = rng_int() % 100; + + if (pvalue == 0 || rnd % 10 >= 10) + return; + if (used) + split = 4; + /* if a resource was mined this round, there is a 6% probability + * of a decline and a 4% probability of a raise. */ + /* if it wasn't mined this round, there is an equal probability + * of 5% for a decline or a raise. */ + if (rnd < split) { + (*pvalue)++; + } else { + (*pvalue)--; + } + if ((*pvalue) < 0) + (*pvalue) = 0; +} +#endif + +static int visible_default(const rawmaterial * res, int skilllevel) +/* resources are visible, if skill equals minimum skill to mine them + * plus current level of difficulty */ +{ + const struct item_type *itype = res->type->rtype->itype; + if (res->level <= 1 + && res->level + itype->construction->minskill <= skilllevel + 1) { + assert(res->amount > 0); + return res->amount; + } else if (res->level + itype->construction->minskill <= skilllevel + 2) { + assert(res->amount > 0); + return res->amount; + } + return -1; +} + +static void use_default(rawmaterial * res, const region * r, int amount) +{ + assert(res->amount > 0 && amount >= 0 && amount <= res->amount); + res->amount -= amount; + while (res->amount == 0) { + double modifier = + 1.0 + ((rng_int() % (SHIFT * 2 + 1)) - SHIFT) * ((rng_int() % (SHIFT * 2 + + 1)) - SHIFT) / 10000.0; + int i; + + for (i = 0; r->terrain->production[i].type; ++i) { + if (res->type->rtype == r->terrain->production[i].type) + break; + } + + ++res->level; + update_resource(res, modifier); + } +} + +struct rawmaterial *rm_get(region * r, const struct resource_type *rtype) +{ + struct rawmaterial *rm = r->resources; + while (rm && rm->type->rtype != rtype) + rm = rm->next; + return rm; +} + +struct rawmaterial_type *rawmaterialtypes = 0; + +struct rawmaterial_type *rmt_find(const char *str) +{ + rawmaterial_type *rmt = rawmaterialtypes; + while (rmt && strcmp(rmt->name, str) != 0) + rmt = rmt->next; + return rmt; +} + +struct rawmaterial_type *rmt_get(const struct resource_type *rtype) +{ + rawmaterial_type *rmt = rawmaterialtypes; + while (rmt && rmt->rtype != rtype) + rmt = rmt->next; + return rmt; +} + +struct rawmaterial_type *rmt_create(const struct resource_type *rtype, + const char *name) +{ + rawmaterial_type *rmtype = malloc(sizeof(rawmaterial_type)); + rmtype->name = strdup(name); + rmtype->rtype = rtype; + rmtype->terraform = terraform_default; + rmtype->update = NULL; + rmtype->use = use_default; + rmtype->visible = visible_default; + rmtype->next = rawmaterialtypes; + rawmaterialtypes = rmtype; + return rmtype; +} diff --git a/core/src/kernel/resources.h b/core/src/kernel/resources.h new file mode 100644 index 000000000..d2c229657 --- /dev/null +++ b/core/src/kernel/resources.h @@ -0,0 +1,66 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#ifndef H_KRNL_RESOURCES +#define H_KRNL_RESOURCES +#ifdef __cplusplus +extern "C" { +#endif + + enum { + RM_USED = 1 << 0, /* resource has been used */ + RM_MALLORN = 1 << 1 /* this is not wood. it's mallorn */ + }; + + typedef struct rawmaterial { + const struct rawmaterial_type *type; + int amount:16; + int level:8; + int flags:8; + int base:8; + int divisor:8; + int startlevel:8; + struct rawmaterial *next; + } rawmaterial; + + typedef struct rawmaterial_type { + char *name; + const struct resource_type *rtype; + + void (*terraform) (struct rawmaterial *, const struct region *); + void (*update) (struct rawmaterial *, const struct region *); + void (*use) (struct rawmaterial *, const struct region *, int amount); + int (*visible) (const struct rawmaterial *, int skilllevel); + + /* no initialization required */ + struct rawmaterial_type *next; + } rawmaterial_type; + + extern struct rawmaterial_type *rawmaterialtypes; + + extern void update_resources(struct region *r); + extern void terraform_resources(struct region *r); + extern void read_resources(struct region *r); + extern void write_resources(struct region *r); + extern struct rawmaterial *rm_get(struct region *, + const struct resource_type *); + extern struct rawmaterial_type *rmt_find(const char *str); + extern struct rawmaterial_type *rmt_get(const struct resource_type *); + + extern void add_resource(struct region *r, int level, int base, int divisor, + const struct resource_type *rtype); + extern struct rawmaterial_type *rmt_create(const struct resource_type *rtype, + const char *name); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/save.c b/core/src/kernel/save.c new file mode 100644 index 000000000..c2a0ac7a9 --- /dev/null +++ b/core/src/kernel/save.c @@ -0,0 +1,2032 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "save.h" + +#include "alchemy.h" +#include "alliance.h" +#include "ally.h" +#include "connection.h" +#include "building.h" +#include "faction.h" +#include "group.h" +#include "item.h" +#include "magic.h" +#include "message.h" +#include "move.h" +#include "objtypes.h" +#include "order.h" +#include "pathfinder.h" +#include "plane.h" +#include "race.h" +#include "region.h" +#include "resources.h" +#include "ship.h" +#include "skill.h" +#include "spell.h" +#include "spellbook.h" +#include "terrain.h" +#include "terrainid.h" /* only for conversion code */ +#include "unit.h" +#include "version.h" + +#include "textstore.h" +#include "binarystore.h" + +/* attributes includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +#define xisdigit(c) (((c) >= '0' && (c) <= '9') || (c) == '-') + +#define ESCAPE_FIX +#define MAXORDERS 256 +#define MAXPERSISTENT 128 + +/* exported symbols symbols */ +const char *game_name = "eressea"; +int firstx = 0, firsty = 0; +int enc_gamedata = 0; + +/* local symbols */ +static region *current_region; + +char *rns(FILE * f, char *c, size_t size) +{ + char *s = c; + do { + *s = (char)getc(f); + } while (*s != '"'); + + for (;;) { + *s = (char)getc(f); + if (*s == '"') + break; + if (s < c + size) + ++s; + } + *s = 0; + return c; +} + +extern unsigned int __at_hashkey(const char *s); + +FILE *cfopen(const char *filename, const char *mode) +{ + FILE *F = fopen(filename, mode); + + if (F == 0) { + perror(filename); + return NULL; + } + setvbuf(F, 0, _IOFBF, 32 * 1024); /* 32 kb buffer size */ + return F; +} + +int freadstr(FILE * F, int encoding, char *start, size_t size) +{ + char *str = start; + bool quote = false; + for (;;) { + int c = fgetc(F); + + if (isxspace(c)) { + if (str == start) { + continue; + } + if (!quote) { + *str = 0; + return (int)(str - start); + } + } + switch (c) { + case EOF: + return EOF; + case '"': + if (!quote && str != start) { + log_error( + ("datafile contains a \" that isn't at the start of a string.\n")); + assert + (!"datafile contains a \" that isn't at the start of a string.\n"); + } + if (quote) { + *str = 0; + return (int)(str - start); + } + quote = true; + break; + case '\\': + c = fgetc(F); + switch (c) { + case EOF: + return EOF; + case 'n': + if ((size_t) (str - start + 1) < size) { + *str++ = '\n'; + } + break; + default: + if ((size_t) (str - start + 1) < size) { + if (encoding == XML_CHAR_ENCODING_8859_1 && c & 0x80) { + char inbuf = (char)c; + size_t inbytes = 1; + size_t outbytes = size - (str - start); + int ret = unicode_latin1_to_utf8(str, &outbytes, &inbuf, &inbytes); + if (ret > 0) + str += ret; + else { + log_error("input data was not iso-8859-1! assuming utf-8\n"); + encoding = XML_CHAR_ENCODING_ERROR; + *str++ = (char)c; + } + } else { + *str++ = (char)c; + } + } + } + break; + default: + if ((size_t) (str - start + 1) < size) { + if (encoding == XML_CHAR_ENCODING_8859_1 && c & 0x80) { + char inbuf = (char)c; + size_t inbytes = 1; + size_t outbytes = size - (str - start); + int ret = unicode_latin1_to_utf8(str, &outbytes, &inbuf, &inbytes); + if (ret > 0) + str += ret; + else { + log_error("input data was not iso-8859-1! assuming utf-8\n"); + encoding = XML_CHAR_ENCODING_ERROR; + *str++ = (char)c; + } + } else { + *str++ = (char)c; + } + } + } + } +} + +/** writes a quoted string to the file +* no trailing space, since this is used to make the creport. +*/ +int fwritestr(FILE * F, const char *str) +{ + int nwrite = 0; + fputc('\"', F); + if (str) + while (*str) { + int c = (int)(unsigned char)*str++; + switch (c) { + case '"': + case '\\': + fputc('\\', F); + fputc(c, F); + nwrite += 2; + break; + case '\n': + fputc('\\', F); + fputc('n', F); + nwrite += 2; + break; + default: + fputc(c, F); + ++nwrite; + } + } + fputc('\"', F); + return nwrite + 2; +} + +static unit *unitorders(FILE * F, int enc, struct faction *f) +{ + int i; + unit *u; + + if (!f) + return NULL; + + i = getid(); + u = findunitg(i, NULL); + + if (u && u_race(u) == new_race[RC_SPELL]) + return NULL; + if (u && u->faction == f) { + order **ordp; + + if (!fval(u, UFL_ORDERS)) { + /* alle wiederholbaren, langen befehle werden gesichert: */ + fset(u, UFL_ORDERS); + u->old_orders = u->orders; + ordp = &u->old_orders; + while (*ordp) { + order *ord = *ordp; + if (!is_repeated(ord)) { + *ordp = ord->next; + ord->next = NULL; + free_order(ord); + } else { + ordp = &ord->next; + } + } + } else { + free_orders(&u->orders); + } + u->orders = 0; + + ordp = &u->orders; + + for (;;) { + const char *s; + /* Erst wenn wir sicher sind, dass kein Befehl + * eingegeben wurde, checken wir, ob nun eine neue + * Einheit oder ein neuer Spieler drankommt */ + + s = getbuf(F, enc); + if (s == NULL) + break; + + if (s[0]) { + if (s[0]!='@') { + const char *stok = s; + stok = parse_token(&stok); + + if (stok) { + bool quit = false; + param_t param = findparam(stok, u->faction->locale); + switch (param) { + case P_UNIT: + case P_REGION: + quit = true; + break; + case P_FACTION: + case P_NEXT: + case P_GAMENAME: + /* these terminate the orders, so we apply extra checking */ + if (strlen(stok) >= 3) { + quit = true; + break; + } else { + quit = false; + } + break; + default: + /* TODO: syntax error message */ + break; + } + if (quit) { + break; + } + } + } + /* Nun wird der Befehl erzeut und eingehängt */ + *ordp = parse_order(s, u->faction->locale); + if (*ordp) { + ordp = &(*ordp)->next; + } + } + } + + } else { + /* cmistake(?, buf, 160, MSG_EVENT); */ + return NULL; + } + return u; +} + +static faction *factionorders(void) +{ + faction *f = NULL; + int fid = getid(); + + f = findfaction(fid); + + if (f != NULL && !is_monsters(f)) { + const char *pass = getstrtoken(); + + if (!checkpasswd(f, (const char *)pass, true)) { + log_warning("Invalid password for faction %s\n", itoa36(fid)); + ADDMSG(&f->msgs, msg_message("wrongpasswd", "faction password", + f->no, pass)); + return 0; + } + /* Die Partei hat sich zumindest gemeldet, so daß sie noch + * nicht als untätig gilt */ + + /* TODO: +1 ist ein Workaround, weil cturn erst in process_orders + * incrementiert wird. */ + f->lastorders = global.data_turn + 1; + + } else { + log_warning("orders for invalid faction %s\n", itoa36(fid)); + } + return f; +} + +double version(void) +{ + return RELEASE_VERSION * 0.1; +} + +/* ------------------------------------------------------------- */ + +static param_t igetparam(const char *s, const struct locale *lang) +{ + return findparam(igetstrtoken(s), lang); +} + +int readorders(const char *filename) +{ + FILE *F = NULL; + const char *b; + int nfactions = 0; + struct faction *f = NULL; + + if (filename) { + F = cfopen(filename, "rb"); + } + if (!F) { + return -1; + } + + if (verbosity >= 1) + puts(" - lese Befehlsdatei...\n"); + + /* TODO: recognize UTF8 BOM */ + b = getbuf(F, enc_gamedata); + + /* Auffinden der ersten Partei, und danach abarbeiten bis zur letzten + * Partei */ + + while (b) { + const struct locale *lang = f ? f->locale : default_locale; + int p; + switch (igetparam(b, lang)) { +#undef LOCALE_CHANGE +#ifdef LOCALE_CHANGE + case P_LOCALE: + { + const char *s = getstrtoken(); + if (f && find_locale(s)) { + f->locale = find_locale(s); + } + } + b = getbuf(F, enc_gamedata); + break; +#endif + case P_GAMENAME: + case P_FACTION: + f = factionorders(); + if (f) { + ++nfactions; + } + + b = getbuf(F, enc_gamedata); + break; + + /* in factionorders wird nur eine zeile gelesen: + * diejenige mit dem passwort. Die befehle der units + * werden geloescht, und die Partei wird als aktiv + * vermerkt. */ + + case P_UNIT: + if (!f || !unitorders(F, enc_gamedata, f)) + do { + b = getbuf(F, enc_gamedata); + if (!b) + break; + p = (b[0]=='@') ? NOPARAM : igetparam(b, lang); + } while ((p != P_UNIT || !f) && p != P_FACTION && p != P_NEXT + && p != P_GAMENAME); + break; + + /* Falls in unitorders() abgebrochen wird, steht dort entweder eine neue + * Partei, eine neue Einheit oder das File-Ende. Das switch() wird erneut + * durchlaufen, und die entsprechende Funktion aufgerufen. Man darf buf + * auf alle Fälle nicht überschreiben! Bei allen anderen Einträgen hier + * muß buf erneut gefüllt werden, da die betreffende Information in nur + * einer Zeile steht, und nun die nächste gelesen werden muß. */ + + case P_NEXT: + f = NULL; + b = getbuf(F, enc_gamedata); + break; + + default: + b = getbuf(F, enc_gamedata); + break; + } + } + + fclose(F); + if (verbosity >= 1) + puts("\n"); + log_printf(stdout, " %d Befehlsdateien gelesen\n", nfactions); + return 0; +} + +/* ------------------------------------------------------------- */ + +/* #define INNER_WORLD */ +/* fürs debuggen nur den inneren Teil der Welt laden */ +/* -9;-27;-1;-19;Sumpfloch */ +int inner_world(region * r) +{ + static int xy[2] = { 18, -45 }; + static int size[2] = { 27, 27 }; + + if (r->x >= xy[0] && r->x < xy[0] + size[0] && r->y >= xy[1] + && r->y < xy[1] + size[1]) + return 2; + if (r->x >= xy[0] - 9 && r->x < xy[0] + size[0] + 9 && r->y >= xy[1] - 9 + && r->y < xy[1] + size[1] + 9) + return 1; + return 0; +} + +int maxregions = -1; +int loadplane = 0; + +enum { + U_MAN, + U_UNDEAD, + U_ILLUSION, + U_FIREDRAGON, + U_DRAGON, + U_WYRM, + U_SPELL, + U_TAVERNE, + U_MONSTER, + U_BIRTHDAYDRAGON, + U_TREEMAN, + MAXTYPES +}; + +race_t typus2race(unsigned char typus) +{ + if (typus > 0 && typus <= 11) + return (race_t) (typus - 1); + return NORACE; +} + +void create_backup(char *file) +{ +#ifdef HAVE_LINK + char bfile[MAX_PATH]; + int c = 1; + + if (access(file, R_OK) == 0) + return; + do { + sprintf(bfile, "%s.backup%d", file, c); + c++; + } while (access(bfile, R_OK) == 0); + link(file, bfile); +#endif +} + +void read_items(struct storage *store, item ** ilist) +{ + for (;;) { + char ibuf[32]; + const item_type *itype; + int i; + store->r_str_buf(store, ibuf, sizeof(ibuf)); + if (!strcmp("end", ibuf)) + break; + itype = it_find(ibuf); + i = store->r_int(store); + if (i <= 0) { + log_error("data contains an entry with %d %s\n", i, itype->rtype->_name[1]); + } else { + assert(itype != NULL); + if (itype != NULL) { + i_change(ilist, itype, i); + } + } + } +} + +static void read_alliances(struct storage *store) +{ + char pbuf[8]; + int id, terminator = 0; + if (store->version < SAVEALLIANCE_VERSION) { + if (!AllianceRestricted() && !AllianceAuto()) + return; + } + if (store->version < ALLIANCELEADER_VERSION) { + terminator = atoi36("end"); + store->r_str_buf(store, pbuf, sizeof(pbuf)); + id = atoi36(pbuf); + } else { + id = store->r_id(store); + } + while (id != terminator) { + char aname[128]; + alliance *al; + store->r_str_buf(store, aname, sizeof(aname)); + al = makealliance(id, aname); + if (store->version >= OWNER_2_VERSION) { + al->flags = store->r_int(store); + } + if (store->version >= ALLIANCELEADER_VERSION) { + read_reference(&al->_leader, store, read_faction_reference, + resolve_faction); + id = store->r_id(store); + } else { + store->r_str_buf(store, pbuf, sizeof(pbuf)); + id = atoi36(pbuf); + } + } +} + +void write_alliances(struct storage *store) +{ + alliance *al = alliances; + while (al) { + if (al->_leader) { + store->w_id(store, al->id); + store->w_str(store, al->name); + store->w_int(store, (int)al->flags); + write_faction_reference(al->_leader, store); + store->w_brk(store); + } + al = al->next; + } + store->w_id(store, 0); + store->w_brk(store); +} + +void write_items(struct storage *store, item * ilist) +{ + item *itm; + for (itm = ilist; itm; itm = itm->next) { + assert(itm->number >= 0); + if (itm->number) { + store->w_tok(store, resourcename(itm->type->rtype, 0)); + store->w_int(store, itm->number); + } + } + store->w_tok(store, "end"); +} + +static int resolve_owner(variant id, void *address) +{ + region_owner *owner = (region_owner *) address; + int result = 0; + faction *f = NULL; + if (id.i != 0) { + f = findfaction(id.i); + if (f == NULL) { + log_error("region has an invalid owner (%s)\n", itoa36(id.i)); + f = get_monsters(); + } + } + owner->owner = f; + if (f) { + owner->alliance = f->alliance; + } + return result; +} + +static void read_owner(struct storage *store, region_owner ** powner) +{ + int since_turn = store->r_int(store); + if (since_turn >= 0) { + region_owner *owner = malloc(sizeof(region_owner)); + owner->since_turn = since_turn; + owner->morale_turn = store->r_int(store); + if (store->version >= MOURNING_VERSION) { + owner->flags = store->r_int(store); + } else { + owner->flags = 0; + } + if (store->version >= OWNER_2_VERSION) { + int id = store->r_int(store); + owner->alliance = id ? findalliance(id) : NULL; + } else { + owner->alliance = NULL; + } + read_reference(owner, store, &read_faction_reference, &resolve_owner); + *powner = owner; + } else { + *powner = 0; + } +} + +static void write_owner(struct storage *store, region_owner * owner) +{ + if (owner) { + store->w_int(store, owner->since_turn); + store->w_int(store, owner->morale_turn); + store->w_int(store, owner->flags); + store->w_id(store, owner->alliance ? owner->alliance->id : 0); + write_faction_reference(owner->owner, store); + } else { + store->w_int(store, -1); + } +} + +int current_turn(void) +{ + char zText[MAX_PATH]; + int cturn = 0; + FILE *f; + + sprintf(zText, "%s/turn", basepath()); + f = cfopen(zText, "r"); + if (f) { + fscanf(f, "%d\n", &cturn); + fclose(f); + } + return cturn; +} + +static void +writeorder(struct storage *store, const struct order *ord, + const struct locale *lang) +{ + char obuf[1024]; + write_order(ord, obuf, sizeof(obuf)); + if (obuf[0]) + store->w_str(store, obuf); +} + +unit *read_unit(struct storage *store) +{ + skill_t sk; + unit *u; + int number, n, p; + order **orderp; + char obuf[1024]; + faction *f; + char rname[32]; + + n = store->r_id(store); + if (n <= 0) + return NULL; + u = findunit(n); + if (u == NULL) { + u = calloc(sizeof(unit), 1); + u->no = n; + uhash(u); + } else { + while (u->attribs) + a_remove(&u->attribs, u->attribs); + while (u->items) + i_free(i_remove(&u->items, u->items)); + free(u->skills); + u->skills = 0; + u->skill_size = 0; + u_setfaction(u, NULL); + } + + n = store->r_id(store); + f = findfaction(n); + if (f != u->faction) + u_setfaction(u, f); + + u->name = store->r_str(store); + if (lomem) { + store->r_str_buf(store, NULL, 0); + } else { + u->display = store->r_str(store); + } + number = store->r_int(store); + u->age = (short)store->r_int(store); + + if (store->version < STORAGE_VERSION) { + char *space; + store->r_str_buf(store, rname, sizeof(rname)); + space = strchr(rname, ' '); + if (space != NULL) { + char *inc = space + 1; + char *outc = space; + do { + while (*inc == ' ') + ++inc; + while (*inc) { + *outc++ = *inc++; + if (*inc == ' ') + break; + } + } while (*inc); + *outc = 0; + } + } else { + store->r_tok_buf(store, rname, sizeof(rname)); + } + u_setrace(u, rc_find(rname)); + + if (store->version < STORAGE_VERSION) { + store->r_str_buf(store, rname, sizeof(rname)); + } else { + store->r_tok_buf(store, rname, sizeof(rname)); + } + if (rname[0] && skill_enabled[SK_STEALTH]) + u->irace = rc_find(rname); + else + u->irace = NULL; + + if (u_race(u)->describe) { + const char *rcdisp = u_race(u)->describe(u, u->faction->locale); + if (u->display && rcdisp) { + /* see if the data file contains old descriptions */ + if (strcmp(rcdisp, u->display) == 0) { + free(u->display); + u->display = NULL; + } + } + } + if (u->faction == NULL) { + log_error("unit %s has faction == NULL\n", unitname(u)); + u_setfaction(u, get_monsters()); + set_number(u, 0); + } + + if (count_unit(u) && u->faction) + u->faction->no_units++; + + set_number(u, number); + + n = store->r_id(store); + if (n > 0) { + building * b = findbuilding(n); + if (b) { + u_set_building(u, b); + if (fval(u, UFL_OWNER)) { + building_set_owner(u); + } + } else { + log_error("read_unit: unit in unkown building '%s'\n", itoa36(n)); + } + } + + n = store->r_id(store); + if (n > 0) { + ship * sh = findship(n); + if (sh) { + u_set_ship(u, sh); + if (fval(u, UFL_OWNER)) { + ship_set_owner(u); + } + } else { + log_error("read_unit: unit in unkown ship '%s'\n", itoa36(n)); + } + } + + setstatus(u, store->r_int(store)); + u->flags = store->r_int(store); + u->flags &= UFL_SAVEMASK; + if ((u->flags & UFL_ANON_FACTION) && !rule_stealth_faction()) { + /* if this rule is broken, then fix broken units */ + u->flags -= UFL_ANON_FACTION; + log_warning("%s was anonymous.\n", unitname(u)); + } + /* Persistente Befehle einlesen */ + free_orders(&u->orders); + store->r_str_buf(store, obuf, sizeof(obuf)); + p = n = 0; + orderp = &u->orders; + while (obuf[0]) { + if (!lomem) { + order *ord = parse_order(obuf, u->faction->locale); + if (ord != NULL) { + if (++n < MAXORDERS) { + if (!is_persistent(ord) || ++p < MAXPERSISTENT) { + *orderp = ord; + orderp = &ord->next; + ord = NULL; + } else if (p == MAXPERSISTENT) { + log_warning("%s had %d or more persistent orders\n", unitname(u), MAXPERSISTENT); + } + } else if (n == MAXORDERS) { + log_warning("%s had %d or more orders\n", unitname(u), MAXORDERS); + } + if (ord != NULL) + free_order(ord); + } + } + store->r_str_buf(store, obuf, sizeof(obuf)); + } + if (store->version < NOLASTORDER_VERSION) { + order *ord; + store->r_str_buf(store, obuf, sizeof(obuf)); + ord = parse_order(obuf, u->faction->locale); + if (ord != NULL) { + addlist(&u->orders, ord); + } + } + set_order(&u->thisorder, NULL); + + assert(u_race(u)); + while ((sk = (skill_t) store->r_int(store)) != NOSKILL) { + int level = store->r_int(store); + int weeks = store->r_int(store); + if (level) { + skill *sv = add_skill(u, sk); + sv->level = sv->old = (unsigned char)level; + sv->weeks = (unsigned char)weeks; + } + } + read_items(store, &u->items); + u->hp = store->r_int(store); + if (u->hp < u->number) { + log_error("Einheit %s hat %u Personen, und %u Trefferpunkte\n", itoa36(u->no), u->number, u->hp); + u->hp = u->number; + } + + a_read(store, &u->attribs, u); + return u; +} + +void write_unit(struct storage *store, const unit * u) +{ + order *ord; + int i, p = 0; + unsigned int flags = u->flags & UFL_SAVEMASK; + const race *irace = u_irace(u); + + write_unit_reference(u, store); + write_faction_reference(u->faction, store); + store->w_str(store, (const char *)u->name); + store->w_str(store, u->display ? (const char *)u->display : ""); + store->w_int(store, u->number); + store->w_int(store, u->age); + store->w_tok(store, u_race(u)->_name[0]); + store->w_tok(store, (irace && irace != u_race(u)) ? irace->_name[0] : ""); + write_building_reference(u->building, store); + write_ship_reference(u->ship, store); + store->w_int(store, u->status); + if (u->building && u==building_owner(u->building)) flags |= UFL_OWNER; + if (u->ship && u==ship_owner(u->ship)) flags |= UFL_OWNER; + store->w_int(store, flags); + store->w_brk(store); + for (ord = u->old_orders; ord; ord = ord->next) { + if (++p < MAXPERSISTENT) { + writeorder(store, ord, u->faction->locale); + } else { + log_error("%s had %d or more persistent orders\n", unitname(u), MAXPERSISTENT); + break; + } + } + for (ord = u->orders; ord; ord = ord->next) { + if (u->old_orders && is_repeated(ord)) + continue; /* has new defaults */ + if (is_persistent(ord)) { + if (++p < MAXPERSISTENT) { + writeorder(store, ord, u->faction->locale); + } else { + log_error("%s had %d or more persistent orders\n", unitname(u), MAXPERSISTENT); + break; + } + } + } + /* write an empty string to terminate the list */ + store->w_str(store, ""); + store->w_brk(store); + + assert(u_race(u)); + + for (i = 0; i != u->skill_size; ++i) { + skill *sv = u->skills + i; + assert(sv->weeks <= sv->level * 2 + 1); + if (sv->level > 0) { + store->w_int(store, sv->id); + store->w_int(store, sv->level); + store->w_int(store, sv->weeks); + } + } + store->w_int(store, -1); + store->w_brk(store); + write_items(store, u->items); + store->w_brk(store); + if (u->hp == 0) { + log_error("unit %s has 0 hitpoints, adjusting.\n", itoa36(u->no)); + ((unit *) u)->hp = u->number; + } + store->w_int(store, u->hp); + store->w_brk(store); + a_write(store, u->attribs, u); + store->w_brk(store); +} + +static region *readregion(struct storage *store, int x, int y) +{ + region *r = findregion(x, y); + const terrain_type *terrain; + char token[32]; + unsigned int uid = 0; + + if (store->version >= UID_VERSION) { + uid = store->r_int(store); + } + + if (r == NULL) { + plane *pl = findplane(x, y); + r = new_region(x, y, pl, uid); + } else { + assert(uid == 0 || r->uid == uid); + current_region = r; + while (r->attribs) + a_remove(&r->attribs, r->attribs); + if (r->land) { + free(r->land); /* mem leak */ + r->land->demands = 0; /* mem leak */ + } + while (r->resources) { + rawmaterial *rm = r->resources; + r->resources = rm->next; + free(rm); + } + r->land = 0; + } + if (lomem) { + store->r_str_buf(store, NULL, 0); + } else { + char info[DISPLAYSIZE]; + store->r_str_buf(store, info, sizeof(info)); + region_setinfo(r, info); + } + + if (store->version < TERRAIN_VERSION) { + int ter = store->r_int(store); + terrain = newterrain((terrain_t) ter); + if (terrain == NULL) { + log_error("while reading datafile from pre-TERRAIN_VERSION, could not find terrain #%d.\n", ter); + terrain = newterrain(T_PLAIN); + } + } else { + char name[64]; + store->r_str_buf(store, name, sizeof(name)); + terrain = get_terrain(name); + if (terrain == NULL) { + log_error("Unknown terrain '%s'\n", name); + assert(!"unknown terrain"); + } + } + r->terrain = terrain; + r->flags = (char)store->r_int(store); + + r->age = (unsigned short)store->r_int(store); + + if (fval(r->terrain, LAND_REGION)) { + r->land = calloc(1, sizeof(land_region)); + r->land->name = store->r_str(store); + } + if (r->land) { + int i; + rawmaterial **pres = &r->resources; + + i = store->r_int(store); + if (i < 0) { + log_error("number of trees in %s is %d.\n", regionname(r, NULL), i); + i = 0; + } + rsettrees(r, 0, i); + i = store->r_int(store); + if (i < 0) { + log_error("number of young trees in %s is %d.\n", regionname(r, NULL), i); + i = 0; + } + rsettrees(r, 1, i); + i = store->r_int(store); + if (i < 0) { + log_error("number of seeds in %s is %d.\n", regionname(r, NULL), i); + i = 0; + } + rsettrees(r, 2, i); + + i = store->r_int(store); + rsethorses(r, i); + assert(*pres == NULL); + for (;;) { + rawmaterial *res; + store->r_str_buf(store, token, sizeof(token)); + if (strcmp(token, "end") == 0) + break; + res = malloc(sizeof(rawmaterial)); + res->type = rmt_find(token); + if (res->type == NULL) { + log_error("invalid resourcetype %s in data.\n", token); + } + assert(res->type != NULL); + res->level = store->r_int(store); + res->amount = store->r_int(store); + res->flags = 0; + + res->startlevel = store->r_int(store); + res->base = store->r_int(store); + res->divisor = store->r_int(store); + + *pres = res; + pres = &res->next; + } + *pres = NULL; + + store->r_str_buf(store, token, sizeof(token)); + if (strcmp(token, "noherb") != 0) { + const resource_type *rtype = rt_find(token); + assert(rtype && rtype->itype && fval(rtype->itype, ITF_HERB)); + rsetherbtype(r, rtype->itype); + } else { + rsetherbtype(r, NULL); + } + rsetherbs(r, (short)store->r_int(store)); + rsetpeasants(r, store->r_int(store)); + rsetmoney(r, store->r_int(store)); + } + + assert(r->terrain != NULL); + assert(rhorses(r) >= 0); + assert(rpeasants(r) >= 0); + assert(rmoney(r) >= 0); + + if (r->land) { + for (;;) { + const struct item_type *itype; + store->r_str_buf(store, token, sizeof(token)); + if (!strcmp(token, "end")) + break; + itype = it_find(token); + assert(itype->rtype->ltype); + r_setdemand(r, itype->rtype->ltype, store->r_int(store)); + } + if (store->version >= REGIONITEMS_VERSION) { + read_items(store, &r->land->items); + } + if (store->version >= REGIONOWNER_VERSION) { + r->land->morale = (short)store->r_int(store); + if (r->land->morale < 0) + r->land->morale = 0; + read_owner(store, &r->land->ownership); + } + } + a_read(store, &r->attribs, r); + + return r; +} + +void writeregion(struct storage *store, const region * r) +{ + store->w_int(store, r->uid); + store->w_str(store, region_getinfo(r)); + store->w_tok(store, r->terrain->_name); + store->w_int(store, r->flags & RF_SAVEMASK); + store->w_int(store, r->age); + store->w_brk(store); + if (fval(r->terrain, LAND_REGION)) { + const item_type *rht; + struct demand *demand; + rawmaterial *res = r->resources; + store->w_str(store, (const char *)r->land->name); + assert(rtrees(r, 0) >= 0); + assert(rtrees(r, 1) >= 0); + assert(rtrees(r, 2) >= 0); + store->w_int(store, rtrees(r, 0)); + store->w_int(store, rtrees(r, 1)); + store->w_int(store, rtrees(r, 2)); + store->w_int(store, rhorses(r)); + + while (res) { + store->w_tok(store, res->type->name); + store->w_int(store, res->level); + store->w_int(store, res->amount); + store->w_int(store, res->startlevel); + store->w_int(store, res->base); + store->w_int(store, res->divisor); + res = res->next; + } + store->w_tok(store, "end"); + + rht = rherbtype(r); + if (rht) { + store->w_tok(store, resourcename(rht->rtype, 0)); + } else { + store->w_tok(store, "noherb"); + } + store->w_int(store, rherbs(r)); + store->w_int(store, rpeasants(r)); + store->w_int(store, rmoney(r)); + if (r->land) + for (demand = r->land->demands; demand; demand = demand->next) { + store->w_tok(store, resourcename(demand->type->itype->rtype, 0)); + store->w_int(store, demand->value); + } + store->w_tok(store, "end"); +#if RELEASE_VERSION>=REGIONITEMS_VERSION + write_items(store, r->land->items); + store->w_brk(store); +#endif +#if RELEASE_VERSION>=REGIONOWNER_VERSION + store->w_int(store, r->land->morale); + write_owner(store, r->land->ownership); + store->w_brk(store); +#endif + } + a_write(store, r->attribs, r); + store->w_brk(store); +} + +static ally **addally(const faction * f, ally ** sfp, int aid, int state) +{ + struct faction *af = findfaction(aid); + ally *sf; + + state &= ~HELP_OBSERVE; +#ifndef REGIONOWNERS + state &= ~HELP_TRAVEL; +#endif + state &= HelpMask(); + + if (state == 0) + return sfp; + + sf = calloc(1, sizeof(ally)); + sf->faction = af; + if (!sf->faction) { + variant id; + id.i = aid; + ur_add(id, &sf->faction, resolve_faction); + } + sf->status = state & HELP_ALL; + + while (*sfp) + sfp = &(*sfp)->next; + *sfp = sf; + return &sf->next; +} + +int get_spell_level_faction(const spell * sp, void * cbdata) +{ + static spellbook * common = 0; + spellbook * book; + faction * f = (faction *)cbdata; + spellbook_entry * sbe; + + book = get_spellbook(magic_school[f->magiegebiet]); + if (book) { + sbe = spellbook_get(book, sp); + if (sbe) return sbe->level; + } + if (!common) { + common = get_spellbook(magic_school[M_COMMON]); + } + sbe = spellbook_get(common, sp); + if (sbe) { + return sbe->level; + } else { + log_error("read_spellbook: faction '%s' has a spell with unknown level: '%s'", factionname(f), sp->sname); + } + return 0; +} + +void read_spellbook(spellbook **bookp, struct storage *store, int (*get_level)(const spell * sp, void *), void * cbdata) +{ + for (;;) { + spell *sp = 0; + char spname[64]; + int level = 0; + + if (store->version < SPELLNAME_VERSION) { + int i = store->r_int(store); + if (i < 0) + break; + if (bookp) { + sp = find_spellbyid((unsigned int) i); + } + } else { + store->r_tok_buf(store, spname, sizeof(spname)); + if (strcmp(spname, "end") == 0) + break; + if (bookp) { + sp = find_spell(spname); + if (!sp) { + log_error("read_spells: could not find spell '%s'\n", spname); + } + } + } + if (store->version >= SPELLBOOK_VERSION) { + level = store->r_int(store); + } + if (sp) { + spellbook * sb = *bookp; + if (level<=0 && get_level) { + level = get_level(sp, cbdata); + } + if (!sb) { + *bookp = create_spellbook(0); + sb = *bookp; + } + if (store->version>=SPELLBOOK_VERSION || !spellbook_get(sb, sp)) { + spellbook_add(sb, sp, level); + } + } + } +} + +void write_spellbook(const struct spellbook *book, struct storage *store) +{ + quicklist *ql; + int qi; + + if (book) { + for (ql = book->spells, qi = 0; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry *sbe = (spellbook_entry *) ql_get(ql, qi); + store->w_tok(store, sbe->sp->sname); + store->w_int(store, sbe->level); + } + } + store->w_tok(store, "end"); +} + +/** Reads a faction from a file. + * This function requires no context, can be called in any state. The + * faction may not already exist, however. + */ +faction *readfaction(struct storage * store) +{ + ally **sfp; + int planes; + int i = store->r_id(store); + faction *f = findfaction(i); + char email[128]; + char token[32]; + + if (f == NULL) { + f = (faction *) calloc(1, sizeof(faction)); + f->no = i; + } else { + f->allies = NULL; /* mem leak */ + while (f->attribs) + a_remove(&f->attribs, f->attribs); + } + f->subscription = store->r_int(store); + + if (alliances || store->version >= OWNER_2_VERSION) { + int allianceid = store->r_id(store); + if (allianceid > 0) + f->alliance = findalliance(allianceid); + if (f->alliance) { + alliance *al = f->alliance; + if (al->flags & ALF_NON_ALLIED) { + assert(!al->members + || !"non-allied dummy-alliance has more than one member"); + } + ql_push(&al->members, f); + } else if (rule_region_owners()) { + /* compat fix for non-allied factions */ + alliance *al = makealliance(0, NULL); + setalliance(f, al); + } + if (store->version >= OWNER_2_VERSION) { + f->alliance_joindate = store->r_int(store); + } else { + f->alliance_joindate = turn - 10; /* we're guessing something safe here */ + } + } + + f->name = store->r_str(store); + f->banner = store->r_str(store); + + log_printf(stdout, " - Lese Partei %s (%s)\n", f->name, factionid(f)); + + store->r_str_buf(store, email, sizeof(email)); + if (set_email(&f->email, email) != 0) { + log_warning("Invalid email address for faction %s: %s\n", itoa36(f->no), email); + set_email(&f->email, ""); + } + + f->passw = store->r_str(store); + if (store->version >= OVERRIDE_VERSION) { + f->override = store->r_str(store); + } else { + f->override = strdup(itoa36(rng_int())); + } + + store->r_str_buf(store, token, sizeof(token)); + f->locale = find_locale(token); + f->lastorders = store->r_int(store); + f->age = store->r_int(store); + store->r_str_buf(store, token, sizeof(token)); + f->race = rc_find(token); + assert(f->race); + f->magiegebiet = (magic_t) store->r_int(store); + + if (store->version < FOSS_VERSION) { + /* ignore karma */ + store->r_int(store); + } + + f->flags = store->r_int(store); + if (f->no == 0) { + f->flags |= FFL_NPC; + } + + a_read(store, &f->attribs, f); + if (store->version >= CLAIM_VERSION) { + read_items(store, &f->items); + } + for (;;) { + store->r_tok_buf(store, token, sizeof(token)); + if (strcmp("end", token) == 0) + break; + store->r_int(store); /* there used to be a level here, which is now ignored */ + } + planes = store->r_int(store); + while (--planes >= 0) { + int id = store->r_int(store); + int ux = store->r_int(store); + int uy = store->r_int(store); + set_ursprung(f, id, ux, uy); + } + f->newbies = 0; + + i = f->options = store->r_int(store); + + if ((i & (want(O_REPORT) | want(O_COMPUTER))) == 0 && !is_monsters(f)) { + /* Kein Report eingestellt, Fehler */ + f->options = f->options | want(O_REPORT) | want(O_ZUGVORLAGE); + } + + sfp = &f->allies; + if (store->version < ALLIANCES_VERSION) { + int p = store->r_int(store); + while (--p >= 0) { + int aid = store->r_id(store); + int state = store->r_int(store); + sfp = addally(f, sfp, aid, state); + } + } else { + for (;;) { + int aid = 0; + if (store->version < STORAGE_VERSION) { + store->r_tok_buf(store, token, sizeof(token)); + if (strcmp(token, "end") != 0) { + aid = atoi36(token); + } + } else { + aid = store->r_id(store); + } + if (aid > 0) { + int state = store->r_int(store); + sfp = addally(f, sfp, aid, state); + } else { + break; + } + } + } + read_groups(store, f); + f->spellbook = 0; + if (store->version >= REGIONOWNER_VERSION) { + read_spellbook(FactionSpells() ? &f->spellbook : 0, store, get_spell_level_faction, (void *)f); + } + return f; +} + +void writefaction(struct storage *store, const faction * f) +{ + ally *sf; + ursprung *ur; + + write_faction_reference(f, store); + store->w_int(store, f->subscription); + if (f->alliance) { + store->w_id(store, f->alliance->id); + if (f->alliance->flags & ALF_NON_ALLIED) { + assert(f == f->alliance->_leader + || !"non-allied faction is not leader of its own dummy-alliance."); + } + } else { + store->w_id(store, 0); + } + store->w_int(store, f->alliance_joindate); + + store->w_str(store, (const char *)f->name); + store->w_str(store, (const char *)f->banner); + store->w_str(store, f->email); + store->w_tok(store, (const char *)f->passw); + store->w_tok(store, (const char *)f->override); + store->w_tok(store, locale_name(f->locale)); + store->w_int(store, f->lastorders); + store->w_int(store, f->age); + store->w_tok(store, f->race->_name[0]); + store->w_brk(store); + store->w_int(store, f->magiegebiet); + + store->w_int(store, f->flags & FFL_SAVEMASK); + a_write(store, f->attribs, f); + store->w_brk(store); + write_items(store, f->items); + store->w_brk(store); + store->w_tok(store, "end"); + store->w_brk(store); + store->w_int(store, listlen(f->ursprung)); + for (ur = f->ursprung; ur; ur = ur->next) { + store->w_int(store, ur->id); + store->w_int(store, ur->x); + store->w_int(store, ur->y); + } + store->w_brk(store); + store->w_int(store, f->options & ~want(O_DEBUG)); + store->w_brk(store); + + for (sf = f->allies; sf; sf = sf->next) { + int no = (sf->faction != NULL) ? sf->faction->no : 0; + int status = alliedfaction(NULL, f, sf->faction, HELP_ALL); + if (status != 0) { + store->w_id(store, no); + store->w_int(store, sf->status); + } + } + store->w_id(store, 0); + store->w_brk(store); + write_groups(store, f->groups); + write_spellbook(f->spellbook, store); +} + +int readgame(const char *filename, int mode, int backup) +{ + int i, n, p; + faction *f, **fp; + region *r; + building *b, **bp; + ship **shp; + unit *u; + int rmax = maxregions; + char path[MAX_PATH]; + char token[32]; + const struct building_type *bt_lighthouse = bt_find("lighthouse"); + storage my_store = (mode == IO_BINARY) ? binary_store : text_store; + storage *store = &my_store; + + sprintf(path, "%s/%s", datapath(), filename); + log_printf(stdout, "- reading game data from %s\n", filename); + if (backup) + create_backup(path); + + store->encoding = enc_gamedata; + if (store->open(store, path, IO_READ) != 0) { + return -1; + } + enc_gamedata = store->encoding; + + assert(store->version >= MIN_VERSION || !"unsupported data format"); + assert(store->version <= RELEASE_VERSION || !"unsupported data format"); + + if (store->version >= SAVEXMLNAME_VERSION) { + char basefile[1024]; + + store->r_str_buf(store, basefile, sizeof(basefile)); + if (strcmp(game_name, basefile) != 0) { + char buffer[64]; + strlcpy(buffer, game_name, sizeof(buffer)); + strlcat(buffer, ".xml", sizeof(buffer)); + if (strcmp(basefile, buffer) != 0) { + log_warning("game mismatch: datafile contains %s, game is %s\n", basefile, game_name); + printf("WARNING: any key to continue, Ctrl-C to stop\n"); + getchar(); + } + } + } + a_read(store, &global.attribs, NULL); + global.data_turn = turn = store->r_int(store); + log_printf(stdout, " - reading turn %d\n", turn); + rng_init(turn); + ++global.cookie; + store->r_int(store); /* max_unique_id = */ + nextborder = store->r_int(store); + + /* Planes */ + planes = NULL; + n = store->r_int(store); + while (--n >= 0) { + int id = store->r_int(store); + variant fno; + plane *pl = getplanebyid(id); + + if (pl == NULL) { + pl = calloc(1, sizeof(plane)); + } else { + log_warning("the plane with id=%d already exists.\n", id); + } + pl->id = id; + pl->name = store->r_str(store); + pl->minx = store->r_int(store); + pl->maxx = store->r_int(store); + pl->miny = store->r_int(store); + pl->maxy = store->r_int(store); + pl->flags = store->r_int(store); + + /* read watchers */ + if (store->version < FIX_WATCHERS_VERSION) { + char rname[64]; + /* before this version, watcher storage was pretty broken. we are incompatible and don't read them */ + for (;;) { + store->r_tok_buf(store, rname, sizeof(rname)); + if (strcmp(rname, "end") == 0) { + break; /* this is most likely the end of the list */ + } else { + log_error( + ("This datafile contains watchers, but we are unable to read them\n")); + } + } + } else { + fno = read_faction_reference(store); + while (fno.i) { + watcher *w = (watcher *) malloc(sizeof(watcher)); + ur_add(fno, &w->faction, resolve_faction); + w->mode = (unsigned char)store->r_int(store); + w->next = pl->watchers; + pl->watchers = w; + fno = read_faction_reference(store); + } + } + a_read(store, &pl->attribs, pl); + addlist(&planes, pl); + } + + /* Read factions */ + if (store->version >= ALLIANCES_VERSION) { + read_alliances(store); + } + n = store->r_int(store); + log_printf(stdout, " - Einzulesende Parteien: %d\n", n); + fp = &factions; + while (*fp) + fp = &(*fp)->next; + + while (--n >= 0) { + faction *f = readfaction(store); + + *fp = f; + fp = &f->next; + fhash(f); + } + *fp = 0; + + /* Benutzte Faction-Ids */ + if (store->version < STORAGE_VERSION) { + i = store->r_int(store); + while (i--) { + store->r_int(store); /* used faction ids. ignore. */ + } + } + + /* Regionen */ + + n = store->r_int(store); + assert(n < MAXREGIONS); + if (rmax < 0) { + rmax = n; + } + log_printf(stdout, " - Einzulesende Regionen: %d/%d\r", rmax, n); + while (--n >= 0) { + unit **up; + int x = store->r_int(store); + int y = store->r_int(store); + + if ((n & 0x3FF) == 0) { /* das spart extrem Zeit */ + log_printf(stdout, " - Einzulesende Regionen: %d/%d * %d,%d \r", rmax, n, x, y); + } + --rmax; + + r = readregion(store, x, y); + + /* Burgen */ + p = store->r_int(store); + bp = &r->buildings; + + while (--p >= 0) { + + b = (building *) calloc(1, sizeof(building)); + b->no = store->r_id(store); + *bp = b; + bp = &b->next; + bhash(b); + b->name = store->r_str(store); + if (lomem) { + store->r_str_buf(store, NULL, 0); + } else { + b->display = store->r_str(store); + } + b->size = store->r_int(store); + store->r_str_buf(store, token, sizeof(token)); + b->type = bt_find(token); + b->region = r; + a_read(store, &b->attribs, b); + if (b->type == bt_lighthouse) { + r->flags |= RF_LIGHTHOUSE; + } + } + /* Schiffe */ + + p = store->r_int(store); + shp = &r->ships; + + while (--p >= 0) { + ship *sh = (ship *) calloc(1, sizeof(ship)); + sh->region = r; + sh->no = store->r_id(store); + *shp = sh; + shp = &sh->next; + shash(sh); + sh->name = store->r_str(store); + if (lomem) { + store->r_str_buf(store, NULL, 0); + } else { + sh->display = store->r_str(store); + } + store->r_str_buf(store, token, sizeof(token)); + sh->type = st_find(token); + if (sh->type == NULL) { + /* old datafiles */ + sh->type = st_find((const char *)locale_string(default_locale, token)); + } + assert(sh->type || !"ship_type not registered!"); + sh->size = store->r_int(store); + sh->damage = store->r_int(store); + if (store->version >= FOSS_VERSION) { + sh->flags = store->r_int(store); + } + + /* Attribute rekursiv einlesen */ + + sh->coast = (direction_t) store->r_int(store); + if (sh->type->flags & SFL_NOCOAST) { + sh->coast = NODIRECTION; + } + a_read(store, &sh->attribs, sh); + } + + *shp = 0; + + /* Einheiten */ + + p = store->r_int(store); + up = &r->units; + + while (--p >= 0) { + unit *u = read_unit(store); + sc_mage *mage; + + assert(u->region == NULL); + u->region = r; + *up = u; + up = &u->next; + + update_interval(u->faction, u->region); + mage = get_mage(u); + if (mage) { + faction *f = u->faction; + int skl = effskill(u, SK_MAGIC); + if (!is_monsters(f) && f->magiegebiet == M_GRAY) { + log_error("faction %s had magic=gray, fixing (%s)\n", factionname(f), magic_school[mage->magietyp]); + f->magiegebiet = mage->magietyp; + } + if (f->max_spelllevel < skl) { + f->max_spelllevel = skl; + } + if (mage->spellcount < 0) { + mage->spellcount = 0; + } + } + } + } + log_printf(stdout, "\n"); + read_borders(store); + + store->close(store); + + /* Unaufgeloeste Zeiger initialisieren */ + log_printf(stdout, "fixing unresolved references.\n"); + resolve(); + + log_printf(stdout, "updating area information for lighthouses.\n"); + for (r = regions; r; r = r->next) { + if (r->flags & RF_LIGHTHOUSE) { + building *b; + for (b = r->buildings; b; b = b->next) + update_lighthouse(b); + } + } + log_printf(stdout, "marking factions as alive.\n"); + for (f = factions; f; f = f->next) { + if (f->flags & FFL_NPC) { + f->alive = 1; + if (f->no == 0) { + int no = 666; + while (findfaction(no)) + ++no; + log_warning("renum(monsters, %d)\n", no); + renumber_faction(f, no); + } + } else { + for (u = f->units; u; u = u->nextF) { + if (u->number > 0) { + f->alive = 1; + break; + } + } + } + } + if (loadplane || maxregions >= 0) { + remove_empty_factions(); + } + log_printf(stdout, "Done loading turn %d.\n", turn); + return 0; +} + +static void clear_monster_orders(void) +{ + faction *f = get_monsters(); + if (f) { + unit *u; + for (u = f->units; u; u = u->nextF) { + free_orders(&u->orders); + } + } +} + +int writegame(const char *filename, int mode) +{ + char *base; + int n; + faction *f; + region *r; + building *b; + ship *sh; + unit *u; + plane *pl; + char path[MAX_PATH]; + storage my_store = (mode == IO_BINARY) ? binary_store : text_store; + storage *store = &my_store; + store->version = RELEASE_VERSION; + + clear_monster_orders(); + sprintf(path, "%s/%s", datapath(), filename); +#ifdef HAVE_UNISTD_H + if (access(path, R_OK) == 0) { + /* make sure we don't overwrite some hardlinkedfile */ + unlink(path); + } +#endif + + store->encoding = enc_gamedata; + if (store->open(store, path, IO_WRITE) != 0) { + int err = os_mkdir(datapath(), 0700); + if (err) + return err; + if (store->open(store, path, IO_WRITE) != 0) { + return -1; + } + } + + /* globale Variablen */ + + base = strrchr(game_name, '/'); + if (base) { + store->w_str(store, base + 1); + } else { + store->w_str(store, game_name); + } + store->w_brk(store); + + a_write(store, global.attribs, NULL); + store->w_brk(store); + + store->w_int(store, turn); + store->w_int(store, 0 /*max_unique_id */ ); + store->w_int(store, nextborder); + + /* Write planes */ + store->w_brk(store); + store->w_int(store, listlen(planes)); + store->w_brk(store); + + for (pl = planes; pl; pl = pl->next) { + watcher *w; + store->w_int(store, pl->id); + store->w_str(store, pl->name); + store->w_int(store, pl->minx); + store->w_int(store, pl->maxx); + store->w_int(store, pl->miny); + store->w_int(store, pl->maxy); + store->w_int(store, pl->flags); + w = pl->watchers; + while (w) { + if (w->faction) { + write_faction_reference(w->faction, store); + store->w_int(store, w->mode); + } + w = w->next; + } + write_faction_reference(NULL, store); /* mark the end of the list */ + a_write(store, pl->attribs, pl); + store->w_brk(store); + } + + /* Write factions */ +#if RELEASE_VERSION>=ALLIANCES_VERSION + write_alliances(store); +#endif + n = listlen(factions); + store->w_int(store, n); + store->w_brk(store); + + log_printf(stdout, " - Schreibe %d Parteien...\n", n); + for (f = factions; f; f = f->next) { + writefaction(store, f); + store->w_brk(store); + } + + /* Write regions */ + + n = listlen(regions); + store->w_int(store, n); + store->w_brk(store); + log_printf(stdout, " - Schreibe Regionen: %d \r", n); + + for (r = regions; r; r = r->next, --n) { + /* plus leerzeile */ + if ((n % 1024) == 0) { /* das spart extrem Zeit */ + log_printf(stdout, " - Schreibe Regionen: %d \r", n); + fflush(stdout); + } + store->w_brk(store); + store->w_int(store, r->x); + store->w_int(store, r->y); + writeregion(store, r); + + store->w_int(store, listlen(r->buildings)); + store->w_brk(store); + for (b = r->buildings; b; b = b->next) { + write_building_reference(b, store); + store->w_str(store, b->name); + store->w_str(store, b->display ? b->display : ""); + store->w_int(store, b->size); + store->w_tok(store, b->type->_name); + store->w_brk(store); + a_write(store, b->attribs, b); + store->w_brk(store); + } + + store->w_int(store, listlen(r->ships)); + store->w_brk(store); + for (sh = r->ships; sh; sh = sh->next) { + assert(sh->region == r); + write_ship_reference(sh, store); + store->w_str(store, (const char *)sh->name); + store->w_str(store, sh->display ? (const char *)sh->display : ""); + store->w_tok(store, sh->type->name[0]); + store->w_int(store, sh->size); + store->w_int(store, sh->damage); + store->w_int(store, sh->flags & SFL_SAVEMASK); + assert((sh->type->flags & SFL_NOCOAST) == 0 || sh->coast == NODIRECTION); + store->w_int(store, sh->coast); + store->w_brk(store); + a_write(store, sh->attribs, sh); + store->w_brk(store); + } + + store->w_int(store, listlen(r->units)); + store->w_brk(store); + for (u = r->units; u; u = u->next) { + write_unit(store, u); + } + } + store->w_brk(store); + write_borders(store); + store->w_brk(store); + + store->close(store); + + log_printf(stdout, "\nOk.\n"); + return 0; +} + +int a_readint(attrib * a, void *owner, struct storage *store) +{ + /* assert(sizeof(int)==sizeof(a->data)); */ + a->data.i = store->r_int(store); + return AT_READ_OK; +} + +void a_writeint(const attrib * a, const void *owner, struct storage *store) +{ + store->w_int(store, a->data.i); +} + +int a_readshorts(attrib * a, void *owner, struct storage *store) +{ + if (store->version < ATTRIBREAD_VERSION) { + return a_readint(a, store, owner); + } + a->data.sa[0] = (short)store->r_int(store); + a->data.sa[1] = (short)store->r_int(store); + return AT_READ_OK; +} + +void a_writeshorts(const attrib * a, const void *owner, struct storage *store) +{ + store->w_int(store, a->data.sa[0]); + store->w_int(store, a->data.sa[1]); +} + +int a_readchars(attrib * a, void *owner, struct storage *store) +{ + int i; + if (store->version < ATTRIBREAD_VERSION) { + return a_readint(a, store, owner); + } + for (i = 0; i != 4; ++i) { + a->data.ca[i] = (char)store->r_int(store); + } + return AT_READ_OK; +} + +void a_writechars(const attrib * a, const void *owner, struct storage *store) +{ + int i; + + for (i = 0; i != 4; ++i) { + store->w_int(store, a->data.ca[i]); + } +} + +int a_readvoid(attrib * a, void *owner, struct storage *store) +{ + if (store->version < ATTRIBREAD_VERSION) { + return a_readint(a, store, owner); + } + return AT_READ_OK; +} + +void a_writevoid(const attrib * a, const void *owner, struct storage *store) +{ +} + +int a_readstring(attrib * a, void *owner, struct storage *store) +{ + a->data.v = store->r_str(store); + return AT_READ_OK; +} + +void a_writestring(const attrib * a, const void *owner, struct storage *store) +{ + assert(a->data.v); + store->w_str(store, (const char *)a->data.v); +} + +void a_finalizestring(attrib * a) +{ + free(a->data.v); +} diff --git a/core/src/kernel/save.h b/core/src/kernel/save.h new file mode 100644 index 000000000..cd1fd6533 --- /dev/null +++ b/core/src/kernel/save.h @@ -0,0 +1,81 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_SAVE +#define H_KRNL_SAVE +#ifdef __cplusplus +extern "C" { +#endif + + double version(void); + +#define MAX_INPUT_SIZE DISPLAYSIZE*2 +/* Nach MAX_INPUT_SIZE brechen wir das Einlesen der Zeile ab und nehmen an, + * dass hier ein Fehler (fehlende ") vorliegt */ + + FILE *cfopen(const char *filename, const char *mode); + int readorders(const char *filename); + int creategame(void); + extern int readgame(const char *filename, int mode, int backup); + int writegame(const char *filename, int mode); + + extern void rsf(FILE * F, char *s, size_t len); + +/* Versionsänderungen: */ + extern int data_version; + extern const char *game_name; + extern int enc_gamedata; + + extern void init_locales(void); + extern int current_turn(void); + + extern void read_items(struct storage *store, struct item **it); + extern void write_items(struct storage *store, struct item *it); + + extern void read_spellbook(struct spellbook **bookp, struct storage *store, int (*get_level)(const struct spell * sp, void *), void * cbdata); + extern void write_spellbook(const struct spellbook *book, struct storage *store); + + extern void write_unit(struct storage *store, const struct unit *u); + extern struct unit *read_unit(struct storage *store); + + extern int a_readint(struct attrib *a, void *owner, struct storage *store); + extern void a_writeint(const struct attrib *a, const void *owner, + struct storage *store); + extern int a_readshorts(struct attrib *a, void *owner, struct storage *store); + extern void a_writeshorts(const struct attrib *a, const void *owner, + struct storage *store); + extern int a_readchars(struct attrib *a, void *owner, struct storage *store); + extern void a_writechars(const struct attrib *a, const void *owner, + struct storage *store); + extern int a_readvoid(struct attrib *a, void *owner, struct storage *store); + extern void a_writevoid(const struct attrib *a, const void *owner, + struct storage *store); + extern int a_readstring(struct attrib *a, void *owner, struct storage *store); + extern void a_writestring(const struct attrib *a, const void *owner, + struct storage *store); + extern void a_finalizestring(struct attrib *a); + + extern int freadstr(FILE * F, int encoding, char *str, size_t size); + extern int fwritestr(FILE * F, const char *str); + + extern void create_backup(char *file); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/ship.c b/core/src/kernel/ship.c new file mode 100644 index 000000000..99e26b9a9 --- /dev/null +++ b/core/src/kernel/ship.c @@ -0,0 +1,348 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "ship.h" + +/* kernel includes */ +#include "build.h" +#include "unit.h" +#include "item.h" +#include "race.h" +#include "region.h" +#include "skill.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +quicklist *shiptypes = NULL; + +static local_names *snames; + +const ship_type *findshiptype(const char *name, const struct locale *lang) +{ + local_names *sn = snames; + variant var; + + while (sn) { + if (sn->lang == lang) + break; + sn = sn->next; + } + if (!sn) { + quicklist *ql; + int qi; + + sn = (local_names *) calloc(sizeof(local_names), 1); + sn->next = snames; + sn->lang = lang; + + for (qi = 0, ql = shiptypes; ql; ql_advance(&ql, &qi, 1)) { + ship_type *stype = (ship_type *) ql_get(ql, qi); + variant var2; + const char *n = locale_string(lang, stype->name[0]); + var2.v = (void *)stype; + addtoken(&sn->names, n, var2); + } + snames = sn; + } + if (findtoken(sn->names, name, &var) == E_TOK_NOMATCH) + return NULL; + return (const ship_type *)var.v; +} + +const ship_type *st_find(const char *name) +{ + quicklist *ql; + int qi; + + for (qi = 0, ql = shiptypes; ql; ql_advance(&ql, &qi, 1)) { + ship_type *stype = (ship_type *) ql_get(ql, qi); + if (strcmp(stype->name[0], name) == 0) { + return stype; + } + } + return NULL; +} + +void st_register(const ship_type * type) +{ + ql_push(&shiptypes, (void *)type); +} + +#define SMAXHASH 7919 +ship *shiphash[SMAXHASH]; +void shash(ship * s) +{ + ship *old = shiphash[s->no % SMAXHASH]; + + shiphash[s->no % SMAXHASH] = s; + s->nexthash = old; +} + +void sunhash(ship * s) +{ + ship **show; + + for (show = &shiphash[s->no % SMAXHASH]; *show; show = &(*show)->nexthash) { + if ((*show)->no == s->no) + break; + } + if (*show) { + assert(*show == s); + *show = (*show)->nexthash; + s->nexthash = 0; + } +} + +static ship *sfindhash(int i) +{ + ship *old; + + for (old = shiphash[i % SMAXHASH]; old; old = old->nexthash) + if (old->no == i) + return old; + return 0; +} + +struct ship *findship(int i) +{ + return sfindhash(i); +} + +struct ship *findshipr(const region * r, int n) +{ + ship *sh; + + for (sh = r->ships; sh; sh = sh->next) { + if (sh->no == n) { + assert(sh->region == r); + return sh; + } + } + return 0; +} + +void damage_ship(ship * sh, double percent) +{ + double damage = + DAMAGE_SCALE * sh->type->damage * percent * sh->size + sh->damage; + sh->damage = (int)damage; +} + +/* Alte Schiffstypen: */ +static ship *deleted_ships; + +ship *new_ship(const ship_type * stype, region * r, const struct locale *lang) +{ + static char buffer[32]; + ship *sh = (ship *) calloc(1, sizeof(ship)); + const char *sname = 0; + + assert(stype); + sh->no = newcontainerid(); + sh->coast = NODIRECTION; + sh->type = stype; + sh->region = r; + + sname = LOC(lang, stype->name[0]); + if (!sname) { + sname = LOC(lang, parameters[P_SHIP]); + if (!sname) { + sname = parameters[P_SHIP]; + } + } + assert(sname); + slprintf(buffer, sizeof(buffer), "%s %s", sname, shipid(sh)); + sh->name = strdup(buffer); + shash(sh); + if (r) { + addlist(&r->ships, sh); + } + return sh; +} + +void remove_ship(ship ** slist, ship * sh) +{ + region *r = sh->region; + unit *u = r->units; + + handle_event(sh->attribs, "destroy", sh); + while (u) { + if (u->ship == sh) { + leave_ship(u); + } + u = u->next; + } + sunhash(sh); + while (*slist && *slist != sh) + slist = &(*slist)->next; + assert(*slist); + *slist = sh->next; + sh->next = deleted_ships; + deleted_ships = sh; + sh->region = NULL; +} + +void free_ship(ship * s) +{ + while (s->attribs) + a_remove(&s->attribs, s->attribs); + free(s->name); + free(s->display); + free(s); +} + +void free_ships(void) +{ + while (deleted_ships) { + ship *s = deleted_ships; + deleted_ships = s->next; + } +} + +const char *write_shipname(const ship * sh, char *ibuf, size_t size) +{ + slprintf(ibuf, size, "%s (%s)", sh->name, itoa36(sh->no)); + return ibuf; +} + +const char *shipname(const ship * sh) +{ + typedef char name[OBJECTIDSIZE + 1]; + static name idbuf[8]; + static int nextbuf = 0; + char *ibuf = idbuf[(++nextbuf) % 8]; + return write_shipname(sh, ibuf, sizeof(name)); +} + +int shipcapacity(const ship * sh) +{ + int i = sh->type->cargo; + + /* sonst ist construction:: size nicht ship_type::maxsize */ + assert(!sh->type->construction + || sh->type->construction->improvement == NULL); + + if (sh->type->construction && sh->size != sh->type->construction->maxsize) + return 0; + +#ifdef SHIPDAMAGE + if (sh->damage) { + i = (int)ceil(i * (1.0 - sh->damage / sh->size / (double)DAMAGE_SCALE)); + } +#endif + return i; +} + +void getshipweight(const ship * sh, int *sweight, int *scabins) +{ + unit *u; + + *sweight = 0; + *scabins = 0; + + for (u = sh->region->units; u; u = u->next) { + if (u->ship == sh) { + *sweight += weight(u); + if (sh->type->cabins) { + int pweight = u->number * u_race(u)->weight; + /* weight goes into number of cabins, not cargo */ + *scabins += pweight; + *sweight -= pweight; + } + } + } +} + +void ship_set_owner(unit * u) { + assert(u && u->ship); + u->ship->_owner = u; +} + +static unit * ship_owner_ex(const ship * sh, const struct faction * last_owner) +{ + unit *u, *heir = 0; + + /* Eigentümer tot oder kein Eigentümer vorhanden. Erste lebende Einheit + * nehmen. */ + for (u = sh->region->units; u; u = u->next) { + if (u->ship == sh) { + if (u->number > 0) { + if (heir && last_owner && heir->faction!=last_owner && u->faction==last_owner) { + heir = u; + break; /* we found someone from the same faction who is not dead. let's take this guy */ + } + else if (!heir) { + heir = u; /* you'll do in an emergency */ + } + } + } + } + return heir; +} + +void ship_update_owner(ship * sh) { + unit * owner = sh->_owner; + sh->_owner = ship_owner_ex(sh, owner?owner->faction:0); +} + +unit *ship_owner(const ship * sh) +{ + unit *owner = sh->_owner; + if (!owner || (owner->ship!=sh || owner->number<=0)) { + unit * heir = ship_owner_ex(sh, owner?owner->faction:0); + return (heir && heir->number>0) ? heir : 0; + } + return owner; +} + +void write_ship_reference(const struct ship *sh, struct storage *store) +{ + store->w_id(store, (sh && sh->region) ? sh->no : 0); +} + +void ship_setname(ship * self, const char *name) +{ + free(self->name); + if (name) + self->name = strdup(name); + else + self->name = NULL; +} + +const char *ship_getname(const ship * self) +{ + return self->name; +} diff --git a/core/src/kernel/ship.h b/core/src/kernel/ship.h new file mode 100644 index 000000000..8249a1beb --- /dev/null +++ b/core/src/kernel/ship.h @@ -0,0 +1,129 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_SHIP +#define H_KRNL_SHIP +#ifdef __cplusplus +extern "C" { +#endif + +#include "types.h" + +#define DAMAGE_SCALE 100 /* multiplier for sh->damage */ + +/* ship_type::flags */ +#define SFL_OPENSEA 0x01 +#define SFL_FLY 0x02 +#define SFL_NOCOAST 0x04 + + typedef struct ship_type { + const char *name[2]; + + int range; /* range in regions */ + int flags; /* flags */ + int combat; /* modifier for combat */ + + double storm; /* multiplier for chance to drift in storm */ + double damage; /* multiplier for damage taken by the ship */ + + int cabins; /* max. cabins (weight) */ + int cargo; /* max. cargo (weight) */ + + int cptskill; /* min. skill of captain */ + int minskill; /* min. skill to sail this (crew) */ + int sumskill; /* min. sum of crew+captain */ + + int fishing; /* weekly income from fishing */ + + int at_bonus; /* Verändert den Angriffsskill (default: 0) */ + int df_bonus; /* Verändert den Verteidigungskill (default: 0) */ + float tac_bonus; + + const struct terrain_type **coasts; /* coast that this ship can land on */ + + struct construction *construction; /* how to build a ship */ + } ship_type; + + extern struct quicklist *shiptypes; + +/* Alte Schiffstypen: */ + + extern const ship_type *st_find(const char *name); + extern void st_register(const ship_type * type); + +#define NOSHIP NULL + +#define SF_DRIFTED 1<<0 +#define SF_MOVED 1<<1 +#define SF_DAMAGED 1<<2 /* for use in combat */ +#define SF_SELECT 1<<3 /* previously FL_DH */ +#define SF_FISHING 1<<4 /* was on an ocean, can fish */ +#define SF_FLYING 1<<5 /* the ship can fly */ + +#define SFL_SAVEMASK (SF_FLYING) +#define INCOME_FISHING 10 + + typedef struct ship { + struct ship *next; + struct ship *nexthash; + struct unit * _owner; /* never use directly, always use ship_owner() */ + int no; + struct region *region; + char *name; + char *display; + struct attrib *attribs; + int size; + int damage; /* damage in 100th of a point of size */ + unsigned int flags; + const struct ship_type *type; + direction_t coast; + } ship; + + extern void damage_ship(struct ship * sh, double percent); + extern void ship_set_owner(struct unit * u); + extern struct unit *ship_owner(const struct ship *sh); + extern void ship_update_owner(struct ship * sh); + + extern const char *shipname(const struct ship *self); + extern int shipcapacity(const struct ship *sh); + extern void getshipweight(const struct ship *sh, int *weight, int *cabins); + + extern ship *new_ship(const struct ship_type *stype, struct region *r, + const struct locale *lang); + extern const char *write_shipname(const struct ship *sh, char *buffer, + size_t size); + extern struct ship *findship(int n); + extern struct ship *findshipr(const struct region *r, int n); + + extern const struct ship_type *findshiptype(const char *s, + const struct locale *lang); + + extern void write_ship_reference(const struct ship *sh, + struct storage *store); + + extern void remove_ship(struct ship **slist, struct ship *s); + extern void free_ship(struct ship *s); + extern void free_ships(void); + + extern const char *ship_getname(const struct ship *self); + extern void ship_setname(struct ship *self, const char *name); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/ship_test.c b/core/src/kernel/ship_test.c new file mode 100644 index 000000000..a1aefeed8 --- /dev/null +++ b/core/src/kernel/ship_test.c @@ -0,0 +1,362 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static void test_register_ship(CuTest * tc) +{ + ship_type *stype; + + test_cleanup(); + + stype = (ship_type *)calloc(sizeof(ship_type), 1); + stype->name[0] = strdup("herp"); + st_register(stype); + + CuAssertPtrNotNull(tc, st_find("herp")); +} + +static void test_ship_set_owner(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u1, *u2; + struct faction *f; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + stype = st_find("boat"); + f = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + u1 = test_create_unit(f, r); + u_set_ship(u1, sh); + CuAssertPtrEquals(tc, u1, ship_owner(sh)); + + u2 = test_create_unit(f, r); + u_set_ship(u2, sh); + CuAssertPtrEquals(tc, u1, ship_owner(sh)); + ship_set_owner(u2); + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +static void test_shipowner_goes_to_next_when_empty(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u, *u2; + struct faction *f; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u = test_create_unit(f, r); + u2 = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_ship(u, sh); + u_set_ship(u2, sh); + CuAssertPtrEquals(tc, u, ship_owner(sh)); + u->number = 0; + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +static void test_shipowner_goes_to_other_when_empty(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u, *u2; + struct faction *f; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u2 = test_create_unit(f, r); + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_ship(u, sh); + u_set_ship(u2, sh); + CuAssertPtrEquals(tc, u, ship_owner(sh)); + u->number = 0; + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +static void test_shipowner_goes_to_same_faction_when_empty(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u, *u2, *u3; + struct faction *f1, *f2; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f1 = test_create_faction(human); + f2 = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u2 = test_create_unit(f2, r); + u3 = test_create_unit(f1, r); + u = test_create_unit(f1, r); + CuAssertPtrNotNull(tc, u); + u_set_ship(u, sh); + u_set_ship(u2, sh); + u_set_ship(u3, sh); + CuAssertPtrEquals(tc, u, ship_owner(sh)); + u->number = 0; + CuAssertPtrEquals(tc, u3, ship_owner(sh)); + u3->number = 0; + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +static void test_shipowner_goes_to_next_after_leave(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u, *u2; + struct faction *f; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u = test_create_unit(f, r); + u2 = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_ship(u, sh); + u_set_ship(u2, sh); + CuAssertPtrEquals(tc, u, ship_owner(sh)); + leave_ship(u); + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +static void test_shipowner_goes_to_other_after_leave(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u, *u2; + struct faction *f; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u2 = test_create_unit(f, r); + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_ship(u, sh); + u_set_ship(u2, sh); + CuAssertPtrEquals(tc, u, ship_owner(sh)); + leave_ship(u); + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +static void test_shipowner_goes_to_same_faction_after_leave(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u, *u2, *u3; + struct faction *f1, *f2; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f1 = test_create_faction(human); + f2 = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u2 = test_create_unit(f2, r); + u3 = test_create_unit(f1, r); + u = test_create_unit(f1, r); + CuAssertPtrNotNull(tc, u); + u_set_ship(u, sh); + u_set_ship(u2, sh); + u_set_ship(u3, sh); + CuAssertPtrEquals(tc, u, ship_owner(sh)); + leave_ship(u); + CuAssertPtrEquals(tc, u3, ship_owner(sh)); + leave_ship(u3); + CuAssertPtrEquals(tc, u2, ship_owner(sh)); + leave_ship(u2); + CuAssertPtrEquals(tc, 0, ship_owner(sh)); +} + +static void test_shipowner_resets_when_empty(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u; + struct faction *f; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u = test_create_unit(f, r); + CuAssertPtrNotNull(tc, u); + u_set_ship(u, sh); + CuAssertPtrEquals(tc, u, ship_owner(sh)); + u->number = 0; + CuAssertPtrEquals(tc, 0, ship_owner(sh)); + u->number = 1; + CuAssertPtrEquals(tc, u, ship_owner(sh)); +} + +void test_shipowner_goes_to_empty_unit_after_leave(CuTest * tc) +{ + struct region *r; + struct ship *sh; + struct unit *u1, *u2, *u3; + struct faction *f1; + const struct ship_type *stype; + const struct race *human; + + test_cleanup(); + test_create_world(); + + human = rc_find("human"); + CuAssertPtrNotNull(tc, human); + + stype = st_find("boat"); + CuAssertPtrNotNull(tc, stype); + + f1 = test_create_faction(human); + r = findregion(0, 0); + + sh = test_create_ship(r, stype); + CuAssertPtrNotNull(tc, sh); + + u1 = test_create_unit(f1, r); + u2 = test_create_unit(f1, r); + u3 = test_create_unit(f1, r); + u_set_ship(u1, sh); + u_set_ship(u2, sh); + u_set_ship(u3, sh); + + CuAssertPtrEquals(tc, u1, ship_owner(sh)); + u2->number = 0; + leave_ship(u1); + CuAssertPtrEquals(tc, u3, ship_owner(sh)); + leave_ship(u3); + CuAssertPtrEquals(tc, 0, ship_owner(sh)); + u2->number = 1; + CuAssertPtrEquals(tc, u2, ship_owner(sh)); +} + +CuSuite *get_ship_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_register_ship); + SUITE_ADD_TEST(suite, test_ship_set_owner); + SUITE_ADD_TEST(suite, test_shipowner_resets_when_empty); + SUITE_ADD_TEST(suite, test_shipowner_goes_to_next_when_empty); + SUITE_ADD_TEST(suite, test_shipowner_goes_to_other_when_empty); + SUITE_ADD_TEST(suite, test_shipowner_goes_to_same_faction_when_empty); + SUITE_ADD_TEST(suite, test_shipowner_goes_to_next_after_leave); + SUITE_ADD_TEST(suite, test_shipowner_goes_to_other_after_leave); + SUITE_ADD_TEST(suite, test_shipowner_goes_to_same_faction_after_leave); + SUITE_ADD_TEST(suite, test_shipowner_goes_to_empty_unit_after_leave); + return suite; +} diff --git a/core/src/kernel/skill.c b/core/src/kernel/skill.c new file mode 100644 index 000000000..2cdc3d25b --- /dev/null +++ b/core/src/kernel/skill.c @@ -0,0 +1,325 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "skill.h" + +#include "curse.h" +#include "item.h" +#include "magic.h" +#include "race.h" +#include "region.h" +#include "terrain.h" +#include "terrainid.h" +#include "unit.h" + +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +const char *skillnames[MAXSKILLS] = { + "alchemy", + "crossbow", + "mining", + "bow", + "building", + "trade", + "forestry", + "catapult", + "herbalism", + "magic", + "training", + "riding", + "armorer", + "shipcraft", + "melee", + "sailing", + "polearm", + "espionage", + "quarrying", + "roadwork", + "tactics", + "stealth", + "entertainment", + "weaponsmithing", + "cartmaking", + "perception", + "taxation", + "stamina", + "unarmed" +}; + +bool skill_enabled[MAXSKILLS]; + +const char *skillname(skill_t sk, const struct locale *lang) +{ + if (skill_enabled[sk]) { + return locale_string(lang, mkname("skill", skillnames[sk])); + } + return NULL; +} + +void enable_skill(const char *skname, bool value) +{ + skill_t sk; + for (sk = 0; sk != MAXSKILLS; ++sk) { + if (strcmp(skillnames[sk], skname) == 0) { + skill_enabled[sk] = value; + return; + } + } + log_error("Trying to set unknown skill %s to %u", skname, value); +} + +skill_t sk_find(const char *name) +{ + skill_t i; + if (name == NULL) + return NOSKILL; + if (strncmp(name, "sk_", 3) == 0) + name += 3; + for (i = 0; i != MAXSKILLS; ++i) { + if (skill_enabled[i]) { + if (strcmp(name, skillnames[i]) == 0) + return i; + } + } + return NOSKILL; +} + +/** skillmod attribut **/ +static void init_skillmod(attrib * a) +{ + a->data.v = calloc(sizeof(skillmod_data), 1); +} + +static void finalize_skillmod(attrib * a) +{ + free(a->data.v); +} + +/** temporary skill modification (NOT SAVED!). */ +attrib_type at_skillmod = { + "skillmod", + init_skillmod, + finalize_skillmod, + NULL, + NULL, /* can't write function pointers */ + NULL, /* can't read function pointers */ + ATF_PRESERVE +}; + +attrib *make_skillmod(skill_t sk, unsigned int flags, skillmod_fun special, + double multiplier, int bonus) +{ + attrib *a = a_new(&at_skillmod); + skillmod_data *smd = (skillmod_data *) a->data.v; + + smd->skill = sk; + smd->special = special; + smd->bonus = bonus; + smd->multiplier = multiplier; + smd->flags = flags; + + return a; +} + +int +skillmod(const attrib * a, const unit * u, const region * r, skill_t sk, + int value, int flags) +{ + for (a = a_find((attrib *) a, &at_skillmod); a && a->type == &at_skillmod; + a = a->next) { + skillmod_data *smd = (skillmod_data *) a->data.v; + if (smd->skill != NOSKILL && smd->skill != sk) + continue; + if (flags != SMF_ALWAYS && (smd->flags & flags) == 0) + continue; + if (smd->special) { + value = smd->special(u, r, sk, value); + if (value < 0) + return value; /* pass errors back to caller */ + } + if (smd->multiplier) + value = (int)(value * smd->multiplier); + value += smd->bonus; + } + return value; +} + +int skill_mod(const race * rc, skill_t sk, const struct terrain_type *terrain) +{ + int result = 0; + + result = rc->bonus[sk]; + + if (rc == new_race[RC_DWARF]) { + if (sk == SK_TACTICS) { + if (terrain == newterrain(T_MOUNTAIN) || fval(terrain, ARCTIC_REGION)) + ++result; + } + } else if (rc == new_race[RC_INSECT]) { + if (terrain == newterrain(T_MOUNTAIN) || fval(terrain, ARCTIC_REGION)) + --result; + else if (terrain == newterrain(T_DESERT) || terrain == newterrain(T_SWAMP)) + ++result; + } + + return result; +} + +#define RCMODMAXHASH 31 +#ifdef FASTER_SKILLMOD +static struct skillmods { + struct skillmods *next; + const struct race *race; + struct modifiers { + int value[MAXSKILLS]; + } mod[MAXTERRAINS]; +} *modhash[RCMODMAXHASH]; + +static struct skillmods *init_skills(const race * rc) +{ + terrain_t t; + struct skillmods *mods = + (struct skillmods *)calloc(1, sizeof(struct skillmods)); + mods->race = rc; + + for (t = 0; t != MAXTERRAINS; ++t) { + skill_t sk; + for (sk = 0; sk != MAXSKILLS; ++sk) { + mods->mod[t].value[sk] = skill_mod(rc, sk, newterrain(t)); + } + } + return mods; +} +#endif + +int rc_skillmod(const struct race *rc, const region * r, skill_t sk) +{ + int mods = 0; + + if (!skill_enabled[sk]) { + return 0; + } +#ifdef FASTER_SKILLMOD + unsigned int index = hashstring(rc->_name[0]) % RCMODMAXHASH; + struct skillmods **imods = &modhash[index]; + while (*imods && (*imods)->race != rc) { + imods = &(*imods)->next; + } + if (*imods == NULL) { + *imods = init_skills(rc); + } + mods = (*imods)->mod[rterrain(r)].value[sk]; +#else + if (r) { + mods = skill_mod(rc, sk, r->terrain); + } +#endif + if (rc == new_race[RC_ELF] && r && r_isforest(r)) { + if (sk == SK_PERCEPTION || sk == SK_STEALTH) { + ++mods; + } else if (sk == SK_TACTICS) { + mods += 2; + } + } + return mods; +} + +int level_days(int level) +{ + return 30 * ((level + 1) * level / 2); +} + +int level(int days) +{ + int i; + static int ldays[64]; + static bool init = false; + if (!init) { + init = true; + for (i = 0; i != 64; ++i) + ldays[i] = level_days(i + 1); + } + for (i = 0; i != 64; ++i) + if (ldays[i] > days) + return i; + return i; +} + +void sk_set(skill * sv, int level) +{ + assert(level != 0); + sv->weeks = (unsigned char)skill_weeks(level); + sv->level = (unsigned char)level; +} + +static int rule_random_progress(void) +{ + return get_param_int(global.parameters, "study.random_progress", 1); +} + +int skill_weeks(int level) +/* how many weeks must i study to get from level to level+1 */ +{ + if (rule_random_progress()) { + int coins = 2 * level; + int heads = 1; + while (coins--) { + heads += rng_int() % 2; + } + return heads; + } + return level + 1; +} + +void reduce_skill(unit * u, skill * sv, unsigned int weeks) +{ + sv->weeks += weeks; + while (sv->level > 0 && sv->level * 2 + 1 < sv->weeks) { + sv->weeks -= sv->level; + --sv->level; + } + if (sv->level == 0) { + /* reroll */ + sv->weeks = (unsigned char)skill_weeks(sv->level); + } +} + +int skill_compare(const skill * sk, const skill * sc) +{ + if (sk->level > sc->level) + return 1; + if (sk->level < sc->level) + return -1; + if (sk->weeks < sc->weeks) + return 1; + if (sk->weeks > sc->weeks) + return -1; + return 0; +} diff --git a/core/src/kernel/skill.h b/core/src/kernel/skill.h new file mode 100644 index 000000000..4db618f5a --- /dev/null +++ b/core/src/kernel/skill.h @@ -0,0 +1,68 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + */ + +#ifndef H_KRNL_SKILL +#define H_KRNL_SKILL +#ifdef __cplusplus +extern "C" { +#endif + +/* skillmod_data::flags -- wann gilt der modifier? */ +#define SMF_ALWAYS (1<<0) /* immer */ +#define SMF_PRODUCTION (1<<1) /* für Produktion - am gebäude, an der einheit */ +#define SMF_RIDING (1<<2) /* Bonus für berittene - an der rasse */ + + typedef struct skill { + int id:8; + unsigned int level:8; + unsigned int weeks:8; + unsigned int old:8; + } skill; + + typedef int (*skillmod_fun) (const struct unit *, const struct region *, + skill_t, int); + typedef struct skillmod_data { + skill_t skill; + skillmod_fun special; + double multiplier; + int number; + int bonus; + int flags; + } skillmod_data; + extern struct attrib_type at_skillmod; + extern int rc_skillmod(const struct race *rc, const struct region *r, + skill_t sk); + extern int skillmod(const struct attrib *a, const struct unit *u, + const struct region *r, skill_t sk, int value, int flags); + extern struct attrib *make_skillmod(skill_t sk, unsigned int flags, + skillmod_fun special, double multiplier, int bonus); + + extern const char *skillname(skill_t, const struct locale *); + extern skill_t sk_find(const char *name); + extern void enable_skill(const char *name, bool value); + extern int level_days(int level); + extern int level(int days); + +#define skill_level(level) (level) + extern void reduce_skill(struct unit *u, skill * sv, unsigned int change); + extern int skill_weeks(int level); + extern int skill_compare(const skill * sk, const skill * sc); + + extern void sk_set(skill * sv, int level); + + extern const char *skillnames[]; + extern bool skill_enabled[]; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/spell.c b/core/src/kernel/spell.c new file mode 100644 index 000000000..41966ef5d --- /dev/null +++ b/core/src/kernel/spell.c @@ -0,0 +1,131 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "spell.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include + +static critbit_tree cb_spells; +quicklist * spells; + +void free_spells(void) { + cb_clear(&cb_spells); + ql_free(spells); + spells = 0; +} + +void add_spell(struct quicklist **slistp, spell * sp) +{ + if (ql_set_insert(slistp, sp) != 0) { + log_error("add_spell: the list already contains the spell '%s'.\n", sp->sname); + } +} + +spell * create_spell(const char * name, unsigned int id) +{ + spell * sp; + char buffer[64]; + size_t len = strlen(name); + + assert(len+sizeof(sp)id = id ? id : hashstring(name); + sp->sname = strdup(name); + add_spell(&spells, sp); + return sp; + } + free(sp); + return 0; +} + +static const char *sp_aliases[][2] = { + {"gwyrrdfamiliar", "summon_familiar"}, + {"illaunfamiliar", "summon_familiar"}, + {"draigfamiliar", "summon_familiar"}, + {"commonfamiliar", "summon_familiar"}, + {NULL, NULL}, +}; + +static const char *sp_alias(const char *zname) +{ + int i; + for (i = 0; sp_aliases[i][0]; ++i) { + if (strcmp(sp_aliases[i][0], zname) == 0) + return sp_aliases[i][1]; + } + return zname; +} + +spell *find_spell(const char *name) +{ + const char * match; + spell * sp = 0; + const char * alias = sp_alias(name); + + match = cb_find_str(&cb_spells, alias); + if (match) { + cb_get_kv(match, &sp, sizeof(sp)); + } else { + log_warning("find_spell: could not find spell '%s'\n", name); + } + return sp; +} + +spell *find_spellbyid(unsigned int id) +{ + quicklist *ql; + int qi; + + if (id == 0) + return NULL; + for (qi = 0, ql = spells; ql; ql_advance(&ql, &qi, 1)) { + spell *sp = (spell *) ql_get(ql, qi); + if (sp->id == id) { + return sp; + } + } + for (qi = 0, ql = spells; ql; ql_advance(&ql, &qi, 1)) { + spell *sp = (spell *) ql_get(ql, qi); + unsigned int hashid = hashstring(sp->sname); + if (hashid == id) { + return sp; + } + } + + log_warning("cannot find spell by id: %u\n", id); + return NULL; +} diff --git a/core/src/kernel/spell.h b/core/src/kernel/spell.h new file mode 100644 index 000000000..eb08a44f3 --- /dev/null +++ b/core/src/kernel/spell.h @@ -0,0 +1,160 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_SPELL +#define H_KRNL_SPELL +#ifdef __cplusplus +extern "C" { +#endif + + struct castorder; + typedef int (*spell_f)(struct castorder * co); + typedef void(*fumble_f)(struct castorder * co); + + typedef struct spell { + unsigned int id; + char *sname; + char *syntax; + char *parameter; + int sptyp; + int rank; /* Reihenfolge der Zauber */ + struct spell_component *components; + spell_f cast; + fumble_f patzer; + } spell; + + int use_item_power(struct region *r, struct unit *u); + int use_item_regeneration(struct region *r, struct unit *u); + void showspells(struct region *r, struct unit *u); + int sp_antimagiczone(struct castorder *co); + + extern struct spell * create_spell(const char * name, unsigned int id); + extern struct spell * find_spell(const char *name); + extern struct spell * find_spellbyid(unsigned int i); + extern void add_spell(struct quicklist **slistp, spell * sp); + extern void free_spells(void); + + /** globals **/ + extern struct attrib_type at_unitdissolve; + extern struct attrib_type at_wdwpyramid; + extern struct quicklist * spells; +#ifdef __cplusplus +} +#endif +#endif +/* ------------------------------------------------------------- *//* Erläuterungen zu den Spruchdefinitionen + * + * Spruchstukturdefinition: + * spell{ + * id, name, + * beschreibung, + * syntax, + * parameter, + * magietyp, + * sptyp, + * rank,level, + * costtyp, aura, + * komponenten[5][2][faktorart], + * &funktion, patzer} + * + * id: + * SPL_NOSPELL muss der letzte Spruch in der Liste spelldaten sein, + * denn nicht auf die Reihenfolge in der Liste sondern auf die id wird + * geprüft + * + * sptyp: + * besondere Spruchtypen und Flags + * (Regionszauber, Kampfzauber, Farcastbar, Stufe variable, ..) + * + * rank: + * gibt die Priorität und damit die Reihenfolge an, in der der Spruch + * gezaubert wird. + * 1: Aura übertragen + * 2: Antimagie + * 3: Magierverändernde Sprüche (Magic Boost, ..) + * 4: Monster erschaffen + * 5: Standartlevel + * 7: Teleport + * + * Komponenten[Anzahl mögl. Items][Art:Anzahl:Kostentyp] + * + * R_AURA: + * Grundkosten für einen Zauber. Soviel Mp müssen mindestens investiert + * werden, um den Spruch zu wirken. Zusätzliche Mp können unterschiedliche + * Auswirkungen haben, die in der Spruchfunktionsroutine definiert werden. + * + * R_PERMAURA: + * Kosten an permantenter Aura + * + * Komponenten Kostentyp: + * SPC_LEVEL == Spruch mit Levelabhängigen Magiekosten. Die angegeben + * Kosten müssen für Stufe 1 berechnet sein. + * SPC_FIX == Feste Kosten + * + * Wenn keine spezielle Syntax angegeben ist, wird die + * Syntaxbeschreibung aus sptyp generiert: + * FARCASTING: ZAUBER [REGION x y] + * SPELLLEVEL: ZAUBER [STUFE n] + * UNITSPELL : ZAUBER [ ..] + * SHIPSPELL : ZAUBER [ ..] + * BUILDINGSPELL: ZAUBER [ ..] + * PRECOMBATSPELL : KAMPFZAUBER [STUFE n] + * COMBATSPELL : KAMPFZAUBER [STUFE n] + * POSTCOMBATSPELL: KAMPFZAUBER [STUFE n] + * + * Das Parsing + * + * Der String spell->parameter gibt die Syntax an, nach der die + * Parameter des Spruches in add_spellparameter() geparst werden sollen. + * + * u : eine Einheitennummer + * r : hier kommen zwei Regionskoordinaten x y + * b : Gebäude- oder Burgnummer + * s : Schiffsnummer + * c : String, wird ohne Weiterverarbeitung übergeben + * i : Zahl (int), wird ohne Weiterverarbeitung übergeben + * k : Keywort - dieser String gibt den Paramter an, der folgt. Der + * Parameter wird mit findparam() identifiziert. + * k muss immer von einem c als Platzhalter für das Objekt gefolgt + * werden. + * Ein gutes Beispiel sind hierfür die Sprüche zur Magieanalyse. + * + : gibt an, das der vorherige Parameter mehrfach vorkommen kann. Da + * ein Ende nicht definiert werden kann, muss dies immer am Schluss + * kommen. + * + * Flags für das Parsing: + * TESTRESISTANCE : alle Zielobjekte, also alle Parameter vom Typ Unit, + * Burg, Schiff oder Region, werden auf ihre + * Magieresistenz überprüft + * TESTCANSEE : jedes Objekt vom Typ Einheit wird auf seine + * Sichtbarkeit überprüft + * SEARCHLOCAL : die Zielobjekte werden nur regional gesucht + * REGIONSPELL : Ziel ist die Region, auch wenn kein Zielobjekt + * angegeben wird. Ist TESTRESISTANCE gesetzt, so wird + * die Magieresistenz der Region überprüft + * + * Bei fehlendem Ziel oder wenn dieses dem Zauber widersteht, wird die + * Spruchfunktion nicht aufgerufen. + * Sind zu wenig Parameter vorhanden, wird der Zauber ebenfalls nicht + * ausgeführt. + * Ist eins von mehreren Zielobjekten resistent, so wird das Flag + * pa->param[n]->flag == TARGET_RESISTS + * Ist eins von mehreren Zielobjekten nicht gefunden worden, so ist + * pa->param[n]->flag == TARGET_NOTFOUND + * + *//* ------------------------------------------------------------- */ diff --git a/core/src/kernel/spell_test.c b/core/src/kernel/spell_test.c new file mode 100644 index 000000000..90ab1cdf0 --- /dev/null +++ b/core/src/kernel/spell_test.c @@ -0,0 +1,57 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +static void test_create_spell(CuTest * tc) +{ + spell * sp; + + test_cleanup(); + CuAssertPtrEquals(tc, 0, spells); + CuAssertPtrEquals(tc, 0, find_spell("testspell")); + + sp = create_spell("testspell", 0); + CuAssertPtrEquals(tc, sp, find_spell("testspell")); + CuAssertPtrNotNull(tc, spells); +} + +static void test_create_duplicate_spell(CuTest * tc) +{ + spell *sp; + + test_cleanup(); + CuAssertPtrEquals(tc, 0, find_spell("testspell")); + + sp = create_spell("testspell", 0); + CuAssertPtrEquals(tc, 0, create_spell("testspell", 0)); + CuAssertPtrEquals(tc, sp, find_spell("testspell")); +} + +static void test_create_spell_with_id(CuTest * tc) +{ + spell *sp; + + test_cleanup(); + CuAssertPtrEquals(tc, 0, find_spellbyid(42)); + sp = create_spell("testspell", 42); + CuAssertPtrEquals(tc, sp, find_spellbyid(42)); + CuAssertPtrEquals(tc, 0, create_spell("testspell", 47)); + CuAssertPtrEquals(tc, 0, find_spellbyid(47)); +} + +CuSuite *get_spell_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_create_spell); + SUITE_ADD_TEST(suite, test_create_duplicate_spell); + SUITE_ADD_TEST(suite, test_create_spell_with_id); + return suite; +} diff --git a/core/src/kernel/spellbook.c b/core/src/kernel/spellbook.c new file mode 100644 index 000000000..4f2b736b6 --- /dev/null +++ b/core/src/kernel/spellbook.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include + +#include "spellbook.h" + +#include + +spellbook * create_spellbook(const char * name) +{ + spellbook *result = (spellbook *)malloc(sizeof(spellbook)); + result->name = name ? strdup(name) : 0; + result->spells = 0; + return result; +} + +void spellbook_add(spellbook *sb, struct spell * sp, int level) +{ + spellbook_entry * sbe; + + assert(sb && sp && level>0); +#ifndef NDEBUG + if (spellbook_get(sb, sp)) { + log_error("duplicate spell in spellbook '%s': '%s'\n", sb->name, sp->sname); + } +#endif + sbe = (spellbook_entry *)malloc(sizeof(spellbook_entry)); + sbe->sp = sp; + sbe->level = level; + ql_push(&sb->spells, sbe); +} + +void spellbook_clear(spellbook *sb) +{ + quicklist *ql; + int qi; + + assert(sb); + for (qi = 0, ql = sb->spells; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry *sbe = (spellbook_entry *) ql_get(ql, qi); + free(sbe); + } + ql_free(sb->spells); +} + +int spellbook_foreach(spellbook *sb, int (*callback)(spellbook_entry *, void *), void * data) +{ + quicklist *ql; + int qi; + + for (qi = 0, ql = sb->spells; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry *sbe = (spellbook_entry *) ql_get(ql, qi); + int result = callback(sbe, data); + if (result) { + return result; + } + } + return 0; +} + +spellbook_entry * spellbook_get(spellbook *sb, const struct spell * sp) +{ + if (sb) { + quicklist *ql; + int qi; + + for (qi = 0, ql = sb->spells; ql; ql_advance(&ql, &qi, 1)) { + spellbook_entry *sbe = (spellbook_entry *) ql_get(ql, qi); + if (sp==sbe->sp) { + return sbe; + } + } + } + return 0; +} + diff --git a/core/src/kernel/spellbook.h b/core/src/kernel/spellbook.h new file mode 100644 index 000000000..94b3eb448 --- /dev/null +++ b/core/src/kernel/spellbook.h @@ -0,0 +1,50 @@ +/* +Copyright (c) 1998-2012, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_SPELLBOOK_H +#define H_KRNL_SPELLBOOK_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct spell; +struct quicklist; + +typedef struct spellbook_entry { + struct spell * sp; + int level; +} spellbook_entry; + +typedef struct spellbook +{ + char * name; + struct quicklist * spells; +} spellbook; + +spellbook * create_spellbook(const char * name); + +void spellbook_add(spellbook *sbp, struct spell * sp, int level); +int spellbook_foreach(spellbook *sb, int (*callback)(spellbook_entry *, void *), void * data); +void spellbook_clear(spellbook *sb); +spellbook_entry * spellbook_get(spellbook *sb, const struct spell * sp); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/spellbook_test.c b/core/src/kernel/spellbook_test.c new file mode 100644 index 000000000..277952e0c --- /dev/null +++ b/core/src/kernel/spellbook_test.c @@ -0,0 +1,63 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + + +int count_spell_cb(spellbook_entry * sbe, void * ptr) +{ + int * counter = (int *)ptr; + ++*counter; + return 0; +} + +void test_named_spellbooks(CuTest * tc) +{ + spell *sp; + spellbook *sb; + spellbook_entry *sbe; + int counter = 0; + + sb = create_spellbook(0); + CuAssertPtrNotNull(tc, sb); + CuAssertPtrEquals(tc, 0, sb->name); + + sb = create_spellbook("spells"); + CuAssertPtrNotNull(tc, sb); + CuAssertStrEquals(tc, "spells", sb->name); + + sp = create_spell("testspell", 0); + spellbook_add(sb, sp, 1); + CuAssertPtrNotNull(tc, sb->spells); + + sbe = spellbook_get(sb, sp); + CuAssertPtrNotNull(tc, sbe); + CuAssertIntEquals(tc, 1, sbe->level); + CuAssertPtrEquals(tc, sp, sbe->sp); + + spellbook_foreach(sb, count_spell_cb, &counter); + CuAssertIntEquals(tc, 1, counter); + +#ifdef TODO + /* try adding the same spell twice. that should fail */ + spellbook_add(sb, sp, 1); + spellbook_foreach(sb, count_spell_cb, &counter); + CuAssertIntEquals(tc, 1, counter); +#endif + spellbook_clear(sb); + free(sb); +} + +CuSuite *get_spellbook_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_named_spellbooks); + return suite; +} diff --git a/core/src/kernel/spellid.h b/core/src/kernel/spellid.h new file mode 100644 index 000000000..8b0e7c668 --- /dev/null +++ b/core/src/kernel/spellid.h @@ -0,0 +1,176 @@ +/* vi: set ts=2: +* +-------------------+ Christian Schlittchen +* | | Enno Rehling +* | Eressea PBEM host | Katja Zedel +* | (c) 1998 - 2005 | +* | | This program may not be used, modified or distributed +* +-------------------+ without prior permission by the authors of Eressea. +* +*/ + +#ifndef H_KRNL_SPELLID +#define H_KRNL_SPELLID + +/* Sprüche. Neue NUR hinten anfügen, oder das Datenfile geht kaputt */ +enum { + SPL_NOSPELL = 0, + SPL_FIREBALL = 4, + SPL_HAGEL, + SPL_RUSTWEAPON, + SPL_COMBATRUST, + SPL_TREEGROW, + SPL_HEALING, + SPL_HEALINGSONG, + SPL_BADDREAMS, + SPL_GOODDREAMS, + SPL_DREAMREADING, + SPL_SWEETDREAMS, + SPL_TIREDSOLDIERS, + SPL_PLAGUE, + SPL_MAGICBOOST, + SPL_CHAOSROW, + SPL_SONG_OF_CONFUSION, + SPL_FLEE, + SPL_SONG_OF_FEAR, + SPL_BERSERK, + SPL_BLOODTHIRST, + SPL_MAELSTROM, + SPL_TRANSFERAURA_DRUIDE = 27, + SPL_TRANSFERAURA_BARDE, + SPL_TRANSFERAURA_CHAOS, + SPL_TRANSFERAURA_TRAUM, + SPL_TRANSFERAURA_ASTRAL, + SPL_STONEGOLEM, + SPL_IRONGOLEM, + SPL_SUMMONSHADOW, + SPL_SUMMONSHADOWLORDS, + SPL_REELING_ARROWS, + SPL_ANTIMAGICZONE = 37, + SPL_KAELTESCHUTZ = 39, + SPL_STEALAURA, + SPL_SUMMONUNDEAD, + SPL_AURALEAK, + SPL_GREAT_DROUGHT, + SPL_STRONG_WALL, + SPL_HOMESTONE, + SPL_DROUGHT, + SPL_FOREST_FIRE = 47, + SPL_SUMMONENT = 49, + SPL_DISTURBINGDREAMS, + SPL_DENYATTACK, + SPL_SLEEP, + SPL_EARTHQUAKE, + SPL_IRONKEEPER, + SPL_STORMWINDS, + SPL_GOODWINDS, + SPL_FLYING_SHIP, + SPL_SUMMON_ALP, + SPL_WINDSHIELD, + SPL_RAISEPEASANTS, + SPL_DEPRESSION, + SPL_HEADACHE = 62, + SPL_ENTERASTRAL = 64, + SPL_LEAVEASTRAL, + SPL_SHOWASTRAL, + SPL_VERSTEINERN, + SPL_TREEWALKENTER, + SPL_TREEWALKEXIT, + SPL_CHAOSSUCTION, + SPL_VIEWREALITY, + SPL_DISRUPTASTRAL, + SPL_SEDUCE, + SPL_PUMP, + SPL_CALM_MONSTER, + SPL_HERO, + SPL_FRIGHTEN, + SPL_MINDBLAST, + SPL_SPEED, + SPL_SPEED2, + SPL_FIREDRAGONODEM, + SPL_DRAGONODEM, + SPL_WYRMODEM, /* 83 */ + SPL_MAGICSTREET, + SPL_REANIMATE, + SPL_RECRUIT, + SPL_GENEROUS, + SPL_PERMTRANSFER, + SPL_SONG_OF_PEACE, + SPL_MIGRANT, + SPL_RALLYPEASANTMOB, + SPL_RAISEPEASANTMOB, + SPL_ILL_SHAPESHIFT, + SPL_WOLFHOWL, + SPL_FOG_OF_CONFUSION, + SPL_DREAM_OF_CONFUSION, + SPL_RESISTMAGICBONUS, + SPL_KEEPLOOT, + SPL_SCHILDRUNEN, + SPL_SONG_RESISTMAGIC, + SPL_SONG_SUSCEPTMAGIC, + SPL_ANALYSEMAGIC, + SPL_ANALYSEDREAM, + SPL_UNIT_ANALYSESONG, + SPL_OBJ_ANALYSESONG, + SPL_TYBIED_DESTROY_MAGIC, + SPL_DESTROY_MAGIC, + SPL_METEORRAIN, + SPL_REDUCESHIELD, + SPL_ARMORSHIELD, + SPL_DEATHCLOUD, + SPL_ORKDREAM, + SPL_SUMMONDRAGON = 113, + SPL_MOVECASTLE = 116, + SPL_BLESSSTONECIRCLE, + SPL_ILLAUN_FAMILIAR, + SPL_GWYRRD_FAMILIAR, + SPL_DRAIG_FAMILIAR, + SPL_CERDDOR_FAMILIAR, + SPL_TYBIED_FAMILIAR, + SPL_SONG_OF_ENSLAVE = 123, + SPL_FUMBLECURSE = 136, + SPL_ICASTLE, + SPL_GWYRRD_DESTROY_MAGIC, + SPL_DRAIG_DESTROY_MAGIC, + SPL_ILLAUN_DESTROY_MAGIC, + SPL_CERDDOR_DESTROY_MAGIC, + SPL_GWYRRD_ARMORSHIELD, + SPL_DRAIG_FUMBLESHIELD, + SPL_GWYRRD_FUMBLESHIELD, + SPL_CERRDOR_FUMBLESHIELD, + SPL_TYBIED_FUMBLESHIELD, + SPL_SHADOWKNIGHTS = 147, + SPL_ITEMCLOAK = 150, + SPL_FIREWALL, + SPL_WISPS, + SPL_SPARKLE_CHAOS, + SPL_SPARKLE_DREAM = 154, + SPL_PULLASTRAL = 156, + SPL_FETCHASTRAL = 157, + SPL_SHOCKWAVE = 163, + SPL_UNDEADHERO = 164, + SPL_BECOMEWYRM = 166, + SPL_ETERNIZEWALL, + SPL_PUTTOREST, + SPL_UNHOLYPOWER, + SPL_HOLYGROUND, + SPL_BLOODSACRIFICE, + SPL_MALLORN, + SPL_CLONECOPY, + SPL_DRAINODEM, + SPL_AURA_OF_FEAR, + SPL_SHADOWCALL, + SPL_MALLORNTREEGROW = 177, + SPL_BIGRECRUIT = 179, + SPL_IMMOLATION, + SPL_FIREODEM, /* 181 */ + SPL_ICEODEM, + SPL_ACIDODEM, + /* no longer used, but kept for reference: */ + XMLSPL_WDWPYRAMID_TRAUM = 184, + XMLSPL_WDWPYRAMID_ASTRAL = 185, + XMLSPL_WDWPYRAMID_DRUIDE = 186, + XMLSPL_WDWPYRAMID_BARDE = 187, + XMLSPL_WDWPYRAMID_CHAOS = 188 +}; + +#endif diff --git a/core/src/kernel/sqlite.c b/core/src/kernel/sqlite.c new file mode 100644 index 000000000..b7c9951a3 --- /dev/null +++ b/core/src/kernel/sqlite.c @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +faction *get_faction_by_id(int uid) +{ + faction *f; + for (f = factions; f; f = f->next) { + if (f->subscription == uid) { + return f; + } + } + return NULL; +} + +#define SQL_EXPECT(res, val) if (res!=val) { return val; } +#define MAX_EMAIL_LENGTH 64 +#define MD5_LENGTH 32 +#define MD5_LENGTH_0 (MD5_LENGTH+1) /* MD5 + zero-terminator */ +#define MAX_FACTION_NAME 64 +#define MAX_FACTION_NAME_0 (MAX_FACTION_NAME+1) +typedef struct stmt_cache { + sqlite3 *db; + sqlite3_stmt *stmt; + const char *sql; + int inuse; +} stmt_cache; + +#define MAX_STMT_CACHE 64 +static stmt_cache cache[MAX_STMT_CACHE]; +static int cache_insert; + +static sqlite3_stmt *stmt_cache_get(sqlite3 * db, const char *sql) +{ + int i; + + for (i = 0; i != MAX_STMT_CACHE && cache[i].db; ++i) { + if (cache[i].sql == sql && cache[i].db == db) { + cache[i].inuse = 1; + sqlite3_reset(cache[i].stmt); + return cache[i].stmt; + } + } + if (i == MAX_STMT_CACHE) { + while (cache[cache_insert].inuse) { + cache[cache_insert].inuse = 0; + cache_insert = (cache_insert + 1) & (MAX_STMT_CACHE - 1); + } + i = cache_insert; + sqlite3_finalize(cache[i].stmt); + } + cache[i].inuse = 1; + cache[i].db = db; + cache[i].sql = sql; + sqlite3_prepare_v2(db, sql, -1, &cache[i].stmt, NULL); + return cache[i].stmt; +} + +typedef struct db_faction { + sqlite3_uint64 id_faction; + sqlite3_uint64 id_email; + int no; + const char *email; + const char *passwd_md5; + const char *name; +} db_faction; + +static int +db_update_email(sqlite3 * db, const faction * f, const db_faction * dbstate, + bool force, /* [OUT] */ sqlite3_uint64 * id_email) +{ + bool update = force; + int res = SQLITE_OK; + char email_lc[MAX_EMAIL_LENGTH]; + + if (update) { + unicode_utf8_tolower(email_lc, sizeof(email_lc), f->email); + } else { + if (strcmp(dbstate->email, f->email) != 0) { + unicode_utf8_tolower(email_lc, sizeof(email_lc), f->email); + if (strcmp(dbstate->email, email_lc) != 0) { + update = true; + } + } + } + + if (update) { + char email_md5[MD5_LENGTH_0]; + int i; + md5_state_t ms; + md5_byte_t digest[16]; + const char sql_insert_email[] = + "INSERT OR FAIL INTO email (email,md5) VALUES (?,?)"; + const char sql_select_email[] = "SELECT id FROM email WHERE md5=?"; + sqlite3_stmt *stmt_insert_email = stmt_cache_get(db, sql_insert_email); + sqlite3_stmt *stmt_select_email = stmt_cache_get(db, sql_select_email); + + md5_init(&ms); + md5_append(&ms, (md5_byte_t *) email_lc, (int)strlen(email_lc)); + md5_finish(&ms, digest); + for (i = 0; i != 16; ++i) + sprintf(email_md5 + 2 * i, "%.02x", digest[i]); + + res = + sqlite3_bind_text(stmt_insert_email, 1, email_lc, -1, SQLITE_TRANSIENT); + res = + sqlite3_bind_text(stmt_insert_email, 2, email_md5, MD5_LENGTH, + SQLITE_TRANSIENT); + res = sqlite3_step(stmt_insert_email); + + if (res == SQLITE_DONE) { + *id_email = sqlite3_last_insert_rowid(db); + } else { + res = + sqlite3_bind_text(stmt_select_email, 1, email_md5, MD5_LENGTH, + SQLITE_TRANSIENT); + res = sqlite3_step(stmt_select_email); + SQL_EXPECT(res, SQLITE_ROW); + *id_email = sqlite3_column_int64(stmt_select_email, 0); + } + } + + return SQLITE_OK; +} + +int db_update_factions(sqlite3 * db, bool force) +{ + int game_id = 6; + const char sql_select[] = + "SELECT faction.id, faction.email_id, faction.code, email.email, faction.password_md5, faction.name, faction.lastturn FROM email, faction" + " WHERE email.id=faction.email_id AND faction.game_id=? AND (lastturn IS NULL OR lastturn>?)"; + sqlite3_stmt *stmt_select = stmt_cache_get(db, sql_select); + faction *f; + int res; + + res = sqlite3_bind_int(stmt_select, 1, game_id); + SQL_EXPECT(res, SQLITE_OK); + res = sqlite3_bind_int(stmt_select, 2, turn - 2); + SQL_EXPECT(res, SQLITE_OK); + for (;;) { + sqlite3_uint64 id_faction; + int lastturn; + + res = sqlite3_step(stmt_select); + if (res != SQLITE_ROW) + break; + + id_faction = sqlite3_column_int64(stmt_select, 0); + lastturn = sqlite3_column_int(stmt_select, 6); + f = get_faction_by_id((int)id_faction); + + if (f == NULL || !f->alive) { + if (lastturn == 0) { + const char sql_update[] = "UPDATE faction SET lastturn=? WHERE id=?"; + sqlite3_stmt *stmt = stmt_cache_get(db, sql_update); + + lastturn = f ? f->lastorders : turn - 1; + sqlite3_bind_int(stmt, 1, lastturn); + sqlite3_bind_int64(stmt, 2, id_faction); + res = sqlite3_step(stmt); + SQL_EXPECT(res, SQLITE_DONE); + } + } else { + md5_state_t ms; + md5_byte_t digest[16]; + int i; + char passwd_md5[MD5_LENGTH_0]; + sqlite3_uint64 id_email; + bool update = force; + db_faction dbstate; + const char *no_b36; + + fset(f, FFL_MARK); + dbstate.id_faction = id_faction; + dbstate.id_email = sqlite3_column_int64(stmt_select, 1); + no_b36 = (const char *)sqlite3_column_text(stmt_select, 2); + dbstate.no = no_b36 ? atoi36(no_b36) : -1; + dbstate.email = (const char *)sqlite3_column_text(stmt_select, 3); + dbstate.passwd_md5 = (const char *)sqlite3_column_text(stmt_select, 4); + dbstate.name = (const char *)sqlite3_column_text(stmt_select, 5); + + id_email = dbstate.id_email; + res = db_update_email(db, f, &dbstate, force, &id_email); + SQL_EXPECT(res, SQLITE_OK); + + md5_init(&ms); + md5_append(&ms, (md5_byte_t *) f->passw, (int)strlen(f->passw)); + md5_finish(&ms, digest); + for (i = 0; i != 16; ++i) + sprintf(passwd_md5 + 2 * i, "%.02x", digest[i]); + + if (!update) { + update = ((id_email != 0 && dbstate.id_email != id_email) + || dbstate.no != f->no || dbstate.passwd_md5 == NULL + || strcmp(passwd_md5, dbstate.passwd_md5) != 0 || dbstate.name == NULL + || strncmp(f->name, dbstate.name, MAX_FACTION_NAME) != 0); + } + if (update) { + const char sql_update_faction[] = + "UPDATE faction SET email_id=?, password_md5=?, code=?, name=?, firstturn=? WHERE id=?"; + sqlite3_stmt *stmt_update_faction = + stmt_cache_get(db, sql_update_faction); + + res = sqlite3_bind_int64(stmt_update_faction, 1, id_email); + SQL_EXPECT(res, SQLITE_OK); + res = + sqlite3_bind_text(stmt_update_faction, 2, passwd_md5, MD5_LENGTH, + SQLITE_TRANSIENT); + SQL_EXPECT(res, SQLITE_OK); + res = + sqlite3_bind_text(stmt_update_faction, 3, no_b36, -1, + SQLITE_TRANSIENT); + SQL_EXPECT(res, SQLITE_OK); + res = + sqlite3_bind_text(stmt_update_faction, 4, f->name, -1, + SQLITE_TRANSIENT); + SQL_EXPECT(res, SQLITE_OK); + res = sqlite3_bind_int(stmt_update_faction, 5, turn - f->age); + SQL_EXPECT(res, SQLITE_OK); + res = sqlite3_bind_int64(stmt_update_faction, 6, f->subscription); + SQL_EXPECT(res, SQLITE_OK); + res = sqlite3_step(stmt_update_faction); + SQL_EXPECT(res, SQLITE_DONE); + } + } + } + + for (f = factions; f; f = f->next) { + if (!fval(f, FFL_MARK)) { + log_error("%s (sub=%d, email=%s) has no entry in the database\n", factionname(f), f->subscription, f->email); + } else { + freset(f, FFL_MARK); + } + } + return SQLITE_OK; +} + +int db_update_scores(sqlite3 * db, bool force) +{ + const char *sql_ins = + "INSERT OR FAIL INTO score (value,faction_id,turn) VALUES (?,?,?)"; + sqlite3_stmt *stmt_ins = stmt_cache_get(db, sql_ins); + const char *sql_upd = + "UPDATE score set value=? WHERE faction_id=? AND turn=?"; + sqlite3_stmt *stmt_upd = stmt_cache_get(db, sql_upd); + faction *f; + sqlite3_exec(db, "BEGIN", 0, 0, 0); + for (f = factions; f; f = f->next) { + int res; + sqlite3_bind_int(stmt_ins, 1, f->score); + sqlite3_bind_int64(stmt_ins, 2, f->subscription); + sqlite3_bind_int(stmt_ins, 3, turn); + res = sqlite3_step(stmt_ins); + if (res == SQLITE_CONSTRAINT) { + sqlite3_bind_int(stmt_upd, 1, f->score); + sqlite3_bind_int64(stmt_upd, 2, f->subscription); + sqlite3_bind_int(stmt_upd, 3, turn); + res = sqlite3_step(stmt_upd); + sqlite3_reset(stmt_upd); + } + sqlite3_reset(stmt_ins); + } + sqlite3_exec(db, "COMMIT", 0, 0, 0); + return SQLITE_OK; +} diff --git a/core/src/kernel/teleport.c b/core/src/kernel/teleport.c new file mode 100644 index 000000000..db5cc646b --- /dev/null +++ b/core/src/kernel/teleport.c @@ -0,0 +1,233 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "teleport.h" + +/* kernel includes */ +#include "equipment.h" +#include "unit.h" +#include "region.h" +#include "race.h" +#include "skill.h" +#include "terrain.h" +#include "faction.h" +#include "plane.h" + +/* util includes */ +#include +#include + +/* libc includes */ +#include + +#define TE_CENTER_X 1000 +#define TE_CENTER_Y 1000 +#define TP_RADIUS 2 +#define TP_DISTANCE 4 + +static int real2tp(int rk) +{ + /* in C: + * -4 / 5 = 0; + * +4 / 5 = 0; + * !!!!!!!!!!; + */ + return (rk + (TP_DISTANCE * 5000)) / TP_DISTANCE - 5000; +} + +static region *tpregion(const region * r) +{ + region *rt = + findregion(TE_CENTER_X + real2tp(r->x), TE_CENTER_Y + real2tp(r->y)); + if (!is_astral(rt)) + return NULL; + return rt; +} + +region_list *astralregions(const region * r, bool(*valid) (const region *)) +{ + region_list *rlist = NULL; + int x, y; + + assert(is_astral(r)); + if (!is_astral(r)) { + log_error("astralregions was called with a non-astral region.\n"); + return NULL; + } + r = r_astral_to_standard(r); + if (r == NULL) + return NULL; + + for (x = -TP_RADIUS; x <= +TP_RADIUS; ++x) { + for (y = -TP_RADIUS; y <= +TP_RADIUS; ++y) { + region *rn; + int dist = koor_distance(0, 0, x, y); + int nx = r->x + x, ny = r->y + y; + + if (dist > TP_RADIUS) + continue; + pnormalize(&nx, &ny, rplane(r)); + rn = findregion(nx, ny); + if (rn != NULL && (valid == NULL || valid(rn))) + add_regionlist(&rlist, rn); + } + } + return rlist; +} + +region *r_standard_to_astral(const region * r) +{ + if (rplane(r) != get_normalplane()) + return NULL; + return tpregion(r); +} + +region *r_astral_to_standard(const region * r) +{ + int x, y; + region *r2; + + assert(is_astral(r)); + x = (r->x - TE_CENTER_X) * TP_DISTANCE; + y = (r->y - TE_CENTER_Y) * TP_DISTANCE; + pnormalize(&x, &y, get_normalplane()); + r2 = findregion(x, y); + if (r2 == NULL || rplane(r2) != get_normalplane()) + return NULL; + + return r2; +} + +region_list *all_in_range(const region * r, int n, + bool(*valid) (const region *)) +{ + int x, y; + region_list *rlist = NULL; + plane *pl = rplane(r); + + if (r == NULL) + return NULL; + + for (x = r->x - n; x <= r->x + n; x++) { + for (y = r->y - n; y <= r->y + n; y++) { + if (koor_distance(r->x, r->y, x, y) <= n) { + region *r2; + int nx = x, ny = y; + pnormalize(&nx, &ny, pl); + r2 = findregion(nx, ny); + if (r2 != NULL && (valid == NULL || valid(r2))) + add_regionlist(&rlist, r2); + } + } + } + + return rlist; +} + +void spawn_braineaters(float chance) +{ + region *r; + faction *f0 = get_monsters(); + int next = rng_int() % (int)(chance * 100); + + if (f0 == NULL) + return; + + for (r = regions; r; r = r->next) { + if (!is_astral(r) || fval(r->terrain, FORBIDDEN_REGION)) + continue; + + /* Neues Monster ? */ + if (next-- == 0) { + unit *u = + createunit(r, f0, 1 + rng_int() % 10 + rng_int() % 10, + new_race[RC_HIRNTOETER]); + equip_unit(u, get_equipment("monster_braineater")); + + next = rng_int() % (int)(chance * 100); + } + } +} + +plane *get_normalplane(void) +{ + return NULL; +} + +bool is_astral(const region * r) +{ + plane *pl = get_astralplane(); + return (pl && rplane(r) == pl); +} + +plane *get_astralplane(void) +{ + static plane *astralspace; + static int rule_astralplane = -1; + static int gamecookie = -1; + if (rule_astralplane < 0) { + rule_astralplane = + get_param_int(global.parameters, "modules.astralspace", 1); + } + if (!rule_astralplane) { + return NULL; + } + if (gamecookie != global.cookie) { + astralspace = getplanebyname("Astralraum"); + gamecookie = global.cookie; + } + + if (astralspace == NULL) { + astralspace = create_new_plane(1, "Astralraum", + TE_CENTER_X - 500, TE_CENTER_X + 500, + TE_CENTER_Y - 500, TE_CENTER_Y + 500, 0); + } + return astralspace; +} + +void create_teleport_plane(void) +{ + region *r; + plane *hplane = get_homeplane(); + plane *aplane = get_astralplane(); + + const terrain_type *fog = get_terrain("fog"); + + for (r = regions; r; r = r->next) { + plane *pl = rplane(r); + if (pl == hplane) { + region *ra = tpregion(r); + + if (ra == NULL) { + int x = TE_CENTER_X + real2tp(r->x); + int y = TE_CENTER_Y + real2tp(r->y); + pnormalize(&x, &y, aplane); + + ra = new_region(x, y, aplane, 0); + terraform_region(ra, fog); + } + } + } +} + +bool inhabitable(const region * r) +{ + return fval(r->terrain, LAND_REGION); +} diff --git a/core/src/kernel/teleport.h b/core/src/kernel/teleport.h new file mode 100644 index 000000000..75be6b5d1 --- /dev/null +++ b/core/src/kernel/teleport.h @@ -0,0 +1,43 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef TELEPORT_H +#define TELEPORT_H +#ifdef __cplusplus +extern "C" { +#endif + + struct region *r_standard_to_astral(const struct region *r); + struct region *r_astral_to_standard(const struct region *); + extern struct region_list *astralregions(const struct region *rastral, + bool(*valid) (const struct region *)); + extern struct region_list *all_in_range(const struct region *r, int n, + bool(*valid) (const struct region *)); + extern bool inhabitable(const struct region *r); + extern bool is_astral(const struct region *r); + extern struct plane *get_astralplane(void); + extern struct plane *get_normalplane(void); + + void create_teleport_plane(void); + void set_teleport_plane_regiontypes(void); + void spawn_braineaters(float chance); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/terrain.c b/core/src/kernel/terrain.c new file mode 100644 index 000000000..14c776e14 --- /dev/null +++ b/core/src/kernel/terrain.c @@ -0,0 +1,169 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include +#include "terrain.h" +#include "terrainid.h" + +/* kernel includes */ +#include "curse.h" +#include "region.h" +#include "resources.h" + +#include +#include + +/* libc includes */ +#include +#include +#include + +#define MAXTERRAINS 20 + +const char *terraindata[MAXTERRAINS] = { + "ocean", + "plain", + "swamp", + "desert", + "highland", + "mountain", + "glacier", + "firewall", + NULL, /* dungeon module */ + NULL, /* former grassland */ + "fog", + "thickfog", + "volcano", + "activevolcano", + "iceberg_sleep", + "iceberg", + + NULL, /* museum module */ + NULL, /* museum module */ + NULL, /* former magicstorm */ + NULL /* museum module */ +}; + +static terrain_type *registered_terrains; + +#ifndef DISABLE_TESTS +void test_clear_terrains(void) +{ + while (registered_terrains) { + terrain_type * t = registered_terrains; + registered_terrains = t->next; + free(t->_name); + free(t->production); + free(t); + } +} +#endif + +const terrain_type *terrains(void) +{ + return registered_terrains; +} + +static const char *plain_name(const struct region *r) +{ + /* TODO: xml defined */ + if (r_isforest(r)) + return "forest"; + return r->terrain->_name; +} + +void register_terrain(struct terrain_type *terrain) +{ + assert(terrain->next == NULL), terrain->next = registered_terrains; + registered_terrains = terrain; + if (strcmp("plain", terrain->_name) == 0) + terrain->name = &plain_name; +} + +const struct terrain_type *get_terrain(const char *name) +{ + const struct terrain_type *terrain; + for (terrain = registered_terrains; terrain; terrain = terrain->next) { + if (strcmp(terrain->_name, name) == 0) + break; + } + return terrain; +} + +static const terrain_type *newterrains[MAXTERRAINS]; + +const struct terrain_type *newterrain(terrain_t t) +{ + if (t == NOTERRAIN) + return NULL; + assert(t >= 0); + assert(t < MAXTERRAINS); + return newterrains[t]; +} + +terrain_t oldterrain(const struct terrain_type * terrain) +{ + terrain_t t; + if (terrain == NULL) + return NOTERRAIN; + for (t = 0; t != MAXTERRAINS; ++t) { + if (newterrains[t] == terrain) + return t; + } + log_warning("%s is not a classic terrain.\n", terrain->_name); + return NOTERRAIN; +} + +const char *terrain_name(const struct region *r) +{ + if (r->attribs) { + attrib *a = a_find(r->attribs, &at_racename); + if (a) { + const char *str = get_racename(a); + if (str) + return str; + } + } + + if (r->terrain->name != NULL) { + return r->terrain->name(r); + } else if (fval(r->terrain, SEA_REGION)) { + if (curse_active(get_curse(r->attribs, ct_find("maelstrom")))) { + return "maelstrom"; + } + } + return r->terrain->_name; +} + +void init_terrains(void) +{ + terrain_t t; + for (t = 0; t != MAXTERRAINS; ++t) { + const terrain_type *newterrain = newterrains[t]; + if (newterrain != NULL) + continue; + if (terraindata[t] != NULL) { + newterrain = get_terrain(terraindata[t]); + if (newterrain != NULL) { + newterrains[t] = newterrain; + } + } + } +} diff --git a/core/src/kernel/terrain.h b/core/src/kernel/terrain.h new file mode 100644 index 000000000..355c66fd2 --- /dev/null +++ b/core/src/kernel/terrain.h @@ -0,0 +1,87 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef TERRAIN_H +#define TERRAIN_H +#ifdef __cplusplus +extern "C" { +#endif + +/* diverse Flags */ +/* Strassen und Gebäude können gebaut werden, wenn max_road > 0 */ +#define LAND_REGION (1<<0) /* Standard-Land-struct region */ +#define SEA_REGION (1<<1) /* hier braucht man ein Boot */ +#define FOREST_REGION (1<<2) /* Elfen- und Kampfvorteil durch Bäume */ +#define ARCTIC_REGION (1<<3) /* Gletscher & co = Keine Insekten! */ +#define CAVALRY_REGION (1<<4) /* riding in combat is possible */ +/* Achtung: SEA_REGION ist nicht das Gegenteil von LAND_REGION. Die zwei schliessen sich nichtmal aus! */ +#define FORBIDDEN_REGION (1<<5) /* unpassierbare Blockade-struct region */ +#define SAIL_INTO (1<<6) /* man darf hierhin segeln */ +#define FLY_INTO (1<<7) /* man darf hierhin fliegen */ +#define SWIM_INTO (1<<8) /* man darf hierhin schwimmen */ +#define WALK_INTO (1<<9) /* man darf hierhin laufen */ +#define LARGE_SHIPS (1<<10) /* grosse Schiffe dürfen hinfahren */ + + typedef struct production_rule { + char *name; + const struct resource_type *rtype; + + void (*terraform) (struct production_rule *, const struct region *); + void (*update) (struct production_rule *, const struct region *); + void (*use) (struct production_rule *, const struct region *, int amount); + int (*visible) (const struct production_rule *, int skilllevel); + + /* no initialization required */ + struct production_rule *next; + } production_rule; + + typedef struct terrain_production { + const struct resource_type *type; + const char *startlevel; + const char *base; + const char *divisor; + float chance; + } terrain_production; + + typedef struct terrain_type { + char *_name; + int size; /* how many peasants can work? */ + unsigned int flags; + short max_road; /* this many stones make a full road */ + short distribution; /* multiplier used for seeding */ + struct terrain_production *production; + const struct item_type **herbs; /* zero-terminated array of herbs */ + const char *(*name) (const struct region * r); + struct terrain_type *next; + } terrain_type; + + extern const terrain_type *terrains(void); + extern void register_terrain(struct terrain_type *terrain); + extern const struct terrain_type *get_terrain(const char *name); + extern const char *terrain_name(const struct region *r); + + extern void init_terrains(void); + +#ifndef DISABLE_TESTS + void test_clear_terrains(void); +#endif + +#ifdef __cplusplus +} +#endif +#endif /* TERRAIN_H */ diff --git a/core/src/kernel/terrainid.h b/core/src/kernel/terrainid.h new file mode 100644 index 000000000..b7f4119d9 --- /dev/null +++ b/core/src/kernel/terrainid.h @@ -0,0 +1,47 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2005 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + */ + +#ifndef H_KRNL_TERRAINID_H +#define H_KRNL_TERRAINID_H + +#ifdef __cplusplus +extern "C" { +#endif + + enum { + T_OCEAN = 0, + T_PLAIN = 1, + T_SWAMP = 2, + T_DESERT = 3, /* kann aus T_PLAIN entstehen */ + T_HIGHLAND = 4, + T_MOUNTAIN = 5, + T_GLACIER = 6, /* kann aus T_MOUNTAIN entstehen */ + T_FIREWALL = 7, /* Unpassierbar */ + /* T_HELL = 8, Hölle */ + /* T_GRASSLAND = 9, */ + T_ASTRAL = 10, + T_ASTRALB = 11, + T_VOLCANO = 12, + T_VOLCANO_SMOKING = 13, + T_ICEBERG_SLEEP = 14, + T_ICEBERG = 15, + /* T_HALL1 = 16, */ + /* T_CORRIDOR1 = 17, */ + /* T_MAGICSTORM = 18, */ + /* T_WALL1 = 19, */ + NOTERRAIN = (terrain_t) - 1 + }; + + extern const struct terrain_type *newterrain(terrain_t t); + extern terrain_t oldterrain(const struct terrain_type *terrain); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/textstore.c b/core/src/kernel/textstore.c new file mode 100644 index 000000000..643697bf9 --- /dev/null +++ b/core/src/kernel/textstore.c @@ -0,0 +1,190 @@ +/* vi: set ts=2: ++-------------------+ Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel ++-------------------+ +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ +#include +#include "config.h" +#include "textstore.h" + +#include "save.h" +#include "version.h" +#include +#include + +#include +#include +#include +#include + +#define NULL_TOKEN '@' + +static int txt_w_brk(struct storage *store) +{ + putc('\n', (FILE *) store->userdata); + return 1; +} + +static int txt_w_id(struct storage *store, int arg) +{ + return fprintf((FILE *) store->userdata, "%s ", itoa36(arg)); +} + +static int txt_r_id(struct storage *store) +{ + char id[8]; + fscanf((FILE *) store->userdata, "%7s", id); + return atoi36(id); +} + +static int txt_w_int(struct storage *store, int arg) +{ + return fprintf((FILE *) store->userdata, "%d ", arg); +} + +static int txt_r_int(struct storage *store) +{ + int result; + fscanf((FILE *) store->userdata, "%d", &result); + return result; +} + +static int txt_w_flt(struct storage *store, float arg) +{ + return fprintf((FILE *) store->userdata, "%f ", arg); +} + +static float txt_r_flt(struct storage *store) +{ + double result; + fscanf((FILE *) store->userdata, "%lf", &result); + return (float)result; +} + +static int txt_w_tok(struct storage *store, const char *tok) +{ + int result; + if (tok == NULL || tok[0] == 0) { + result = fputc(NULL_TOKEN, (FILE *) store->userdata); + } else { +#ifndef NDEBUG + const char *find = strchr(tok, ' '); + if (!find) + find = strchr(tok, NULL_TOKEN); + assert(!find || !"reserved character in token"); +#endif + assert(tok[0] != ' '); + result = fputs(tok, (FILE *) store->userdata); + } + fputc(' ', (FILE *) store->userdata); + return result; +} + +static char *txt_r_tok(struct storage *store) +{ + char result[256]; + fscanf((FILE *) store->userdata, "%256s", result); + if (result[0] == NULL_TOKEN || result[0] == 0) { + return NULL; + } + return strdup(result); +} + +static void txt_r_tok_buf(struct storage *store, char *result, size_t size) +{ + char format[16]; + if (result && size > 0) { + format[0] = '%'; + sprintf(format + 1, "%lus", (unsigned long)size); + fscanf((FILE *) store->userdata, format, result); + if (result[0] == NULL_TOKEN) { + result[0] = 0; + } + } else { + /* trick to skip when no result expected */ + fscanf((FILE *) store->userdata, "%*s"); + } +} + +static int txt_w_str(struct storage *store, const char *str) +{ + int result = fwritestr((FILE *) store->userdata, str); + fputc(' ', (FILE *) store->userdata); + return result + 1; +} + +static char *txt_r_str(struct storage *store) +{ + char buffer[DISPLAYSIZE]; + /* you should not use this */ + freadstr((FILE *) store->userdata, store->encoding, buffer, sizeof(buffer)); + return strdup(buffer); +} + +static void txt_r_str_buf(struct storage *store, char *result, size_t size) +{ + freadstr((FILE *) store->userdata, store->encoding, result, size); +} + +static int txt_open(struct storage *store, const char *filename, int mode) +{ + const char *modes[] = { 0, "rt", "wt", "at" }; + FILE *F = fopen(filename, modes[mode]); + store->userdata = F; + if (F) { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf }; + if (mode == IO_READ) { + char token[8]; + /* recognize UTF8 BOM */ + store->r_tok_buf(store, token, sizeof(token)); + if (memcmp(token, utf8_bom, 3) == 0) { + if (enc_gamedata != XML_CHAR_ENCODING_UTF8) { + log_warning("Found UTF-8 BOM, assuming unicode game data.\n"); + store->encoding = XML_CHAR_ENCODING_UTF8; + } + store->version = atoi(token + 3); + } else { + if (store->encoding == XML_CHAR_ENCODING_NONE) { + store->encoding = XML_CHAR_ENCODING_8859_1; + log_warning("No BOM, assuming 8859-1 game data.\n"); + } + store->version = atoi(token); + } + } else if (store->encoding == XML_CHAR_ENCODING_UTF8) { + fputs((const char *)utf8_bom, F); + fprintf(F, "%d\n", RELEASE_VERSION); + } + } + return (F == NULL); +} + +static int txt_w_bin(struct storage *store, void *arg, size_t size) +{ + assert(!"not implemented!"); + return 0; +} + +static void txt_r_bin(struct storage *store, void *result, size_t size) +{ + assert(!"not implemented!"); +} + +static int txt_close(struct storage *store) +{ + return fclose((FILE *) store->userdata); +} + +const storage text_store = { + txt_w_brk, + txt_w_int, txt_r_int, + txt_w_flt, txt_r_flt, + txt_w_id, txt_r_id, + txt_w_tok, txt_r_tok, txt_r_tok_buf, + txt_w_str, txt_r_str, txt_r_str_buf, + txt_w_bin, txt_r_bin, + txt_open, txt_close, + 0, 0, NULL +}; diff --git a/core/src/kernel/textstore.h b/core/src/kernel/textstore.h new file mode 100644 index 000000000..46eb2090a --- /dev/null +++ b/core/src/kernel/textstore.h @@ -0,0 +1,23 @@ +/* vi: set ts=2: ++-------------------+ Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel ++-------------------+ +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifndef H_KERNEL_TEXTSTORE +#define H_KERNEL_TEXTSTORE +#ifdef __cplusplus +extern "C" { +#endif + +#include + + extern const storage text_store; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/types.h b/core/src/kernel/types.h new file mode 100644 index 000000000..f023e435a --- /dev/null +++ b/core/src/kernel/types.h @@ -0,0 +1,401 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef ERESSEA_TYPES_H +#define ERESSEA_TYPES_H + +/* + * Features enabled: + * If you are lacking the settings.h, create a new file common/settings.h, + * and write #include (or whatever settings you want + * your game to use) in there. + * !!! DO NOT COMMIT THE SETTINGS.H FILE TO CVS !!! + * settings.h should always be the first thing you include (after platform.h). + */ +#include +#include + +typedef short terrain_t; +typedef short item_t; + +struct attrib; +struct attrib_type; +struct ally; +struct building; +struct building_type; +struct curse; +struct curse_type; +struct castorder; +struct equipment; +struct faction; +struct fighter; +struct item; +struct item_type; +struct locale; +struct luxury_type; +struct order; +struct plane; +struct potion_type; +struct quicklist; +struct race; +struct region; +struct region_list; +struct resource_type; +struct ship; +struct ship_type; +struct skill; +struct spell; +struct spellbook; +struct storage; +struct strlist; +struct terrain_type; +struct unit; +struct weapon_type; + +typedef struct ursprung { + struct ursprung *next; + int id; + int x, y; +} ursprung; + +/* ----------------- Befehle ----------------------------------- */ + +typedef enum { + K_KOMMENTAR, + K_BANNER, + K_WORK, + K_ATTACK, + K_STEAL, + K_BESIEGE, + K_NAME, + K_USE, + K_DISPLAY, + K_ENTER, + K_GUARD, + K_MAIL, + K_END, + K_DRIVE, + K_NUMBER, + K_FOLLOW, + K_RESEARCH, + K_GIVE, + K_ALLY, + K_STATUS, + K_COMBATSPELL, + K_BUY, + K_CONTACT, + K_TEACH, + K_STUDY, + K_MAKE, + K_MOVE, + K_PASSWORD, + K_RECRUIT, + K_RESERVE, + K_ROUTE, + K_SABOTAGE, + K_SEND, + K_SPY, + K_QUIT, + K_SETSTEALTH, + K_TRANSPORT, + K_TAX, + K_ENTERTAIN, + K_SELL, + K_LEAVE, + K_FORGET, + K_CAST, + K_RESHOW, + K_DESTROY, + K_BREED, + K_DEFAULT, + K_URSPRUNG, + K_EMAIL, + K_PIRACY, + K_GROUP, + K_SORT, + K_GM, /* perform GM commands */ + K_INFO, /* set player-info */ + K_PREFIX, + K_PLANT, + K_ALLIANCE, + K_CLAIM, + K_PROMOTION, + K_PAY, + MAXKEYWORDS, + NOKEYWORD = -1 +} keyword_t; + +/* ------------------ Status von Einheiten --------------------- */ + +typedef unsigned char status_t; +enum { + ST_AGGRO, + ST_FIGHT, + ST_BEHIND, + ST_CHICKEN, + ST_AVOID, + ST_FLEE +}; + +/* ----------------- Parameter --------------------------------- */ + +typedef enum { + P_LOCALE, + P_ANY, + P_EACH, + P_PEASANT, + P_BUILDING, + P_UNIT, + P_PRIVAT, + P_BEHIND, + P_CONTROL, + P_HERBS, + P_NOT, + P_NEXT, + P_FACTION, + P_GAMENAME, + P_PERSON, + P_REGION, + P_SHIP, + P_MONEY, + P_ROAD, + P_TEMP, + P_FLEE, + P_GEBAEUDE, + P_GIVE, + P_FIGHT, + P_TRAVEL, + P_GUARD, + P_ZAUBER, + P_PAUSE, + P_VORNE, + P_AGGRO, + P_CHICKEN, + P_LEVEL, + P_HELP, + P_FOREIGN, + P_AURA, + P_FOR, + P_AID, + P_MERCY, + P_AFTER, + P_BEFORE, + P_NUMBER, + P_ITEMS, + P_POTIONS, + P_GROUP, + P_FACTIONSTEALTH, + P_TREES, + P_XEPOTION, + P_XEBALLOON, + P_XELAEN, + P_ALLIANCE, + MAXPARAMS, + NOPARAM = -1 +} param_t; + +typedef enum { /* Fehler und Meldungen im Report */ + MSG_BATTLE, + MSG_EVENT, + MSG_MOVE, + MSG_INCOME, + MSG_COMMERCE, + MSG_PRODUCE, + MSG_ORCVERMEHRUNG, + MSG_MESSAGE, + MSG_COMMENT, + MSG_MAGIC, + MAX_MSG +} msg_t; + +enum { /* Message-Level */ + ML_IMPORTANT, /* Sachen, die IMO erscheinen _muessen_ */ + ML_DEBUG, + ML_MISTAKE, + ML_WARN, + ML_INFO, + ML_MAX +}; + +extern const char *parameters[MAXPARAMS]; + +/* --------------- Reports Typen ------------------------------- */ + +enum { + O_REPORT, /* 1 */ + O_COMPUTER, /* 2 */ + O_ZUGVORLAGE, /* 4 */ + O_UNUSED_3, + O_STATISTICS, /* 16 */ + O_DEBUG, /* 32 */ + O_COMPRESS, /* 64 */ + O_NEWS, /* 128 */ + O_UNUSED_8, + O_ADRESSEN, /* 512 */ + O_BZIP2, /* 1024 - compress as bzip2 */ + O_SCORE, /* 2048 - punkte anzeigen? */ + O_SHOWSKCHANGE, /* 4096 - Skillveränderungen anzeigen? */ + O_XML, /* 8192 - XML report versenden */ + MAXOPTIONS +}; + +/* ------------------ Talente ---------------------------------- */ + +typedef enum { + SK_ALCHEMY, + SK_CROSSBOW, + SK_MINING, + SK_LONGBOW, + SK_BUILDING, + SK_TRADE, + SK_LUMBERJACK, + SK_CATAPULT, + SK_HERBALISM, + SK_MAGIC, + SK_HORSE_TRAINING, /* 10 */ + SK_RIDING, + SK_ARMORER, + SK_SHIPBUILDING, + SK_MELEE, + SK_SAILING, + SK_SPEAR, + SK_SPY, + SK_QUARRYING, + SK_ROAD_BUILDING, + SK_TACTICS, /* 20 */ + SK_STEALTH, + SK_ENTERTAINMENT, + SK_WEAPONSMITH, + SK_CARTMAKER, + SK_PERCEPTION, + SK_TAXING, + SK_STAMINA, + SK_WEAPONLESS, + MAXSKILLS, + NOSKILL = -1 +} skill_t; + +/* ------------- Typ von Einheiten ----------------------------- */ + +typedef enum { + RC_DWARF, /* 0 - Zwerg */ + RC_ELF, + RC_GOBLIN = 3, + RC_HUMAN, + + RC_TROLL, + RC_DAEMON, + RC_INSECT, + RC_HALFLING, + RC_CAT, + + RC_AQUARIAN, + RC_ORC, + RC_SNOTLING, + RC_UNDEAD, + RC_ILLUSION, + + RC_FIREDRAGON, + RC_DRAGON, + RC_WYRM, + RC_TREEMAN, + RC_BIRTHDAYDRAGON, + + RC_DRACOID, + RC_SPECIAL, + RC_SPELL, + RC_IRONGOLEM, + RC_STONEGOLEM, + + RC_SHADOW, + RC_SHADOWLORD, + RC_IRONKEEPER, + RC_ALP, + RC_TOAD, + + RC_HIRNTOETER, + RC_PEASANT, + RC_WOLF = 32, + + RC_SONGDRAGON = 37, + + RC_SEASERPENT = 51, + RC_SHADOWKNIGHT, + RC_CENTAUR, + RC_SKELETON, + + RC_SKELETON_LORD, + RC_ZOMBIE, + RC_ZOMBIE_LORD, + RC_GHOUL, + RC_GHOUL_LORD, + + RC_MUS_SPIRIT, + RC_GNOME, + RC_TEMPLATE, + RC_CLONE, + + MAXRACES, + NORACE = -1 +} race_t; + +/* Richtungen */ +typedef enum { + D_NORTHWEST, + D_NORTHEAST, + D_EAST, + D_SOUTHEAST, + D_SOUTHWEST, + D_WEST, + MAXDIRECTIONS, + D_PAUSE, + D_SPECIAL, + NODIRECTION = -1 +} direction_t; + +typedef enum { + M_GRAY = 0, /* Gray */ + M_ILLAUN = 1, /* Illaun */ + M_TYBIED = 2, /* Tybied */ + M_CERDDOR = 3, /* Cerddor */ + M_GWYRRD = 4, /* Gwyrrd */ + M_DRAIG = 5, /* Draig */ + M_COMMON = 6, /* common spells */ + MAXMAGIETYP, + /* this enum is stored in the datafile, so do not change the numbers around */ + M_NONE = -1 +} magic_t; + +#define DONT_HELP 0 +#define HELP_MONEY 1 /* Mitversorgen von Einheiten */ +#define HELP_FIGHT 2 /* Bei Verteidigung mithelfen */ +#define HELP_OBSERVE 4 /* Bei Wahrnehmung mithelfen */ +#define HELP_GIVE 8 /* Dinge annehmen ohne KONTAKTIERE */ +#define HELP_GUARD 16 /* Laesst Steuern eintreiben etc. */ +#define HELP_FSTEALTH 32 /* Parteitarnung anzeigen. */ +#define HELP_TRAVEL 64 /* Laesst Regionen betreten. */ +#define HELP_ALL (127-HELP_TRAVEL-HELP_OBSERVE) /* Alle "positiven" HELPs zusammen */ +/* HELP_OBSERVE deaktiviert */ +/* ------------------------------------------------------------- */ +/* Prototypen */ + +/* alle vierstelligen zahlen: */ +#define MAX_UNIT_NR (36*36*36*36-1) +#define MAX_CONTAINER_NR (36*36*36*36-1) + +#endif diff --git a/core/src/kernel/unit.c b/core/src/kernel/unit.c new file mode 100644 index 000000000..ff1dfb444 --- /dev/null +++ b/core/src/kernel/unit.c @@ -0,0 +1,1777 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include +#include "unit.h" + +#include "building.h" +#include "faction.h" +#include "group.h" +#include "connection.h" +#include "curse.h" +#include "item.h" +#include "move.h" +#include "order.h" +#include "plane.h" +#include "race.h" +#include "region.h" +#include "spell.h" +#include "spellbook.h" +#include "save.h" +#include "ship.h" +#include "skill.h" +#include "terrain.h" + +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +#define FIND_FOREIGN_TEMP + +attrib_type at_creator = { + "creator" + /* Rest ist NULL; temporäres, nicht alterndes Attribut */ +}; + +#define UMAXHASH MAXUNITS +static unit *unithash[UMAXHASH]; +static unit *delmarker = (unit *) unithash; /* a funny hack */ + +#define HASH_STATISTICS 1 +#if HASH_STATISTICS +static int hash_requests; +static int hash_misses; +#endif + +void uhash(unit * u) +{ + int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); + while (unithash[key] != NULL && unithash[key] != delmarker + && unithash[key] != u) { + key = (key + gk) % UMAXHASH; + } + assert(unithash[key] != u || !"trying to add the same unit twice"); + unithash[key] = u; +} + +void uunhash(unit * u) +{ + int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); + while (unithash[key] != NULL && unithash[key] != u) { + key = (key + gk) % UMAXHASH; + } + assert(unithash[key] == u || !"trying to remove a unit that is not hashed"); + unithash[key] = delmarker; +} + +unit *ufindhash(int uid) +{ + assert(uid >= 0); +#if HASH_STATISTICS + ++hash_requests; +#endif + if (uid >= 0) { + int key = HASH1(uid, UMAXHASH), gk = HASH2(uid, UMAXHASH); + while (unithash[key] != NULL && (unithash[key] == delmarker + || unithash[key]->no != uid)) { + key = (key + gk) % UMAXHASH; +#if HASH_STATISTICS + ++hash_misses; +#endif + } + return unithash[key]; + } + return NULL; +} + +#define DMAXHASH 7919 +typedef struct dead { + struct dead *nexthash; + faction *f; + int no; +} dead; + +static dead *deadhash[DMAXHASH]; + +static void dhash(int no, faction * f) +{ + dead *hash = (dead *) calloc(1, sizeof(dead)); + dead *old = deadhash[no % DMAXHASH]; + hash->no = no; + hash->f = f; + deadhash[no % DMAXHASH] = hash; + hash->nexthash = old; +} + +faction *dfindhash(int no) +{ + dead *old; + + if (no < 0) + return 0; + + for (old = deadhash[no % DMAXHASH]; old; old = old->nexthash) { + if (old->no == no) { + return old->f; + } + } + return 0; +} + +typedef struct buddy { + struct buddy *next; + int number; + faction *faction; + unit *unit; +} buddy; + +static buddy *get_friends(const unit * u, int *numfriends) +{ + buddy *friends = 0; + faction *f = u->faction; + region *r = u->region; + int number = 0; + unit *u2; + + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction != f && u2->number > 0) { + int allied = 0; + if (get_param_int(global.parameters, "rules.alliances", 0) != 0) { + allied = (f->alliance && f->alliance == u2->faction->alliance); + } else if (alliedunit(u, u2->faction, HELP_MONEY) + && alliedunit(u2, f, HELP_GIVE)) { + allied = 1; + } + if (allied) { + buddy *nf, **fr = &friends; + + /* some units won't take stuff: */ + if (u_race(u2)->ec_flags & GETITEM) { + while (*fr && (*fr)->faction->no < u2->faction->no) + fr = &(*fr)->next; + nf = *fr; + if (nf == NULL || nf->faction != u2->faction) { + nf = malloc(sizeof(buddy)); + nf->next = *fr; + nf->faction = u2->faction; + nf->unit = u2; + nf->number = 0; + *fr = nf; + } else if (nf->faction == u2->faction + && (u_race(u2)->ec_flags & GIVEITEM)) { + /* we don't like to gift it to units that won't give it back */ + if ((u_race(nf->unit)->ec_flags & GIVEITEM) == 0) { + nf->unit = u2; + } + } + nf->number += u2->number; + number += u2->number; + } + } + } + } + if (numfriends) + *numfriends = number; + return friends; +} + +/** give all items to friends or peasants. + * this function returns 0 on success, or 1 if there are items that + * could not be destroyed. + */ +int gift_items(unit * u, int flags) +{ + region *r = u->region; + item **itm_p = &u->items; + int retval = 0; + int rule = rule_give(); + + assert(u->region); + + if ((u->faction->flags & FFL_QUIT) == 0 || (rule & GIVE_ONDEATH) == 0) { + if ((rule & GIVE_ALLITEMS) == 0 && (flags & GIFT_FRIENDS)) + flags -= GIFT_FRIENDS; + if ((rule & GIVE_PEASANTS) == 0 && (flags & GIFT_PEASANTS)) + flags -= GIFT_PEASANTS; + if ((rule & GIVE_SELF) == 0 && (flags & GIFT_SELF)) + flags -= GIFT_SELF; + } + + if (u->items == NULL || fval(u_race(u), RCF_ILLUSIONARY)) + return 0; + if ((u_race(u)->ec_flags & GIVEITEM) == 0) + return 0; + + /* at first, I should try giving my crap to my own units in this region */ + if (u->faction && (u->faction->flags & FFL_QUIT) == 0 && (flags & GIFT_SELF)) { + unit *u2, *u3 = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (u2 != u && u2->faction == u->faction && u2->number > 0) { + /* some units won't take stuff: */ + if (u_race(u2)->ec_flags & GETITEM) { + /* we don't like to gift it to units that won't give it back */ + if (u_race(u2)->ec_flags & GIVEITEM) { + i_merge(&u2->items, &u->items); + u->items = NULL; + break; + } else { + u3 = u2; + } + } + } + } + if (u->items && u3) { + /* if nobody else takes it, we give it to a unit that has issues */ + i_merge(&u3->items, &u->items); + u->items = NULL; + } + if (u->items == NULL) + return 0; + } + + /* if I have friends, I'll try to give my stuff to them */ + if (u->faction && (flags & GIFT_FRIENDS)) { + int number = 0; + buddy *friends = get_friends(u, &number); + + while (friends) { + struct buddy *nf = friends; + unit *u2 = nf->unit; + item *itm = u->items; + while (itm != NULL) { + const item_type *itype = itm->type; + item *itn = itm->next; + int n = itm->number; + n = n * nf->number / number; + if (n > 0) { + i_change(&u->items, itype, -n); + i_change(&u2->items, itype, n); + } + itm = itn; + } + number -= nf->number; + friends = nf->next; + free(nf); + } + if (u->items == NULL) + return 0; + } + + /* last, but not least, give money and horses to peasants */ + while (*itm_p) { + item *itm = *itm_p; + + if (flags & GIFT_PEASANTS) { + if (!fval(u->region->terrain, SEA_REGION)) { + if (itm->type == olditemtype[I_HORSE]) { + rsethorses(r, rhorses(r) + itm->number); + itm->number = 0; + } else if (itm->type == i_silver) { + rsetmoney(r, rmoney(r) + itm->number); + itm->number = 0; + } + } + } + if (itm->number > 0 && (itm->type->flags & ITF_NOTLOST)) { + itm_p = &itm->next; + retval = -1; + } else { + i_remove(itm_p, itm); + i_free(itm); + } + } + return retval; +} + +void make_zombie(unit * u) +{ + u_setfaction(u, get_monsters()); + scale_number(u, 1); + u_setrace(u, new_race[RC_ZOMBIE]); + u->irace = NULL; +} + +/** remove the unit from the list of active units. + * the unit is not actually freed, because there may still be references + * dangling to it (from messages, for example). To free all removed units, + * call free_units(). + * returns 0 on success, or -1 if unit could not be removed. + */ + +static unit *deleted_units = NULL; + +int remove_unit(unit ** ulist, unit * u) +{ + int result; + + assert(ufindhash(u->no)); + handle_event(u->attribs, "destroy", u); + + result = gift_items(u, GIFT_SELF | GIFT_FRIENDS | GIFT_PEASANTS); + if (result != 0) { + make_zombie(u); + return -1; + } + + if (u->number) + set_number(u, 0); + leave(u, true); + u->region = NULL; + + uunhash(u); + if (ulist) { + while (*ulist != u) { + ulist = &(*ulist)->next; + } + assert(*ulist == u); + *ulist = u->next; + } + + u->next = deleted_units; + deleted_units = u; + dhash(u->no, u->faction); + + u_setfaction(u, NULL); + u->region = NULL; + + return 0; +} + +unit *findnewunit(const region * r, const faction * f, int n) +{ + unit *u2; + + if (n == 0) + return 0; + + for (u2 = r->units; u2; u2 = u2->next) + if (u2->faction == f && ualias(u2) == n) + return u2; +#ifdef FIND_FOREIGN_TEMP + for (u2 = r->units; u2; u2 = u2->next) + if (ualias(u2) == n) + return u2; +#endif + return 0; +} + +/* ------------------------------------------------------------- */ + +/*********************/ +/* at_alias */ +/*********************/ +attrib_type at_alias = { + "alias", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ +}; + +int ualias(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_alias); + if (!a) + return 0; + return a->data.i; +} + +int a_readprivate(attrib * a, void *owner, struct storage *store) +{ + a->data.v = store->r_str(store); + if (a->data.v) + return AT_READ_OK; + return AT_READ_FAIL; +} + +/*********************/ +/* at_private */ +/*********************/ +attrib_type at_private = { + "private", + DEFAULT_INIT, + a_finalizestring, + DEFAULT_AGE, + a_writestring, + a_readprivate +}; + +const char *u_description(const unit * u, const struct locale *lang) +{ + if (u->display && u->display[0]) { + return u->display; + } else if (u_race(u)->describe) { + return u_race(u)->describe(u, lang); + } + return NULL; +} + +const char *uprivate(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_private); + if (!a) + return NULL; + return a->data.v; +} + +void usetprivate(unit * u, const char *str) +{ + attrib *a = a_find(u->attribs, &at_private); + + if (str == NULL) { + if (a) + a_remove(&u->attribs, a); + return; + } + if (!a) + a = a_add(&u->attribs, a_new(&at_private)); + if (a->data.v) + free(a->data.v); + a->data.v = strdup((const char *)str); +} + +/*********************/ +/* at_potionuser */ +/*********************/ +/* Einheit BENUTZT einen Trank */ +attrib_type at_potionuser = { + "potionuser", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ +}; + +void usetpotionuse(unit * u, const potion_type * ptype) +{ + attrib *a = a_find(u->attribs, &at_potionuser); + if (!a) + a = a_add(&u->attribs, a_new(&at_potionuser)); + a->data.v = (void *)ptype; +} + +const potion_type *ugetpotionuse(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_potionuser); + if (!a) + return NULL; + return (const potion_type *)a->data.v; +} + +/*********************/ +/* at_target */ +/*********************/ +attrib_type at_target = { + "target", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ +}; + +unit *utarget(const unit * u) +{ + attrib *a; + if (!fval(u, UFL_TARGET)) + return NULL; + a = a_find(u->attribs, &at_target); + assert(a || !"flag set, but no target found"); + return (unit *) a->data.v; +} + +void usettarget(unit * u, const unit * t) +{ + attrib *a = a_find(u->attribs, &at_target); + if (!a && t) + a = a_add(&u->attribs, a_new(&at_target)); + if (a) { + if (!t) { + a_remove(&u->attribs, a); + freset(u, UFL_TARGET); + } else { + a->data.v = (void *)t; + fset(u, UFL_TARGET); + } + } +} + +/*********************/ +/* at_siege */ +/*********************/ + +void a_writesiege(const attrib * a, const void *owner, struct storage *store) +{ + struct building *b = (struct building *)a->data.v; + write_building_reference(b, store); +} + +int a_readsiege(attrib * a, void *owner, struct storage *store) +{ + int result = read_reference(&a->data.v, store, read_building_reference, + resolve_building); + if (result == 0 && !a->data.v) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +attrib_type at_siege = { + "siege", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + a_writesiege, + a_readsiege +}; + +struct building *usiege(const unit * u) +{ + attrib *a; + if (!fval(u, UFL_SIEGE)) + return NULL; + a = a_find(u->attribs, &at_siege); + assert(a || !"flag set, but no siege found"); + return (struct building *)a->data.v; +} + +void usetsiege(unit * u, const struct building *t) +{ + attrib *a = a_find(u->attribs, &at_siege); + if (!a && t) + a = a_add(&u->attribs, a_new(&at_siege)); + if (a) { + if (!t) { + a_remove(&u->attribs, a); + freset(u, UFL_SIEGE); + } else { + a->data.v = (void *)t; + fset(u, UFL_SIEGE); + } + } +} + +/*********************/ +/* at_contact */ +/*********************/ +attrib_type at_contact = { + "contact", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ +}; + +void usetcontact(unit * u, const unit * u2) +{ + attrib *a = a_find(u->attribs, &at_contact); + while (a && a->type == &at_contact && a->data.v != u2) + a = a->next; + if (a && a->type == &at_contact) + return; + a_add(&u->attribs, a_new(&at_contact))->data.v = (void *)u2; +} + +bool ucontact(const unit * u, const unit * u2) +/* Prüft, ob u den Kontaktiere-Befehl zu u2 gesetzt hat. */ +{ + attrib *ru; + if (u->faction == u2->faction) + return true; + + /* Explizites KONTAKTIERE */ + for (ru = a_find(u->attribs, &at_contact); ru && ru->type == &at_contact; + ru = ru->next) { + if (((unit *) ru->data.v) == u2) { + return true; + } + } + + return false; +} + +/*** +** init & cleanup module +**/ + +void free_units(void) +{ + while (deleted_units) { + unit *u = deleted_units; + deleted_units = deleted_units->next; + free_unit(u); + free(u); + } +} + +void write_unit_reference(const unit * u, struct storage *store) +{ + store->w_id(store, (u && u->region) ? u->no : 0); +} + +int resolve_unit(variant id, void *address) +{ + unit *u = NULL; + if (id.i != 0) { + u = findunit(id.i); + if (u == NULL) { + *(unit **) address = NULL; + return -1; + } + } + *(unit **) address = u; + return 0; +} + +variant read_unit_reference(struct storage * store) +{ + variant var; + var.i = store->r_id(store); + return var; +} + +attrib_type at_stealth = { + "stealth", NULL, NULL, NULL, a_writeint, a_readint +}; + +void u_seteffstealth(unit * u, int value) +{ + if (skill_enabled[SK_STEALTH]) { + attrib *a = NULL; + if (fval(u, UFL_STEALTH)) { + a = a_find(u->attribs, &at_stealth); + } + if (value < 0) { + if (a != NULL) { + freset(u, UFL_STEALTH); + a_remove(&u->attribs, a); + } + return; + } + if (a == NULL) { + a = a_add(&u->attribs, a_new(&at_stealth)); + fset(u, UFL_STEALTH); + } + a->data.i = value; + } +} + +int u_geteffstealth(const struct unit *u) +{ + if (skill_enabled[SK_STEALTH]) { + if (fval(u, UFL_STEALTH)) { + attrib *a = a_find(u->attribs, &at_stealth); + if (a != NULL) + return a->data.i; + } + } + return -1; +} + +int get_level(const unit * u, skill_t id) +{ + if (skill_enabled[id]) { + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + if (sv->id == id) { + return sv->level; + } + ++sv; + } + } + return 0; +} + +void set_level(unit * u, skill_t sk, int value) +{ + skill *sv = u->skills; + + if (!skill_enabled[sk]) + return; + + if (value == 0) { + remove_skill(u, sk); + return; + } + while (sv != u->skills + u->skill_size) { + if (sv->id == sk) { + sk_set(sv, value); + return; + } + ++sv; + } + sk_set(add_skill(u, sk), value); +} + +static int leftship_age(struct attrib *a) +{ + /* must be aged, so it doesn't affect report generation (cansee) */ + unused(a); + return AT_AGE_REMOVE; /* remove me */ +} + +static attrib_type at_leftship = { + "leftship", NULL, NULL, leftship_age +}; + +static attrib *make_leftship(struct ship *leftship) +{ + attrib *a = a_new(&at_leftship); + a->data.v = leftship; + return a; +} + +void set_leftship(unit * u, ship * sh) +{ + a_add(&u->attribs, make_leftship(sh)); +} + +ship *leftship(const unit * u) +{ + attrib *a = a_find(u->attribs, &at_leftship); + + /* Achtung: Es ist nicht garantiert, daß der Rückgabewert zu jedem + * Zeitpunkt noch auf ein existierendes Schiff zeigt! */ + + if (a) + return (ship *) (a->data.v); + + return NULL; +} + +void u_set_building(unit * u, building * b) +{ + assert(!u->building); /* you must leave first */ + u->building = b; + if (b && !b->_owner) { + building_set_owner(u); + } +} + +void u_set_ship(unit * u, ship * sh) +{ + assert(!u->ship); /* you must leave_ship */ + u->ship = sh; + if (sh && !sh->_owner) { + ship_set_owner(u); + } +} + +void leave_ship(unit * u) +{ + struct ship *sh = u->ship; + + u->ship = 0; + if (sh->_owner==u) { + ship_update_owner(sh); + sh->_owner = ship_owner(sh); + } + set_leftship(u, sh); +} + +void leave_building(unit * u) +{ + building * b = u->building; + + u->building = 0; + if (b->_owner==u) { + building_update_owner(b); + assert(b->_owner!=u); + } +} + +bool can_leave(unit * u) +{ + static int gamecookie = -1; + static int rule_leave = -1; + + if (!u->building) { + return true; + } + + if (rule_leave < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + rule_leave = get_param_int(global.parameters, "rules.move.owner_leave", 0); + } + + if (rule_leave && u->building && u == building_owner(u->building)) { + return false; + } + return true; +} + +bool leave(unit * u, bool force) +{ + if (!force) { + if (!can_leave(u)) { + return false; + } + } + if (u->building) { + leave_building(u); + } else if (u->ship) { + leave_ship(u); + } + return true; +} + +const struct race *urace(const struct unit *u) +{ + return u->race_; +} + +bool can_survive(const unit * u, const region * r) +{ + if ((fval(r->terrain, WALK_INTO) && (u_race(u)->flags & RCF_WALK)) + || (fval(r->terrain, SWIM_INTO) && (u_race(u)->flags & RCF_SWIM)) + || (fval(r->terrain, FLY_INTO) && (u_race(u)->flags & RCF_FLY))) { + static const curse_type *ctype = NULL; + + if (has_horses(u) && !fval(r->terrain, WALK_INTO)) + return false; + + if (!ctype) + ctype = ct_find("holyground"); + if (fval(u_race(u), RCF_UNDEAD) && curse_active(get_curse(r->attribs, ctype))) + return false; + + return true; + } + return false; +} + +void move_unit(unit * u, region * r, unit ** ulist) +{ + assert(u && r); + + assert(u->faction || !"this unit is dead"); + if (u->region == r) + return; + if (!ulist) + ulist = (&r->units); + if (u->region) { + setguard(u, GUARD_NONE); + fset(u, UFL_MOVED); + if (u->ship || u->building) { + /* can_leave must be checked in travel_i */ +#ifndef NDEBUG + bool result = leave(u, false); + assert(result); +#else + leave(u, false); +#endif + } + translist(&u->region->units, ulist, u); + } else { + addlist(ulist, u); + } + +#ifdef SMART_INTERVALS + update_interval(u->faction, r); +#endif + u->region = r; +} + +/* ist mist, aber wegen nicht skalierender attribute notwendig: */ +#include "alchemy.h" + +void transfermen(unit * u, unit * u2, int n) +{ + const attrib *a; + int hp = u->hp; + region *r = u->region; + + if (n == 0) + return; + assert(n > 0); + /* "hat attackiert"-status wird übergeben */ + + if (u2) { + skill *sv, *sn; + skill_t sk; + ship *sh; + + assert(u2->number + n > 0); + + for (sk = 0; sk != MAXSKILLS; ++sk) { + int weeks, level = 0; + + sv = get_skill(u, sk); + sn = get_skill(u2, sk); + + if (sv == NULL && sn == NULL) + continue; + if (sn == NULL && u2->number == 0) { + /* new unit, easy to solve */ + level = sv->level; + weeks = sv->weeks; + } else { + double dlevel = 0.0; + + if (sv && sv->level) { + dlevel += (sv->level + 1 - sv->weeks / (sv->level + 1.0)) * n; + level += sv->level * n; + } + if (sn && sn->level) { + dlevel += + (sn->level + 1 - sn->weeks / (sn->level + 1.0)) * u2->number; + level += sn->level * u2->number; + } + + dlevel = dlevel / (n + u2->number); + level = level / (n + u2->number); + if (level <= dlevel) { + /* apply the remaining fraction to the number of weeks to go. + * subtract the according number of weeks, getting closer to the + * next level */ + level = (int)dlevel; + weeks = (level + 1) - (int)((dlevel - level) * (level + 1)); + } else { + /* make it harder to reach the next level. + * weeks+level is the max difficulty, 1 - the fraction between + * level and dlevel applied to the number of weeks between this + * and the previous level is the added difficutly */ + level = (int)dlevel + 1; + weeks = 1 + 2 * level - (int)((1 + dlevel - level) * level); + } + } + if (level) { + if (sn == NULL) + sn = add_skill(u2, sk); + sn->level = (unsigned char)level; + sn->weeks = (unsigned char)weeks; + assert(sn->weeks > 0 && sn->weeks <= sn->level * 2 + 1); + assert(u2->number != 0 || (sn->level == sv->level + && sn->weeks == sv->weeks)); + } else if (sn) { + remove_skill(u2, sk); + sn = NULL; + } + } + a = a_find(u->attribs, &at_effect); + while (a && a->type == &at_effect) { + effect_data *olde = (effect_data *) a->data.v; + if (olde->value) + change_effect(u2, olde->type, olde->value); + a = a->next; + } + sh = leftship(u); + if (sh != NULL) + set_leftship(u2, sh); + u2->flags |= + u->flags & (UFL_LONGACTION | UFL_NOTMOVING | UFL_HUNGER | UFL_MOVED | + UFL_ENTER); + if (u->attribs) { + transfer_curse(u, u2, n); + } + } + scale_number(u, u->number - n); + if (u2) { + set_number(u2, u2->number + n); + hp -= u->hp; + u2->hp += hp; + /* TODO: Das ist schnarchlahm! und gehört nicht hierhin */ + a = a_find(u2->attribs, &at_effect); + while (a && a->type == &at_effect) { + attrib *an = a->next; + effect_data *olde = (effect_data *) a->data.v; + int e = get_effect(u, olde->type); + if (e != 0) + change_effect(u2, olde->type, -e); + a = an; + } + } else if (r->land) { + if ((u_race(u)->ec_flags & ECF_REC_ETHEREAL) == 0) { + const race *rc = u_race(u); + if (rc->ec_flags & ECF_REC_HORSES) { /* Zentauren an die Pferde */ + int h = rhorses(r) + n; + rsethorses(r, h); + } else { + int p = rpeasants(r); + p += (int)(n * rc->recruit_multi); + rsetpeasants(r, p); + } + } + } +} + +struct building *inside_building(const struct unit *u) +{ + if (u->building == NULL) + return NULL; + + if (!fval(u->building, BLD_WORKING)) { + /* Unterhalt nicht bezahlt */ + return NULL; + } else if (u->building->size < u->building->type->maxsize) { + /* Gebäude noch nicht fertig */ + return NULL; + } else { + int p = 0, cap = buildingcapacity(u->building); + const unit *u2; + for (u2 = u->region->units; u2; u2 = u2->next) { + if (u2->building == u->building) { + p += u2->number; + if (u2 == u) { + if (p <= cap) + return u->building; + return NULL; + } + if (p > cap) + return NULL; + } + } + } + return NULL; +} + +void u_setfaction(unit * u, faction * f) +{ + int cnt = u->number; + + if (u->faction == f) + return; + if (u->faction) { + set_number(u, 0); + if (count_unit(u)) + --u->faction->no_units; + join_group(u, NULL); + free_orders(&u->orders); + set_order(&u->thisorder, NULL); + + if (u->nextF) + u->nextF->prevF = u->prevF; + if (u->prevF) + u->prevF->nextF = u->nextF; + else + u->faction->units = u->nextF; + } + + if (f != NULL) { + if (f->units) + f->units->prevF = u; + u->prevF = NULL; + u->nextF = f->units; + f->units = u; + } else + u->nextF = NULL; + + u->faction = f; + if (u->region) + update_interval(f, u->region); + if (cnt && f) { + set_number(u, cnt); + if (count_unit(u)) + ++f->no_units; + } +} + +/* vorsicht Sprüche können u->number == RS_FARVISION haben! */ +void set_number(unit * u, int count) +{ + assert(count >= 0); + assert(count <= UNIT_MAXSIZE); + + if (count == 0) { + u->flags &= ~(UFL_HERO); + } + if (u->faction && playerrace(u_race(u))) { + u->faction->num_people += count - u->number; + } + u->number = (unsigned short)count; +} + +bool learn_skill(unit * u, skill_t sk, double chance) +{ + skill *sv = u->skills; + if (chance < 1.0 && rng_int() % 10000 >= chance * 10000) + return false; + while (sv != u->skills + u->skill_size) { + assert(sv->weeks > 0); + if (sv->id == sk) { + if (sv->weeks <= 1) { + sk_set(sv, sv->level + 1); + } else { + sv->weeks--; + } + return true; + } + ++sv; + } + sv = add_skill(u, sk); + sk_set(sv, 1); + return true; +} + +void remove_skill(unit * u, skill_t sk) +{ + skill *sv = u->skills; + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->id == sk) { + skill *sl = u->skills + u->skill_size - 1; + if (sl != sv) { + *sv = *sl; + } + --u->skill_size; + return; + } + } +} + +skill *add_skill(unit * u, skill_t id) +{ + skill *sv = u->skills; +#ifndef NDEBUG + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + assert(sv->id != id); + } +#endif + ++u->skill_size; + u->skills = realloc(u->skills, u->skill_size * sizeof(skill)); + sv = (u->skills + u->skill_size - 1); + sv->level = (unsigned char)0; + sv->weeks = (unsigned char)1; + sv->old = (unsigned char)0; + sv->id = (unsigned char)id; + return sv; +} + +skill *get_skill(const unit * u, skill_t sk) +{ + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + if (sv->id == sk) + return sv; + ++sv; + } + return NULL; +} + +bool has_skill(const unit * u, skill_t sk) +{ + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + if (sv->id == sk) { + return (sv->level > 0); + } + ++sv; + } + return false; +} + +static int item_modification(const unit * u, skill_t sk, int val) +{ + /* Presseausweis: *2 Spionage, 0 Tarnung */ + if (sk == SK_SPY && get_item(u, I_PRESSCARD) >= u->number) { + val = val * 2; + } else if (sk == SK_STEALTH) { +#if NEWATSROI == 1 + if (get_item(u, I_RING_OF_INVISIBILITY) + + 100 * get_item(u, I_SPHERE_OF_INVISIBILITY) >= u->number) { + val += ROIBONUS; + } +#endif + if (get_item(u, I_PRESSCARD) >= u->number) { + val = 0; + } + } +#if NEWATSROI == 1 + if (sk == SK_PERCEPTION) { + if (get_item(u, I_AMULET_OF_TRUE_SEEING) >= u->number) { + val += ATSBONUS; + } + } +#endif + return val; +} + +static int att_modification(const unit * u, skill_t sk) +{ + double result = 0; + static bool init = false; + static const curse_type *skillmod_ct, *gbdream_ct, *worse_ct; + curse *c; + + if (!init) { + init = true; + skillmod_ct = ct_find("skillmod"); + gbdream_ct = ct_find("gbdream"); + worse_ct = ct_find("worse"); + } + + c = get_curse(u->attribs, worse_ct); + if (c != NULL) + result += curse_geteffect(c); + if (skillmod_ct) { + attrib *a = a_find(u->attribs, &at_curse); + while (a && a->type == &at_curse) { + curse *c = (curse *) a->data.v; + if (c->type == skillmod_ct && c->data.i == sk) { + result += curse_geteffect(c); + break; + } + a = a->next; + } + } + + /* TODO hier kann nicht mit get/iscursed gearbeitet werden, da nur der + * jeweils erste vom Typ C_GBDREAM zurückgegen wird, wir aber alle + * durchsuchen und aufaddieren müssen */ + if (u->region) { + double bonus = 0, malus = 0; + attrib *a = a_find(u->region->attribs, &at_curse); + while (a && a->type == &at_curse) { + curse *c = (curse *) a->data.v; + if (curse_active(c) && c->type == gbdream_ct) { + double mod = curse_geteffect(c); + unit *mage = c->magician; + /* wir suchen jeweils den größten Bonus und den größten Malus */ + if (mod > bonus) { + if (mage == NULL || mage->number == 0 + || alliedunit(mage, u->faction, HELP_GUARD)) { + bonus = mod; + } + } else if (mod < malus) { + if (mage == NULL || !alliedunit(mage, u->faction, HELP_GUARD)) { + malus = mod; + } + } + } + a = a->next; + } + result = result + bonus + malus; + } + + return (int)result; +} + +int +get_modifier(const unit * u, skill_t sk, int level, const region * r, + bool noitem) +{ + int bskill = level; + int skill = bskill; + + if (r && sk == SK_STEALTH) { + plane *pl = rplane(r); + if (pl && fval(pl, PFL_NOSTEALTH)) { + return 0; + } + } + + skill += rc_skillmod(u_race(u), r, sk); + skill += att_modification(u, sk); + + if (!noitem) { + skill = item_modification(u, sk, skill); + } + skill = skillmod(u->attribs, u, r, sk, skill, SMF_ALWAYS); + +#ifdef HUNGER_REDUCES_SKILL + if (fval(u, UFL_HUNGER)) { + skill = skill / 2; + } +#endif + return skill - bskill; +} + +int eff_skill(const unit * u, skill_t sk, const region * r) +{ + if (skill_enabled[sk]) { + int level = get_level(u, sk); + if (level > 0) { + int mlevel = level + get_modifier(u, sk, level, r, false); + + if (mlevel > 0) { + int skillcap = SkillCap(sk); + if (skillcap && mlevel > skillcap) { + return skillcap; + } + return mlevel; + } + } + } + return 0; +} + +int eff_skill_study(const unit * u, skill_t sk, const region * r) +{ + int level = get_level(u, sk); + if (level > 0) { + int mlevel = level + get_modifier(u, sk, level, r, true); + + if (mlevel > 0) + return mlevel; + } + return 0; +} + +int invisible(const unit * target, const unit * viewer) +{ +#if NEWATSROI == 1 + return 0; +#else + if (viewer && viewer->faction == target->faction) + return 0; + else { + int hidden = + get_item(target, I_RING_OF_INVISIBILITY) + 100 * get_item(target, + I_SPHERE_OF_INVISIBILITY); + if (hidden) { + hidden = MIN(hidden, target->number); + if (viewer) + hidden -= get_item(viewer, I_AMULET_OF_TRUE_SEEING); + } + return hidden; + } +#endif +} + +/** remove the unit from memory. + * this frees all memory that's only accessible through the unit, + * and you should already have called uunhash and removed the unit from the + * region. + */ +void free_unit(unit * u) +{ + free(u->name); + free(u->display); + free_order(u->thisorder); + free_orders(&u->orders); + if (u->skills) + free(u->skills); + while (u->items) { + item *it = u->items->next; + u->items->next = NULL; + i_free(u->items); + u->items = it; + } + while (u->attribs) + a_remove(&u->attribs, u->attribs); + while (u->reservations) { + struct reservation *res = u->reservations; + u->reservations = res->next; + free(res); + } +} + +static void createunitid(unit * u, int id) +{ + if (id <= 0 || id > MAX_UNIT_NR || ufindhash(id) || dfindhash(id) + || forbiddenid(id)) + u->no = newunitid(); + else + u->no = id; + uhash(u); +} + +void name_unit(unit * u) +{ + if (u_race(u)->generate_name) { + const char *gen_name = u_race(u)->generate_name(u); + if (gen_name) { + unit_setname(u, gen_name); + } else { + unit_setname(u, racename(u->faction->locale, u, u_race(u))); + } + } else { + char name[32]; + const char * result; + const struct locale * lang = u->faction ? u->faction->locale : default_locale; + if (lang) { + static const char * prefix[MAXLOCALES]; + int i = locale_index(lang); + if (!prefix[i]) { + prefix[i] = LOC(lang, "unitdefault"); + if (!prefix[i]) { + prefix[i] = parameters[P_UNIT]; + } + } + result = prefix[i]; + } else { + result = parameters[P_UNIT]; + } + strlcpy(name, result, sizeof(name)); + strlcat(name, " ", sizeof(name)); + strlcat(name, itoa36(u->no), sizeof(name)); + unit_setname(u, name); + } +} + +/** creates a new unit. +* +* @param dname: name, set to NULL to get a default. +* @param creator: unit to inherit stealth, group, building, ship, etc. from +*/ +unit *create_unit(region * r, faction * f, int number, const struct race *urace, + int id, const char *dname, unit * creator) +{ + unit *u = (unit *)calloc(1, sizeof(unit)); + + assert(urace); + if (f) { + assert(f->alive); + u_setfaction(u, f); + + if (f->locale) { + order *deford = default_order(f->locale); + if (deford) { + set_order(&u->thisorder, NULL); + addlist(&u->orders, deford); + } + } + } + u_seteffstealth(u, -1); + u_setrace(u, urace); + u->irace = NULL; + + set_number(u, number); + + /* die nummer der neuen einheit muss vor name_unit generiert werden, + * da der default name immer noch 'Nummer u->no' ist */ + createunitid(u, id); + + /* zuerst in die Region setzen, da zb Drachennamen den Regionsnamen + * enthalten */ + if (r) + move_unit(u, r, NULL); + + /* u->race muss bereits gesetzt sein, wird für default-hp gebraucht */ + /* u->region auch */ + u->hp = unit_max_hp(u) * number; + + if (!dname) { + name_unit(u); + } else { + u->name = strdup(dname); + } + + if (creator) { + attrib *a; + + /* erbt Kampfstatus */ + setstatus(u, creator->status); + + /* erbt Gebäude/Schiff */ + if (creator->region == r) { + if (creator->building) { + u_set_building(u, creator->building); + } + if (creator->ship && fval(u_race(u), RCF_CANSAIL)) { + u_set_ship(u, creator->ship); + } + } + + /* Tarnlimit wird vererbt */ + if (fval(creator, UFL_STEALTH)) { + attrib *a = a_find(creator->attribs, &at_stealth); + if (a) { + int stealth = a->data.i; + a = a_add(&u->attribs, a_new(&at_stealth)); + a->data.i = stealth; + } + } + + /* Temps von parteigetarnten Einheiten sind wieder parteigetarnt */ + if (fval(creator, UFL_ANON_FACTION)) { + fset(u, UFL_ANON_FACTION); + } + /* Daemonentarnung */ + set_racename(&u->attribs, get_racename(creator->attribs)); + if (fval(u_race(u), RCF_SHAPESHIFT) && fval(u_race(creator), RCF_SHAPESHIFT)) { + u->irace = creator->irace; + } + + /* Gruppen */ + if (creator->faction == f && fval(creator, UFL_GROUP)) { + a = a_find(creator->attribs, &at_group); + if (a) { + group *g = (group *) a->data.v; + set_group(u, g); + } + } + a = a_find(creator->attribs, &at_otherfaction); + if (a) { + a_add(&u->attribs, make_otherfaction(get_otherfaction(a))); + } + + a = a_add(&u->attribs, a_new(&at_creator)); + a->data.v = creator; + } + + return u; +} + +int maxheroes(const struct faction *f) +{ + int nsize = count_all(f); + if (nsize == 0) + return 0; + else { + int nmax = (int)(log10(nsize / 50.0) * 20); + return (nmax < 0) ? 0 : nmax; + } +} + +int countheroes(const struct faction *f) +{ + const unit *u = f->units; + int n = 0; + + while (u) { + if (fval(u, UFL_HERO)) + n += u->number; + u = u->nextF; + } +#ifdef DEBUG_MAXHEROES + int m = maxheroes(f); + if (n > m) { + log_warning("%s has %d of %d heroes\n", factionname(f), n, m); + } +#endif + return n; +} + +const char *unit_getname(const unit * u) +{ + return (const char *)u->name; +} + +void unit_setname(unit * u, const char *name) +{ + free(u->name); + if (name) + u->name = strdup(name); + else + u->name = NULL; +} + +const char *unit_getinfo(const unit * u) +{ + return (const char *)u->display; +} + +void unit_setinfo(unit * u, const char *info) +{ + free(u->display); + if (info) + u->display = strdup(info); + else + u->display = NULL; +} + +int unit_getid(const unit * u) +{ + return u->no; +} + +void unit_setid(unit * u, int id) +{ + unit *nu = findunit(id); + if (nu == NULL) { + uunhash(u); + u->no = id; + uhash(u); + } +} + +int unit_gethp(const unit * u) +{ + return u->hp; +} + +void unit_sethp(unit * u, int hp) +{ + u->hp = hp; +} + +status_t unit_getstatus(const unit * u) +{ + return u->status; +} + +void unit_setstatus(unit * u, status_t status) +{ + u->status = status; +} + +int unit_getweight(const unit * u) +{ + return weight(u); +} + +int unit_getcapacity(const unit * u) +{ + return walkingcapacity(u); +} + +void unit_addorder(unit * u, order * ord) +{ + order **ordp = &u->orders; + while (*ordp) + ordp = &(*ordp)->next; + *ordp = ord; + u->faction->lastorders = turn; +} + +int unit_max_hp(const unit * u) +{ + static int rules_stamina = -1; + int h; + double p; + static const curse_type *heal_ct = NULL; + + if (rules_stamina < 0) { + rules_stamina = + get_param_int(global.parameters, "rules.stamina", STAMINA_AFFECTS_HP); + } + h = u_race(u)->hitpoints; + if (heal_ct == NULL) + heal_ct = ct_find("healingzone"); + + if (rules_stamina & 1) { + p = pow(effskill(u, SK_STAMINA) / 2.0, 1.5) * 0.2; + h += (int)(h * p + 0.5); + } + /* der healing curse verändert die maximalen hp */ + if (heal_ct) { + curse *c = get_curse(u->region->attribs, heal_ct); + if (c) { + h = (int)(h * (1.0 + (curse_geteffect(c) / 100))); + } + } + + return h; +} + +void scale_number(unit * u, int n) +{ + skill_t sk; + const attrib *a; + int remain; + + if (n == u->number) + return; + if (n && u->number > 0) { + int full; + remain = ((u->hp % u->number) * (n % u->number)) % u->number; + + full = u->hp / u->number; /* wieviel kriegt jede person mindestens */ + u->hp = full * n + (u->hp - full * u->number) * n / u->number; + assert(u->hp >= 0); + if ((rng_int() % u->number) < remain) + ++u->hp; /* Nachkommastellen */ + } else { + remain = 0; + u->hp = 0; + } + if (u->number > 0) { + for (a = a_find(u->attribs, &at_effect); a && a->type == &at_effect; + a = a->next) { + effect_data *data = (effect_data *) a->data.v; + int snew = data->value / u->number * n; + if (n) { + remain = data->value - snew / n * u->number; + snew += remain * n / u->number; + remain = (remain * n) % u->number; + if ((rng_int() % u->number) < remain) + ++snew; /* Nachkommastellen */ + } + data->value = snew; + } + } + if (u->number == 0 || n == 0) { + for (sk = 0; sk < MAXSKILLS; sk++) { + remove_skill(u, sk); + } + } + + set_number(u, n); +} + +const struct race *u_irace(const struct unit *u) +{ + if (u->irace && skill_enabled[SK_STEALTH]) { + return u->irace; + } + return u->race_; +} + +const struct race *u_race(const struct unit *u) +{ + return u->race_; +} + +void u_setrace(struct unit *u, const struct race *rc) +{ + assert(rc); + u->race_ = rc; +} + +void unit_add_spell(unit * u, sc_mage * m, struct spell * sp, int level) +{ + sc_mage *mage = m ? m : get_mage(u); + + if (!mage) { + log_debug("adding new spell %s to a previously non-mage unit %s\n", sp->sname, unitname(u)); + mage = create_mage(u, u->faction?u->faction->magiegebiet:M_GRAY); + } + if (!mage->spellbook) { + mage->spellbook = create_spellbook(0); + } + spellbook_add(mage->spellbook, sp, level); +} + +struct spellbook * unit_get_spellbook(const struct unit * u) +{ + sc_mage * mage = get_mage(u); + if (mage) { + if (mage->spellbook) { + return mage->spellbook; + } + if (mage->magietyp!=M_GRAY) { + return faction_get_spellbook(u->faction); + } + } + return 0; +} diff --git a/core/src/kernel/unit.h b/core/src/kernel/unit.h new file mode 100644 index 000000000..8e09d40f8 --- /dev/null +++ b/core/src/kernel/unit.h @@ -0,0 +1,247 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_UNIT_H +#define H_KRNL_UNIT_H + +#include +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + + struct skill; + struct item; + struct sc_mage; + +#define UFL_DEBUG (1<<0) +#define UFL_ISNEW (1<<1) /* 2 */ +#define UFL_LONGACTION (1<<2) /* 4 */ +#define UFL_OWNER (1<<3) /* 8 */ +#define UFL_ANON_FACTION (1<<4) /* 16 */ +#define UFL_DISBELIEVES (1<<5) /* 32 */ +#define UFL_WARMTH (1<<6) /* 64 */ +#define UFL_HERO (1<<7) +#define UFL_MOVED (1<<8) +#define UFL_NOTMOVING (1<<9) /* Die Einheit kann sich wg. langen Kampfes nicht bewegen */ +#define UFL_DEFENDER (1<<10) +#define UFL_HUNGER (1<<11) /* kann im Folgemonat keinen langen Befehl außer ARBEITE ausführen */ +#define UFL_SIEGE (1<<12) /* speedup: belagert eine burg, siehe attribut */ +#define UFL_TARGET (1<<13) /* speedup: hat ein target, siehe attribut */ +#define UFL_WERE (1<<14) +#define UFL_ENTER (1<<15) /* unit has entered a ship/building and will not leave it */ + +/* warning: von 512/1024 gewechslet, wegen konflikt mit NEW_FOLLOW */ +#define UFL_LOCKED (1<<16) /* Einheit kann keine Personen aufnehmen oder weggeben, nicht rekrutieren. */ +#define UFL_FLEEING (1<<17) /* unit was in a battle, fleeing. */ +#define UFL_STORM (1<<19) /* Kapitän war in einem Sturm */ +#define UFL_FOLLOWING (1<<20) +#define UFL_FOLLOWED (1<<21) + +#define UFL_NOAID (1<<22) /* Einheit hat Noaid-Status */ +#define UFL_MARK (1<<23) /* same as FL_MARK */ +#define UFL_ORDERS (1<<24) /* Einheit hat Befehle erhalten */ +#define UFL_TAKEALL (1<<25) /* Einheit nimmt alle Gegenstände an */ + +/* flags that speed up attribute access: */ +#define UFL_STEALTH (1<<26) +#define UFL_GUARD (1<<27) +#define UFL_GROUP (1<<28) + +/* Flags, die gespeichert werden sollen: */ +#define UFL_SAVEMASK (UFL_DEFENDER|UFL_MOVED|UFL_NOAID|UFL_ANON_FACTION|UFL_LOCKED|UFL_HUNGER|UFL_TAKEALL|UFL_GUARD|UFL_STEALTH|UFL_GROUP|UFL_HERO) + +#define UNIT_MAXSIZE 50000 + extern int maxheroes(const struct faction *f); + extern int countheroes(const struct faction *f); + + typedef struct reservation { + struct reservation *next; + const struct resource_type *type; + int value; + } reservation; + + typedef struct unit { + struct unit *next; /* needs to be first entry, for region's unitlist */ + struct unit *nextF; /* nächste Einheit der Partei */ + struct unit *prevF; /* vorherige Einheit der Partei */ + struct region *region; + int no; + int hp; + char *name; + char *display; + struct faction *faction; + struct building *building; + struct ship *ship; + unsigned short number; + short age; + + /* skill data */ + short skill_size; + struct skill *skills; + struct item *items; + reservation *reservations; + + /* orders */ + struct order *orders; + struct order *thisorder; + struct order *old_orders; + + /* race and illusionary race */ + const struct race *race_; + const struct race *irace; + + unsigned int flags; + struct attrib *attribs; + status_t status; + int n; /* enno: attribut? */ + int wants; /* enno: attribut? */ + } unit; + + extern struct attrib_type at_alias; + extern struct attrib_type at_siege; + extern struct attrib_type at_target; + extern struct attrib_type at_potionuser; + extern struct attrib_type at_contact; + extern struct attrib_type at_effect; + extern struct attrib_type at_private; + extern struct attrib_type at_showskchange; + + int ualias(const struct unit *u); + + extern struct attrib_type at_stealth; + + void u_seteffstealth(struct unit *u, int value); + int u_geteffstealth(const struct unit *u); + const struct race *u_irace(const struct unit *u); + const struct race *u_race(const struct unit *u); + void u_setrace(struct unit *u, const struct race *); + struct building *usiege(const struct unit *u); + void usetsiege(struct unit *u, const struct building *b); + + struct unit *utarget(const struct unit *u); + void usettarget(struct unit *u, const struct unit *b); + + struct unit *utarget(const struct unit *u); + void usettarget(struct unit *u, const struct unit *b); + + extern const struct race *urace(const struct unit *u); + + const char *uprivate(const struct unit *u); + void usetprivate(struct unit *u, const char *c); + + const struct potion_type *ugetpotionuse(const struct unit *u); /* benutzt u einein trank? */ + void usetpotionuse(struct unit *u, const struct potion_type *p); /* u benutzt trank p (es darf halt nur einer pro runde) */ + + bool ucontact(const struct unit *u, const struct unit *u2); + void usetcontact(struct unit *u, const struct unit *c); + + struct unit *findnewunit(const struct region *r, const struct faction *f, + int alias); + + extern const char *u_description(const unit * u, const struct locale *lang); + extern struct skill *add_skill(struct unit *u, skill_t id); + extern void remove_skill(struct unit *u, skill_t sk); + extern struct skill *get_skill(const struct unit *u, skill_t id); + extern bool has_skill(const unit * u, skill_t sk); + + extern void set_level(struct unit *u, skill_t id, int level); + extern int get_level(const struct unit *u, skill_t id); + extern void transfermen(struct unit *u, struct unit *u2, int n); + + extern int eff_skill(const struct unit *u, skill_t sk, + const struct region *r); + extern int eff_skill_study(const struct unit *u, skill_t sk, + const struct region *r); + + extern int get_modifier(const struct unit *u, skill_t sk, int lvl, + const struct region *r, bool noitem); + extern int remove_unit(struct unit **ulist, struct unit *u); + +#define GIFT_SELF 1<<0 +#define GIFT_FRIENDS 1<<1 +#define GIFT_PEASANTS 1<<2 + int gift_items(struct unit *u, int flags); + void make_zombie(unit * u); + +/* see resolve.h */ + extern int resolve_unit(variant data, void *address); + extern void write_unit_reference(const struct unit *u, struct storage *store); + extern variant read_unit_reference(struct storage *store); + + extern bool leave(struct unit *u, bool force); + extern bool can_leave(struct unit *u); + + extern void u_set_building(struct unit * u, struct building * b); + extern void u_set_ship(struct unit * u, struct ship * sh); + extern void leave_ship(struct unit * u); + extern void leave_building(struct unit * u); + + extern void set_leftship(struct unit *u, struct ship *sh); + extern struct ship *leftship(const struct unit *); + extern bool can_survive(const struct unit *u, const struct region *r); + extern void move_unit(struct unit *u, struct region *target, + struct unit **ulist); + + extern struct building *inside_building(const struct unit *u); + +/* cleanup code for this module */ + extern void free_units(void); + extern struct faction *dfindhash(int no); + extern void u_setfaction(struct unit *u, struct faction *f); + extern void set_number(struct unit *u, int count); + + extern bool learn_skill(struct unit *u, skill_t sk, double chance); + + extern int invisible(const struct unit *target, const struct unit *viewer); + extern void free_unit(struct unit *u); + + extern void name_unit(struct unit *u); + extern struct unit *create_unit(struct region *r1, struct faction *f, + int number, const struct race *rc, int id, const char *dname, + struct unit *creator); + + extern void uhash(struct unit *u); + extern void uunhash(struct unit *u); + extern struct unit *ufindhash(int i); + + const char *unit_getname(const struct unit *u); + void unit_setname(struct unit *u, const char *name); + const char *unit_getinfo(const struct unit *u); + void unit_setinfo(struct unit *u, const char *name); + int unit_getid(const unit * u); + void unit_setid(unit * u, int id); + int unit_gethp(const unit * u); + void unit_sethp(unit * u, int id); + status_t unit_getstatus(const unit * u); + void unit_setstatus(unit * u, status_t status); + int unit_getweight(const unit * u); + int unit_getcapacity(const unit * u); + void unit_addorder(unit * u, struct order *ord); + int unit_max_hp(const struct unit *u); + void scale_number(struct unit *u, int n); + + struct spellbook * unit_get_spellbook(const struct unit * u); + void unit_add_spell(struct unit * u, struct sc_mage * m, struct spell * sp, int level); + + extern struct attrib_type at_creator; +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/version.h b/core/src/kernel/version.h new file mode 100644 index 000000000..b7ec62b01 --- /dev/null +++ b/core/src/kernel/version.h @@ -0,0 +1,74 @@ +/* vi: set ts=2: + +-------------------+ + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2007 | Christian Schlittchen + | | + +-------------------+ + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ + +/* changes from->to: 72->73: struct unit::lock entfernt. + * 73->74: struct unit::flags eingeführt. + * 74->75: parteitarnung als flag. + * 75->76: #ifdef NEW_HP: hp + * 76->77: ship->damage + * 77->78: neue Message-Option "Orkvermehrung" (MAX_MSG +1) + * 78->79: showdata nicht mehr speichern + * 79->HEX_VERSION: hex + * 80->82: ATTRIB_VERSION + * 90: Ebenen + * 92: Magiegebiet-Auswahl f->magiegebiet + * 94: f->attribs wird gespeichert + * 100: NEWMAGIC, neue Message-Option "Zauber" (MAX_MSG +1) + * 108: Speichern von Timeouts + * 193: curse bekommen id aus struct unit-nummernraum + */ + +/* +#define HEX_VERSION 81 +#define GROWTREE_VERSION 305 +#define RANDOMIZED_RESOURCES_VERSION 306 +#define NEWRACE_VERSION 307 +#define INTERIM_VERSION 309 +#define NEWSKILL_VERSION 309 +#define WATCHERS_VERSION 310 +*/ +#define OVERRIDE_VERSION 311 +#define CURSETYPE_VERSION 312 /* turn 287 */ +#define ALLIANCES_VERSION 313 +#define DBLINK_VERSION 314 +#define CURSEVIGOURISFLOAT_VERSION 315 +#define SAVEXMLNAME_VERSION 316 +#define SAVEALLIANCE_VERSION 317 +#define CLAIM_VERSION 318 +/* 319 is the HSE4 data version */ +#define BACTION_VERSION 319 /* building action gets a param string */ +#define NOLASTORDER_VERSION 320 /* do not use lastorder */ +#define SPELLNAME_VERSION 321 /* reference spells by name */ +#define TERRAIN_VERSION 322 /* terrains are a full type and saved by name */ +#define REGIONITEMS_VERSION 323 /* regions have items */ +#define ATTRIBREAD_VERSION 324 /* remove a_readint */ +#define CURSEFLAGS_VERSION 325 /* remove a_readint */ +#define UNICODE_VERSION 326 /* 2007-06-27 everything is stored as UTF8 */ +#define UID_VERSION 327 /* regions have a unique id */ +#define STORAGE_VERSION 328 /* with storage.h, some things are stored smarter (ids as base36, fractions as float) */ +#define INTPAK_VERSION 329 /* in binary, ints can get packed */ +#define NOZEROIDS_VERSION 330 /* 2008-05-16 zero is not a valid ID for anything (including factions) */ +#define NOBORDERATTRIBS_VERSION 331 /* 2008-05-17 connection::attribs has been moved to userdata */ +#define UIDHASH_VERSION 332 /* 2008-05-22 borders use the region.uid to store */ +#define REGIONOWNER_VERSION 333 /* 2009-05-14 regions have owners and morale */ +#define ALLIANCELEADER_VERSION 333 /* alliances have a leader */ +#define CURSEFLOAT_VERSION 334 /* all curse-effects are float */ +#define MOURNING_VERSION 335 /* mourning peasants */ +#define FOSS_VERSION 336 /* the open source release */ +#define OWNER_2_VERSION 337 /* region owners contain an alliance */ +#define FIX_WATCHERS_VERSION 338 /* fixed storage of watchers */ +#define UNIQUE_SPELLS_VERSION 339 /* turn 775, spell names are now unique globally, not just per school */ +#define SPELLBOOK_VERSION 340 /* turn 775, full spellbooks are stored for factions */ + +#define MIN_VERSION CURSETYPE_VERSION /* minimal datafile we support */ +#define RELEASE_VERSION SPELLBOOK_VERSION /* current datafile */ diff --git a/core/src/kernel/xmlkernel.h b/core/src/kernel/xmlkernel.h new file mode 100644 index 000000000..b07b93ee5 --- /dev/null +++ b/core/src/kernel/xmlkernel.h @@ -0,0 +1,28 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2007 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifndef H_KRNL_XML +#define H_KRNL_XML + +#ifdef __cplusplus +extern "C" { +#endif + +#include + + extern void xml_readconstruction(xmlXPathContextPtr xpath, + xmlNodeSetPtr nodeSet, struct construction **consPtr); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/kernel/xmlreader.c b/core/src/kernel/xmlreader.c new file mode 100644 index 000000000..70da69915 --- /dev/null +++ b/core/src/kernel/xmlreader.c @@ -0,0 +1,2402 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2004 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "xmlreader.h" + +/* kernel includes */ +#include "building.h" +#include "equipment.h" +#include "item.h" +#include "message.h" +#include "race.h" +#include "region.h" +#include "resources.h" +#include "ship.h" +#include "terrain.h" +#include "skill.h" +#include "spell.h" +#include "spellbook.h" +#include "calendar.h" + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libxml includes */ +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +static bool gamecode_enabled = false; + +static building_type *bt_get_or_create(const char *name) +{ + if (name != NULL) { + building_type *btype = bt_find(name); + if (btype == NULL) { + btype = calloc(sizeof(building_type), 1); + btype->_name = strdup(name); + bt_register(btype); + } + return btype; + } + return NULL; +} + +void enable_xml_gamecode(void) +{ + gamecode_enabled = true; +} + +static void xml_readtext(xmlNodePtr node, struct locale **lang, xmlChar ** text) +{ + xmlChar *propValue = xmlGetProp(node, BAD_CAST "locale"); + assert(propValue != NULL); + *lang = find_locale((const char *)propValue); +#ifdef MAKE_LOCALES + if (*lang == NULL) + *lang = make_locale((const char *)propValue); +#endif + xmlFree(propValue); + + *text = xmlNodeListGetString(node->doc, node->children, 1); +} + +static const spell *xml_spell(xmlNode * node, const char *name) +{ + const spell *sp = NULL; + xmlChar *propValue = xmlGetProp(node, BAD_CAST name); + if (propValue != NULL) { + sp = find_spell((const char *)propValue); + assert(sp); + xmlFree(propValue); + } + return sp; +} + +static xmlChar *xml_cleanup_string(xmlChar * str) +{ + xmlChar *read = str; + xmlChar *write = str; + + while (*read) { + /* eat leading whitespace */ + if (*read && isxspace(*read)) { + while (*read && isxspace(*read)) { + ++read; + } + *write++ = ' '; + } + while (*read) { + if (*read == '\n') + break; + if (*read == '\r') + break; + *write++ = *read++; + } + } + *write = 0; + return str; +} + +static const resource_type *rt_findorcreate(const char *name) +{ + resource_type *rtype = rt_find(name); + if (rtype == NULL) { + const char *names[2]; + char *namep = strcat(strcpy((char *)malloc(strlen(name) + 3), name), "_p"); + /* we'll make a placeholder */ + names[0] = name; + names[1] = namep; + rtype = new_resourcetype(names, NULL, RTF_NONE); + rt_register(rtype); + free(namep); + } + return rtype; +} + +static void +xml_readrequirements(xmlNodePtr * nodeTab, int nodeNr, requirement ** reqArray) +{ + int req; + requirement *radd = *reqArray; + + assert(radd == NULL); + if (nodeNr == 0) + return; + + radd = *reqArray = calloc(sizeof(requirement), nodeNr + 1); + + for (req = 0; req != nodeNr; ++req) { + xmlNodePtr node = nodeTab[req]; + xmlChar *propValue; + + radd->number = xml_ivalue(node, "quantity", 1); + radd->recycle = xml_fvalue(node, "recycle", 0.5); + + propValue = xmlGetProp(node, BAD_CAST "type"); + radd->rtype = rt_findorcreate((const char *)propValue); + xmlFree(propValue); + + ++radd; + } +} + +void +xml_readconstruction(xmlXPathContextPtr xpath, xmlNodeSetPtr nodeSet, + construction ** consPtr) +{ + xmlNodePtr pushNode = xpath->node; + int k; + for (k = 0; k != nodeSet->nodeNr; ++k) { + xmlNodePtr node = nodeSet->nodeTab[k]; + xmlChar *propValue; + construction *con; + xmlXPathObjectPtr req; + int m; + skill_t sk = NOSKILL; + + propValue = xmlGetProp(node, BAD_CAST "skill"); + if (propValue != NULL) { + sk = sk_find((const char *)propValue); + if (sk == NOSKILL) { + log_error("construction requires skill '%s' that does not exist.\n", (const char *)propValue); + xmlFree(propValue); + continue; + } + xmlFree(propValue); + } + + assert(*consPtr == NULL); + + *consPtr = con = (construction *)calloc(sizeof(construction), 1); + consPtr = &con->improvement; + + con->skill = sk; + con->maxsize = xml_ivalue(node, "maxsize", -1); + con->minskill = xml_ivalue(node, "minskill", -1); + con->reqsize = xml_ivalue(node, "reqsize", -1); + + propValue = xmlGetProp(node, BAD_CAST "building"); + if (propValue != NULL) { + con->btype = bt_get_or_create((const char *)propValue); + xmlFree(propValue); + } + + /* read construction/requirement */ + xpath->node = node; + req = xmlXPathEvalExpression(BAD_CAST "requirement", xpath); + xml_readrequirements(req->nodesetval->nodeTab, + req->nodesetval->nodeNr, &con->materials); + xmlXPathFreeObject(req); + + /* read construction/modifier */ + xpath->node = node; + req = xmlXPathEvalExpression(BAD_CAST "modifier", xpath); + for (m = 0; m != req->nodesetval->nodeNr; ++m) { + xmlNodePtr node = req->nodesetval->nodeTab[m]; + + propValue = xmlGetProp(node, BAD_CAST "function"); + if (propValue != NULL) { + pf_generic foo = get_function((const char *)propValue); + a_add(&con->attribs, make_skillmod(NOSKILL, SMF_PRODUCTION, + (skillmod_fun) foo, 1.0, 0)); + xmlFree(propValue); + } + + } + xmlXPathFreeObject(req); + } + xpath->node = pushNode; +} + +static int +parse_function(xmlNodePtr node, pf_generic * funPtr, xmlChar ** namePtr) +{ + pf_generic fun; + xmlChar *propValue = xmlGetProp(node, BAD_CAST "value"); + assert(propValue != NULL); + fun = get_function((const char *)propValue); + if (fun != NULL) { + xmlFree(propValue); + + propValue = xmlGetProp(node, BAD_CAST "name"); + } + *namePtr = propValue; + *funPtr = fun; + return 0; +} + +static int parse_buildings(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr buildings; + int i; + + /* reading eressea/buildings/building */ + buildings = + xmlXPathEvalExpression(BAD_CAST "/eressea/buildings/building", xpath); + if (buildings->nodesetval != NULL) { + xmlNodeSetPtr nodes = buildings->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + xmlChar *propValue; + building_type *btype; + xmlXPathObjectPtr result; + int k; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + btype = bt_get_or_create((const char *)propValue); + xmlFree(propValue); + + btype->capacity = xml_ivalue(node, "capacity", -1); + btype->maxcapacity = xml_ivalue(node, "maxcapacity", -1); + btype->maxsize = xml_ivalue(node, "maxsize", -1); + + btype->magres = xml_ivalue(node, "magres", 0); + btype->magresbonus = xml_ivalue(node, "magresbonus", 0); + btype->fumblebonus = xml_ivalue(node, "fumblebonus", 0); + btype->auraregen = xml_fvalue(node, "auraregen", 1.0); + + if (xml_bvalue(node, "nodestroy", false)) + btype->flags |= BTF_INDESTRUCTIBLE; + if (xml_bvalue(node, "oneperturn", false)) + btype->flags |= BTF_ONEPERTURN; + if (xml_bvalue(node, "nobuild", false)) + btype->flags |= BTF_NOBUILD; + if (xml_bvalue(node, "namechange", true)) + btype->flags |= BTF_NAMECHANGE; + if (xml_bvalue(node, "unique", false)) + btype->flags |= BTF_UNIQUE; + if (xml_bvalue(node, "decay", false)) + btype->flags |= BTF_DECAY; + if (xml_bvalue(node, "magic", false)) + btype->flags |= BTF_MAGIC; + + /* reading eressea/buildings/building/construction */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "construction", xpath); + xml_readconstruction(xpath, result->nodesetval, &btype->construction); + xmlXPathFreeObject(result); + + if (gamecode_enabled) { + /* reading eressea/buildings/building/function */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "function", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + pf_generic fun; + parse_function(node, &fun, &propValue); + + if (fun == NULL) { + log_error("unknown function name '%s' for building %s\n", (const char *)propValue, btype->_name); + xmlFree(propValue); + continue; + } + assert(propValue != NULL); + if (strcmp((const char *)propValue, "name") == 0) { + btype->name = + (const char *(*)(const struct building_type *, + const struct building *, int))fun; + } else if (strcmp((const char *)propValue, "init") == 0) { + btype->init = (void (*)(struct building_type *))fun; + } else if (strcmp((const char *)propValue, "age") == 0) { + btype->age = (void (*)(struct building *))fun; + } else if (strcmp((const char *)propValue, "protection") == 0) { + btype->protection = (int (*)(struct building *, struct unit *))fun; + } else if (strcmp((const char *)propValue, "taxes") == 0) { + btype->taxes = (double (*)(const struct building *, int))fun; + } else if (strcmp((const char *)propValue, "age") == 0) { + btype->age = (void (*)(struct building *))fun; + } else { + log_error("unknown function type '%s' for building %s\n", (const char *)propValue, btype->_name); + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + } + + /* reading eressea/buildings/building/maintenance */ + result = xmlXPathEvalExpression(BAD_CAST "maintenance", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + maintenance *mt; + + if (btype->maintenance == NULL) { + btype->maintenance = (struct maintenance *) + calloc(sizeof(struct maintenance), result->nodesetval->nodeNr + 1); + } + mt = btype->maintenance + k; + mt->number = xml_ivalue(node, "amount", 0); + + propValue = xmlGetProp(node, BAD_CAST "type"); + assert(propValue != NULL); + mt->rtype = rt_find((const char *)propValue); + assert(mt->rtype != NULL); + xmlFree(propValue); + + if (xml_bvalue(node, "variable", false)) + mt->flags |= MTF_VARIABLE; + if (xml_bvalue(node, "vital", false)) + mt->flags |= MTF_VITAL; + + } + xmlXPathFreeObject(result); + + /* finally, initialize the new building type */ + if (btype->init) + btype->init(btype); + } + } + xmlXPathFreeObject(buildings); + + xmlXPathFreeContext(xpath); + return 0; +} + +static int parse_calendar(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr xpathCalendars; + xmlNodeSetPtr nsetCalendars; + int c, rv = 0; + + /* reading eressea/buildings/building */ + xpathCalendars = xmlXPathEvalExpression(BAD_CAST "/eressea/calendar", xpath); + nsetCalendars = xpathCalendars->nodesetval; + months_per_year = 0; + if (nsetCalendars == NULL || nsetCalendars->nodeNr == 0) { + rv = -1; + } else + for (c = 0; c != nsetCalendars->nodeNr; ++c) { + xmlNodePtr calendar = nsetCalendars->nodeTab[c]; + xmlXPathObjectPtr xpathWeeks, xpathMonths, xpathSeasons; + xmlNodeSetPtr nsetWeeks, nsetMonths, nsetSeasons; + xmlChar *propValue = xmlGetProp(calendar, BAD_CAST "name"); + xmlChar *newyear = xmlGetProp(calendar, BAD_CAST "newyear"); + + first_turn = xml_ivalue(calendar, "start", first_turn); + if (propValue) { + free(agename); + agename = strdup(mkname("calendar", (const char *)propValue)); + xmlFree(propValue); + } + + xpath->node = calendar; + xpathWeeks = xmlXPathEvalExpression(BAD_CAST "week", xpath); + nsetWeeks = xpathWeeks->nodesetval; + if (nsetWeeks != NULL && nsetWeeks->nodeNr) { + int i; + + weeks_per_month = nsetWeeks->nodeNr; + assert(!weeknames); + weeknames = malloc(sizeof(char *) * weeks_per_month); + weeknames2 = malloc(sizeof(char *) * weeks_per_month); + for (i = 0; i != nsetWeeks->nodeNr; ++i) { + xmlNodePtr week = nsetWeeks->nodeTab[i]; + xmlChar *propValue = xmlGetProp(week, BAD_CAST "name"); + if (propValue) { + weeknames[i] = strdup(mkname("calendar", (const char *)propValue)); + weeknames2[i] = malloc(strlen(weeknames[i]) + 3); + sprintf(weeknames2[i], "%s_d", weeknames[i]); + xmlFree(propValue); + } + } + } + xmlXPathFreeObject(xpathWeeks); + + xpathSeasons = xmlXPathEvalExpression(BAD_CAST "season", xpath); + nsetSeasons = xpathSeasons->nodesetval; + if (nsetSeasons != NULL && nsetSeasons->nodeNr) { + int i; + + seasons = nsetSeasons->nodeNr; + assert(!seasonnames); + seasonnames = malloc(sizeof(char *) * seasons); + + for (i = 0; i != nsetSeasons->nodeNr; ++i) { + xmlNodePtr season = nsetSeasons->nodeTab[i]; + xmlChar *propValue = xmlGetProp(season, BAD_CAST "name"); + if (propValue) { + seasonnames[i] = + strdup(mkname("calendar", (const char *)propValue)); + xmlFree(propValue); + } + } + } + + xpathMonths = xmlXPathEvalExpression(BAD_CAST "season/month", xpath); + nsetMonths = xpathMonths->nodesetval; + if (nsetMonths != NULL && nsetMonths->nodeNr) { + int i; + + months_per_year = nsetMonths->nodeNr; + assert(!monthnames); + monthnames = malloc(sizeof(char *) * months_per_year); + month_season = malloc(sizeof(int) * months_per_year); + storms = malloc(sizeof(int) * months_per_year); + + for (i = 0; i != nsetMonths->nodeNr; ++i) { + xmlNodePtr month = nsetMonths->nodeTab[i]; + xmlChar *propValue = xmlGetProp(month, BAD_CAST "name"); + int j; + + if (propValue) { + if (newyear + && strcmp((const char *)newyear, (const char *)propValue) == 0) { + first_month = i; + xmlFree(newyear); + newyear = NULL; + } + monthnames[i] = strdup(mkname("calendar", (const char *)propValue)); + xmlFree(propValue); + } + for (j = 0; j != seasons; ++j) { + xmlNodePtr season = month->parent; + if (season == nsetSeasons->nodeTab[j]) { + month_season[i] = j; + break; + } + } + assert(j != seasons); + storms[i] = xml_ivalue(nsetMonths->nodeTab[i], "storm", 0); + } + } + xmlXPathFreeObject(xpathMonths); + xmlXPathFreeObject(xpathSeasons); + } + xmlXPathFreeObject(xpathCalendars); + xmlXPathFreeContext(xpath); + + return rv; +} + +static int parse_directions(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr xpathDirections; + xmlNodeSetPtr nsetDirections; + int rv = 0; + + /* reading eressea/directions/dir */ + xpathDirections = + xmlXPathEvalExpression(BAD_CAST "/eressea/directions/dir", xpath); + nsetDirections = xpathDirections->nodesetval; + if (nsetDirections != NULL) { + int k; + for (k = 0; k != nsetDirections->nodeNr; ++k) { + xmlNodePtr dir = nsetDirections->nodeTab[k]; + xmlChar *propValue = xmlGetProp(dir, BAD_CAST "name"); + + register_special_direction((const char *)propValue); + xmlFree(propValue); + } + } + xmlXPathFreeObject(xpathDirections); + xmlXPathFreeContext(xpath); + + return rv; +} + +static int parse_ships(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr ships; + int i; + + /* reading eressea/ships/ship */ + ships = xmlXPathEvalExpression(BAD_CAST "/eressea/ships/ship", xpath); + if (ships->nodesetval != NULL) { + xmlNodeSetPtr nodes = ships->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr child, node = nodes->nodeTab[i]; + xmlChar *propValue; + ship_type *st = (ship_type *)calloc(sizeof(ship_type), 1); + xmlXPathObjectPtr result; + int k, c; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + st->name[0] = strdup((const char *)propValue); + st->name[1] = + strcat(strcpy(malloc(strlen(st->name[0]) + 3), st->name[0]), "_a"); + xmlFree(propValue); + + st->cabins = xml_ivalue(node, "cabins", 0) * PERSON_WEIGHT; + st->cargo = xml_ivalue(node, "cargo", 0); + st->combat = xml_ivalue(node, "combat", 0); + st->cptskill = xml_ivalue(node, "cptskill", 0); + st->damage = xml_fvalue(node, "damage", 0.0); + if (xml_bvalue(node, "nocoast", false)) + st->flags |= SFL_NOCOAST; + if (xml_bvalue(node, "fly", false)) + st->flags |= SFL_FLY; + if (xml_bvalue(node, "opensea", false)) + st->flags |= SFL_OPENSEA; + st->fishing = xml_ivalue(node, "fishing", 0); + st->minskill = xml_ivalue(node, "minskill", 0); + st->range = xml_ivalue(node, "range", 0); + st->storm = xml_fvalue(node, "storm", 1.0); + st->sumskill = xml_ivalue(node, "sumskill", 0); + + /* reading eressea/ships/ship/construction */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "construction", xpath); + xml_readconstruction(xpath, result->nodesetval, &st->construction); + xmlXPathFreeObject(result); + + for (child = node->children; child; child = child->next) { + if (strcmp((const char *)child->name, "modifier") == 0) { + double value = xml_fvalue(child, "value", 0.0); + propValue = xmlGetProp(child, BAD_CAST "type"); + if (strcmp((const char *)propValue, "tactics") == 0) + st->tac_bonus = (float)value; + else if (strcmp((const char *)propValue, "attack") == 0) + st->at_bonus = (int)value; + else if (strcmp((const char *)propValue, "defense") == 0) + st->df_bonus = (int)value; + xmlFree(propValue); + } + } + /* reading eressea/ships/ship/coast */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "coast", xpath); + for (c = 0, k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + + if (k == 0) { + assert(st->coasts == NULL); + st->coasts = + (const terrain_type **)malloc(sizeof(const terrain_type *) * + (result->nodesetval->nodeNr + 1)); + st->coasts[result->nodesetval->nodeNr] = NULL; + } + + propValue = xmlGetProp(node, BAD_CAST "terrain"); + assert(propValue != NULL); + st->coasts[c] = get_terrain((const char *)propValue); + if (st->coasts[c] != NULL) + ++c; + else { + log_warning("ship %s mentions a non-existing terrain %s.\n", st->name[0], propValue); + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + + /* finally, register the new building type */ + st_register(st); + } + } + xmlXPathFreeObject(ships); + + xmlXPathFreeContext(xpath); + return 0; +} + +static void race_compat(void) +{ + /* required for old_race, do not change order! */ + const char *oldracenames[MAXRACES] = { + "dwarf", "elf", NULL, "goblin", "human", "troll", "demon", "insect", + "halfling", "cat", "aquarian", "orc", "snotling", "undead", "illusion", + "youngdragon", "dragon", "wyrm", "ent", "catdragon", "dracoid", + "special", "spell", "irongolem", "stonegolem", "shadowdemon", + "shadowmaster", "mountainguard", "alp", "toad", "braineater", "peasant", + "wolf", NULL, NULL, NULL, NULL, "songdragon", NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, "seaserpent", + "shadowknight", "centaur", "skeleton", "skeletonlord", "zombie", + "juju-zombie", "ghoul", "ghast", "museumghost", "gnome", "template", + "clone" + }; + int i; + + for (i = 0; i != MAXRACES; ++i) { + const char *rcname = oldracenames[i]; + if (rcname == NULL) { + new_race[i] = NULL; + } else { + race *rc = rc_find(oldracenames[i]); + if (rc) { + new_race[i] = rc; + if (rc == new_race[RC_TROLL]) { + a_add(&rc->attribs, make_skillmod(NOSKILL, SMF_RIDING, NULL, 0.0, + -1)); + } + } + } + } +} + +static potion_type *xml_readpotion(xmlXPathContextPtr xpath, item_type * itype) +{ + int level = xml_ivalue(xpath->node, "level", 0); + + assert(level > 0); + return new_potiontype(itype, level); +} + +static luxury_type *xml_readluxury(xmlXPathContextPtr xpath, item_type * itype) +{ + int price = xml_ivalue(xpath->node, "price", 0); + return new_luxurytype(itype, price); +} + +static armor_type *xml_readarmor(xmlXPathContextPtr xpath, item_type * itype) +{ + xmlNodePtr node = xpath->node; + armor_type *atype = NULL; + unsigned int flags = ATF_NONE; + int ac = xml_ivalue(node, "ac", 0); + double penalty = xml_fvalue(node, "penalty", 0.0); + double magres = xml_fvalue(node, "magres", 0.0); + + if (xml_bvalue(node, "laen", false)) + flags |= ATF_LAEN; + if (xml_bvalue(node, "shield", false)) + flags |= ATF_SHIELD; + + atype = new_armortype(itype, penalty, magres, ac, flags); + atype->projectile = (float)xml_fvalue(node, "projectile", 0.0); + return atype; +} + +static weapon_type *xml_readweapon(xmlXPathContextPtr xpath, item_type * itype) +{ + xmlNodePtr node = xpath->node; + weapon_type *wtype = NULL; + unsigned int flags = WTF_NONE; + xmlXPathObjectPtr result; + xmlChar *propValue; + int k; + skill_t sk; + int minskill = xml_ivalue(node, "minskill", 0); + int offmod = xml_ivalue(node, "offmod", 0); + int defmod = xml_ivalue(node, "defmod", 0); + int reload = xml_ivalue(node, "reload", 0); + double magres = xml_fvalue(node, "magres", 0.0); + + if (xml_bvalue(node, "armorpiercing", false)) + flags |= WTF_ARMORPIERCING; + if (xml_bvalue(node, "magical", false)) + flags |= WTF_MAGICAL; + if (xml_bvalue(node, "missile", false)) + flags |= WTF_MISSILE; + if (xml_bvalue(node, "pierce", false)) + flags |= WTF_PIERCE; + if (xml_bvalue(node, "cut", false)) + flags |= WTF_CUT; + if (xml_bvalue(node, "blunt", false)) + flags |= WTF_BLUNT; + if (xml_bvalue(node, "siege", false)) + flags |= WTF_SIEGE; + if (xml_bvalue(node, "horse", (flags & WTF_MISSILE) == 0)) + flags |= WTF_HORSEBONUS; + if (xml_bvalue(node, "useshield", true)) + flags |= WTF_USESHIELD; + + propValue = xmlGetProp(node, BAD_CAST "skill"); + assert(propValue != NULL); + sk = sk_find((const char *)propValue); + assert(sk != NOSKILL); + xmlFree(propValue); + + wtype = + new_weapontype(itype, flags, magres, NULL, offmod, defmod, reload, sk, + minskill); + + /* reading weapon/damage */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "damage", xpath); + assert(result->nodesetval->nodeNr <= 2); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + int pos = 0; + + propValue = xmlGetProp(node, BAD_CAST "type"); + if (strcmp((const char *)propValue, "footman") != 0) { + pos = 1; + } + xmlFree(propValue); + + propValue = xmlGetProp(node, BAD_CAST "value"); + wtype->damage[pos] = gc_add(strdup((const char *)propValue)); + if (k == 0) + wtype->damage[1 - pos] = wtype->damage[pos]; + xmlFree(propValue); + } + xmlXPathFreeObject(result); + + /* reading weapon/modifier */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "modifier", xpath); + assert(wtype->modifiers == NULL); + wtype->modifiers = calloc(result->nodesetval->nodeNr + 1, sizeof(weapon_mod)); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + xmlXPathObjectPtr races; + int r, flags = 0; + + if (xml_bvalue(node, "walking", false)) + flags |= WMF_WALKING; + if (xml_bvalue(node, "riding", false)) + flags |= WMF_RIDING; + if (xml_bvalue(node, "against_walking", false)) + flags |= WMF_AGAINST_WALKING; + if (xml_bvalue(node, "against_riding", false)) + flags |= WMF_AGAINST_RIDING; + if (xml_bvalue(node, "offensive", false)) + flags |= WMF_OFFENSIVE; + if (xml_bvalue(node, "defensive", false)) + flags |= WMF_DEFENSIVE; + + propValue = xmlGetProp(node, BAD_CAST "type"); + if (strcmp((const char *)propValue, "damage") == 0) + flags |= WMF_DAMAGE; + else if (strcmp((const char *)propValue, "skill") == 0) + flags |= WMF_SKILL; + else if (strcmp((const char *)propValue, "missile_target") == 0) + flags |= WMF_MISSILE_TARGET; + xmlFree(propValue); + + wtype->modifiers[k].flags = flags; + wtype->modifiers[k].value = xml_ivalue(node, "value", 0); + + xpath->node = node; + races = xmlXPathEvalExpression(BAD_CAST "race", xpath); + for (r = 0; r != races->nodesetval->nodeNr; ++r) { + xmlNodePtr node = races->nodesetval->nodeTab[r]; + + propValue = xmlGetProp(node, BAD_CAST "name"); + if (propValue != NULL) { + const race *rc = rc_find((const char *)propValue); + if (rc == NULL) + rc = rc_add(rc_new((const char *)propValue)); + racelist_insert(&wtype->modifiers[k].races, rc); + xmlFree(propValue); + } + } + xmlXPathFreeObject(races); + } + xmlXPathFreeObject(result); + + if (gamecode_enabled) { + /* reading weapon/function */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "function", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + xmlChar *propValue; + pf_generic fun; + + parse_function(node, &fun, &propValue); + if (fun == NULL) { + log_error("unknown function name '%s' for item '%s'\n", (const char *)propValue, itype->rtype->_name[0]); + xmlFree(propValue); + continue; + } + assert(propValue != NULL); + if (strcmp((const char *)propValue, "attack") == 0) { + wtype->attack = + (bool(*)(const struct troop *, const struct weapon_type *, + int *))fun; + } else { + log_error("unknown function type '%s' for item '%s'\n", (const char *)propValue, itype->rtype->_name[0]); + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + } + + xpath->node = node; + return wtype; +} + +static item_type *xml_readitem(xmlXPathContextPtr xpath, resource_type * rtype) +{ + xmlNodePtr node = xpath->node; + item_type *itype = NULL; + unsigned int flags = ITF_NONE; + xmlXPathObjectPtr result; + int k; + + int weight = xml_ivalue(node, "weight", 0); + int capacity = xml_ivalue(node, "capacity", 0); + + if (xml_bvalue(node, "cursed", false)) + flags |= ITF_CURSED; + if (xml_bvalue(node, "notlost", false)) + flags |= ITF_NOTLOST; + if (xml_bvalue(node, "herb", false)) + flags |= ITF_HERB; + if (xml_bvalue(node, "big", false)) + flags |= ITF_BIG; + if (xml_bvalue(node, "animal", false)) + flags |= ITF_ANIMAL; + if (xml_bvalue(node, "vehicle", false)) + flags |= ITF_VEHICLE; + itype = new_itemtype(rtype, flags, weight, capacity); +#if SCORE_MODULE + itype->score = xml_ivalue(node, "score", 0); +#endif + + /* reading item/construction */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "construction", xpath); + xml_readconstruction(xpath, result->nodesetval, &itype->construction); + xmlXPathFreeObject(result); + + /* reading item/weapon */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "weapon", xpath); + assert(result->nodesetval->nodeNr <= 1); + if (result->nodesetval->nodeNr != 0) { + xpath->node = result->nodesetval->nodeTab[0]; + rtype->wtype = xml_readweapon(xpath, itype); + } + xmlXPathFreeObject(result); + + /* reading item/potion */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "potion", xpath); + assert(result->nodesetval->nodeNr <= 1); + if (result->nodesetval->nodeNr != 0) { + xpath->node = result->nodesetval->nodeTab[0]; + rtype->ptype = xml_readpotion(xpath, itype); + } + xmlXPathFreeObject(result); + + /* reading item/luxury */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "luxury", xpath); + assert(result->nodesetval->nodeNr <= 1); + if (result->nodesetval->nodeNr != 0) { + xpath->node = result->nodesetval->nodeTab[0]; + rtype->ltype = xml_readluxury(xpath, itype); + } + xmlXPathFreeObject(result); + + /* reading item/armor */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "armor", xpath); + assert(result->nodesetval->nodeNr <= 1); + if (result->nodesetval->nodeNr != 0) { + xpath->node = result->nodesetval->nodeTab[0]; + rtype->atype = xml_readarmor(xpath, itype); + } + xmlXPathFreeObject(result); + + if (gamecode_enabled) { + /* reading item/function */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "function", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + xmlChar *propValue; + pf_generic fun; + + parse_function(node, &fun, &propValue); + if (fun == NULL) { + log_error("unknown function name '%s' for item '%s'\n", (const char *)propValue, rtype->_name[0]); + xmlFree(propValue); + continue; + } + assert(propValue != NULL); + if (strcmp((const char *)propValue, "give") == 0) { + itype->give = + (int (*)(struct unit *, struct unit *, const struct item_type *, int, + struct order *))fun; + } else if (strcmp((const char *)propValue, "use") == 0) { + itype->use = + (int (*)(struct unit *, const struct item_type *, int, + struct order *))fun; + } else if (strcmp((const char *)propValue, "canuse") == 0) { + itype->canuse = + (bool(*)(const struct unit *, const struct item_type *))fun; + } else if (strcmp((const char *)propValue, "useonother") == 0) { + itype->useonother = + (int (*)(struct unit *, int, const struct item_type *, int, + struct order *))fun; + } else { + log_error("unknown function type '%s' for item '%s'\n", (const char *)propValue, rtype->_name[0]); + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + } + + return itype; +} + +static int parse_rules(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr functions; + xmlNodeSetPtr nodes; + int i; + + /* reading eressea/resources/resource */ + functions = xmlXPathEvalExpression(BAD_CAST "/eressea/rules/function", xpath); + nodes = functions->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + xmlChar *propValue; + pf_generic fun; + + parse_function(node, &fun, &propValue); + + if (fun == NULL) { + log_error("unknown function for rule '%s' %s\n", (const char *)propValue); + xmlFree(propValue); + continue; + } + assert(propValue != NULL); + if (strcmp((const char *)propValue, "wage") == 0) { + global.functions.wage = + (int (*)(const struct region *, const struct faction *, + const struct race *, int))fun; + } else if (strcmp((const char *)propValue, "maintenance") == 0) { + global.functions.maintenance = (int (*)(const struct unit *))fun; + } else { + log_error("unknown function for rule '%s'\n", (const char *)propValue); + } + xmlFree(propValue); + } + xmlXPathFreeObject(functions); + xmlXPathFreeContext(xpath); + return 0; +} + +static int parse_resources(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr resources; + xmlNodeSetPtr nodes; + int i; + + /* reading eressea/resources/resource */ + resources = + xmlXPathEvalExpression(BAD_CAST "/eressea/resources/resource", xpath); + nodes = resources->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + xmlChar *propValue, *name, *appearance; + const char *names[2], *appearances[2]; + char *namep = NULL, *appearancep = NULL; + resource_type *rtype; + unsigned int flags = RTF_NONE; + xmlXPathObjectPtr result; + int k; + + if (xml_bvalue(node, "pooled", true)) + flags |= RTF_POOLED; + if (xml_bvalue(node, "limited", false)) + flags |= RTF_LIMITED; + + name = xmlGetProp(node, BAD_CAST "name"); + appearance = xmlGetProp(node, BAD_CAST "appearance"); + assert(name != NULL); + + if (appearance != NULL) { + appearancep = + strcat(strcpy((char *)malloc(strlen((char *)appearance) + 3), + (char *)appearance), "_p"); + } + + rtype = rt_find((const char *)name); + if (rtype != NULL) { + /* dependency from another item, was created earlier */ + rtype->flags |= flags; + if (appearance) { + rtype->_appearance[0] = strdup((const char *)appearance); + rtype->_appearance[1] = appearancep; + free(appearancep); + } + } else { + namep = + strcat(strcpy((char *)malloc(strlen((char *)name) + 3), (char *)name), + "_p"); + names[0] = (const char *)name; + names[1] = namep; + if (appearance) { + appearances[0] = (const char *)appearance; + appearances[1] = appearancep; + rtype = new_resourcetype((const char **)names, (const char **)appearances, flags); + rt_register(rtype); + free(appearancep); + } else { + rtype = new_resourcetype(names, NULL, flags); + rt_register(rtype); + } + free(namep); + } + + if (name) + xmlFree(name); + if (appearance) + xmlFree(appearance); + + name = xmlGetProp(node, BAD_CAST "material"); + if (name) { + rmt_create(rtype, (const char *)name); + xmlFree(name); + } + + if (gamecode_enabled) { + /* reading eressea/resources/resource/function */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "function", xpath); + if (result->nodesetval != NULL) + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + pf_generic fun; + + parse_function(node, &fun, &propValue); + if (fun == NULL) { + log_error("unknown function name '%s' for resource %s\n", (const char *)propValue, rtype->_name[0]); + xmlFree(propValue); + continue; + } + + assert(propValue != NULL); + if (strcmp((const char *)propValue, "change") == 0) { + rtype->uchange = (rtype_uchange) fun; + } else if (strcmp((const char *)propValue, "get") == 0) { + rtype->uget = (rtype_uget) fun; + } else if (strcmp((const char *)propValue, "name") == 0) { + rtype->name = (rtype_name) fun; + } else { + log_error("unknown function type '%s' for resource %s\n", (const char *)propValue, rtype->_name[0]); + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + } + + /* reading eressea/resources/resource/resourcelimit */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "resourcelimit", xpath); + assert(result->nodesetval->nodeNr <= 1); + if (result->nodesetval->nodeNr != 0) { + resource_limit *rdata; + attrib *a = a_find(rtype->attribs, &at_resourcelimit); + xmlNodePtr limit = result->nodesetval->nodeTab[0]; + + if (a == NULL) + a = a_add(&rtype->attribs, a_new(&at_resourcelimit)); + rdata = (resource_limit *) a->data.v; + rtype->flags |= RTF_LIMITED; + xpath->node = limit; + xmlXPathFreeObject(result); + + result = xmlXPathEvalExpression(BAD_CAST "modifier", xpath); + if (result->nodesetval != NULL) { + rdata->modifiers = + calloc(result->nodesetval->nodeNr + 1, sizeof(resource_mod)); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + building_type *btype = NULL; + const race *rc = NULL; + + propValue = xmlGetProp(node, BAD_CAST "race"); + if (propValue != NULL) { + rc = rc_find((const char *)propValue); + if (rc == NULL) + rc = rc_add(rc_new((const char *)propValue)); + xmlFree(propValue); + } + rdata->modifiers[k].race = rc; + + propValue = xmlGetProp(node, BAD_CAST "building"); + if (propValue != NULL) { + btype = bt_get_or_create((const char *)propValue); + xmlFree(propValue); + } + rdata->modifiers[k].btype = btype; + + propValue = xmlGetProp(node, BAD_CAST "type"); + assert(propValue != NULL); + if (strcmp((const char *)propValue, "skill") == 0) { + rdata->modifiers[k].value.i = xml_ivalue(node, "value", 0); + rdata->modifiers[k].flags = RMF_SKILL; + } else if (strcmp((const char *)propValue, "material") == 0) { + rdata->modifiers[k].value.f = (float)xml_fvalue(node, "value", 0); + rdata->modifiers[k].flags = RMF_SAVEMATERIAL; + } else if (strcmp((const char *)propValue, "resource") == 0) { + rdata->modifiers[k].value.f = (float)xml_fvalue(node, "value", 0); + rdata->modifiers[k].flags = RMF_SAVERESOURCE; + } else if (strcmp((const char *)propValue, "require") == 0) { + xmlChar *propBldg = xmlGetProp(node, BAD_CAST "building"); + if (propBldg != NULL) { + btype = bt_get_or_create((const char *)propBldg); + rdata->modifiers[k].btype = btype; + rdata->modifiers[k].flags = RMF_REQUIREDBUILDING; + xmlFree(propBldg); + } + } else { + log_error("unknown type '%s' for resourcelimit-modifier '%s'\n", (const char *)propValue, rtype->_name[0]); + } + xmlFree(propValue); + } + } + xmlXPathFreeObject(result); + + result = xmlXPathEvalExpression(BAD_CAST "guard", xpath); + if (result->nodesetval != NULL) + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + xmlChar *propFlag = xmlGetProp(node, BAD_CAST "flag"); + + if (propFlag != NULL) { + if (strcmp((const char *)propFlag, "logging") == 0) { + rdata->guard |= GUARD_TREES; + } else if (strcmp((const char *)propFlag, "mining") == 0) { + rdata->guard |= GUARD_MINING; + } + xmlFree(propFlag); + } + } + xmlXPathFreeObject(result); + + /* reading eressea/resources/resource/resourcelimit/function */ + result = xmlXPathEvalExpression(BAD_CAST "function", xpath); + if (result->nodesetval != NULL) + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + pf_generic fun; + + propValue = xmlGetProp(node, BAD_CAST "value"); + assert(propValue != NULL); + fun = get_function((const char *)propValue); + if (fun == NULL) { + log_error("unknown limit '%s' for resource %s\n", (const char *)propValue, rtype->_name[0]); + xmlFree(propValue); + continue; + } + xmlFree(propValue); + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + if (strcmp((const char *)propValue, "produce") == 0) { + rdata->produce = (rlimit_produce) fun; + } else if (strcmp((const char *)propValue, "limit") == 0) { + rdata->limit = (rlimit_limit) fun; + } else { + log_error("unknown limit '%s' for resource %s\n", (const char *)propValue, rtype->_name[0]); + } + xmlFree(propValue); + } + } + xmlXPathFreeObject(result); + + /* reading eressea/resources/resource/resourcelimit/function */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "resourcelimit/function", xpath); + xmlXPathFreeObject(result); + + /* reading eressea/resources/resource/item */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "item", xpath); + assert(result->nodesetval->nodeNr <= 1); + if (result->nodesetval->nodeNr != 0) { + rtype->flags |= RTF_ITEM; + xpath->node = result->nodesetval->nodeTab[0]; + rtype->itype = xml_readitem(xpath, rtype); + } + xmlXPathFreeObject(result); + } + xmlXPathFreeObject(resources); + + xmlXPathFreeContext(xpath); + + /* make sure old items (used in requirements) are available */ + init_resources(); + init_itemtypes(); + + return 0; +} + +static void add_items(equipment * eq, xmlNodeSetPtr nsetItems) +{ + if (nsetItems != NULL && nsetItems->nodeNr > 0) { + int i; + for (i = 0; i != nsetItems->nodeNr; ++i) { + xmlNodePtr node = nsetItems->nodeTab[i]; + xmlChar *propValue; + const struct item_type *itype; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + itype = it_find((const char *)propValue); + xmlFree(propValue); + if (itype != NULL) { + propValue = xmlGetProp(node, BAD_CAST "amount"); + if (propValue != NULL) { + equipment_setitem(eq, itype, (const char *)propValue); + xmlFree(propValue); + } + } + } + } +} + +static void add_callbacks(equipment * eq, xmlNodeSetPtr nsetItems) +{ + if (nsetItems != NULL && nsetItems->nodeNr > 0) { + int i; + for (i = 0; i != nsetItems->nodeNr; ++i) { + xmlNodePtr node = nsetItems->nodeTab[i]; + xmlChar *propValue; + pf_generic fun; + + propValue = xmlGetProp(node, BAD_CAST "name"); + if (propValue != NULL) { + fun = get_function((const char *)propValue); + if (fun) { + equipment_setcallback(eq, (void (*)(const struct equipment *, + struct unit *))fun); + } + xmlFree(propValue); + } + } + } +} + +static void add_spells(equipment * eq, xmlNodeSetPtr nsetItems) +{ + if (nsetItems != NULL && nsetItems->nodeNr > 0) { + int i; + for (i = 0; i != nsetItems->nodeNr; ++i) { + xmlNodePtr node = nsetItems->nodeTab[i]; + xmlChar *propValue; + struct spell *sp; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + sp = find_spell((const char *)propValue); + if (!sp) { + log_error("no spell '%s' for equipment-set '%s'\n", (const char *)propValue, eq->name); + } else { + int level = xml_ivalue(node, "level", 0); + if (level>0) { + equipment_addspell(eq, sp, level); + } else { + log_error("spell '%s' for equipment-set '%s' has no level\n", sp->sname, eq->name); + } + } + xmlFree(propValue); + } + } +} + +static void add_skills(equipment * eq, xmlNodeSetPtr nsetSkills) +{ + if (nsetSkills != NULL && nsetSkills->nodeNr > 0) { + int i; + for (i = 0; i != nsetSkills->nodeNr; ++i) { + xmlNodePtr node = nsetSkills->nodeTab[i]; + xmlChar *propValue; + skill_t sk; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + sk = sk_find((const char *)propValue); + if (sk == NOSKILL) { + log_error("unknown skill '%s' in equipment-set %s\n", (const char *)propValue, eq->name); + xmlFree(propValue); + } else { + xmlFree(propValue); + propValue = xmlGetProp(node, BAD_CAST "level"); + if (propValue != NULL) { + equipment_setskill(eq, sk, (const char *)propValue); + xmlFree(propValue); + } + } + } + } +} + +static void +add_subsets(xmlDocPtr doc, equipment * eq, xmlNodeSetPtr nsetSubsets) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + if (nsetSubsets != NULL && nsetSubsets->nodeNr > 0) { + int i; + + eq->subsets = calloc(nsetSubsets->nodeNr + 1, sizeof(subset)); + for (i = 0; i != nsetSubsets->nodeNr; ++i) { + xmlXPathObjectPtr xpathResult; + xmlNodePtr node = nsetSubsets->nodeTab[i]; + xmlChar *propValue; + + eq->subsets[i].chance = 1.0f; + propValue = xmlGetProp(node, BAD_CAST "chance"); + if (propValue != NULL) { + eq->subsets[i].chance = (float)atof((const char *)propValue); + xmlFree(propValue); + } + xpath->node = node; + xpathResult = xmlXPathEvalExpression(BAD_CAST "set", xpath); + if (xpathResult->nodesetval) { + xmlNodeSetPtr nsetSets = xpathResult->nodesetval; + float totalChance = 0.0f; + + if (nsetSets->nodeNr > 0) { + int set; + eq->subsets[i].sets = + calloc(nsetSets->nodeNr + 1, sizeof(subsetitem)); + for (set = 0; set != nsetSets->nodeNr; ++set) { + xmlNodePtr nodeSet = nsetSets->nodeTab[set]; + float chance = 1.0f; + + propValue = xmlGetProp(nodeSet, BAD_CAST "chance"); + if (propValue != NULL) { + chance = (float)atof((const char *)propValue); + xmlFree(propValue); + } + totalChance += chance; + + propValue = xmlGetProp(nodeSet, BAD_CAST "name"); + assert(propValue != NULL); + eq->subsets[i].sets[set].chance = chance; + eq->subsets[i].sets[set].set = + create_equipment((const char *)propValue); + xmlFree(propValue); + } + } + if (totalChance > 1.0f) { + log_error("total chance exceeds 1.0: %f in equipment set %s.\n", totalChance, eq->name); + } + } + xmlXPathFreeObject(xpathResult); + } + } + xmlXPathFreeContext(xpath); +} + +static int parse_equipment(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr xpathRaces; + + /* reading eressea/equipment/set */ + xpathRaces = xmlXPathEvalExpression(BAD_CAST "/eressea/equipment/set", xpath); + if (xpathRaces->nodesetval) { + xmlNodeSetPtr nsetRaces = xpathRaces->nodesetval; + int i; + + for (i = 0; i != nsetRaces->nodeNr; ++i) { + xmlNodePtr node = nsetRaces->nodeTab[i]; + xmlChar *propName = xmlGetProp(node, BAD_CAST "name"); + + if (propName != NULL) { + equipment *eq = create_equipment((const char *)propName); + xmlXPathObjectPtr xpathResult; + + xpath->node = node; + + xpathResult = xmlXPathEvalExpression(BAD_CAST "callback", xpath); + assert(!eq->callback); + add_callbacks(eq, xpathResult->nodesetval); + xmlXPathFreeObject(xpathResult); + + xpathResult = xmlXPathEvalExpression(BAD_CAST "item", xpath); + assert(!eq->items); + add_items(eq, xpathResult->nodesetval); + xmlXPathFreeObject(xpathResult); + + xpathResult = xmlXPathEvalExpression(BAD_CAST "spell", xpath); + assert(!eq->spellbook); + add_spells(eq, xpathResult->nodesetval); + xmlXPathFreeObject(xpathResult); + + xpathResult = xmlXPathEvalExpression(BAD_CAST "skill", xpath); + add_skills(eq, xpathResult->nodesetval); + xmlXPathFreeObject(xpathResult); + + xpathResult = xmlXPathEvalExpression(BAD_CAST "subset", xpath); + assert(!eq->subsets); + add_subsets(doc, eq, xpathResult->nodesetval); + xmlXPathFreeObject(xpathResult); + + xmlFree(propName); + } + } + } + + xmlXPathFreeObject(xpathRaces); + xmlXPathFreeContext(xpath); + + return 0; +} + +static int parse_spellbooks(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr spellbooks; + + /* reading eressea/spells/spell */ + spellbooks = xmlXPathEvalExpression(BAD_CAST "/eressea/spells/spellbook", xpath); + + if (spellbooks->nodesetval != NULL) { + xmlNodeSetPtr nodes = spellbooks->nodesetval; + int i, k; + + for (i = 0; i != nodes->nodeNr; ++i) { + xmlXPathObjectPtr result; + xmlNodePtr node = nodes->nodeTab[i]; + xmlChar *propValue; + spellbook * sb; + + propValue = xmlGetProp(node, BAD_CAST "name"); + if (propValue) { + sb = get_spellbook((const char *)propValue); + xmlFree(propValue); + } else { + log_error("spellbook at index '%d' has n name\n", i); + continue; + } + + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "entry", xpath); + + if (result->nodesetval->nodeNr > 0) { + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + spell * sp = 0; + int level = xml_ivalue(node, "level", -1); + + propValue = xmlGetProp(node, BAD_CAST "spell"); + if (propValue) { + sp = find_spell((const char *)propValue); + xmlFree(propValue); + } + if (sp && level>0) { + spellbook_add(sb, sp, level); + } else { + log_error("invalid entry at index '%d' in spellbook '%s'\n", k, sb->name); + } + } + } + xmlXPathFreeObject(result); + } + } + xmlXPathFreeObject(spellbooks); + xmlXPathFreeContext(xpath); + return 0; +} + +static int parse_spells(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr spells; + char zText[32]; + strcpy(zText, "fumble_"); + + /* reading eressea/spells/spell */ + spells = xmlXPathEvalExpression(BAD_CAST "/eressea/spells/spell", xpath); + if (spells->nodesetval != NULL) { + xmlNodeSetPtr nodes = spells->nodesetval; + int i; + + for (i = 0; i != nodes->nodeNr; ++i) { + xmlXPathObjectPtr result; + xmlNodePtr node = nodes->nodeTab[i]; + xmlChar *propValue; + int k; + spell_component *component; + spell *sp; + unsigned int index; + static int modes[] = { 0, PRECOMBATSPELL, COMBATSPELL, POSTCOMBATSPELL }; + + /* spellname */ + index = xml_ivalue(node, "index", 0); + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + sp = create_spell((const char *)propValue, index); + xmlFree(propValue); + if (!sp) { + continue; + } + + propValue = xmlGetProp(node, BAD_CAST "parameters"); + if (propValue) { + sp->parameter = strdup((const char *)propValue); + xmlFree(propValue); + } + + propValue = xmlGetProp(node, BAD_CAST "syntax"); + if (propValue) { + sp->syntax = strdup((const char *)propValue); + xmlFree(propValue); + } +#ifdef TODO /* no longer need it, spellbooks! */ + /* magic type */ + propValue = xmlGetProp(node, BAD_CAST "type"); + assert(propValue != NULL); + for (sp->magietyp = 0; sp->magietyp != MAXMAGIETYP; ++sp->magietyp) { + if (strcmp(magic_school[sp->magietyp], (const char *)propValue) == 0) + break; + } + assert(sp->magietyp != MAXMAGIETYP); + xmlFree(propValue); + /* level, rank and flags */ +#endif + sp->rank = (char)xml_ivalue(node, "rank", -1); + if (xml_bvalue(node, "los", false)) + sp->sptyp |= TESTCANSEE; /* must see or have contact */ + if (!xml_bvalue(node, "target_global", false)) + sp->sptyp |= SEARCHLOCAL; /* must be in same region */ + if (xml_bvalue(node, "ship", false)) + sp->sptyp |= ONSHIPCAST; + if (xml_bvalue(node, "ocean", false)) + sp->sptyp |= OCEANCASTABLE; + if (xml_bvalue(node, "far", false)) + sp->sptyp |= FARCASTING; + if (xml_bvalue(node, "variable", false)) + sp->sptyp |= SPELLLEVEL; + k = xml_ivalue(node, "combat", 0); + if (k >= 0 && k <= 3) + sp->sptyp |= modes[k]; + + if (gamecode_enabled) { + /* reading eressea/spells/spell/function */ + pf_generic cast = 0; + pf_generic fumble = 0; + + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "function", xpath); + + if (result->nodesetval->nodeNr == 0) { + cast = get_function(sp->sname); + if (!cast) { + log_error("no spell cast function registered for '%s'\n", sp->sname); + } + strlcpy(zText+7, sp->sname, sizeof(zText)-7); + fumble = get_function(zText); + } else { + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + pf_generic fun; + + parse_function(node, &fun, &propValue); + assert(propValue != NULL); + if (strcmp((const char *)propValue, "cast") == 0) { + if (fun) { + cast = fun; + } else { + log_error("unknown function name '%s' for spell '%s'\n", (const char *)propValue, sp->sname); + } + } else if (fun && strcmp((const char *)propValue, "fumble") == 0) { + fumble = fun; + } else { + log_error("unknown function type '%s' for spell '%s'\n", (const char *)propValue, sp->sname); + } + xmlFree(propValue); + } + } + sp->cast = (spell_f)cast; + sp->patzer = (fumble_f)fumble; + xmlXPathFreeObject(result); + } + + /* reading eressea/spells/spell/resource */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "resource", xpath); + if (result->nodesetval->nodeNr) { + sp->components = + (spell_component *) malloc(sizeof(spell_component) * + (result->nodesetval->nodeNr + 1)); + sp->components[result->nodesetval->nodeNr].type = 0; + } + for (component = sp->components, k = 0; k != result->nodesetval->nodeNr; + ++k) { + const resource_type *rtype; + xmlNodePtr node = result->nodesetval->nodeTab[k]; + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue); + rtype = rt_find((const char *)propValue); + if (!rtype) { + log_error("spell %s uses unknown component %s.\n", sp->sname, (const char *)propValue); + xmlFree(propValue); + continue; + } + component->type = rtype; + xmlFree(propValue); + component->amount = xml_ivalue(node, "amount", 1); + component->cost = SPC_FIX; + propValue = xmlGetProp(node, BAD_CAST "cost"); + if (propValue != NULL) { + if (strcmp((const char *)propValue, "linear") == 0) { + if ((sp->sptyp&SPELLLEVEL)==0) { + log_error("spell '%s' has linear cost but fixed level\n", sp->sname); + } + component->cost = SPC_LINEAR; + } else if (strcmp((const char *)propValue, "level") == 0) { + if ((sp->sptyp&SPELLLEVEL)==0) { + log_error("spell '%s' has levelled cost but fixed level\n", sp->sname); + } + component->cost = SPC_LEVEL; + } + xmlFree(propValue); + } + component++; + } + xmlXPathFreeObject(result); + } + } + + xmlXPathFreeObject(spells); + + xmlXPathFreeContext(xpath); + + return 0; +} + +static void parse_param(struct param **params, xmlNodePtr node) +{ + xmlChar *propName = xmlGetProp(node, BAD_CAST "name"); + xmlChar *propValue = xmlGetProp(node, BAD_CAST "value"); + + set_param(params, (const char *)propName, (const char *)propValue); + + xmlFree(propName); + xmlFree(propValue); +} + +static void parse_ai(race * rc, xmlNodePtr node) +{ + rc->splitsize = xml_ivalue(node, "splitsize", 0); + rc->aggression = (float)xml_fvalue(node, "aggression", 0.04); + if (xml_bvalue(node, "killpeasants", false)) + rc->flags |= RCF_KILLPEASANTS; + if (xml_bvalue(node, "moverandom", false)) + rc->flags |= RCF_MOVERANDOM; + if (xml_bvalue(node, "learn", false)) + rc->flags |= RCF_LEARN; +} + +static int parse_races(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr races; + xmlNodeSetPtr nodes; + int i; + + /* reading eressea/races/race */ + races = xmlXPathEvalExpression(BAD_CAST "/eressea/races/race", xpath); + nodes = races->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + xmlNodePtr child; + xmlChar *propValue; + race *rc; + xmlXPathObjectPtr result; + int k, study_speed_base; + struct att *attack; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + rc = rc_find((const char *)propValue); + if (rc == NULL) + rc = rc_add(rc_new((const char *)propValue)); + xmlFree(propValue); + + propValue = xmlGetProp(node, BAD_CAST "damage"); + assert(propValue != NULL); + rc->def_damage = strdup((const char *)propValue); + xmlFree(propValue); + + rc->magres = (float)xml_fvalue(node, "magres", 0.0); + rc->maxaura = (float)xml_fvalue(node, "maxaura", 0.0); + rc->regaura = (float)xml_fvalue(node, "regaura", 1.0); + rc->recruitcost = xml_ivalue(node, "recruitcost", 0); + rc->maintenance = xml_ivalue(node, "maintenance", 0); + rc->weight = xml_ivalue(node, "weight", PERSON_WEIGHT); + rc->capacity = xml_ivalue(node, "capacity", 540); + rc->speed = (float)xml_fvalue(node, "speed", 1.0F); + rc->hitpoints = xml_ivalue(node, "hp", 0); + rc->armor = (char)xml_ivalue(node, "ac", 0); + study_speed_base = xml_ivalue(node, "studyspeed", 0); + + rc->at_default = (char)xml_ivalue(node, "unarmedattack", -2); + rc->df_default = (char)xml_ivalue(node, "unarmeddefense", -2); + rc->at_bonus = (char)xml_ivalue(node, "attackmodifier", 0); + rc->df_bonus = (char)xml_ivalue(node, "defensemodifier", 0); + + if (xml_bvalue(node, "playerrace", false)) + rc->flags |= RCF_PLAYERRACE; + if (xml_bvalue(node, "scarepeasants", false)) + rc->flags |= RCF_SCAREPEASANTS; + if (xml_bvalue(node, "cansteal", true)) + rc->flags |= RCF_CANSTEAL; + if (xml_bvalue(node, "cansail", true)) + rc->flags |= RCF_CANSAIL; + if (xml_bvalue(node, "cannotmove", false)) + rc->flags |= RCF_CANNOTMOVE; + if (xml_bvalue(node, "fly", false)) + rc->flags |= RCF_FLY; + if (xml_bvalue(node, "invisible", false)) + rc->flags |= RCF_INVISIBLE; + if (xml_bvalue(node, "coastal", false)) + rc->flags |= RCF_COASTAL; + if (xml_bvalue(node, "unarmedguard", false)) + rc->flags |= RCF_UNARMEDGUARD; + if (xml_bvalue(node, "swim", false)) + rc->flags |= RCF_SWIM; + if (xml_bvalue(node, "walk", false)) + rc->flags |= RCF_WALK; + if (!xml_bvalue(node, "canlearn", true)) + rc->flags |= RCF_NOLEARN; + if (!xml_bvalue(node, "canteach", true)) + rc->flags |= RCF_NOTEACH; + if (xml_bvalue(node, "horse", false)) + rc->flags |= RCF_HORSE; + if (xml_bvalue(node, "desert", false)) + rc->flags |= RCF_DESERT; + if (xml_bvalue(node, "absorbpeasants", false)) + rc->flags |= RCF_ABSORBPEASANTS; + if (xml_bvalue(node, "noheal", false)) + rc->flags |= RCF_NOHEAL; + if (xml_bvalue(node, "noweapons", false)) + rc->flags |= RCF_NOWEAPONS; + if (xml_bvalue(node, "shapeshift", false)) + rc->flags |= RCF_SHAPESHIFT; + if (xml_bvalue(node, "shapeshiftany", false)) + rc->flags |= RCF_SHAPESHIFTANY; + if (xml_bvalue(node, "illusionary", false)) + rc->flags |= RCF_ILLUSIONARY; + if (xml_bvalue(node, "undead", false)) + rc->flags |= RCF_UNDEAD; + if (xml_bvalue(node, "dragon", false)) + rc->flags |= RCF_DRAGON; + if (xml_bvalue(node, "shipspeed", false)) + rc->flags |= RCF_SHIPSPEED; + if (xml_bvalue(node, "stonegolem", false)) + rc->flags |= RCF_STONEGOLEM; + if (xml_bvalue(node, "irongolem", false)) + rc->flags |= RCF_IRONGOLEM; + + if (xml_bvalue(node, "giveitem", false)) + rc->ec_flags |= GIVEITEM; + if (xml_bvalue(node, "giveperson", false)) + rc->ec_flags |= GIVEPERSON; + if (xml_bvalue(node, "giveunit", false)) + rc->ec_flags |= GIVEUNIT; + if (xml_bvalue(node, "getitem", false)) + rc->ec_flags |= GETITEM; + if (xml_bvalue(node, "recruithorses", false)) + rc->ec_flags |= ECF_REC_HORSES; + if (xml_bvalue(node, "recruitethereal", false)) + rc->ec_flags |= ECF_REC_ETHEREAL; + if (xml_bvalue(node, "recruitunlimited", false)) + rc->ec_flags |= ECF_REC_UNLIMITED; + + if (xml_bvalue(node, "equipment", false)) + rc->battle_flags |= BF_EQUIPMENT; + if (xml_bvalue(node, "noblock", false)) + rc->battle_flags |= BF_NOBLOCK; + if (xml_bvalue(node, "invinciblenonmagic", false)) + rc->battle_flags |= BF_INV_NONMAGIC; + if (xml_bvalue(node, "resistbash", false)) + rc->battle_flags |= BF_RES_BASH; + if (xml_bvalue(node, "resistcut", false)) + rc->battle_flags |= BF_RES_CUT; + if (xml_bvalue(node, "resistpierce", false)) + rc->battle_flags |= BF_RES_PIERCE; + if (xml_bvalue(node, "canattack", true)) + rc->battle_flags |= BF_CANATTACK; + + for (child = node->children; child; child = child->next) { + if (strcmp((const char *)child->name, "ai") == 0) { + parse_ai(rc, child); + } else if (strcmp((const char *)child->name, "param") == 0) { + parse_param(&rc->parameters, child); + } + } + rc->recruit_multi = get_param_flt(rc->parameters, "recruit_multi", 1.0); + + /* reading eressea/races/race/skill */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "skill", xpath); + memset(rc->bonus, 0, sizeof(rc->bonus)); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + int mod = xml_ivalue(node, "modifier", 0); + int speed = xml_ivalue(node, "speed", study_speed_base); + skill_t sk; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + sk = sk_find((const char *)propValue); + if (sk != NOSKILL) { + rc->bonus[sk] = (char)mod; + if (speed) { + if (!rc->study_speed) + rc->study_speed = calloc(1, MAXSKILLS); + rc->study_speed[sk] = (char)speed; + } + } else { + log_error("unknown skill '%s' in race '%s'\n", (const char *)propValue, rc->_name[0]); + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + + if (gamecode_enabled) { + /* reading eressea/races/race/function */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "function", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + pf_generic fun; + + parse_function(node, &fun, &propValue); + if (fun == NULL) { + log_error("unknown function name '%s' for race %s\n", (const char *)propValue, rc->_name[0]); + xmlFree(propValue); + continue; + } + assert(propValue != NULL); + if (strcmp((const char *)propValue, "name") == 0) { + rc->generate_name = (const char *(*)(const struct unit *))fun; + } else if (strcmp((const char *)propValue, "describe") == 0) { + rc->describe = + (const char *(*)(const struct unit *, const struct locale *))fun; + } else if (strcmp((const char *)propValue, "age") == 0) { + rc->age = (void (*)(struct unit *))fun; + } else if (strcmp((const char *)propValue, "move") == 0) { + rc->move_allowed = + (bool(*)(const struct region *, const struct region *))fun; + } else if (strcmp((const char *)propValue, "itemdrop") == 0) { + rc->itemdrop = (struct item * (*)(const struct race *, int))fun; + } else if (strcmp((const char *)propValue, "initfamiliar") == 0) { + rc->init_familiar = (void (*)(struct unit *))fun; + } else { + log_error("unknown function type '%s' for race %s\n", (const char *)propValue, rc->_name[0]); + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + } + + /* reading eressea/races/race/familiar */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "familiar", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + race *frc; + + propValue = xmlGetProp(node, BAD_CAST "race"); + assert(propValue != NULL); + frc = rc_find((const char *)propValue); + if (!frc) { + frc = rc_add(rc_new((const char *)propValue)); + } + if (xml_bvalue(node, "default", false)) { + rc->familiars[k] = rc->familiars[0]; + rc->familiars[0] = frc; + } else { + rc->familiars[k] = frc; + } + xmlFree(propValue); + } + xmlXPathFreeObject(result); + + /* reading eressea/races/race/precombatspell */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "precombatspell", xpath); + assert(rc->precombatspell == NULL + || !"precombatspell is already initialized"); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + rc->precombatspell = xml_spell(node, "spell"); + } + xmlXPathFreeObject(result); + + /* reading eressea/races/race/attack */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "attack", xpath); + attack = rc->attack; + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + while (attack->type != AT_NONE) + ++attack; + + propValue = xmlGetProp(node, BAD_CAST "damage"); + if (propValue != NULL) { + attack->data.dice = strdup((const char *)propValue); + xmlFree(propValue); + } else { + attack->data.sp = xml_spell(node, "spell"); + if (attack->data.sp) { + attack->level = xml_ivalue(node, "level", 0); + if (attack->level <= 0) { + log_error("magical attack '%s' for race '%s' needs a level: %d\n", attack->data.sp->sname, rc->_name[0], attack->level); + } + } + } + attack->type = xml_ivalue(node, "type", 0); + attack->flags = xml_ivalue(node, "flags", 0); + } + xmlXPathFreeObject(result); + } + xmlXPathFreeObject(races); + + xmlXPathFreeContext(xpath); + + race_compat(); + return 0; +} + +static int parse_terrains(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath; + xmlXPathObjectPtr terrains; + xmlNodeSetPtr nodes; + int i; + + xpath = xmlXPathNewContext(doc); + + /* reading eressea/terrains/terrain */ + terrains = + xmlXPathEvalExpression(BAD_CAST "/eressea/terrains/terrain", xpath); + nodes = terrains->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + terrain_type *terrain = calloc(1, sizeof(terrain_type)); + xmlChar *propValue; + xmlXPathObjectPtr xpathChildren; + xmlNodeSetPtr children; + + propValue = xmlGetProp(node, BAD_CAST "name"); + assert(propValue != NULL); + terrain->_name = strdup((const char *)propValue); + xmlFree(propValue); + + terrain->max_road = (short)xml_ivalue(node, "road", 0); + assert(terrain->max_road >= 0); + terrain->size = xml_ivalue(node, "size", 0); + + if (xml_bvalue(node, "forbidden", false)) + terrain->flags |= FORBIDDEN_REGION; + else { + if (xml_bvalue(node, "fly", true)) + terrain->flags |= FLY_INTO; + if (xml_bvalue(node, "sail", true)) + terrain->flags |= SAIL_INTO; + if (xml_bvalue(node, "walk", true)) + terrain->flags |= WALK_INTO; + if (xml_bvalue(node, "swim", false)) + terrain->flags |= SWIM_INTO; + if (xml_bvalue(node, "shallow", true)) + terrain->flags |= LARGE_SHIPS; + if (xml_bvalue(node, "cavalry", false)) + terrain->flags |= CAVALRY_REGION; + } + if (xml_bvalue(node, "sea", false)) + terrain->flags |= SEA_REGION; + if (xml_bvalue(node, "arctic", false)) + terrain->flags |= ARCTIC_REGION; + if (xml_bvalue(node, "land", true)) + terrain->flags |= LAND_REGION; + if (xml_bvalue(node, "forest", false)) + terrain->flags |= FOREST_REGION; + + terrain->distribution = (short)xml_ivalue(node, "seed", 0); + + xpath->node = node; + xpathChildren = xmlXPathEvalExpression(BAD_CAST "herb", xpath); + children = xpathChildren->nodesetval; + if (children->nodeNr > 0) { + int k; + + terrain->herbs = malloc((children->nodeNr + 1) * sizeof(item_type *)); + terrain->herbs[children->nodeNr] = NULL; + for (k = 0; k != children->nodeNr; ++k) { + xmlNodePtr nodeHerb = children->nodeTab[k]; + const struct resource_type *rtype; + + propValue = xmlGetProp(nodeHerb, BAD_CAST "name"); + assert(propValue != NULL); + rtype = rt_find((const char *)propValue); + assert(rtype != NULL && rtype->itype != NULL + && fval(rtype->itype, ITF_HERB)); + terrain->herbs[k] = rtype->itype; + xmlFree(propValue); + } + } + xmlXPathFreeObject(xpathChildren); + + xpath->node = node; + xpathChildren = xmlXPathEvalExpression(BAD_CAST "resource", xpath); + children = xpathChildren->nodesetval; + if (children->nodeNr > 0) { + int k; + + terrain->production = + malloc((children->nodeNr + 1) * sizeof(terrain_production)); + terrain->production[children->nodeNr].type = NULL; + for (k = 0; k != children->nodeNr; ++k) { + xmlNodePtr nodeProd = children->nodeTab[k]; + + propValue = xmlGetProp(nodeProd, BAD_CAST "name"); + assert(propValue != NULL); + terrain->production[k].type = rt_find((const char *)propValue); + assert(terrain->production[k].type); + xmlFree(propValue); + + propValue = xmlGetProp(nodeProd, BAD_CAST "level"); + assert(propValue); + terrain->production[k].startlevel = strdup((const char *)propValue); + xmlFree(propValue); + + propValue = xmlGetProp(nodeProd, BAD_CAST "base"); + assert(propValue); + terrain->production[k].base = strdup((const char *)propValue); + xmlFree(propValue); + + propValue = xmlGetProp(nodeProd, BAD_CAST "div"); + assert(propValue); + terrain->production[k].divisor = strdup((const char *)propValue); + xmlFree(propValue); + + terrain->production[k].chance = + (float)xml_fvalue(nodeProd, "chance", 1.0); + } + } + xmlXPathFreeObject(xpathChildren); + + register_terrain(terrain); + } + xmlXPathFreeObject(terrains); + + xmlXPathFreeContext(xpath); + + init_terrains(); + return 0; +} + +static int parse_messages(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath; + xmlXPathObjectPtr messages; + xmlNodeSetPtr nodes; + int i; + + if (!gamecode_enabled) + return 0; + + xpath = xmlXPathNewContext(doc); + + /* reading eressea/messages/message */ + messages = + xmlXPathEvalExpression(BAD_CAST "/eressea/messages/message", xpath); + nodes = messages->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + const char *default_section = "events"; + xmlChar *propSection; + xmlChar *propValue; + xmlXPathObjectPtr result; + int k; + char **argv = NULL; + const message_type *mtype; + + /* arguments */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "type/arg", xpath); + if (result->nodesetval && result->nodesetval->nodeNr > 0) { + argv = malloc(sizeof(char *) * (result->nodesetval->nodeNr + 1)); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + char zBuffer[128]; + xmlChar *propName, *propType; + + propName = xmlGetProp(node, BAD_CAST "name"); + propType = xmlGetProp(node, BAD_CAST "type"); + sprintf(zBuffer, "%s:%s", (const char *)propName, + (const char *)propType); + xmlFree(propName); + xmlFree(propType); + argv[k] = strdup(zBuffer); + } + argv[result->nodesetval->nodeNr] = NULL; + } + xmlXPathFreeObject(result); + + /* add the messagetype */ + propValue = xmlGetProp(node, BAD_CAST "name"); + mtype = mt_find((const char *)propValue); + if (mtype == NULL) { + mtype = mt_register(mt_new((const char *)propValue, (const char **)argv)); + } else { + assert(argv != NULL || !"cannot redefine arguments of message now"); + } + xmlFree(propValue); + + /* register the type for the CR */ + crt_register(mtype); + + /* let's clean up the mess */ + if (argv != NULL) { + for (k = 0; argv[k] != NULL; ++k) + free(argv[k]); + free(argv); + } + + propSection = xmlGetProp(node, BAD_CAST "section"); + if (propSection == NULL) + propSection = BAD_CAST default_section; + + /* strings */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "text", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr node = result->nodesetval->nodeTab[k]; + struct locale *lang; + xmlChar *propText; + + xml_readtext(node, &lang, &propText); + if (lang) { + xml_cleanup_string(propText); + nrt_register(mtype, lang, (const char *)propText, 0, + (const char *)propSection); + } + xmlFree(propText); + + } + xmlXPathFreeObject(result); + + if (propSection != BAD_CAST default_section) + xmlFree(propSection); + } + + xmlXPathFreeObject(messages); + + xmlXPathFreeContext(xpath); + return 0; +} + +static void +xml_readstrings(xmlXPathContextPtr xpath, xmlNodePtr * nodeTab, int nodeNr, + bool names) +{ + int i; + + for (i = 0; i != nodeNr; ++i) { + xmlNodePtr stringNode = nodeTab[i]; + xmlChar *propName = xmlGetProp(stringNode, BAD_CAST "name"); + xmlChar *propNamespace = NULL; + xmlXPathObjectPtr result; + int k; + char zName[128]; + + assert(propName != NULL); + if (names) + propNamespace = xmlGetProp(stringNode->parent, BAD_CAST "name"); + mkname_buf((const char *)propNamespace, (const char *)propName, zName); + if (propNamespace != NULL) + xmlFree(propNamespace); + xmlFree(propName); + + /* strings */ + xpath->node = stringNode; + result = xmlXPathEvalExpression(BAD_CAST "text", xpath); + for (k = 0; k != result->nodesetval->nodeNr; ++k) { + xmlNodePtr textNode = result->nodesetval->nodeTab[k]; + struct locale *lang; + xmlChar *propText; + + xml_readtext(textNode, &lang, &propText); + if (propText != NULL) { + assert(strcmp(zName, + (const char *)xml_cleanup_string(BAD_CAST zName)) == 0); + if (lang) { + xml_cleanup_string(propText); + locale_setstring(lang, zName, (const char *)propText); + } + xmlFree(propText); + } else { + log_warning("string %s has no text in locale %s\n", zName, locale_name(lang)); + } + } + xmlXPathFreeObject(result); + } +} + +static int parse_strings(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr strings; + + /* reading eressea/strings/string */ + strings = xmlXPathEvalExpression(BAD_CAST "/eressea/strings/string", xpath); + xml_readstrings(xpath, strings->nodesetval->nodeTab, + strings->nodesetval->nodeNr, false); + xmlXPathFreeObject(strings); + + strings = + xmlXPathEvalExpression(BAD_CAST "/eressea/strings/namespace/string", xpath); + xml_readstrings(xpath, strings->nodesetval->nodeTab, + strings->nodesetval->nodeNr, true); + xmlXPathFreeObject(strings); + + xmlXPathFreeContext(xpath); + return 0; +} + +static void +xml_readprefixes(xmlXPathContextPtr xpath, xmlNodePtr * nodeTab, int nodeNr, + bool names) +{ + int i; + + for (i = 0; i != nodeNr; ++i) { + xmlNodePtr node = nodeTab[i]; + xmlChar *propText = xmlNodeListGetString(node->doc, node->children, 1); + + if (propText != NULL) { + add_raceprefix((const char *)propText); + xmlFree(propText); + } + } +} + +static int parse_prefixes(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr strings; + + /* reading eressea/strings/string */ + strings = xmlXPathEvalExpression(BAD_CAST "/eressea/prefixes/prefix", xpath); + xml_readprefixes(xpath, strings->nodesetval->nodeTab, + strings->nodesetval->nodeNr, false); + xmlXPathFreeObject(strings); + + xmlXPathFreeContext(xpath); + return 0; +} + +static int parse_main(xmlDocPtr doc) +{ + xmlXPathContextPtr xpath = xmlXPathNewContext(doc); + xmlXPathObjectPtr result = + xmlXPathEvalExpression(BAD_CAST "/eressea/game", xpath); + xmlNodeSetPtr nodes = result->nodesetval; + int i; + + xmlChar *propValue; + if (nodes->nodeNr > 0) { + xmlNodePtr node = nodes->nodeTab[0]; + + global.producexpchance = + (float)xml_fvalue(node, "learningbydoing", 1.0 / 3); + + propValue = xmlGetProp(node, BAD_CAST "name"); + if (propValue != NULL) { + global.gamename = strdup((const char *)propValue); + xmlFree(propValue); + } + + xmlXPathFreeObject(result); + + /* reading eressea/game/param */ + xpath->node = node; + result = xmlXPathEvalExpression(BAD_CAST "param", xpath); + nodes = result->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + parse_param(&global.parameters, node); + } + + xmlXPathFreeObject(result); + + /* reading eressea/game/order */ + result = xmlXPathEvalExpression(BAD_CAST "order", xpath); + nodes = result->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + xmlChar *propName = xmlGetProp(node, BAD_CAST "name"); + bool disable = xml_bvalue(node, "disable", false); + + if (disable) { + int k; + for (k = 0; k != MAXKEYWORDS; ++k) { + if (strcmp(keywords[k], (const char *)propName) == 0) { + global.disabled[k] = 1; + break; + } + } + if (k == MAXKEYWORDS) { + log_error("trying to disable unknown comand %s\n", (const char *)propName); + } + } + xmlFree(propName); + } + + xmlXPathFreeObject(result); + + /* reading eressea/game/skill */ + result = xmlXPathEvalExpression(BAD_CAST "skill", xpath); + nodes = result->nodesetval; + for (i = 0; i != nodes->nodeNr; ++i) { + xmlNodePtr node = nodes->nodeTab[i]; + xmlChar *propName = xmlGetProp(node, BAD_CAST "name"); + bool enable = xml_bvalue(node, "enable", true); + enable_skill((const char *)propName, enable); + xmlFree(propName); + } + } + xmlXPathFreeObject(result); + + xmlXPathFreeContext(xpath); + return 0; +} + +void register_xmlreader(void) +{ + xml_register_callback(parse_main); + + xml_register_callback(parse_strings); + xml_register_callback(parse_prefixes); + xml_register_callback(parse_messages); + xml_register_callback(parse_resources); + xml_register_callback(parse_rules); + + xml_register_callback(parse_terrains); /* requires resources */ + xml_register_callback(parse_buildings); /* requires resources */ + xml_register_callback(parse_ships); /* requires terrains */ + xml_register_callback(parse_spells); /* requires resources */ + xml_register_callback(parse_spellbooks); /* requires spells */ + xml_register_callback(parse_equipment); /* requires spells */ + xml_register_callback(parse_races); /* requires spells */ + xml_register_callback(parse_calendar); + xml_register_callback(parse_directions); +} diff --git a/core/src/kernel/xmlreader.h b/core/src/kernel/xmlreader.h new file mode 100644 index 000000000..7209b40eb --- /dev/null +++ b/core/src/kernel/xmlreader.h @@ -0,0 +1,26 @@ +/* vi: set ts=2: ++-------------------+ +| | Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2007 | Katja Zedel +| | Henning Peters ++-------------------+ + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#ifndef H_KRNL_XMLREADER_H +#define H_KRNL_XMLREADER_H +#ifdef __cplusplus +extern "C" { +#endif + extern void register_xmlreader(void); + extern void enable_xml_gamecode(void); + + /* game-specific callbacks */ + extern void (*set_spelldata_cb) (struct spell * sp); +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/arena.c b/core/src/modules/arena.c new file mode 100644 index 000000000..f3acefddd --- /dev/null +++ b/core/src/modules/arena.c @@ -0,0 +1,556 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include + +#if ARENA_MODULE +#include "arena.h" + +/* modules include */ +#include "score.h" + +/* attributes include */ +#include + +/* items include */ +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util include */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc include */ +#include +#include +#include + +/* exports: */ +plane *arena = NULL; + +/* local vars */ +#define CENTRAL_VOLCANO 1 + +#ifdef ARENA_CREATION +static unsigned int arena_id = 0; +static region *arena_center = NULL; +static int newarena = 0; +#endif +static region *tower_region[6]; +static region *start_region[6]; + +static region *arena_region(int school) +{ + return tower_region[school]; +} + +static building *arena_tower(int school) +{ + return arena_region(school)->buildings; +} + +static int leave_fail(unit * u) +{ + ADDMSG(&u->faction->msgs, msg_message("arena_leave_fail", "unit", u)); + return 1; +} + +static int +leave_arena(struct unit *u, const struct item_type *itype, int amount, + order * ord) +{ + if (!u->building && leave_fail(u)) + return -1; + if (u->building != arena_tower(u->faction->magiegebiet) && leave_fail(u)) + return -1; + unused(amount); + unused(ord); + unused(itype); + assert(!"not implemented"); + return 0; +} + +static int enter_fail(unit * u) +{ + ADDMSG(&u->faction->msgs, msg_message("arena_enter_fail", "region unit", + u->region, u)); + return 1; +} + +static int +enter_arena(unit * u, const item_type * itype, int amount, order * ord) +{ + skill_t sk; + region *r = u->region; + unit *u2; + int fee = u->faction->score / 5; + unused(ord); + unused(amount); + unused(itype); + if (fee > 2000) + fee = 2000; + if (getplane(r) == arena) + return -1; + if (u->number != 1 && enter_fail(u)) + return -1; + if (get_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, fee) < fee + && enter_fail(u)) + return -1; + for (sk = 0; sk != MAXSKILLS; ++sk) { + if (get_level(u, sk) > 1 && enter_fail(u)) + return -1; + } + for (u2 = r->units; u2; u2 = u2->next) + if (u2->faction == u->faction) + break; + + assert(!"not implemented"); +/* + for (res=0;res!=MAXRESOURCES;++res) if (res!=R_SILVER && res!=R_ARENA_GATE && (is_item(res) || is_herb(res) || is_potion(res))) { + int x = get_resource(u, res); + if (x) { + if (u2) { + change_resource(u2, res, x); + change_resource(u, res, -x); + } + else if (enter_fail(u)) return -1; + } + } +*/ + if (get_money(u) > fee) { + if (u2) + change_money(u2, get_money(u) - fee); + else if (enter_fail(u)) + return -1; + } + ADDMSG(&u->faction->msgs, msg_message("arena_enter_fail", "region unit", + u->region, u)); + use_pooled(u, itype->rtype, GET_SLACK | GET_RESERVE, 1); + use_pooled(u, oldresourcetype[R_SILVER], GET_DEFAULT, fee); + set_money(u, 109); + fset(u, UFL_ANON_FACTION); + move_unit(u, start_region[rng_int() % 6], NULL); + return 0; +} + +/*** + ** Szepter der Tränen, Demo-Item + ***/ + +static int +use_wand_of_tears(unit * user, const struct item_type *itype, int amount, + order * ord) +{ + int n; + unused(ord); + for (n = 0; n != amount; ++n) { + unit *u; + for (u = user->region->units; u; u = u->next) { + if (u->faction != user->faction) { + int i; + + for (i = 0; i != u->skill_size; ++i) { + if (rng_int() % 3) + reduce_skill(u, u->skills + i, 1); + } + ADDMSG(&u->faction->msgs, msg_message("wand_of_tears_effect", + "unit", u)); + } + } + } + ADDMSG(&user->region->msgs, msg_message("wand_of_tears_usage", "unit", user)); + return 0; +} + +/** + * Tempel der Schreie, Demo-Gebäude **/ + +static int age_hurting(attrib * a) +{ + building *b = (building *) a->data.v; + unit *u; + int active = 0; + if (b == NULL) + return AT_AGE_REMOVE; + for (u = b->region->units; u; u = u->next) { + if (u->building == b) { + if (u->faction->magiegebiet == M_DRAIG) { + active++; + ADDMSG(&b->region->msgs, msg_message("praytoigjarjuk", "unit", u)); + } + } + } + if (active) + for (u = b->region->units; u; u = u->next) + if (playerrace(u->faction->race)) { + int i; + if (u->faction->magiegebiet != M_DRAIG) { + for (i = 0; i != active; ++i) + u->hp = (u->hp + 1) / 2; /* make them suffer, but not die */ + ADDMSG(&b->region->msgs, msg_message("cryinpain", "unit", u)); + } + } + return AT_AGE_KEEP; +} + +static void +write_hurting(const attrib * a, const void *owner, struct storage *store) +{ + building *b = a->data.v; + store->w_int(store, b->no); +} + +static int read_hurting(attrib * a, void *owner, struct storage *store) +{ + int i; + i = store->r_int(store); + a->data.v = (void *)findbuilding(i); + if (a->data.v == NULL) { + log_error("temple of pain is broken\n"); + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +static attrib_type at_hurting = { + "hurting", NULL, NULL, age_hurting, write_hurting, read_hurting +}; + +#ifdef ARENA_CREATION +static void make_temple(region * r) +{ + const building_type *btype = bt_find("temple"); + building *b; + if (btype == NULL) { + log_error("could not find buildingtype 'temple'\n"); + return; + } + + b = r->buildings; + while (b != NULL && b->type != btype) + b = b->next; + if (b != NULL) + return; /* gibt schon einen */ + + b = new_building(btype, r, NULL); + b->size = btype->maxsize; + b->name = strdup("Igjarjuk's Tempel der Schreie"); + b->display = + strdup + ("Ein Schrein aus spitzen Knochen und lodernden Flammen, gewidmet dem Wyrm der Wyrme"); + a_add(&b->attribs, a_new(&at_hurting))->data.v = b; +} +#endif + +/** + * Initialisierung Türme */ + +#ifdef ARENA_CREATION +static void tower_init(void) +{ + int i, first = newarena; + item_type *it_demonseye = it_find("demonseye"); + item_type *it_griphonwing = it_find("griphonwing"); + assert(it_griphonwing && it_demonseye); + for (i = 0; i != 6; ++i) { + region *r = tower_region[i] = + findregion(arena_center->x + delta_x[i] * 3, + arena_center->y + delta_y[i] * 3); + if (r) { + start_region[i] = + findregion(arena_center->x + delta_x[i] * 2, + arena_center->y + delta_y[i] * 2); + if (rterrain(r) != T_DESERT) + terraform(r, T_DESERT); + if (!r->buildings) { + building *b = new_building(bt_find("castle"), r, NULL); + b->size = 10; + if (i != 0) { + sprintf(buf, "Turm des %s", + LOC(default_locale, mkname("school", magic_school[i]))); + } else + sprintf(buf, "Turm der Ahnungslosen"); + set_string(&b->name, buf); + } + } + } + if (first && !arena_center->buildings) { + building *b = new_building(bt_find("castle"), arena_center, NULL); + attrib *a; + item *items; + + i_add(&items, i_new(it_griphonwing, 1)); + i_add(&items, i_new(it_demonseye, 1)); + a = a_add(&b->attribs, make_giveitem(b, items)); + + b->size = 10; + set_string(&b->name, "Höhle des Greifen"); + } +} +#endif + +#ifdef ARENA_CREATION +static void guardian_faction(plane * pl, int id) +{ + region *r; + faction *f = findfaction(id); + + if (!f) { + f = calloc(1, sizeof(faction)); + f->banner = strdup("Sie dienen dem großen Wyrm"); + f->passw = strdup(itoa36(rng_int())); + f->override = strdup(itoa36(rng_int())); + set_email(&f->email, "igjarjuk@eressea.de"); + f->name = strdup("Igjarjuks Kundschafter"); + f->race = new_race[RC_ILLUSION]; + f->age = turn; + f->locale = find_locale("de"); + f->options = + want(O_COMPRESS) | want(O_REPORT) | want(O_COMPUTER) | want(O_ADRESSEN) | + want(O_DEBUG); + + f->no = id; + addlist(&factions, f); + fhash(f); + } + if (f->race != new_race[RC_ILLUSION]) { + assert(!"guardian id vergeben"); + exit(0); + } + f->lastorders = turn; + f->alive = true; + for (r = regions; r; r = r->next) + if (getplane(r) == pl && rterrain(r) != T_FIREWALL) { + unit *u; + freset(r, RF_ENCOUNTER); + for (u = r->units; u; u = u->next) { + if (u->faction == f) + break; + } + if (u) + continue; + u = createunit(r, f, 1, new_race[RC_GOBLIN]); + set_string(&u->name, "Igjarjuks Auge"); + set_item(u, I_RING_OF_INVISIBILITY, 1); + set_order(&u->thisorder, NULL); + fset(u, UFL_ANON_FACTION); + set_money(u, 1000); + } +} +#endif + +#define BLOCKSIZE 9 + +#ifdef ARENA_CREATION +static void block_create(int x1, int y1, char terrain) +{ + int x, y; + for (x = 0; x != BLOCKSIZE; ++x) { + for (y = 0; y != BLOCKSIZE; ++y) { + region *r = new_region(x1 + x, y1 + y, 0); + terraform(r, terrain); + } + } +} +#endif + +#ifdef CENTRAL_VOLCANO + +static int caldera_handle(trigger * t, void *data) +{ + /* call an event handler on caldera. + * data.v -> ( variant event, int timer ) + */ + building *b = (building *) t->data.v; + if (b != NULL) { + unit **up = &b->region->units; + while (*up) { + unit *u = *up; + if (u->building == b) { + message *msg; + if (u->items) { + item **ip = &u->items; + msg = msg_message("caldera_handle_1", "unit items", u, u->items); + while (*ip) { + item *i = *ip; + i_remove(ip, i); + if (*ip == i) + ip = &i->next; + } + } else { + msg = msg_message("caldera_handle_0", "unit", u); + } + add_message(&u->region->msgs, msg); + set_number(u, 0); + } + if (*up == u) + up = &u->next; + } + } else { + log_error("could not perform caldera::handle()\n"); + } + unused(data); + return 0; +} + +static void caldera_write(const trigger * t, struct storage *store) +{ + building *b = (building *) t->data.v; + write_building_reference(b, store); +} + +static int caldera_read(trigger * t, struct storage *store) +{ + int rb = + read_reference(&t->data.v, store, read_building_reference, + resolve_building); + if (rb == 0 && !t->data.v) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +struct trigger_type tt_caldera = { + "caldera", + NULL, + NULL, + caldera_handle, + caldera_write, + caldera_read +}; + +#ifdef ARENA_CREATION +static trigger *trigger_caldera(building * b) +{ + trigger *t = t_new(&tt_caldera); + t->data.v = b; + return t; +} +#endif + +#ifdef ARENA_CREATION +static void init_volcano(void) +{ + building *b; + region *r = arena_center; + assert(arena_center); + if (rterrain(r) != T_DESERT) + return; /* been done before */ + terraform(arena_center, T_VOLCANO_SMOKING); + b = new_building(bt_find("caldera"), r, NULL); + b->size = 1; + b->name = strdup("Igjarjuk's Schlund"); + b->display = + strdup + ("Feurige Lava fließt aus dem Krater des großen Vulkans. Alles wird von ihr verschlungen."); + add_trigger(&b->attribs, "timer", trigger_caldera(b)); + tt_register(&tt_caldera); +} +#endif +#endif + +#ifdef ARENA_CREATION +void create_arena(void) +{ + int x; + arena_id = hashstring("arena"); + arena = getplanebyid(arena_id); + if (arena != NULL) + return; + score(); /* ist wichtig, damit alle Parteien einen score haben, wenn sie durchs Tor wollen. */ + guardian_faction(arena, 999); + if (arena) + arena_center = findregion(plane_center_x(arena), plane_center_y(arena)); + if (!arena_center) { + newarena = 1; + arena = + create_new_plane(arena_id, "Arena", -10000, -10000, 0, BLOCKSIZE - 1, + PFL_LOWSTEALING | PFL_NORECRUITS | PFL_NOALLIANCES); + block_create(arena->minx, arena->miny, T_OCEAN); + arena_center = findregion(plane_center_x(arena), plane_center_y(arena)); + for (x = 0; x != BLOCKSIZE; ++x) { + int y; + for (y = 0; y != BLOCKSIZE; ++y) { + region *r = findregion(arena->minx + x, arena->miny + y); + freset(r, RF_ENCOUNTER); + r->planep = arena; + switch (distance(r, arena_center)) { + case 4: + terraform(r, T_FIREWALL); + break; + case 0: + terraform(r, T_GLACIER); + break; + case 1: + terraform(r, T_SWAMP); + break; + case 2: + terraform(r, T_MOUNTAIN); + break; + } + } + } + } + make_temple(arena_center); +#ifdef CENTRAL_VOLCANO + init_volcano(); +#else + if (arena_center->terrain != T_DESERT) + terraform(arena_center, T_DESERT); +#endif + rsetmoney(arena_center, 0); + rsetpeasants(arena_center, 0); + tower_init(); +} +#endif +void register_arena(void) +{ + at_register(&at_hurting); + register_item_use(use_wand_of_tears, "use_wand_of_tears"); + register_function((pf_generic) enter_arena, "enter_arena"); + register_function((pf_generic) leave_arena, "leave_arena"); + tt_register(&tt_caldera); +} + +#endif /* def ARENA_MODULE */ diff --git a/core/src/modules/arena.h b/core/src/modules/arena.h new file mode 100644 index 000000000..0b68d0471 --- /dev/null +++ b/core/src/modules/arena.h @@ -0,0 +1,39 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef ARENA_H +#define ARENA_H +#ifdef __cplusplus +extern "C" { +#endif + +#if ARENA_MODULE == 0 +#error "must define ARENA_MODULE to use this module" +#endif +/* exports: */ + extern struct plane *arena; + + extern void register_arena(void); +#ifdef ARENA_CREATION + extern void create_arena(void); +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/autoseed.c b/core/src/modules/autoseed.c new file mode 100644 index 000000000..4a004a0ab --- /dev/null +++ b/core/src/modules/autoseed.c @@ -0,0 +1,1064 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "autoseed.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +const terrain_type *random_terrain(const terrain_type * terrains[], + int distribution[], int size) +{ + int ndistribution = size; + const terrain_type *terrain; + int n; + + if (distribution) { + ndistribution = 0; + for (n = 0; n != size; ++n) { + ndistribution += distribution[n]; + } + } + + n = rng_int() % ndistribution; + if (distribution) { + int i; + for (i = 0; i != size; ++i) { + n -= distribution[i]; + if (n < 0) + break; + } + assert(i < size); + terrain = terrains[i]; + } else { + terrain = terrains[n]; + } + return terrain; +} + +int seed_adamantium(region * r, int base) +{ + const resource_type *rtype = rt_find("adamantium"); + rawmaterial *rm; + for (rm = r->resources; rm; rm = rm->next) { + if (rm->type->rtype == rtype) + break; + } + if (!rm) { + add_resource(r, 1, base, 150, rtype); + } + return 0; +} + +static int count_demand(const region * r) +{ + struct demand *dmd; + int c = 0; + if (r->land) { + for (dmd = r->land->demands; dmd; dmd = dmd->next) + c++; + } + return c; +} + +static int +recurse_regions(region * r, region_list ** rlist, + bool(*fun) (const region * r)) +{ + if (!fun(r)) + return 0; + else { + int len = 0; + direction_t d; + region_list *rl = calloc(sizeof(region_list), 1); + rl->next = *rlist; + rl->data = r; + (*rlist) = rl; + fset(r, RF_MARK); + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *nr = rconnect(r, d); + if (nr && !fval(nr, RF_MARK)) + len += recurse_regions(nr, rlist, fun); + } + return len + 1; + } +} + +static bool f_nolux(const region * r) +{ + if (r->land && count_demand(r) != get_maxluxuries()) + return true; + return false; +} + +int fix_demand(region * rd) +{ + region_list *rl, *rlist = NULL; + static const struct luxury_type **mlux = 0, **ltypes; + const luxury_type *sale = NULL; + int maxlux = 0; + int maxluxuries = get_maxluxuries(); + + if (maxluxuries == 0) + return 0; + recurse_regions(rd, &rlist, f_nolux); + if (mlux == 0) { + int i = 0; + mlux = + (const luxury_type **)gc_add(calloc(maxluxuries, + sizeof(const luxury_type *))); + ltypes = + (const luxury_type **)gc_add(calloc(maxluxuries, + sizeof(const luxury_type *))); + for (sale = luxurytypes; sale; sale = sale->next) { + ltypes[i++] = sale; + } + } else { + int i; + for (i = 0; i != maxluxuries; ++i) + mlux[i] = 0; + } + for (rl = rlist; rl; rl = rl->next) { + region *r = rl->data; + direction_t d; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *nr = rconnect(r, d); + if (nr && nr->land && nr->land->demands) { + struct demand *dmd; + for (dmd = nr->land->demands; dmd; dmd = dmd->next) { + if (dmd->value == 0) { + int i; + for (i = 0; i != maxluxuries; ++i) { + if (mlux[i] == NULL) { + maxlux = i; + mlux[i] = dmd->type; + break; + } else if (mlux[i] == dmd->type) { + break; + } + } + break; + } + } + } + } + freset(r, RF_MARK); /* undo recursive marker */ + } + if (maxlux < 2) { + int i; + for (i = maxlux; i != 2; ++i) { + int j; + do { + int k = rng_int() % maxluxuries; + mlux[i] = ltypes[k]; + for (j = 0; j != i; ++j) { + if (mlux[j] == mlux[i]) + break; + } + } while (j != i); + } + maxlux = 2; + } + for (rl = rlist; rl; rl = rl->next) { + region *r = rl->data; + if (!fval(r, RF_CHAOTIC)) { + log_debug("fixing demand in %s\n", regionname(r, NULL)); + } + sale = mlux[rng_int() % maxlux]; + if (sale) + setluxuries(r, sale); + } + while (rlist) { + rl = rlist->next; + free(rlist); + rlist = rl; + } + return 0; +} + +/* nach 150 Runden ist Neustart erlaubt */ +#define MINAGE_MULTI 150 +newfaction *read_newfactions(const char *filename) +{ + newfaction *newfactions = NULL; + FILE *F = fopen(filename, "r"); + char buf[1024]; + + if (F == NULL) + return NULL; + for (;;) { + faction *f; + char race[20], email[64], lang[8], password[16]; + newfaction *nf, **nfi; + int bonus = 0, subscription = 0; + int alliance = 0; + + if (fgets(buf, sizeof(buf), F) == NULL) + break; + + email[0] = '\0'; + password[0] = '\0'; + + if (sscanf(buf, "%54s %20s %8s %d %d %16s %d", email, race, lang, &bonus, + &subscription, password, &alliance) < 6) + break; + if (email[0] == '\0') + break; + if (password[0] == '\0') { + strcpy(password, itoa36(rng_int())); + strcat(password, itoa36(rng_int())); + } + for (f = factions; f; f = f->next) { + if (strcmp(f->email, email) == 0 && f->subscription + && f->age < MINAGE_MULTI) + break; + } + if (f && f->units) + continue; /* skip the ones we've already got */ + for (nf = newfactions; nf; nf = nf->next) { + if (strcmp(nf->email, email) == 0) + break; + } + if (nf) + continue; + nf = calloc(sizeof(newfaction), 1); + if (set_email(&nf->email, email) != 0) { + log_error("Invalid email address for subscription %s: %s\n", itoa36(subscription), email); + continue; + } + nf->password = strdup(password); + nf->race = rc_find(race); + nf->subscription = subscription; + if (alliances != NULL) { + struct alliance *al = findalliance(alliance); + if (al == NULL) { + char zText[64]; + sprintf(zText, "Allianz %d", alliance); + al = makealliance(alliance, zText); + } + nf->allies = al; + } else { + nf->allies = NULL; + } + if (nf->race == NULL) { + /* if the script didn't supply the race as a token, then it gives us a + * race in the default locale (which means that itis a UTF8 string) */ + nf->race = findrace(race, default_locale); + if (nf->race == NULL) { + char buffer[32]; + size_t outbytes = sizeof(buffer) - 1; + size_t inbytes = strlen(race); + unicode_latin1_to_utf8(buffer, &outbytes, race, &inbytes); + buffer[outbytes] = 0; + nf->race = findrace(buffer, default_locale); + if (nf->race == NULL) { + log_error("new faction has unknown race '%s'.\n", race); + free(nf); + continue; + } + } + } + nf->lang = find_locale(lang); + nf->bonus = bonus; + assert(nf->race && nf->email && nf->lang); + nfi = &newfactions; + while (*nfi) { + if ((*nfi)->race == nf->race) + break; + nfi = &(*nfi)->next; + } + nf->next = *nfi; + *nfi = nf; + } + fclose(F); + return newfactions; +} + +extern int numnewbies; + +static const terrain_type *preferred_terrain(const struct race *rc) +{ + terrain_t t = T_PLAIN; + if (rc == rc_find("dwarf")) + t = T_MOUNTAIN; + if (rc == rc_find("insect")) + t = T_DESERT; + if (rc == rc_find("halfling")) + t = T_SWAMP; + if (rc == rc_find("troll")) + t = T_MOUNTAIN; + return newterrain(t); +} + +#define REGIONS_PER_FACTION 2 +#define PLAYERS_PER_ISLAND 20 +#define MAXISLANDSIZE 50 +#define MINFACTIONS 1 +#define VOLCANO_CHANCE 100 + +static bool virgin_region(const region * r) +{ + direction_t d; + if (r == NULL) + return true; + if (fval(r->terrain, FORBIDDEN_REGION)) + return false; + if (r->units) + return false; + for (d = 0; d != MAXDIRECTIONS; ++d) { + const region *rn = rconnect(r, d); + if (rn) { + if (rn->age > r->age + 1) + return false; + if (rn->units) + return false; + if (fval(rn->terrain, FORBIDDEN_REGION)) { + /* because it kinda sucks to have islands that are adjacent to a firewall */ + return false; + } + } + } + return true; +} + +static quicklist * get_island(region * root) +{ + quicklist * ql, * result = 0; + int qi = 0; + + fset(root, RF_MARK); + ql_push(&result, root); + + for (ql=result,qi=0; ql; ql_advance(&ql, &qi, 1)) { + int dir; + region *r = (region *)ql_get(ql, qi); + region * next[MAXDIRECTIONS]; + + get_neighbours(r, next); + + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *rn = next[dir]; + if (rn != NULL && rn->land && !fval(rn, RF_MARK)) { + fset(rn, RF_MARK); + ql_push(&result, rn); + } + } + } + + for (ql=result,qi=0; ql; ql_advance(&ql, &qi, 1)) { + region *r = (region *)ql_get(ql, qi); + freset(r, RF_MARK); + } + return result; +} + +static void +get_island_info(region * root, int *size_p, int *inhabited_p, int *maxage_p) +{ + int qi, size = 0, maxage = 0, inhabited = 0; + quicklist *ql, *island = NULL; + + ql_push(&island, root); + fset(root, RF_MARK); + + for (ql=island,qi=0; ql; ql_advance(&ql, &qi, 1)) { + int d; + region *r = (region *)ql_get(ql, qi); + if (r->units) { + unit *u; + for (u = r->units; u; u = u->next) { + if (!is_monsters(u->faction) && u->faction->age > maxage) { + maxage = u->faction->age; + } + } + ++inhabited; + } + ++size; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn && !fval(rn, RF_MARK) && rn->land) { + ql_push(&island, rn); + fset(rn, RF_MARK); + } + } + } + for (ql=island,qi=0; ql; ql_advance(&ql, &qi, 1)) { + region *r = (region *)ql_get(ql, qi); + freset(r, RF_MARK); + } + ql_free(island); + if (size_p) + *size_p = size; + if (inhabited_p) + *inhabited_p = inhabited; + if (maxage_p) + *maxage_p = maxage; +} + +void free_newfaction(newfaction * nf) +{ + free(nf->email); + free(nf->password); + free(nf); +} + +static void frame_regions(int age, const terrain_type * terrain) +{ + plane *hplane = get_homeplane(); + region *r = regions; + for (r = regions; r; r = r->next) { + plane *pl = rplane(r); + direction_t d; + if (r->age < age) + continue; + if (pl != hplane) + continue; /* only do this on the main world */ + if (r->terrain == terrain) + continue; + + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn == NULL) { + int x = r->x + delta_x[d]; + int y = r->y + delta_y[d]; + pnormalize(&x, &y, pl); + rn = new_region(x, y, pl, 0); + terraform_region(rn, terrain); + rn->age = r->age; + } + } + } +} + +static void prepare_starting_region(region * r) +{ + int n, t; + double p; + + assert(r->land); + + /* population between 30% and 60% of max */ + p = rng_double(); + n = (int)(r->terrain->size * (0.3 + p * 0.3)); + rsetpeasants(r, n); + + /* trees: don't squash the peasants, and at least 5% should be forrest */ + t = (rtrees(r, 2) + rtrees(r, 1) / 2) * TREESIZE; + if (t < r->terrain->size / 20 || t + n > r->terrain->size) { + double p2 = 0.05 + rng_double() * (1.0 - p - 0.05); + int maxtrees = (int)(r->terrain->size / 1.25 / TREESIZE); /* 1.25 = each young tree will take 1/2 the space of old trees */ + int trees = (int)(p2 * maxtrees); + + rsettrees(r, 2, trees); + rsettrees(r, 1, trees / 2); + rsettrees(r, 0, trees / 4); + } + + /* horses: between 1% and 2% */ + p = rng_double(); + rsethorses(r, (int)(r->terrain->size * (0.01 + p * 0.01))); + + if (!markets_module()) { + fix_demand(r); + } +} + +/** create new island with up to nsize players + * returns the number of players placed on the new island. + */ +int autoseed(newfaction ** players, int nsize, int max_agediff) +{ + region *r = NULL; + region_list *rlist = NULL; + int rsize = 0, tsize = 0; + int isize = REGIONS_PER_FACTION; /* target size for the island */ + int psize = 0; /* players on this island */ + const terrain_type *volcano_terrain = get_terrain("volcano"); + static int nterrains = -1; + static const terrain_type **terrainarr = 0; + static int *distribution; + + if (nterrains < 0) { + int n = 0; + const terrain_type *terrain = terrains(); + for (nterrains = 0; terrain; terrain = terrain->next) { + if (terrain->distribution) { + ++nterrains; + } + } + terrainarr = malloc(sizeof(terrain_type *) * nterrains); + distribution = malloc(sizeof(int) * nterrains); + for (terrain = terrains(); terrain; terrain = terrain->next) { + if (terrain->distribution) { + terrainarr[n] = terrain; + distribution[n++] = terrain->distribution; + } + } + } + frame_regions(16, newterrain(T_FIREWALL)); + + if (listlen(*players) < MINFACTIONS) + return 0; + + if (max_agediff > 0) { + region *rmin = NULL; + plane *hplane = get_homeplane(); + /* find a spot that's adjacent to the previous island, but virgin. + * like the last land virgin ocean region adjacent to land. + */ + for (r = regions; r; r = r->next) { + struct plane *pl = rplane(r); + if (r->age <= max_agediff && r->terrain == newterrain(T_OCEAN) + && pl == hplane && virgin_region(r)) { + direction_t d; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn && rn->land && rn->age <= max_agediff && virgin_region(rn)) { + /* only expand islands that aren't single-islands and not too big already */ + int size, inhabitants, maxage; + get_island_info(rn, &size, &inhabitants, &maxage); + if (maxage <= max_agediff && size >= 2 && size < MAXISLANDSIZE) { + rmin = rn; + break; + } + } + } + } + } + if (rmin != NULL) { + faction *f; + quicklist *ql, *rlist = get_island(rmin); + int qi; + + for (ql=rlist,qi=0;ql;ql_advance(&ql, &qi, 1)) { + region *r = (region *)ql_get(ql, qi); + unit *u; + for (u = r->units; u; u = u->next) { + f = u->faction; + if (!fval(f, FFL_MARK)) { + ++psize; + fset(f, FFL_MARK); + } + } + } + ql_free(rlist); + if (psize > 0) { + for (f = factions; f; f = f->next) { + freset(f, FFL_MARK); + } + } + if (psize < PLAYERS_PER_ISLAND) { + r = rmin; + } + } + } + if (r == NULL) { + region *rmin = NULL; + direction_t dmin = MAXDIRECTIONS; + plane *hplane = get_homeplane(); + /* find an empty spot. + * rmin = the youngest ocean region that has a missing neighbour + * dmin = direction in which it's empty + */ + for (r = regions; r; r = r->next) { + struct plane *pl = rplane(r); + if (r->terrain == newterrain(T_OCEAN) && pl == hplane && (rmin == NULL + || r->age <= max_agediff)) { + direction_t d; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn == NULL) + break; + } + if (d != MAXDIRECTIONS) { + rmin = r; + dmin = d; + } + } + } + + /* create a new region where we found the empty spot, and make it the first + * in our island. island regions are kept in rlist, so only new regions can + * get populated, and old regions are not overwritten */ + if (rmin != NULL) { + plane *pl = rplane(rmin); + int x = rmin->x + delta_x[dmin]; + int y = rmin->y + delta_y[dmin]; + pnormalize(&x, &y, pl); + assert(virgin_region(rconnect(rmin, dmin))); + r = new_region(x, y, pl, 0); + terraform_region(r, newterrain(T_OCEAN)); + } + } + if (r != NULL) { + add_regionlist(&rlist, r); + fset(r, RF_MARK); + rsize = 1; + } + + while (rsize && (nsize || isize >= REGIONS_PER_FACTION)) { + int i = rng_int() % rsize; + region_list **rnext = &rlist; + region_list *rfind; + direction_t d; + while (i--) + rnext = &(*rnext)->next; + rfind = *rnext; + r = rfind->data; + freset(r, RF_MARK); + *rnext = rfind->next; + free(rfind); + --rsize; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn && fval(rn, RF_MARK)) + continue; + if (rn == NULL) { + plane *pl = rplane(r); + int x = r->x + delta_x[d]; + int y = r->y + delta_y[d]; + pnormalize(&x, &y, pl); + rn = new_region(x, y, pl, 0); + terraform_region(rn, newterrain(T_OCEAN)); + } + if (virgin_region(rn)) { + add_regionlist(&rlist, rn); + fset(rn, RF_MARK); + ++rsize; + } + } + if (volcano_terrain != NULL && (rng_int() % VOLCANO_CHANCE == 0)) { + terraform_region(r, volcano_terrain); + } else if (nsize && (rng_int() % isize == 0 || rsize == 0)) { + newfaction **nfp, *nextf = *players; + faction *f; + unit *u; + + isize += REGIONS_PER_FACTION; + terraform_region(r, preferred_terrain(nextf->race)); + prepare_starting_region(r); + ++tsize; + assert(r->land && r->units == 0); + u = addplayer(r, addfaction(nextf->email, nextf->password, nextf->race, + nextf->lang, nextf->subscription)); + f = u->faction; + fset(f, FFL_ISNEW); + f->alliance = nextf->allies; + log_printf(stdout, "New faction (%s), %s at %s\n", itoa36(f->no), + f->email, regionname(r, NULL)); + if (f->subscription) { + sql_print( + ("UPDATE subscriptions SET status='ACTIVE', faction='%s', firstturn=%d, lastturn=%d, password='%s' WHERE id=%u;\n", + factionid(f), f->lastorders, f->lastorders, f->override, + f->subscription)); + } + + /* remove duplicate email addresses */ + nfp = &nextf->next; + while (*nfp) { + newfaction *nf = *nfp; + if (strcmp(nextf->email, nf->email) == 0) { + *nfp = nf->next; + free_newfaction(nf); + } else + nfp = &nf->next; + } + *players = nextf->next; + free_newfaction(nextf); + + ++psize; + --nsize; + --isize; + if (psize >= PLAYERS_PER_ISLAND) + break; + } else { + terraform_region(r, random_terrain(terrainarr, distribution, nterrains)); + --isize; + } + } + + if (nsize != 0) { + log_error( + ("Could not place all factions on the same island as requested\n")); + } + + if (rlist) { +#define MINOCEANDIST 3 +#define MAXOCEANDIST 6 +#define MAXFILLDIST 10 +#define SPECIALCHANCE 80 + region_list **rbegin = &rlist; + int special = 1; + int oceandist = MINOCEANDIST + (rng_int() % (MAXOCEANDIST - MINOCEANDIST)); + while (oceandist--) { + region_list **rend = rbegin; + while (*rend) + rend = &(*rend)->next; + while (rbegin != rend) { + direction_t d; + region *r = (*rbegin)->data; + rbegin = &(*rbegin)->next; + for (d = 0; d != MAXDIRECTIONS; ++d) { + region *rn = rconnect(r, d); + if (rn == NULL) { + const struct terrain_type *terrain = newterrain(T_OCEAN); + plane *pl = rplane(r); + int x = r->x + delta_x[d]; + int y = r->y + delta_y[d]; + pnormalize(&x, &y, pl); + rn = new_region(x, y, pl, 0); + if (rng_int() % SPECIALCHANCE < special) { + terrain = random_terrain(terrainarr, distribution, nterrains); + special = SPECIALCHANCE / 3; /* 33% chance auf noch eines */ + } else { + special = 1; + } + terraform_region(rn, terrain); + /* the new region has an extra 20% chance to have mallorn */ + if (rng_int() % 100 < 20) + fset(r, RF_MALLORN); + add_regionlist(rend, rn); + } + } + } + + } + while (*rbegin) { + region *r = (*rbegin)->data; + plane *pl = rplane(r); + direction_t d; + rbegin = &(*rbegin)->next; + for (d = 0; d != MAXDIRECTIONS; ++d) + if (rconnect(r, d) == NULL) { + int i; + for (i = 1; i != MAXFILLDIST; ++i) { + int x = r->x + delta_x[d] * i; + int y = r->y + delta_y[d] * i; + pnormalize(&x, &y, pl); + if (findregion(x, y)) { + break; + } + } + if (i != MAXFILLDIST) { + while (--i) { + region *rn; + int x = r->x + delta_x[d] * i; + int y = r->y + delta_y[d] * i; + pnormalize(&x, &y, pl); + rn = new_region(x, y, pl, 0); + terraform_region(rn, newterrain(T_OCEAN)); + } + } + } + } + while (rlist) { + region_list *self = rlist; + rlist = rlist->next; + freset(self->data, RF_MARK); + free(self); + } + } + return tsize; +} + +region_list *regionqueue_push(region_list ** rlist, region * r) +{ + region_list *rnew = malloc(sizeof(region_list)); + rnew->data = r; + rnew->next = 0; + while (*rlist) { + rlist = &(*rlist)->next; + } + *rlist = rnew; + return rnew; +} + +region *regionqueue_pop(region_list ** rlist) +{ + if (*rlist) { + region *r = (*rlist)->data; + region_list *rpop = *rlist; + *rlist = rpop->next; + free(rpop); + return r; + } + return 0; +} + +#define GEOMAX 8 +static struct geo { + int distribution; + terrain_t type; +} geography_e3[GEOMAX] = { + { + 8, T_OCEAN}, { + 3, T_SWAMP}, { + 1, T_VOLCANO}, { + 3, T_DESERT}, { + 4, T_HIGHLAND}, { + 3, T_MOUNTAIN}, { + 2, T_GLACIER}, { + 1, T_PLAIN} +}; + +const terrain_type *random_terrain_e3(direction_t dir) +{ + static const terrain_type **terrainarr = 0; + static int *distribution = 0; + + if (!distribution) { + int n = 0; + + terrainarr = malloc(GEOMAX * sizeof(const terrain_type *)); + distribution = malloc(GEOMAX * sizeof(int)); + for (n = 0; n != GEOMAX; ++n) { + terrainarr[n] = newterrain(geography_e3[n].type); + distribution[n] = geography_e3[n].distribution; + } + } + return random_terrain(terrainarr, distribution, GEOMAX); +} + +int +random_neighbours(region * r, region_list ** rlist, + const terrain_type * (*terraformer) (direction_t)) +{ + int nsize = 0; + direction_t dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *rn = rconnect(r, dir); + if (rn == NULL) { + const terrain_type *terrain = terraformer(dir); + plane *pl = rplane(r); + int x = r->x + delta_x[dir]; + int y = r->y + delta_y[dir]; + pnormalize(&x, &y, pl); + rn = new_region(x, y, pl, 0); + terraform_region(rn, terrain); + regionqueue_push(rlist, rn); + if (rn->land) { + ++nsize; + } + } + } + return nsize; +} + +const terrain_type *get_ocean(direction_t dir) +{ + return newterrain(T_OCEAN); +} + +int region_quality(const region * r, region * rn[]) +{ + int n, result = 0; + + for (n = 0; n != MAXDIRECTIONS; ++n) { + if (rn[n] && rn[n]->land) { + if (rn[n]->terrain == newterrain(T_VOLCANO)) { + /* nobody likes volcanoes */ + result -= 2000; + } + result += rn[n]->land->peasants; + } + } + return result; +} + +static void oceans_around(region * r, region * rn[]) +{ + int n; + for (n = 0; n != MAXDIRECTIONS; ++n) { + region *rx = rn[n]; + if (rx == NULL) { + plane *pl = rplane(r); + int x = r->x + delta_x[n]; + int y = r->y + delta_y[n]; + pnormalize(&x, &y, pl); + rx = new_region(x, y, pl, 0); + terraform_region(rx, newterrain(T_OCEAN)); + rn[n] = rx; + } + } +} + +static void smooth_island(region_list * island) +{ + region *rn[MAXDIRECTIONS]; + region_list *rlist = NULL; + for (rlist = island; rlist; rlist = rlist->next) { + region *r = rlist->data; + int n, nland = 0; + + if (r->land) { + get_neighbours(r, rn); + for (n = 0; n != MAXDIRECTIONS && nland <= 1; ++n) { + if (rn[n]->land) { + ++nland; + r = rn[n]; + } + } + + if (nland == 1) { + get_neighbours(r, rn); + oceans_around(r, rn); + for (n = 0; n != MAXDIRECTIONS; ++n) { + int n1 = (n + 1) % MAXDIRECTIONS; + int n2 = (n + 1 + MAXDIRECTIONS) % MAXDIRECTIONS; + if (!rn[n]->land && rn[n1] != r && rn[n2] != r) { + r = rlist->data; + runhash(r); + runhash(rn[n]); + SWAP_VARS(int, r->x, rn[n]->x); + SWAP_VARS(int, r->y, rn[n]->y); + rhash(r); + rhash(rn[n]); + rlist->data = r; + oceans_around(r, rn); + break; + } + } + } + } + } +} + +static void starting_region(region * r, region * rn[]) +{ + int n; + + oceans_around(r, rn); + freset(r, RF_MARK); + for (n = 0; n != MAXDIRECTIONS; ++n) { + freset(rn[n], RF_MARK); + } + terraform_region(r, newterrain(T_PLAIN)); + prepare_starting_region(r); + addplayer(r, addfaction("enno@eressea.de", itoa36(rng_int()), races, default_locale, 0)); +} + +/* E3A island generation */ +int build_island_e3(int x, int y, int numfactions, int minsize) +{ +#define MIN_QUALITY 1000 + int nfactions = 0; + region_list *rlist = NULL; + region_list *island = NULL; + plane *pl = findplane(x, y); + region *r = findregion(x, y); + int nsize = 1; + int q, maxq = INT_MIN, minq = INT_MAX; + + if (!r) + r = new_region(x, y, pl, 0); + assert(!r->units); + do { + terraform_region(r, random_terrain_e3(NODIRECTION)); + } while (!r->land); + + while (r) { + fset(r, RF_MARK); + if (r->land) { + if (nsize < minsize) { + nsize += random_neighbours(r, &rlist, &random_terrain_e3); + } else { + nsize += random_neighbours(r, &rlist, &get_ocean); + } + } + regionqueue_push(&island, r); + r = regionqueue_pop(&rlist); + } + + smooth_island(island); + + if (nsize > minsize / 2) { + for (rlist = island; rlist; rlist = rlist->next) { + r = rlist->data; + if (r->land && fval(r, RF_MARK)) { + region *rn[MAXDIRECTIONS]; + + get_neighbours(r, rn); + q = region_quality(r, rn); + if (q >= MIN_QUALITY && nfactions < numfactions) { + starting_region(r, rn); + minq = MIN(minq, q); + maxq = MAX(maxq, q); + ++nfactions; + } + } + } + + for (rlist = island; rlist && nfactions < numfactions; rlist = rlist->next) { + r = rlist->data; + if (!r->land && fval(r, RF_MARK)) { + region *rn[MAXDIRECTIONS]; + get_neighbours(r, rn); + q = region_quality(r, rn); + if (q >= MIN_QUALITY * 4 / 3 && nfactions < numfactions) { + starting_region(r, rn); + minq = MIN(minq, q); + maxq = MAX(maxq, q); + ++nfactions; + } + } + } + } + + for (rlist = island; rlist; rlist = rlist->next) { + r = rlist->data; + if (r->units) { + region *rn[MAXDIRECTIONS]; + get_neighbours(r, rn); + q = region_quality(r, rn); + if (q - minq > (maxq - minq) * 2 / 3) { + terraform_region(r, newterrain(T_HIGHLAND)); + prepare_starting_region(r); + } + r->land->money = 50000; /* 2% = 1000 silver */ + } else if (r->land) { + r->land->money *= 4; + } + } + return nfactions; +} diff --git a/core/src/modules/autoseed.h b/core/src/modules/autoseed.h new file mode 100644 index 000000000..923534020 --- /dev/null +++ b/core/src/modules/autoseed.h @@ -0,0 +1,48 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef _REGIONLIST_H +#define _REGIONLIST_H +#ifdef __cplusplus +extern "C" { +#endif + + struct newfaction; + + typedef struct newfaction { + struct newfaction *next; + char *email; + char *password; + const struct locale *lang; + const struct race *race; + int bonus; + int subscription; + bool oldregions; + struct alliance *allies; + } newfaction; + +#define ISLANDSIZE 20 +#define TURNS_PER_ISLAND 5 + + extern int autoseed(newfaction ** players, int nsize, int max_agediff); + extern newfaction *read_newfactions(const char *filename); + extern int fix_demand(struct region *r); + extern const struct terrain_type *random_terrain(const struct terrain_type + *terrains[], int distribution[], int size); + + extern int seed_adamantium(struct region *r, int base); + extern int build_island_e3(int x, int y, int numfactions, int minsize); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/dungeon.c b/core/src/modules/dungeon.c new file mode 100644 index 000000000..657b20837 --- /dev/null +++ b/core/src/modules/dungeon.c @@ -0,0 +1,273 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include + +#if DUNGEON_MODULE +#include "dungeon.h" +#include "gmcmd.h" + +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include + +/* libc includes */ +#include +#include +#include + +typedef struct treasure { + const struct item_type *itype; + int amount; + struct treasure *next; +} treasure; + +typedef struct monster { + const struct race *race; + double chance; + int maxunits; + int avgsize; + struct treasure *treasures; + struct monster *next; + struct itemtype_list *weapons; +} monster; + +typedef struct skilllimit { + skill_t skill; + int minskill; + int maxskill; + struct skilllimit *next; +} skilllimit; + +typedef struct dungeon { + int level; + int radius; + int size; + int maxpeople; + struct skilllimit *limits; + double connect; + struct monster *boss; + struct monster *monsters; + struct dungeon *next; +} dungeon; + +dungeon *dungeonstyles; + +region *make_dungeon(const dungeon * data) +{ + int nb[2][3][2] = { + {{-1, 0}, {0, 1}, {1, -1}}, + {{1, 0}, {-1, 1}, {0, -1}} + }; + const struct race *bossrace = data->boss->race; + char name[128]; + int size = data->size; + int iterations = size * size; + unsigned int flags = PFL_NORECRUITS; + int n = 0; + struct faction *fmonsters = get_monsters(); + plane *p; + region *r, *center; + region *rnext; + region_list *iregion, *rlist = NULL; + const terrain_type *terrain_hell = get_terrain("hell"); + + assert(terrain_hell != NULL); + sprintf(name, "Die Höhlen von %s", bossrace->generate_name(NULL)); + p = gm_addplane(data->radius, flags, name); + + center = + findregion(p->minx + (p->maxx - p->minx) / 2, + p->miny + (p->maxy - p->miny) / 2); + assert(center); + terraform_region(center, terrain_hell); + add_regionlist(&rlist, center); + rnext = r = center; + while (size > 0 && iterations--) { + int d, o = rng_int() % 3; + for (d = 0; d != 3; ++d) { + int index = (d + o) % 3; + region *rn = findregion(r->x + nb[n][index][0], r->y + nb[n][index][1]); + assert(r->terrain == terrain_hell); + if (rn) { + if (rn->terrain == terrain_hell) { + rnext = rn; + } else if (fval(rn->terrain, SEA_REGION)) { + if (rng_int() % 100 < data->connect * 100) { + terraform_region(rn, terrain_hell); + --size; + rnext = rn; + add_regionlist(&rlist, rn); + } else + terraform(rn, T_FIREWALL); + } + if (size == 0) + break; + } + rn = + findregion(r->x + nb[(n + 1) % 2][index][0], + r->y + nb[(n + 1) % 2][index][1]); + if (rn && fval(rn->terrain, SEA_REGION)) { + terraform(rn, T_FIREWALL); + } + } + if (size == 0) + break; + if (r == rnext) { + /* error */ + break; + } + r = rnext; + n = (n + 1) % 2; + } + + for (iregion = rlist; iregion; iregion = iregion->next) { + monster *m = data->monsters; + region *r = iregion->data; + while (m) { + if ((rng_int() % 100) < (m->chance * 100)) { + /* TODO: check maxunits. */ + treasure *loot = m->treasures; + struct itemtype_list *weapon = m->weapons; + int size = 1 + (rng_int() % m->avgsize) + (rng_int() % m->avgsize); + unit *u = createunit(r, fmonsters, size, m->race); + while (weapon) { + i_change(&u->items, weapon->type, size); + weapon = weapon->next; + } + while (loot) { + i_change(&u->items, loot->itype, loot->amount * size); + loot = loot->next; + } + } + m = m->next; + } + } + return center; +} + +void make_dungeongate(region * source, region * target, const struct dungeon *d) +{ + building *bsource, *btarget; + + if (source == NULL || target == NULL || d == NULL) + return; + bsource = new_building(bt_find("castle"), source, default_locale); + set_string(&bsource->name, "Pforte zur Hölle"); + bsource->size = 50; + add_trigger(&bsource->attribs, "timer", trigger_gate(bsource, target)); + add_trigger(&bsource->attribs, "create", trigger_unguard(bsource)); + fset(bsource, BLD_UNGUARDED); + + btarget = new_building(bt_find("castle"), target, default_locale); + set_string(&btarget->name, "Pforte zur Außenwelt"); + btarget->size = 50; + add_trigger(&btarget->attribs, "timer", trigger_gate(btarget, source)); + add_trigger(&btarget->attribs, "create", trigger_unguard(btarget)); + fset(btarget, BLD_UNGUARDED); +} + +static int tagbegin(xml_stack * stack) +{ + xml_tag *tag = stack->tag; + if (strcmp(tag->name, "dungeon") == 0) { + dungeon *d = (dungeon *) calloc(sizeof(dungeon), 1); + d->maxpeople = xml_ivalue(tag, "maxpeople"); + if (d->maxpeople == 0) + d->maxpeople = INT_MAX; + d->level = xml_ivalue(tag, "level"); + d->radius = xml_ivalue(tag, "radius"); + d->connect = xml_fvalue(tag, "connect"); + d->size = xml_ivalue(tag, "size"); + stack->state = d; + } else { + dungeon *d = (dungeon *) stack->state; + if (strcmp(tag->name, "skilllimit") == 0) { + skill_t sk = sk_find(xml_value(tag, "name")); + if (sk != NOSKILL) { + skilllimit *skl = calloc(sizeof(skilllimit), 1); + skl->skill = sk; + if (xml_value(tag, "max") != NULL) { + skl->maxskill = xml_ivalue(tag, "max"); + } else + skl->maxskill = INT_MAX; + if (xml_value(tag, "min") != NULL) { + skl->minskill = xml_ivalue(tag, "min"); + } else + skl->maxskill = INT_MIN; + skl->next = d->limits; + d->limits = skl; + } + } else if (strcmp(tag->name, "monster") == 0) { + monster *m = calloc(sizeof(monster), 1); + m->race = rc_find(xml_value(tag, "race")); + m->chance = xml_fvalue(tag, "chance"); + m->avgsize = MAX(1, xml_ivalue(tag, "size")); + m->maxunits = MIN(1, xml_ivalue(tag, "maxunits")); + + if (m->race) { + if (xml_bvalue(tag, "boss")) { + d->boss = m; + } else { + m->next = d->monsters; + d->monsters = m; + } + } + } else if (strcmp(tag->name, "weapon") == 0) { + monster *m = d->monsters; + itemtype_list *w = calloc(sizeof(itemtype_list), 1); + w->type = it_find(xml_value(tag, "type")); + if (w->type) { + w->next = m->weapons; + m->weapons = w; + } + } + } + return XML_OK; +} + +static int tagend(xml_stack * stack) +{ + xml_tag *tag = stack->tag; + if (strcmp(tag->name, "dungeon")) { + dungeon *d = (dungeon *) stack->state; + stack->state = NULL; + d->next = dungeonstyles; + dungeonstyles = d; + } + return XML_OK; +} + +xml_callbacks xml_dungeon = { + tagbegin, tagend, NULL +}; + +void register_dungeon(void) +{ + xml_register(&xml_dungeon, "eressea dungeon", 0); +} + +#endif diff --git a/core/src/modules/dungeon.h b/core/src/modules/dungeon.h new file mode 100644 index 000000000..cb7715856 --- /dev/null +++ b/core/src/modules/dungeon.h @@ -0,0 +1,37 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_MOD_DUNGEON +#define H_MOD_DUNGEON +#ifdef __cplusplus +extern "C" { +#endif + +#if DUNGEON_MODULE == 0 +#error "must define DUNGEON_MODULE to use this module" +#endif + + struct region; + struct plane; + struct building; + struct dungeon; + + extern struct dungeon *dungeonstyles; + extern struct region *make_dungeon(const struct dungeon *); + extern void make_dungeongate(struct region *source, struct region *target, + const struct dungeon *); + extern void register_dungeon(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/gmcmd.c b/core/src/modules/gmcmd.c new file mode 100644 index 000000000..78c26e235 --- /dev/null +++ b/core/src/modules/gmcmd.c @@ -0,0 +1,759 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "gmcmd.h" +#include + +/* misc includes */ +#include +#include +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +/** + ** at_permissions + **/ + +static void mistake(const unit * u, struct order *ord, const char *comment) +{ + if (!is_monsters(u->faction)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "mistake", + "error", comment)); + } +} + +static void +write_permissions(const attrib * a, const void *owner, struct storage *store) +{ + a_write(store, (attrib *) a->data.v, owner); +} + +static int read_permissions(attrib * a, void *owner, struct storage *store) +{ + attrib *attr = NULL; + a_read(store, &attr, NULL); + a->data.v = attr; + return AT_READ_OK; +} + +struct attrib_type at_permissions = { + "GM:permissions", + NULL, NULL, NULL, + write_permissions, read_permissions, + ATF_UNIQUE +}; + +attrib *make_atpermissions(void) +{ + return a_new(&at_permissions); +} + +/** + ** GM: CREATE + **/ + +static void +write_gmcreate(const attrib * a, const void *owner, struct storage *store) +{ + const item_type *itype = (const item_type *)a->data.v; + assert(itype); + store->w_tok(store, resourcename(itype->rtype, 0)); +} + +static int read_gmcreate(attrib * a, void *owner, struct storage *store) +{ + char zText[32]; + store->r_tok_buf(store, zText, sizeof(zText)); + a->data.v = it_find(zText); + if (a->data.v == NULL) { + log_error("unknown itemtype %s in gmcreate attribute\n", zText); + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +/* at_gmcreate specifies that the owner can create items of a particular type */ +attrib_type at_gmcreate = { + "GM:create", + NULL, NULL, NULL, + write_gmcreate, read_gmcreate +}; + +attrib *make_atgmcreate(const struct item_type * itype) +{ + attrib *a = a_new(&at_gmcreate); + a->data.v = (void *)itype; + return a; +} + +static void gm_create(const void *tnext, struct unit *u, struct order *ord) +{ + int i; + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (permissions) + permissions = (attrib *) permissions->data.v; + if (!permissions) + return; + i = getint(); + + if (i > 0) { + const char *iname = getstrtoken(); + const item_type *itype = finditemtype(iname, u->faction->locale); + if (itype == NULL) { + mistake(u, ord, "unknown item."); + } else { + attrib *a = a_find(permissions, &at_gmcreate); + + while (a && a->type == &at_gmcreate && a->data.v != (void *)itype) + a = a->next; + if (a) + i_change(&u->items, itype, i); + else + mistake(u, ord, "your faction cannot create this item."); + } + } +} + +static bool has_permission(const attrib * permissions, unsigned int key) +{ + return (find_key((attrib *) permissions->data.v, key) || + find_key((attrib *) permissions->data.v, atoi36("master"))); +} + +/** + ** GM: GATE + ** requires: permission-key "gmgate" + **/ +static void gm_gate(const void *tnext, struct unit * u, struct order *ord) +{ + const struct plane *pl = rplane(u->region); + int id = getid(); + int x = rel_to_abs(pl, u->faction, getint(), 0); + int y = rel_to_abs(pl, u->faction, getint(), 1); + building *b = findbuilding(id); + region *r; + + pnormalize(&x, &y, pl); + r = findregion(x, y); + if (b == NULL || r == NULL || pl != rplane(b->region) || pl != rplane(r)) { + mistake(u, ord, "the unit cannot transform this building."); + return; + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (permissions && has_permission(permissions, atoi36("gmgate"))) { + remove_triggers(&b->attribs, "timer", &tt_gate); + remove_triggers(&b->attribs, "create", &tt_unguard); + if (r != b->region) { + add_trigger(&b->attribs, "timer", trigger_gate(b, r)); + add_trigger(&b->attribs, "create", trigger_unguard(b)); + fset(b, BLD_UNGUARDED); + } + } + } +} + +/** + ** GM: TERRAFORM + ** requires: permission-key "gmterf" + **/ +static void gm_terraform(const void *tnext, struct unit *u, struct order *ord) +{ + const struct plane *p = rplane(u->region); + int x = rel_to_abs(p, u->faction, getint(), 0); + int y = rel_to_abs(p, u->faction, getint(), 1); + const char *c = getstrtoken(); + variant token; + void **tokens = get_translations(u->faction->locale, UT_TERRAINS); + region *r; + pnormalize(&x, &y, p); + r = findregion(x, y); + + if (r == NULL || p != rplane(r)) { + mistake(u, ord, "region is in another plane."); + return; + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmterf"))) + return; + } + + if (findtoken(*tokens, c, &token) != E_TOK_NOMATCH) { + const terrain_type *terrain = (const terrain_type *)token.v; + terraform_region(r, terrain); + } +} + +/** + ** GM: TELEPORT + ** requires: permission-key "gmtele" + **/ +static void gm_teleport(const void *tnext, struct unit *u, struct order *ord) +{ + const struct plane *p = rplane(u->region); + unit *to = findunit(getid()); + int x = rel_to_abs(p, u->faction, getint(), 0); + int y = rel_to_abs(p, u->faction, getint(), 1); + region *r = findregion(x, y); + + if (r == NULL || p != rplane(r)) { + mistake(u, ord, "region is in another plane."); + } else if (to == NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + } else if (rplane(to->region) != rplane(r) && !ucontact(to, u)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_no_contact", + "target", to)); + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmtele"))) { + mistake(u, ord, "permission denied."); + } else + move_unit(to, r, NULL); + } +} + +/** + ** GM: TELL PLANE + ** requires: permission-key "gmmsgr" + **/ +static void gm_messageplane(const void *tnext, struct unit *gm, struct order *ord) +{ + const struct plane *p = rplane(gm->region); + const char *zmsg = getstrtoken(); + if (p == NULL) { + mistake(gm, ord, "In diese Ebene kann keine Nachricht gesandt werden."); + } else { + /* checking permissions */ + attrib *permissions = a_find(gm->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmmsgr"))) { + mistake(gm, ord, "permission denied."); + } else { + message *msg = msg_message("msg_event", "string", zmsg); + faction *f; + region *r; + for (f = factions; f; f = f->next) { + freset(f, FFL_SELECT); + } + for (r = regions; r; r = r->next) { + unit *u; + if (rplane(r) != p) + continue; + for (u = r->units; u; u = u->next) + if (!fval(u->faction, FFL_SELECT)) { + f = u->faction; + fset(f, FFL_SELECT); + add_message(&f->msgs, msg); + } + } + msg_release(msg); + } + } +} + +static void +gm_messagefaction(const void *tnext, struct unit *gm, struct order *ord) +{ + int n = getid(); + faction *f = findfaction(n); + const char *msg = getstrtoken(); + plane *p = rplane(gm->region); + attrib *permissions = a_find(gm->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmmsgr"))) { + mistake(gm, ord, "permission denied."); + return; + } + if (f != NULL) { + region *r; + for (r = regions; r; r = r->next) + if (rplane(r) == p) { + unit *u; + for (u = r->units; u; u = u->next) + if (u->faction == f) { + add_message(&f->msgs, msg_message("msg_event", "string", msg)); + return; + } + } + } + mistake(gm, ord, "cannot send messages to this faction."); +} + +/** + ** GM: TELL REGION + ** requires: permission-key "gmmsgr" + **/ +static void gm_messageregion(const void *tnext, struct unit *u, struct order *ord) +{ + const struct plane *p = rplane(u->region); + int x = rel_to_abs(p, u->faction, getint(), 0); + int y = rel_to_abs(p, u->faction, getint(), 1); + const char *msg = getstrtoken(); + region *r = findregion(x, y); + + if (r == NULL || p != rplane(r)) { + mistake(u, ord, "region is in another plane."); + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmmsgr"))) { + mistake(u, ord, "permission denied."); + } else { + add_message(&r->msgs, msg_message("msg_event", "string", msg)); + } + } +} + +/** + ** GM: KILL UNIT + ** requires: permission-key "gmkill" + **/ +static void gm_killunit(const void *tnext, struct unit *u, struct order *ord) +{ + const struct plane *p = rplane(u->region); + unit *target = findunit(getid()); + const char *msg = getstrtoken(); + region *r = target->region; + + if (r == NULL || p != rplane(r)) { + mistake(u, ord, "region is in another plane."); + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmkill"))) { + mistake(u, ord, "permission denied."); + } else { + scale_number(target, 0); + ADDMSG(&target->faction->msgs, msg_message("killedbygm", + "region unit string", r, target, msg)); + } + } +} + +/** + ** GM: KILL FACTION + ** requires: permission-key "gmmsgr" + **/ +static void gm_killfaction(const void *tnext, struct unit *u, struct order *ord) +{ + int n = getid(); + faction *f = findfaction(n); + const char *msg = getstrtoken(); + plane *p = rplane(u->region); + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmkill"))) { + mistake(u, ord, "permission denied."); + return; + } + if (f != NULL) { + region *r; + for (r = regions; r; r = r->next) + if (rplane(r) == p) { + unit *target; + for (target = r->units; target; target = target->next) { + if (target->faction == f) { + scale_number(target, 0); + ADDMSG(&target->faction->msgs, msg_message("killedbygm", + "region unit string", r, target, msg)); + return; + } + } + } + } + mistake(u, ord, "cannot remove a unit from this faction."); +} + +/** + ** GM: TELL + ** requires: permission-key "gmmsgr" + **/ +static void gm_messageunit(const void *tnext, struct unit *u, struct order *ord) +{ + const struct plane *p = rplane(u->region); + unit *target = findunit(getid()); + const char *msg = getstrtoken(); + region *r; + + if (target == NULL) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + return; + } + + r = target->region; + + if (r == NULL || p != rplane(r)) { + mistake(u, ord, "region is in another plane."); + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmmsgu"))) { + mistake(u, ord, "permission denied."); + } else { + add_message(&target->faction->msgs, + msg_message("regionmessage", "region sender string", r, u, msg)); + } + } +} + +/** + ** GM: GIVE + ** requires: permission-key "gmgive" + **/ +static void gm_give(const void *tnext, struct unit *u, struct order *ord) +{ + unit *to = findunit(getid()); + int num = getint(); + const item_type *itype = finditemtype(getstrtoken(), u->faction->locale); + + if (to == NULL || rplane(to->region) != rplane(u->region)) { + /* unknown or in another plane */ + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + } else if (itype == NULL || i_get(u->items, itype) == 0) { + /* unknown or not enough */ + mistake(u, ord, "invalid item or item not found."); + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmgive"))) { + mistake(u, ord, "permission denied."); + } else { + int i = i_get(u->items, itype); + if (i < num) + num = i; + if (num) { + i_change(&u->items, itype, -num); + i_change(&to->items, itype, num); + } + } + } +} + +/** + ** GM: TAKE + ** requires: permission-key "gmtake" + **/ +static void gm_take(const void *tnext, struct unit *u, struct order *ord) +{ + unit *to = findunit(getid()); + int num = getint(); + const item_type *itype = finditemtype(getstrtoken(), u->faction->locale); + + if (to == NULL || rplane(to->region) != rplane(u->region)) { + /* unknown or in another plane */ + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + } else if (itype == NULL || i_get(to->items, itype) == 0) { + /* unknown or not enough */ + mistake(u, ord, "invalid item or item not found."); + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmtake"))) { + mistake(u, ord, "permission denied."); + } else { + int i = i_get(to->items, itype); + if (i < num) + num = i; + if (num) { + i_change(&to->items, itype, -num); + i_change(&u->items, itype, num); + } + } + } +} + +/** + ** GM: SKILL + ** requires: permission-key "gmskil" + **/ +static void gm_skill(const void *tnext, struct unit *u, struct order *ord) +{ + unit *to = findunit(getid()); + skill_t skill = findskill(getstrtoken(), u->faction->locale); + int num = getint(); + + if (to == NULL || rplane(to->region) != rplane(u->region)) { + /* unknown or in another plane */ + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found", + "")); + } else if (skill == NOSKILL || skill == SK_MAGIC || skill == SK_ALCHEMY) { + /* unknown or not enough */ + mistake(u, ord, "unknown skill, or skill cannot be raised."); + } else if (num < 0 || num > 30) { + /* sanity check failed */ + mistake(u, ord, "invalid value."); + } else { + /* checking permissions */ + attrib *permissions = a_find(u->faction->attribs, &at_permissions); + if (!permissions || !has_permission(permissions, atoi36("gmskil"))) { + mistake(u, ord, "permission denied."); + } else { + set_level(to, skill, num); + } + } +} + +static void * g_keys; +static void * g_root; +static void * g_tell; +static void * g_kill; + +void register_gmcmd(void) +{ + at_register(&at_gmcreate); + at_register(&at_permissions); + add_command(&g_root, &g_keys, "gm", NULL); + add_command(&g_keys, NULL, "terraform", &gm_terraform); + add_command(&g_keys, NULL, "create", &gm_create); + add_command(&g_keys, NULL, "gate", &gm_gate); + add_command(&g_keys, NULL, "give", &gm_give); + add_command(&g_keys, NULL, "take", &gm_take); + add_command(&g_keys, NULL, "teleport", &gm_teleport); + add_command(&g_keys, NULL, "skill", &gm_skill); + add_command(&g_keys, &g_tell, "tell", NULL); + add_command(&g_tell, NULL, "region", &gm_messageregion); + add_command(&g_tell, NULL, "unit", &gm_messageunit); + add_command(&g_tell, NULL, "plane", &gm_messageplane); + add_command(&g_tell, NULL, "faction", &gm_messagefaction); + add_command(&g_keys, &g_kill, "kill", NULL); + add_command(&g_kill, NULL, "unit", &gm_killunit); + add_command(&g_kill, NULL, "faction", &gm_killfaction); +} + +/* + * execute gm-commands for all units in the game + */ + +void gmcommands(void) +{ + region **rp = ®ions; + while (*rp) { + region *r = *rp; + unit **up = &r->units; + while (*up) { + unit *u = *up; + struct order *ord; + for (ord = u->orders; ord; ord = ord->next) { + if (get_keyword(ord) == K_GM) { + do_command(&g_root, u, ord); + } + } + if (u == *up) + up = &u->next; + } + if (*rp == r) + rp = &r->next; + } +} + +#define EXTENSION 10000 + +faction *gm_addquest(const char *email, const char *name, int radius, + unsigned int flags) +{ + plane *pl; + watcher *w = calloc(sizeof(watcher), 1); + region *center; + bool invalid = false; + int minx, miny, maxx, maxy, cx, cy; + int x; + faction *f; + + /* GM playfield */ + do { + minx = ((rng_int() % (2 * EXTENSION)) - EXTENSION); + miny = ((rng_int() % (2 * EXTENSION)) - EXTENSION); + for (x = 0; !invalid && x <= radius * 2; ++x) { + int y; + for (y = 0; !invalid && y <= radius * 2; ++y) { + region *r = findregion(minx + x, miny + y); + if (r) + invalid = true; + } + } + } while (invalid); + maxx = minx + 2 * radius; + cx = minx + radius; + maxy = miny + 2 * radius; + cy = miny + radius; + pl = create_new_plane(rng_int(), name, minx, maxx, miny, maxy, flags); + center = new_region(cx, cy, pl, 0); + for (x = 0; x <= 2 * radius; ++x) { + int y; + for (y = 0; y <= 2 * radius; ++y) { + region *r = findregion(minx + x, miny + y); + if (!r) { + r = new_region(minx + x, miny + y, pl, 0); + } + freset(r, RF_ENCOUNTER); + if (distance(r, center) == radius) { + terraform_region(r, newterrain(T_FIREWALL)); + } else if (r == center) { + terraform_region(r, newterrain(T_PLAIN)); + } else { + terraform_region(r, newterrain(T_OCEAN)); + } + } + } + + /* watcher: */ + f = gm_addfaction(email, pl, center); + w->faction = f; + w->mode = see_unit; + w->next = pl->watchers; + pl->watchers = w; + + return f; +} + +faction *gm_addfaction(const char *email, plane * p, region * r) +{ + attrib *a; + unit *u; + faction *f = calloc(1, sizeof(faction)); + + assert(p != NULL); + + /* GM faction */ + a_add(&f->attribs, make_key(atoi36("quest"))); + f->banner = strdup("quest faction"); + f->name = strdup("quest faction"); + f->passw = strdup(itoa36(rng_int())); + f->override = strdup(itoa36(rng_int())); + if (set_email(&f->email, email) != 0) { + log_error("Invalid email address for faction %s: %s\n", itoa36(f->no), email); + } + f->race = new_race[RC_TEMPLATE]; + f->age = 0; + f->lastorders = turn; + f->alive = true; + f->locale = default_locale; + f->options = + want(O_COMPRESS) | want(O_REPORT) | want(O_COMPUTER) | want(O_ADRESSEN); + { + faction *xist; + int id = atoi36("gm00") - 1; + do { + xist = findfaction(++id); + } while (xist); + + f->no = id; + addlist(&factions, f); + fhash(f); + } + + /* generic permissions */ + a = a_add(&f->attribs, a_new(&at_permissions)); + if (a) { + attrib *ap = (attrib *) a->data.v; + const char *keys[] = + { "gmterf", "gmtele", "gmgive", "gmskil", "gmtake", "gmmsgr", "gmmsgu", + "gmgate", 0 }; + const char **key_p = keys; + while (*key_p) { + add_key(&ap, atoi36(*key_p)); + ++key_p; + } + a_add(&ap, make_atgmcreate(resource2item(r_silver))); + + a->data.v = ap; + } + + /* one initial unit */ + u = create_unit(r, f, 1, new_race[RC_TEMPLATE], 1, "quest master", NULL); + u->irace = new_race[RC_GNOME]; + + return f; +} + +plane *gm_addplane(int radius, unsigned int flags, const char *name) +{ + region *center; + plane *pl; + bool invalid = false; + int minx, miny, maxx, maxy, cx, cy; + int x; + + /* GM playfield */ + do { + minx = (rng_int() % (2 * EXTENSION)) - EXTENSION; + miny = (rng_int() % (2 * EXTENSION)) - EXTENSION; + for (x = 0; !invalid && x <= radius * 2; ++x) { + int y; + for (y = 0; !invalid && y <= radius * 2; ++y) { + region *r = findregion(minx + x, miny + y); + if (r) + invalid = true; + } + } + } while (invalid); + maxx = minx + 2 * radius; + cx = minx + radius; + maxy = miny + 2 * radius; + cy = miny + radius; + pl = create_new_plane(rng_int(), name, minx, maxx, miny, maxy, flags); + center = new_region(cx, cy, pl, 0); + for (x = 0; x <= 2 * radius; ++x) { + int y; + for (y = 0; y <= 2 * radius; ++y) { + region *r = findregion(minx + x, miny + y); + if (!r) + r = new_region(minx + x, miny + y, pl, 0); + freset(r, RF_ENCOUNTER); + if (distance(r, center) == radius) { + terraform_region(r, newterrain(T_FIREWALL)); + } else if (r == center) { + terraform_region(r, newterrain(T_PLAIN)); + } else { + terraform_region(r, newterrain(T_OCEAN)); + } + } + } + return pl; +} diff --git a/core/src/modules/gmcmd.h b/core/src/modules/gmcmd.h new file mode 100644 index 000000000..490792d2e --- /dev/null +++ b/core/src/modules/gmcmd.h @@ -0,0 +1,50 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_MOD_GMCMD +#define H_MOD_GMCMD +#ifdef __cplusplus +extern "C" { +#endif + + struct plane; + struct attrib; + struct unit; + struct faction; + struct region; + + extern void register_gmcmd(void); +/* initialize this module */ + + extern void gmcommands(void); +/* execute commands */ + + extern struct faction *gm_addfaction(const char *email, struct plane *p, + struct region *r); + extern struct plane *gm_addplane(int radius, unsigned int flags, + const char *name); + +/* + * doesn't belong in here: + */ + struct attrib *find_key(struct attrib *attribs, int key); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/museum.c b/core/src/modules/museum.c new file mode 100644 index 000000000..ba9a28cfa --- /dev/null +++ b/core/src/modules/museum.c @@ -0,0 +1,410 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include + +#include + +#if MUSEUM_MODULE +#include "museum.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +#define PFL_MUSEUM PFL_NOMONSTERS | PFL_NORECRUITS | PFL_NOGIVE | PFL_NOATTACK | PFL_NOTERRAIN | PFL_NOMAGIC | PFL_NOSTEALTH | PFL_NOTEACH | PFL_NOBUILD | PFL_NOFEED + +attrib_type at_museumexit = { + "museumexit", NULL, NULL, NULL, a_writeshorts, a_readshorts +}; + +static void a_initmuseumgivebackcookie(attrib * a) +{ + a->data.v = calloc(1, sizeof(museumgivebackcookie)); +} + +static void a_finalizemuseumgivebackcookie(attrib * a) +{ + free(a->data.v); +} + +static void +a_writemuseumgivebackcookie(const attrib * a, const void *owner, + struct storage *store) +{ + museumgivebackcookie *gbc = (museumgivebackcookie *) a->data.v; + store->w_int(store, gbc->warden_no); + store->w_int(store, gbc->cookie); +} + +static int +a_readmuseumgivebackcookie(attrib * a, void *owner, struct storage *store) +{ + museumgivebackcookie *gbc = (museumgivebackcookie *) a->data.v; + gbc->warden_no = store->r_int(store); + gbc->cookie = store->r_int(store); + return AT_READ_OK; +} + +attrib_type at_museumgivebackcookie = { + "museumgivebackcookie", + a_initmuseumgivebackcookie, + a_finalizemuseumgivebackcookie, + NULL, + a_writemuseumgivebackcookie, + a_readmuseumgivebackcookie +}; + +attrib_type at_warden = { + "itemwarden", NULL, NULL, NULL, a_writeint, a_readint +}; + +static void a_initmuseumgiveback(attrib * a) +{ + a->data.v = calloc(1, sizeof(museumgiveback)); +} + +static void a_finalizemuseumgiveback(attrib * a) +{ + museumgiveback *gb = (museumgiveback *) a->data.v; + i_freeall(&gb->items); + free(a->data.v); +} + +static void +a_writemuseumgiveback(const attrib * a, const void *owner, + struct storage *store) +{ + museumgiveback *gb = (museumgiveback *) a->data.v; + store->w_int(store, gb->cookie); + write_items(store, gb->items); +} + +static int a_readmuseumgiveback(attrib * a, void *owner, struct storage *store) +{ + museumgiveback *gb = (museumgiveback *) a->data.v; + gb->cookie = store->r_int(store); + read_items(store, &gb->items); + return AT_READ_OK; +} + +attrib_type at_museumgiveback = { + "museumgiveback", + a_initmuseumgiveback, + a_finalizemuseumgiveback, + NULL, + a_writemuseumgiveback, + a_readmuseumgiveback +}; + +void warden_add_give(unit * src, unit * u, const item_type * itype, int n) +{ + attrib *aw = a_find(u->attribs, &at_warden); + museumgiveback *gb = NULL; + museumgivebackcookie *gbc; + attrib *a; + + /* has the giver a cookie corresponding to the warden */ + for (a = a_find(src->attribs, &at_museumgivebackcookie); + a && a->type == &at_museumgivebackcookie; a = a->next) { + if (((museumgivebackcookie *) (a->data.v))->warden_no == u->no) + break; + } + + /* if not give it one */ + if (a == NULL || a->type != &at_museumgivebackcookie) { + a = a_add(&src->attribs, a_new(&at_museumgivebackcookie)); + gbc = (museumgivebackcookie *) a->data.v; + gbc->warden_no = u->no; + gbc->cookie = aw->data.i; + assert(aw->data.i < INT_MAX); + aw->data.i++; + } else { + gbc = (museumgivebackcookie *) (a->data.v); + } + + /* now we search for the warden's corresponding item list */ + for (a = a_find(u->attribs, &at_museumgiveback); + a && a->type == &at_museumgiveback; a = a->next) { + gb = (museumgiveback *) a->data.v; + if (gb->cookie == gbc->cookie) { + break; + } + } + + /* if there's none, give it one */ + if (!gb) { + a = a_add(&u->attribs, a_new(&at_museumgiveback)); + gb = (museumgiveback *) a->data.v; + gb->cookie = gbc->cookie; + } + + /* now register the items */ + i_change(&gb->items, itype, n); + + /* done */ + + /* this has a caveat: If the src-unit is destroyed while inside + * the museum, the corresponding itemlist of the warden will never + * be removed. to circumvent that in a generic way will be extremly + * difficult. */ +} + +void create_museum(void) +{ +#if 0 /* TODO: move this to LUA. It should be possible. */ + unsigned int museum_id = hashstring("museum"); + plane *museum = getplanebyid(museum_id); + region *r; + building *b; + const terrain_type *terrain_hall = get_terrain("hall1"); + const terrain_type *terrain_corridor = get_terrain("corridor1"); + + assert(terrain_corridor && terrain_hall); + + if (!museum) { + museum = create_new_plane(museum_id, "Museum", 9500, 9550, + 9500, 9550, PFL_MUSEUM); + } + + if (findregion(9525, 9525) == NULL) { + /* Eingangshalle */ + r = new_region(9525, 9525, 0); + terraform_region(r, terrain_hall); + r->planep = museum; + rsetname(r, "Eingangshalle"); + rsethorses(r, 0); + rsetmoney(r, 0); + rsetpeasants(r, 0); + set_string(&r->display, + "Die Eingangshalle des Großen Museum der 1. Welt ist bereits jetzt ein beeindruckender Anblick. Obwohl das Museum noch nicht eröffnet ist, vermittelt sie bereits einen Flair exotischer Welten. In den Boden ist ein großer Kompass eingelassen, der den Besuchern bei Orientierung helfen soll."); + } + + r = findregion(9526, 9525); + if (!r) { + /* Lounge */ + r = new_region(9526, 9525, 0); + terraform_region(r, terrain_hall); + r->planep = museum; + rsetname(r, "Lounge"); + rsethorses(r, 0); + rsetmoney(r, 0); + rsetpeasants(r, 0); + set_string(&r->display, + "Die Lounge des großen Museums ist ein Platz, in dem sich die Besucher treffen, um die Eindrücke, die sie gewonnen haben, zu verarbeiten. Gemütliche Sitzgruppen laden zum Verweilen ein."); + } + + r = findregion(9526, 9525); + if (!r->buildings) { + const building_type *bt_generic = bt_find("generic"); + b = new_building(bt_generic, r, NULL); + set_string(&b->name, "Séparée im dämonischen Stil"); + set_string(&b->display, + "Diese ganz im dämonischen Stil gehaltene Sitzgruppe ist ganz in dunklen Schwarztönen gehalten. Muster fremdartiger Runen bedecken das merkwürdig geformte Mobiliar, das unangenehm lebendig wirkt."); + + b = new_building(bt_generic, r, NULL); + set_string(&b->name, "Séparée im elfischen Stil"); + set_string(&b->display, + "Ganz in Grün- und Brauntönen gehalten wirkt die Sitzgruppe fast lebendig. Bei näherer Betrachtung erschließt sich dem Betrachter, daß sie tatsächlich aus lebenden Pflanzen erstellt ist. So ist der Tisch aus einem eizigen Baum gewachsen, und die Polster bestehen aus weichen Grassoden. Ein wunderschön gemusterter Webteppich mit tausenden naturgetreu eingestickter Blumensarten bedeckt den Boden."); + + b = new_building(bt_generic, r, NULL); + set_string(&b->name, "Séparée im halblingschen Stil"); + set_string(&b->display, + "Dieses rustikale Mobiliar ist aus einem einzigen, gewaltigen Baum hergestellt worden. Den Stamm haben fleißige Halblinge der Länge nach gevierteilt und aus den vier langen Viertelstämmen die Sitzbänke geschnitzt, während der verbleibende Stumpf als Tisch dient. Schon von weitem steigen dem Besucher die Gerüche der Köstlichkeiten entgegen, die auf dem Tisch stapeln."); + + b = new_building(bt_generic, r, NULL); + set_string(&b->name, "Séparée im orkischen Stil"); + set_string(&b->display, + "Grobgeschreinerte, elfenhautbespannte Stühle und ein Tisch aus Knochen, über deren Herkunft man sich lieber keine Gedanken macht, bilden die Sitzgruppe im orkischen Stil. Überall haben Orks ihre Namen, und anderes wenig zitierenswertes in das Holz und Gebein geritzt."); + + b = new_building(bt_generic, r, NULL); + set_string(&b->name, "Séparée im Meermenschenstil"); + set_string(&b->display, + "Ganz in Blau- und Grüntönen gehalten, mit Algen und Muscheln verziert wirken die aus altem Meerholz geschnitzten Stühle immer ein wenig feucht. Seltsammerweise hat der schwere aus alten Planken gezimmerte Tisch einen Mast mit kompletten Segel in der Mitte."); + + b = new_building(bt_generic, r, NULL); + set_string(&b->name, "Séparée im Katzenstil"); + set_string(&b->display, + "Die Wände dieses Séparée sind aus dunklem Holz. Was aus der Ferne wie ein chaotisch durchbrochenes Flechtwerk wirkt, entpuppt sich bei näherer Betrachtung als eine bis in winzige Details gestaltete dschungelartige Landschaft, in die eine Vielzahl von kleinen Bildergeschichten eingewoben sind. Wie es scheint hat sich der Künstler Mühe gegeben wirklich jedes Katzenvolk Eresseas zu porträtieren. Das schummrige Innere wird von einem Kamin dominiert, vor dem einige Sessel und weiche Kissen zu einem gemütlichen Nickerchen einladen. Feiner Anduner Sisal bezieht die Lehnen der Sessel und verlockt dazu, seine Krallen hinein zu versenken. Auf einem kleinen Ecktisch steht ein großer Korb mit roten Wollknäulen und grauen und braunen Spielmäusen."); + } else { + for (b = r->buildings; b; b = b->next) { + b->size = b->type->maxsize; + } + } + + r = findregion(9524, 9526); + if (!r) { + r = new_region(9524, 9526, 0); + terraform_region(r, terrain_corridor); + r->planep = museum; + rsetname(r, "Nördliche Promenade"); + rsethorses(r, 0); + rsetmoney(r, 0); + rsetpeasants(r, 0); + set_string(&r->display, + "Die Nördliche Promenade führt direkt in den naturgeschichtlichen Teil des Museums."); + } + r = findregion(9525, 9524); + if (!r) { + r = new_region(9525, 9524, 0); + terraform_region(r, terrain_corridor); + r->planep = museum; + rsetname(r, "Südliche Promenade"); + rsethorses(r, 0); + rsetmoney(r, 0); + rsetpeasants(r, 0); + set_string(&r->display, + "Die Südliche Promenade führt den Besucher in den kulturgeschichtlichen Teil des Museums."); + } +#endif +} + +static int +use_museumexitticket(unit * u, const struct item_type *itype, int amount, + order * ord) +{ + attrib *a; + region *r; + unit *warden = findunit(atoi36("mwar")); + int unit_cookie; + + unused(amount); + + /* Prüfen ob in Eingangshalle */ + if (u->region->x != 9525 || u->region->y != 9525) { + cmistake(u, ord, 266, MSG_MAGIC); + return 0; + } + + a = a_find(u->attribs, &at_museumexit); + assert(a); + r = findregion(a->data.sa[0], a->data.sa[1]); + assert(r); + a_remove(&u->attribs, a); + + /* Übergebene Gegenstände zurückgeben */ + + a = a_find(u->attribs, &at_museumgivebackcookie); + unit_cookie = a->data.i; + a_remove(&u->attribs, a); + + if (a) { + for (a = a_find(warden->attribs, &at_museumgiveback); + a && a->type == &at_museumgiveback; a = a->next) { + if (((museumgiveback *) (a->data.v))->cookie == unit_cookie) + break; + } + if (a && a->type == &at_museumgiveback) { + museumgiveback *gb = (museumgiveback *) (a->data.v); + item *it; + + for (it = gb->items; it; it = it->next) { + i_change(&u->items, it->type, it->number); + } + ADDMSG(&u->faction->msgs, msg_message("museumgiveback", + "region unit sender items", r, u, warden, gb->items)); + a_remove(&warden->attribs, a); + } + } + + /* Benutzer zurück teleportieren */ + move_unit(u, r, NULL); + + /* Exitticket abziehen */ + i_change(&u->items, itype, -1); + + return 0; +} + +static int +use_museumticket(unit * u, const struct item_type *itype, int amount, + order * ord) +{ + attrib *a; + region *r = u->region; + plane *pl = rplane(r); + + unused(amount); + + /* Prüfen ob in normaler Plane und nur eine Person */ + if (pl != get_homeplane()) { + cmistake(u, ord, 265, MSG_MAGIC); + return 0; + } + if (u->number != 1) { + cmistake(u, ord, 267, MSG_MAGIC); + return 0; + } + if (has_horses(u)) { + cmistake(u, ord, 272, MSG_MAGIC); + return 0; + } + + /* In diesem Attribut merken wir uns, wohin die Einheit zurückgesetzt + * wird, wenn sie das Museum verläßt. */ + + a = a_add(&u->attribs, a_new(&at_museumexit)); + a->data.sa[0] = (short)r->x; + a->data.sa[1] = (short)r->y; + + /* Benutzer in die Halle teleportieren */ + move_unit(u, findregion(9525, 9525), NULL); + + /* Ticket abziehen */ + i_change(&u->items, itype, -1); + + /* Benutzer ein Exitticket geben */ + i_change(&u->items, itype, 1); + + return 0; +} + +void register_museum(void) +{ + at_register(&at_warden); + at_register(&at_museumexit); + at_register(&at_museumgivebackcookie); + at_register(&at_museumgiveback); + + register_item_use(use_museumticket, "use_museumticket"); + register_item_use(use_museumexitticket, "use_museumexitticket"); +} + +#endif diff --git a/core/src/modules/museum.h b/core/src/modules/museum.h new file mode 100644 index 000000000..1073afcf0 --- /dev/null +++ b/core/src/modules/museum.h @@ -0,0 +1,52 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef HEADER_MUSEUM_H +#define HEADER_MUSEUM_H +#ifdef __cplusplus +extern "C" { +#endif + +#if MUSEUM_MODULE == 0 +#error "must define MUSEUM_MODULE to use this module" +#endif + + extern struct attrib_type at_warden; + extern struct attrib_type at_museumexit; + extern struct attrib_type at_museumgivebackcookie; + extern struct attrib_type at_museumgiveback; + + typedef struct { + int warden_no; + int cookie; + } museumgivebackcookie; + + typedef struct { + int cookie; + struct item *items; + } museumgiveback; + + extern void register_museum(void); + extern void create_museum(void); + extern void warden_add_give(struct unit *src, struct unit *u, + const struct item_type *itype, int n); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/score.c b/core/src/modules/score.c new file mode 100644 index 000000000..062479432 --- /dev/null +++ b/core/src/modules/score.c @@ -0,0 +1,218 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#if SCORE_MODULE +#include "score.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include + +/* libc includes */ +#include + +int average_score_of_age(int age, int a) +{ + faction *f; + int sum = 0, count = 0; + + for (f = factions; f; f = f->next) { + if (!is_monsters(f) && f->race != new_race[RC_TEMPLATE] && f->age <= age + a + && f->age >= age - a) { + sum += f->score; + count++; + } + } + + if (count == 0) { + return 0; + } + return sum / count; +} + +void score(void) +{ + FILE *scoreFP; + region *r; + faction *fc; + int allscores = 0; + char path[MAX_PATH]; + + for (fc = factions; fc; fc = fc->next) + fc->score = 0; + + for (r = regions; r; r = r->next) { + unit *u; + building *b; + ship *s; + + if (rule_region_owners()) { + fc = region_get_owner(r); + if (fc) + fc->score += 10; + } + + for (b = r->buildings; b; b = b->next) { + u = building_owner(b); + if (u != NULL) { + faction *fbo = u->faction; + + if (fbo) { + fbo->score += b->size * 5; + } + } + } + + for (s = r->ships; s; s = s->next) { + unit *cap = ship_owner(s); + if (cap && cap->faction) { + cap->faction->score += s->size * 2; + } + } + + for (u = r->units; u; u = u->next) { + item *itm; + int itemscore = 0; + int i; + faction *f = u->faction; + + if (f == NULL || u_race(u) == new_race[RC_SPELL] + || u_race(u) == new_race[RC_BIRTHDAYDRAGON]) { + continue; + } + + if (old_race(u_race(u)) <= RC_AQUARIAN) { + f->score += (u_race(u)->recruitcost * u->number) / 50; + } + f->score += get_money(u) / 50; + for (itm = u->items; itm; itm = itm->next) { + itemscore += itm->number * itm->type->score; + } + f->score += itemscore / 10; + + for (i = 0; i != u->skill_size; ++i) { + skill *sv = u->skills + i; + switch (sv->id) { + case SK_MAGIC: + f->score += (int)(u->number * pow(sv->level, 4)); + break; + case SK_TACTICS: + f->score += (int)(u->number * pow(sv->level, 3)); + break; + case SK_SPY: + case SK_ALCHEMY: + case SK_HERBALISM: + f->score += (int)(u->number * pow(sv->level, 2.5)); + break; + default: + f->score += (int)(u->number * pow(sv->level, 2.5) / 10); + break; + } + } + } + } + + for (fc = factions; fc; fc = fc->next) { + fc->score = fc->score / 5; + if (!is_monsters(fc) && fc->race != new_race[RC_TEMPLATE]) { + allscores += fc->score; + } + } + if (allscores == 0) { + allscores = 1; + } + + sprintf(path, "%s/score", basepath()); + scoreFP = fopen(path, "w"); + if (scoreFP) { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + faction *f; + fwrite(utf8_bom, 1, 3, scoreFP); + for (f = factions; f; f = f->next) + if (f->num_total != 0) { + fprintf(scoreFP, "%8d (%8d/%4.2f%%/%5.2f) %30.30s (%3.3s) %5s (%3d)\n", + f->score, f->score - average_score_of_age(f->age, f->age / 24 + 1), + ((float)f->score / (float)allscores) * 100.0, + (float)f->score / f->num_total, + f->name, LOC(default_locale, rc_name(f->race, 0)), factionid(f), + f->age); + } + fclose(scoreFP); + } + if (alliances != NULL) { + alliance *a; + const item_type *token = it_find("conquesttoken"); + + sprintf(path, "%s/score.alliances", basepath()); + scoreFP = fopen(path, "w"); + if (scoreFP) { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + fwrite(utf8_bom, 1, 3, scoreFP); + + fprintf(scoreFP, "# alliance:factions:persons:score\n"); + + for (a = alliances; a; a = a->next) { + int alliance_score = 0, alliance_number = 0, alliance_factions = 0; + int grails = 0; + faction *f; + + for (f = factions; f; f = f->next) { + if (f->alliance && f->alliance == a) { + alliance_factions++; + alliance_score += f->score; + alliance_number += f->num_total; + if (token != NULL) { + unit *u = f->units; + while (u != NULL) { + item **iitem = i_find(&u->items, token); + if (iitem != NULL && *iitem != NULL) { + grails += (*iitem)->number; + } + u = u->nextF; + } + } + } + } + + fprintf(scoreFP, "%d:%d:%d:%d", a->id, alliance_factions, + alliance_number, alliance_score); + if (token != NULL) + fprintf(scoreFP, ":%d", grails); + fputc('\n', scoreFP); + } + fclose(scoreFP); + } + } +} + +#endif diff --git a/core/src/modules/score.h b/core/src/modules/score.h new file mode 100644 index 000000000..1140e0568 --- /dev/null +++ b/core/src/modules/score.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef SCORE_H +#define SCORE_H +#ifdef __cplusplus +extern "C" { +#endif + +#if SCORE_MODULE == 0 +#error "must define SCORE_MODULE to use this module" +#endif + + extern void score(void); + extern int average_score_of_age(int age, int a); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/weather.c b/core/src/modules/weather.c new file mode 100644 index 000000000..793e7da73 --- /dev/null +++ b/core/src/modules/weather.c @@ -0,0 +1,132 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifdef WEATHER + +#include +#include +#include "weather.h" + +/* libc includes */ +#include + +weather *create_weather(region * r, weather_t type) +{ + weather *w; + + w = calloc(1, sizeof(weather)); + w->center[0] = r->x; + w->center[1] = r->y; + w->type = type; + w->move[0] = (rng_int() % 3) - 1; + w->move[1] = (rng_int() % 3) - 1; + + switch (type) { + case WEATHER_STORM: + w->radius = rng_int() % 2 + 1; + break; + case WEATHER_HURRICANE: + w->radius = 1; + break; + default: + w->radius = 0; + } + + addlist(&weathers, w); + + return w; +} + +double distance(int x1, int y1, int x2, int y2) +{ + return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); +} + +/* Diese Funktion ermittelt für jede Region, welches Wetter in ihr + herrscht. Die Wettertypen sind dabei nach ihrer Reihenfolge in der + enumeration priorisiert. + + - Einladen + set_weather(); + - Eigentliche Auswertung + - Veränderungen des Wetters + set_weather(); + - Report generieren + - Abspeichern + + Diese Routine ist sehr rechenaufwendig! +*/ + +void set_weather(void) +{ + weather_t i; + weather *w; + short x, y; + int d; + region *r; + + for (r = regions; r; r = r->next) { + r->weathertype = WEATHER_NONE; + } + + for (i = 0; i < MAXWEATHERS; i++) { + for (w = weathers; w; w = w->next) { + if (w->type == i) { + for (x = w->center[0] - w->radius; x <= w->center[0] + w->radius; x++) { + for (y = w->center[1] - w->radius; y <= w->center[1] + w->radius; y++) { + d = distance(w->center[0], w->center[1], x, y); + if (floor(d + 0.5) <= w->radius) { + r = findregion(x, y); + if (r) { + r->weathertype = w->type; + } + } + } + } + } + } + } +} + +void move_weather(void) +{ + weather *w, *wnext; + region *r; + + for (w = weathers; w;) { + wnext = w->next; + w->center[0] = w->center[0] + w->move[0]; + w->center[1] = w->center[1] + w->move[1]; + r = findregion(w->center[0], w->center[1]); + if (!r || rng_int() % 100 < 5) { + removelist(&weathers, w); + } + w = wnext; + } +} + +#else +#include +static const char *copyright = "(c) Eressea PBEM 2000"; + +void init_weather(void) +{ + fputs(copyright, stderr); + /* TODO: Initialization */ +} +#endif diff --git a/core/src/modules/weather.h b/core/src/modules/weather.h new file mode 100644 index 000000000..b991dbaf9 --- /dev/null +++ b/core/src/modules/weather.h @@ -0,0 +1,54 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_MOD_WEATHER_H +#define H_MOD_WEATHER_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WEATHER +# error "the weather system is disabled" +#endif + + enum { + WEATHER_NONE, + WEATHER_STORM, + WEATHER_HURRICANE, + MAXWEATHERS + }; + + typedef unsigned char weather_t; + + typedef struct weather { + struct weather *next; + weather_t type; /* Typ der Wetterzone */ + int center[2]; /* Koordinaten des Zentrums */ + int radius; + int move[2]; + } weather; + + weather *weathers; + + void set_weather(void); + void move_weather(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/wormhole.c b/core/src/modules/wormhole.c new file mode 100644 index 000000000..4a4730df3 --- /dev/null +++ b/core/src/modules/wormhole.c @@ -0,0 +1,225 @@ +/* vi: set ts=2: + +-------------------+ + | | Christian Schlittchen + | Eressea PBEM host | Enno Rehling + | (c) 1998 - 2004 | Katja Zedel + | | + +-------------------+ + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "settings.h" + +#include "wormhole.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include + +static bool good_region(const region * r) +{ + return (!fval(r, RF_CHAOTIC) && r->age > 30 && rplane(r) == NULL + && r->units != NULL && r->land != NULL); +} + +static int cmp_age(const void *v1, const void *v2) +{ + const region *r1 = (const region *)v1; + const region *r2 = (const region *)v2; + if (r1->age < r2->age) + return -1; + if (r1->age > r2->age) + return 1; + return 0; +} + +typedef struct wormhole_data { + building *entry; + region *exit; +} wormhole_data; + +static void wormhole_init(struct attrib *a) +{ + a->data.v = calloc(1, sizeof(wormhole_data)); +} + +static void wormhole_done(struct attrib *a) +{ + free(a->data.v); +} + +static int wormhole_age(struct attrib *a) +{ + wormhole_data *data = (wormhole_data *) a->data.v; + int maxtransport = data->entry->size; + region *r = data->entry->region; + unit *u = r->units; + + for (; u != NULL && maxtransport != 0; u = u->next) { + if (u->building == data->entry) { + message *m = NULL; + if (u->number > maxtransport || has_limited_skills(u)) { + m = msg_message("wormhole_requirements", "unit region", u, u->region); + } else if (data->exit != NULL) { + move_unit(u, data->exit, NULL); + maxtransport -= u->number; + m = msg_message("wormhole_exit", "unit region", u, data->exit); + add_message(&data->exit->msgs, m); + } + if (m != NULL) { + add_message(&u->faction->msgs, m); + msg_release(m); + } + } + } + + remove_building(&r->buildings, data->entry); + ADDMSG(&r->msgs, msg_message("wormhole_dissolve", "region", r)); + + /* age returns 0 if the attribute needs to be removed, !=0 otherwise */ + return AT_AGE_KEEP; +} + +static void +wormhole_write(const struct attrib *a, const void *owner, struct storage *store) +{ + wormhole_data *data = (wormhole_data *) a->data.v; + write_building_reference(data->entry, store); + write_region_reference(data->exit, store); +} + +/** conversion code, turn 573, 2008-05-23 */ +static int resolve_exit(variant id, void *address) +{ + building *b = findbuilding(id.i); + region **rp = address; + if (b) { + *rp = b->region; + return 0; + } + *rp = NULL; + return -1; +} + +static int wormhole_read(struct attrib *a, void *owner, struct storage *store) +{ + wormhole_data *data = (wormhole_data *) a->data.v; + resolve_fun resolver = + (store->version < UIDHASH_VERSION) ? resolve_exit : resolve_region_id; + read_fun reader = + (store->version < + UIDHASH_VERSION) ? read_building_reference : read_region_reference; + + int rb = + read_reference(&data->entry, store, read_building_reference, + resolve_building); + int rr = read_reference(&data->exit, store, reader, resolver); + if (rb == 0 && rr == 0) { + if (!data->exit || !data->entry) { + return AT_READ_FAIL; + } + } + return AT_READ_OK; +} + +static attrib_type at_wormhole = { + "wormhole", + wormhole_init, + wormhole_done, + wormhole_age, + wormhole_write, + wormhole_read, + ATF_UNIQUE +}; + +static void +make_wormhole(const building_type * bt_wormhole, region * r1, region * r2) +{ + building *b1 = new_building(bt_wormhole, r1, default_locale); + building *b2 = new_building(bt_wormhole, r2, default_locale); + attrib *a1 = a_add(&b1->attribs, a_new(&at_wormhole)); + attrib *a2 = a_add(&b2->attribs, a_new(&at_wormhole)); + wormhole_data *d1 = (wormhole_data *) a1->data.v; + wormhole_data *d2 = (wormhole_data *) a2->data.v; + d1->entry = b1; + d2->entry = b2; + d1->exit = b2->region; + d2->exit = b1->region; + b1->size = bt_wormhole->maxsize; + b2->size = bt_wormhole->maxsize; + ADDMSG(&r1->msgs, msg_message("wormhole_appear", "region", r1)); + ADDMSG(&r2->msgs, msg_message("wormhole_appear", "region", r2)); +} + +void create_wormholes(void) +{ +#define WORMHOLE_CHANCE 10000 + const building_type *bt_wormhole = bt_find("wormhole"); + quicklist *ql, *rlist = 0; + region *r = regions; + int qi, i = 0, count = 0; + region **match; + + if (bt_wormhole == NULL) + return; + /* + * select a list of regions. we'll sort them by age later. + */ + while (r != NULL) { + int next = rng_int() % (2 * WORMHOLE_CHANCE); + while (r != NULL && (next != 0 || !good_region(r))) { + if (good_region(r)) { + --next; + } + r = r->next; + } + if (r == NULL) + break; + ql_push(&rlist, r); + ++count; + r = r->next; + } + + if (count < 2) + return; + + match = (region **) malloc(sizeof(region *) * count); + + for (ql = rlist,qi = 0; i != count; ql_advance(&ql, &qi, 1)) { + match[i++] = (region *)ql_get(ql, qi); + } + qsort(match, count, sizeof(region *), cmp_age); + ql_free(rlist); + + count /= 2; + for (i = 0; i != count; ++i) { + make_wormhole(bt_wormhole, match[i], match[i + count]); + } + free(match); +} + +void register_wormholes(void) +{ + at_register(&at_wormhole); +} diff --git a/core/src/modules/wormhole.h b/core/src/modules/wormhole.h new file mode 100644 index 000000000..dea7a6aab --- /dev/null +++ b/core/src/modules/wormhole.h @@ -0,0 +1,31 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_MOD_WORMHOLE +#define H_MOD_WORMHOLE +#ifdef __cplusplus +extern "C" { +#endif + + extern void create_wormholes(void); + extern void register_wormholes(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/modules/xmas.c b/core/src/modules/xmas.c new file mode 100644 index 000000000..9c44011b1 --- /dev/null +++ b/core/src/modules/xmas.c @@ -0,0 +1,78 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "xmas.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include + +static int xmasgate_handle(trigger * t, void *data) +{ + return -1; +} + +static void xmasgate_write(const trigger * t, struct storage *store) +{ + building *b = (building *) t->data.v; + store->w_tok(store, itoa36(b->no)); +} + +static int xmasgate_read(trigger * t, struct storage *store) +{ + int bc = + read_reference(&t->data.v, store, read_building_reference, + resolve_building); + if (bc == 0 && !t->data.v) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +struct trigger_type tt_xmasgate = { + "xmasgate", + NULL, + NULL, + xmasgate_handle, + xmasgate_write, + xmasgate_read +}; + +trigger *trigger_xmasgate(building * b) +{ + trigger *t = t_new(&tt_xmasgate); + t->data.v = b; + return t; +} + +void register_xmas(void) +{ + tt_register(&tt_xmasgate); +} diff --git a/core/src/modules/xmas.h b/core/src/modules/xmas.h new file mode 100644 index 000000000..8cd6086fe --- /dev/null +++ b/core/src/modules/xmas.h @@ -0,0 +1,29 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_MOD_XMAS +#define H_MOD_XMAS +#ifdef __cplusplus +extern "C" { +#endif + + struct region; + struct unit; + + extern struct trigger *trigger_xmasgate(struct building *b); + + extern void register_xmas(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/platform.h b/core/src/platform.h new file mode 100644 index 000000000..7e1fd72b4 --- /dev/null +++ b/core/src/platform.h @@ -0,0 +1,259 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef CONFIG_H +#define CONFIG_H + +#ifdef _MSC_VER +# define VC_EXTRALEAN +# define WIN32_LEAN_AND_MEAN +# include +# undef MOUSE_MOVED +# define STDIO_CP 1252 /* log.c, convert to console character set */ +# pragma warning (disable: 4201 4214 4514 4115 4711) +# pragma warning(disable: 4056) +/* warning C4056: overflow in floating point constant arithmetic */ +# pragma warning(disable: 4201) +/* warning C4201: nonstandard extension used : nameless struct/union */ +# pragma warning(disable: 4214) +/* warning C4214: nonstandard extension used : bit field types other than int */ +# pragma warning(disable: 4100) +/* warning C4100: : unreferenced formal parameter */ +# pragma warning(disable: 4996) +/* is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +# pragma warning(disable: 4668) + +/* warning C4100: was declared deprecated */ +#ifndef _CRT_SECURE_NO_DEPRECATE +# define _CRT_SECURE_NO_DEPRECATE +#endif + +/* http://msdn2.microsoft.com/en-us/library/ms235505(VS.80).aspx */ +#ifndef _CRT_DISABLE_PERFCRIT_LOCKS +# define _CRT_DISABLE_PERFCRIT_LOCKS +#endif + +#endif /* _MSC_VER_ */ + +#ifdef __cplusplus +# include +# include +extern "C" { +#else +# include +# include +#endif + +/**** **** + ** Debugging Libraries ** + **** ****/ +#if defined __GNUC__ +# define HAVE_INLINE +# define INLINE_FUNCTION static __inline +#endif + +/* define USE_DMALLOC to enable use of the dmalloc library */ +#ifdef USE_DMALLOC +# include +# include +# include +#endif + +/* define CRTDBG to enable MSVC CRT Debug library functions */ +#if defined(_DEBUG) && defined(_MSC_VER) && defined(CRTDBG) +# include +# define _CRTDBG_MAP_ALLOC +#endif + +/**** **** + ** Architecture Dependent ** + **** ****/ + +/* für solaris: */ +#ifdef SOLARIS +# define _SYS_PROCSET_H +# define _XOPEN_SOURCE +#endif + +#ifdef __GNUC__ +# ifndef _BSD_SOURCE +# define _BSD_SOURCE +# define __USE_BSD +# endif +/* # include */ +# include /* strncasecmp-Prototyp */ +#endif + +#ifdef _BSD_SOURCE +# define __EXTENSIONS__ +#endif + +#ifdef WIN32 +# define HAVE__MKDIR_WITHOUT_PERMISSION +# define HAVE__SLEEP_MSEC +#endif + +#if defined(__USE_SVID) || defined(_BSD_SOURCE) || defined(__USE_XOPEN_EXTENDED) || defined(_BE_SETUP_H) || defined(CYGWIN) +# include +# define HAVE_UNISTD_H +# define HAVE_STRCASECMP +# define HAVE_STRNCASECMP +# define HAVE_ACCESS +# define HAVE_STAT +typedef struct stat stat_type; +# include +# define HAVE_STRDUP +# define HAVE_SNPRINTF +#ifdef _POSIX_SOURCE /* MINGW doesn't seem to have these */ +# define HAVE_EXECINFO +# define HAVE_SIGACTION +# define HAVE_LINK +# define HAVE_SLEEP +#endif +#endif + +/* TinyCC */ +#ifdef TINYCC +# undef HAVE_INLINE +# define INLINE_FUNCTION +#endif + +/* lcc-win32 */ +#ifdef __LCC__ +# include +# include +# include +# define HAVE_ACCESS +# define HAVE_STAT +typedef struct stat stat_type; +# define HAVE_STRICMP +# define HAVE_STRNICMP +# define HAVE_STRDUP +# define HAVE_SLEEP +# define snprintf _snprintf +# define HAVE_SNPRINTF +# undef HAVE_STRCASECMP +# undef HAVE_STRNCASECMP +# define R_OK 4 +#endif + +/* Microsoft Visual C */ +#ifdef _MSC_VER +# include /* must be included here so strdup is not redefined */ +# define R_OK 4 +# define HAVE_INLINE +# define INLINE_FUNCTION __inline + +# define snprintf _snprintf +# define HAVE_SNPRINTF + +/* MSVC has _access, not access */ +#ifndef access +#include +# define access(f, m) _access(f, m) +#endif +#define HAVE_ACCESS + +/* MSVC has _stat, not stat */ +# define HAVE_STAT +#include +# define stat(a, b) _stat(a, b) +typedef struct _stat stat_type; + +/* MSVC has _strdup */ +# define strdup _strdup +# define HAVE_STRDUP + +# define stricmp(a, b) _stricmp(a, b) +# define HAVE_STRICMP + +# define strnicmp(a, b, c) _strnicmp(a, b, c) +# define HAVE_STRNICMP +# undef HAVE_STRCASECMP +# undef HAVE_STRNCASECMP +#endif + +/* replacements for missing functions: */ + +#ifndef HAVE_STRCASECMP +# if defined(HAVE_STRICMP) +# define strcasecmp stricmp +# elif defined(HAVE__STRICMP) +# define strcasecmp _stricmp +# endif +#endif + +#ifndef HAVE_STRNCASECMP +# if defined(HAVE_STRNICMP) +# define strncasecmp strnicmp +# elif defined(HAVE__STRNICMP) +# define strncasecmp _strnicmp +# endif +#endif + +#ifndef HAVE_STRDUP +extern char *strdup(const char *s); +#endif + +#ifndef HAVE_SLEEP +#ifdef HAVE__SLEEP_MSEC +# define sleep(sec) _sleep(1000*sec) +#elif defined(HAVE__SLEEP) +# define sleep(sec) _sleep(sec) +#endif +#endif + +#if !defined(MAX_PATH) +# if defined(PATH_MAX) +# define MAX_PATH PATH_MAX +# else +# define MAX_PATH 1024 +# endif +#endif + +/**** **** + ** min/max macros ** + **** ****/ +#ifndef NOMINMAX +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +# define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#endif + +#if defined (__GNUC__) +# define unused(a) /* unused: a */ +#elif defined (ghs) || defined (__hpux) || defined (__sgi) || defined (__DECCXX) || defined (__KCC) || defined (__rational__) || defined (__USLC__) || defined (ACE_RM544) +# define unused(a) do {/* null */} while (&a == 0) +#else /* ghs || __GNUC__ || ..... */ +# define unused(a) (a) +#endif /* ghs || __GNUC__ || ..... */ + +#include "util/bool.h" + +#ifndef INLINE_FUNCTION +# define INLINE_FUNCTION +#endif + +#define iswxspace(c) (c==160 || iswspace(c)) +#define isxspace(c) (c==160 || isspace(c)) + +#define TOLUA_CAST (char*) +#endif diff --git a/core/src/settings.h b/core/src/settings.h new file mode 100644 index 000000000..552e4c1c9 --- /dev/null +++ b/core/src/settings.h @@ -0,0 +1,59 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +/* + * Contains defines for the "free" game (Eressea) . + * Include this file from settings.h to make eressea work. + */ +#define ENTERTAINFRACTION 20 +#define TEACHDIFFERENCE 2 +#define GUARD_DISABLES_RECRUIT 1 +#define GUARD_DISABLES_PRODUCTION 1 +#define RESOURCE_QUANTITY 0.5 +#define RECRUITFRACTION 40 /* 100/RECRUITFRACTION% */ +#define COMBAT_TURNS 5 +#define NEWATSROI 0 + +/* Vermehrungsrate Bauern in 1/10000. +* Evt. Berechnungsfehler, reale Vermehrungsraten scheinen höher. */ +#define PEASANTGROWTH 10 +#define BATTLE_KILLS_PEASANTS 20 +#define PEASANTLUCK 10 + +#define HUNGER_REDUCES_SKILL /* Hunger reduziert den Talentwert + auf die Hälfte */ + +#define ASTRAL_ITEM_RESTRICTIONS /* keine grossen dinge im astralraum */ +#define NEW_DAEMONHUNGER_RULE +#define NEW_COMBATSKILLS_RULE +#define ROW_FACTOR 3 /* factor for combat row advancement rule */ + +/* optional game components. TODO: These should either be + * configuration variables (XML), script extensions (lua), + * or both. We don't want separate binaries for different games + */ +#define SCORE_MODULE 1 +#define MUSEUM_MODULE 1 +#define ARENA_MODULE 1 +#define DUNGEON_MODULE 0 +#define CHANGED_CROSSBOWS 0 /* use the WTF_ARMORPIERCING flag */ +#undef GLOBAL_WARMING /* number of turns before global warming sets in */ + +#if defined(BINDINGS_LUABIND) +# undef BINDINGS_TOLUA +#elif defined(BINDINGS_TOLUA) +# undef BINDINGS_LUABIND +#else +# define BINDINGS_TOLUA /* new default */ +#endif + +#undef REGIONOWNERS /* (WIP) region-owner uses HELP_TRAVEL to control entry to region */ diff --git a/core/src/stdafx.h b/core/src/stdafx.h new file mode 100644 index 000000000..f6db3cac1 --- /dev/null +++ b/core/src/stdafx.h @@ -0,0 +1 @@ +/* empty, only used in non-msvc builds */ diff --git a/core/src/tests.c b/core/src/tests.c new file mode 100644 index 000000000..568e65ac4 --- /dev/null +++ b/core/src/tests.c @@ -0,0 +1,219 @@ +#include +#include + +#include +#include +#include "tests.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int RunAllTests(void) +{ + CuString *output = CuStringNew(); + CuSuite *suite = CuSuiteNew(); + int flags = log_flags; + + log_flags = LOG_FLUSH | LOG_CPERROR; + + /* self-test */ + CuSuiteAddSuite(suite, get_tests_suite()); + /* util */ + CuSuiteAddSuite(suite, get_base36_suite()); + CuSuiteAddSuite(suite, get_bsdstring_suite()); + CuSuiteAddSuite(suite, get_functions_suite()); + CuSuiteAddSuite(suite, get_umlaut_suite()); + /* kernel */ + CuSuiteAddSuite(suite, get_pool_suite()); + CuSuiteAddSuite(suite, get_curse_suite()); + CuSuiteAddSuite(suite, get_equipment_suite()); + CuSuiteAddSuite(suite, get_item_suite()); + CuSuiteAddSuite(suite, get_magic_suite()); + CuSuiteAddSuite(suite, get_move_suite()); + CuSuiteAddSuite(suite, get_reports_suite()); + CuSuiteAddSuite(suite, get_ship_suite()); + CuSuiteAddSuite(suite, get_spellbook_suite()); + CuSuiteAddSuite(suite, get_building_suite()); + CuSuiteAddSuite(suite, get_spell_suite()); + CuSuiteAddSuite(suite, get_battle_suite()); + CuSuiteAddSuite(suite, get_ally_suite()); + /* gamecode */ + CuSuiteAddSuite(suite, get_market_suite()); + CuSuiteAddSuite(suite, get_laws_suite()); + CuSuiteAddSuite(suite, get_economy_suite()); + + CuSuiteRun(suite); + CuSuiteSummary(suite, output); + CuSuiteDetails(suite, output); + printf("%s\n", output->buffer); + + log_flags = flags; + return suite->failCount; +} + +struct race *test_create_race(const char *name) +{ + race *rc = rc_add(rc_new(name)); + rc->flags |= RCF_PLAYERRACE; + rc->maintenance = 10; + return rc; +} + +struct region *test_create_region(int x, int y, const terrain_type *terrain) +{ + region *r = new_region(x, y, NULL, 0); + terraform_region(r, terrain); + rsettrees(r, 0, 0); + rsettrees(r, 1, 0); + rsettrees(r, 2, 0); + rsethorses(r, 0); + rsetpeasants(r, terrain->size); + return r; +} + +struct faction *test_create_faction(const struct race *rc) +{ + faction *f = addfaction("nobody@eressea.de", NULL, rc?rc:rc_find("human"), default_locale, 0); + return f; +} + +struct unit *test_create_unit(struct faction *f, struct region *r) +{ + unit *u = create_unit(r, f, 1, f?f->race:rc_find("human"), 0, 0, 0); + return u; +} + +void test_cleanup(void) +{ + test_clear_terrains(); + test_clear_resources(); + global.functions.maintenance = NULL; + global.functions.wage = NULL; + default_locale = 0; + free_locales(); + free_spells(); + free_spellbooks(); + free_gamedata(); +} + +terrain_type * +test_create_terrain(const char * name, unsigned int flags) +{ + terrain_type * t; + + assert(!get_terrain(name)); + t = (terrain_type*)calloc(1, sizeof(terrain_type)); + t->_name = strdup(name); + t->flags = flags; + register_terrain(t); + return t; +} + +building * test_create_building(region * r, const building_type * btype) +{ + building * b = new_building(btype?btype:bt_find("castle"), r, default_locale); + b->size = b->type->maxsize>0?b->type->maxsize:1; + return b; +} + +ship * test_create_ship(region * r, const ship_type * stype) +{ + ship * s = new_ship(stype?stype:st_find("boat"), r, default_locale); + s->size = s->type->construction?s->type->construction->maxsize:1; + return s; +} + +ship_type * test_create_shiptype(const char ** names) +{ + ship_type * stype = (ship_type*)calloc(sizeof(ship_type), 1); + stype->name[0] = strdup(names[0]); + stype->name[1] = strdup(names[1]); + locale_setstring(default_locale, names[0], names[0]); + st_register(stype); + return stype; +} + +building_type * test_create_buildingtype(const char * name) +{ + building_type * btype = (building_type*)calloc(sizeof(building_type), 1); + btype->flags = BTF_NAMECHANGE; + btype->_name = strdup(name); + locale_setstring(default_locale, name, name); + bt_register(btype); + return btype; +} + +item_type * test_create_itemtype(const char ** names) { + resource_type * rtype; + item_type * itype; + + rtype = new_resourcetype(names, NULL, RTF_ITEM); + itype = new_itemtype(rtype, ITF_ANIMAL|ITF_BIG, 5000, 7000); + + return itype; +} + +/** creates a small world and some stuff in it. + * two terrains: 'plain' and 'ocean' + * one race: 'human' + * one ship_type: 'boat' + * one building_type: 'castle' + * in 0.0 and 1.0 is an island of two plains, around it is ocean. + */ +void test_create_world(void) +{ + terrain_type *t_plain, *t_ocean; + region *island[2]; + int i; + const char * names[] = { "horse", "horse_p", "boat", "boat_p", "iron", "iron_p", "stone", "stone_p" }; + + make_locale("de"); + init_resources(); + assert(!olditemtype[I_HORSE]); + + olditemtype[I_HORSE] = test_create_itemtype(names+0); + olditemtype[I_IRON] = test_create_itemtype(names+4); + olditemtype[I_STONE] = test_create_itemtype(names+6); + + t_plain = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION); + t_plain->size = 1000; + t_ocean = test_create_terrain("ocean", SEA_REGION | SAIL_INTO | SWIM_INTO); + t_ocean->size = 0; + + island[0] = test_create_region(0, 0, t_plain); + island[1] = test_create_region(1, 0, t_plain); + for (i = 0; i != 2; ++i) { + int j; + region *r = island[i]; + for (j = 0; j != MAXDIRECTIONS; ++j) { + region *rn = r_connect(r, (direction_t)j); + if (!rn) { + rn = test_create_region(r->x + delta_x[j], r->y + delta_y[j], t_ocean); + } + } + } + + test_create_race("human"); + + test_create_buildingtype("castle"); + test_create_shiptype(names+2); +} + +int main(int argc, char ** argv) { + return RunAllTests(); +} diff --git a/core/src/tests.h b/core/src/tests.h new file mode 100644 index 000000000..15aa7a230 --- /dev/null +++ b/core/src/tests.h @@ -0,0 +1,50 @@ +#ifndef ERESSEA_TESTS_H +#define ERESSEA_TESTS_H +#ifdef __cplusplus +extern "C" { +#endif + #include + + CuSuite *get_tests_suite(void); + CuSuite *get_economy_suite(void); + CuSuite *get_laws_suite(void); + CuSuite *get_market_suite(void); + CuSuite *get_battle_suite(void); + CuSuite *get_building_suite(void); + CuSuite *get_curse_suite(void); + CuSuite *get_equipment_suite(void); + CuSuite *get_item_suite(void); + CuSuite *get_magic_suite(void); + CuSuite *get_move_suite(void); + CuSuite *get_pool_suite(void); + CuSuite *get_reports_suite(void); + CuSuite *get_ship_suite(void); + CuSuite *get_spellbook_suite(void); + CuSuite *get_spell_suite(void); + CuSuite *get_base36_suite(void); + CuSuite *get_bsdstring_suite(void); + CuSuite *get_functions_suite(void); + CuSuite *get_umlaut_suite(void); + CuSuite *get_ally_suite(void); + + void test_cleanup(void); + + struct terrain_type * test_create_terrain(const char * name, unsigned int flags); + struct race *test_create_race(const char *name); + struct region *test_create_region(int x, int y, + const struct terrain_type *terrain); + struct faction *test_create_faction(const struct race *rc); + struct unit *test_create_unit(struct faction *f, struct region *r); + void test_create_world(void); + struct building * test_create_building(struct region * r, const struct building_type * btype); + struct ship * test_create_ship(struct region * r, const struct ship_type * stype); + struct item_type * test_create_itemtype(const char ** names); + struct ship_type *test_create_shiptype(const char **names); + struct building_type *test_create_buildingtype(const char *name); + + int RunAllTests(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/tests_test.c b/core/src/tests_test.c new file mode 100644 index 000000000..250ea5ec6 --- /dev/null +++ b/core/src/tests_test.c @@ -0,0 +1,52 @@ +#include + +#include +#include +#include + +#include + +#include +#include + +static void test_recreate_world(CuTest * tc) +{ + test_cleanup(); + CuAssertPtrEquals(tc, 0, find_locale("de")); + CuAssertPtrEquals(tc, 0, it_find("money")); + CuAssertPtrEquals(tc, 0, it_find("horse")); + + test_create_world(); + CuAssertPtrEquals(tc, default_locale, find_locale("de")); + CuAssertPtrNotNull(tc, default_locale); + CuAssertPtrNotNull(tc, findregion(0, 0)); + CuAssertPtrNotNull(tc, it_find("money")); + CuAssertPtrNotNull(tc, it_find("horse")); + CuAssertPtrNotNull(tc, rt_find("horse")); + CuAssertPtrNotNull(tc, rt_find("hp")); + CuAssertPtrNotNull(tc, rt_find("money")); + CuAssertPtrNotNull(tc, rt_find("aura")); + CuAssertPtrNotNull(tc, rt_find("permaura")); + CuAssertPtrNotNull(tc, rt_find("peasant")); + CuAssertPtrNotNull(tc, rt_find("unit")); + + test_cleanup(); + CuAssertPtrEquals(tc, 0, find_locale("de")); + CuAssertPtrEquals(tc, 0, it_find("money")); + CuAssertPtrEquals(tc, 0, it_find("horse")); + CuAssertPtrEquals(tc, 0, rt_find("horse")); + CuAssertPtrEquals(tc, 0, rt_find("hp")); + CuAssertPtrEquals(tc, 0, rt_find("money")); + CuAssertPtrEquals(tc, 0, rt_find("aura")); + CuAssertPtrEquals(tc, 0, rt_find("permaura")); + CuAssertPtrEquals(tc, 0, rt_find("peasant")); + CuAssertPtrEquals(tc, 0, rt_find("unit")); + CuAssertPtrEquals(tc, 0, findregion(0, 0)); +} + +CuSuite *get_tests_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_recreate_world); + return suite; +} diff --git a/core/src/triggers/changefaction.c b/core/src/triggers/changefaction.c new file mode 100644 index 000000000..60d821412 --- /dev/null +++ b/core/src/triggers/changefaction.c @@ -0,0 +1,107 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "changefaction.h" + +/* kernel includes */ +#include +#include +#include /* FIXME: resolve_faction */ + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* ansi includes */ +#include +#include +#include + +#include +/*** + ** restore a mage that was turned into a toad + **/ + +typedef struct changefaction_data { + struct unit *unit; + struct faction *faction; +} changefaction_data; + +static void changefaction_init(trigger * t) +{ + t->data.v = calloc(sizeof(changefaction_data), 1); +} + +static void changefaction_free(trigger * t) +{ + free(t->data.v); +} + +static int changefaction_handle(trigger * t, void *data) +{ + /* call an event handler on changefaction. + * data.v -> ( variant event, int timer ) + */ + changefaction_data *td = (changefaction_data *) t->data.v; + if (td->unit && td->faction) { + u_setfaction(td->unit, td->faction); + } else { + log_error("could not perform changefaction::handle()\n"); + } + unused(data); + return 0; +} + +static void changefaction_write(const trigger * t, struct storage *store) +{ + changefaction_data *td = (changefaction_data *) t->data.v; + write_unit_reference(td->unit, store); + write_faction_reference(td->faction, store); +} + +static int changefaction_read(trigger * t, struct storage *store) +{ + changefaction_data *td = (changefaction_data *) t->data.v; + read_reference(&td->unit, store, read_unit_reference, resolve_unit); + read_reference(&td->faction, store, read_faction_reference, resolve_faction); + return AT_READ_OK; +} + +trigger_type tt_changefaction = { + "changefaction", + changefaction_init, + changefaction_free, + changefaction_handle, + changefaction_write, + changefaction_read +}; + +trigger *trigger_changefaction(unit * u, struct faction * f) +{ + trigger *t = t_new(&tt_changefaction); + changefaction_data *td = (changefaction_data *) t->data.v; + td->unit = u; + td->faction = f; + return t; +} diff --git a/core/src/triggers/changefaction.h b/core/src/triggers/changefaction.h new file mode 100644 index 000000000..02039d9f0 --- /dev/null +++ b/core/src/triggers/changefaction.h @@ -0,0 +1,40 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef CHANGEFACTION_H +#define CHANGEFACTION_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + + struct unit; + struct faction; + + extern struct trigger_type tt_changefaction; + + extern struct trigger *trigger_changefaction(struct unit *u, + struct faction *f); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/changerace.c b/core/src/triggers/changerace.c new file mode 100644 index 000000000..a78ac2d55 --- /dev/null +++ b/core/src/triggers/changerace.c @@ -0,0 +1,116 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "changerace.h" + +/* kernel includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* ansi includes */ +#include +#include +#include + +#include +/*** + ** restore a mage that was turned into a toad + **/ + +typedef struct changerace_data { + struct unit *u; + const struct race *race; + const struct race *irace; +} changerace_data; + +static void changerace_init(trigger * t) +{ + t->data.v = calloc(sizeof(changerace_data), 1); +} + +static void changerace_free(trigger * t) +{ + free(t->data.v); +} + +static int changerace_handle(trigger * t, void *data) +{ + /* call an event handler on changerace. + * data.v -> ( variant event, int timer ) + */ + changerace_data *td = (changerace_data *) t->data.v; + if (td->u) { + if (td->race != NULL) + u_setrace(td->u, td->race); + if (td->irace != NULL) + td->u->irace = td->irace; + } else { + log_error("could not perform changerace::handle()\n"); + } + unused(data); + return 0; +} + +static void changerace_write(const trigger * t, struct storage *store) +{ + changerace_data *td = (changerace_data *) t->data.v; + write_unit_reference(td->u, store); + write_race_reference(td->race, store); + write_race_reference(td->irace, store); +} + +static int changerace_read(trigger * t, struct storage *store) +{ + changerace_data *td = (changerace_data *) t->data.v; + read_reference(&td->u, store, read_unit_reference, resolve_unit); + td->race = (const struct race *)read_race_reference(store).v; + td->irace = (const struct race *)read_race_reference(store).v; + return AT_READ_OK; +} + +trigger_type tt_changerace = { + "changerace", + changerace_init, + changerace_free, + changerace_handle, + changerace_write, + changerace_read +}; + +trigger *trigger_changerace(struct unit * u, const struct race * prace, + const struct race * irace) +{ + trigger *t = t_new(&tt_changerace); + changerace_data *td = (changerace_data *) t->data.v; + + assert(u_race(u) == u_irace(u) || "!changerace-triggers cannot stack!"); + td->u = u; + td->race = prace; + td->irace = irace; + return t; +} diff --git a/core/src/triggers/changerace.h b/core/src/triggers/changerace.h new file mode 100644 index 000000000..b04b1a3e1 --- /dev/null +++ b/core/src/triggers/changerace.h @@ -0,0 +1,38 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef CHANGERACE_H +#define CHANGERACE_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + struct unit; + + extern struct trigger_type tt_changerace; + + extern struct trigger *trigger_changerace(struct unit *u, + const struct race *urace, const struct race *irace); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/clonedied.c b/core/src/triggers/clonedied.c new file mode 100644 index 000000000..665379ae7 --- /dev/null +++ b/core/src/triggers/clonedied.c @@ -0,0 +1,92 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "clonedied.h" + +/* kernel includes */ +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +/** + clonedied. + + This trigger ist called when a clone of a mage dies. + It simply removes the clone-attribute from the mage. + */ + +static int clonedied_handle(trigger * t, void *data) +{ + /* destroy the unit */ + unit *u = (unit *) t->data.v; + if (u) { + attrib *a = a_find(u->attribs, &at_clone); + if (a) + a_remove(&u->attribs, a); + } else + log_error("could not perform clonedied::handle()\n"); + unused(data); + return 0; +} + +static void clonedied_write(const trigger * t, struct storage *store) +{ + unit *u = (unit *) t->data.v; + write_unit_reference(u, store); +} + +static int clonedied_read(trigger * t, struct storage *store) +{ + int result = + read_reference(&t->data.v, store, read_unit_reference, resolve_unit); + if (result == 0 && t->data.v == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +trigger_type tt_clonedied = { + "clonedied", + NULL, + NULL, + clonedied_handle, + clonedied_write, + clonedied_read +}; + +trigger *trigger_clonedied(unit * u) +{ + trigger *t = t_new(&tt_clonedied); + t->data.v = (void *)u; + return t; +} diff --git a/core/src/triggers/clonedied.h b/core/src/triggers/clonedied.h new file mode 100644 index 000000000..51b846057 --- /dev/null +++ b/core/src/triggers/clonedied.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef CLONEDIED_H +#define CLONEDIED_H +#ifdef __cplusplus +extern "C" { +#endif + + struct trigger_type; + struct trigger; + + struct unit; + + extern struct trigger_type tt_clonedied; + extern struct trigger *trigger_clonedied(struct unit *u); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/createcurse.c b/core/src/triggers/createcurse.c new file mode 100644 index 000000000..01135f588 --- /dev/null +++ b/core/src/triggers/createcurse.c @@ -0,0 +1,149 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "createcurse.h" + +/* kernel includes */ +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* ansi includes */ +#include +#include +#include + +#include +/*** + ** restore a mage that was turned into a toad + **/ + +typedef struct createcurse_data { + struct unit *mage; + struct unit *target; + const curse_type *type; + double vigour; + int duration; + double effect; + int men; +} createcurse_data; + +static void createcurse_init(trigger * t) +{ + t->data.v = calloc(sizeof(createcurse_data), 1); +} + +static void createcurse_free(trigger * t) +{ + free(t->data.v); +} + +static int createcurse_handle(trigger * t, void *data) +{ + /* call an event handler on createcurse. + * data.v -> ( variant event, int timer ) + */ + createcurse_data *td = (createcurse_data *) t->data.v; + if (td->mage && td->target && td->mage->number && td->target->number) { + create_curse(td->mage, &td->target->attribs, + td->type, td->vigour, td->duration, td->effect, td->men); + } else { + log_error("could not perform createcurse::handle()\n"); + } + unused(data); + return 0; +} + +static void createcurse_write(const trigger * t, struct storage *store) +{ + createcurse_data *td = (createcurse_data *) t->data.v; + write_unit_reference(td->mage, store); + write_unit_reference(td->target, store); + store->w_tok(store, td->type->cname); + store->w_flt(store, (float)td->vigour); + store->w_int(store, td->duration); + store->w_flt(store, (float)td->effect); + store->w_int(store, td->men); +} + +static int createcurse_read(trigger * t, struct storage *store) +{ + createcurse_data *td = (createcurse_data *) t->data.v; + char zText[128]; + + read_reference(&td->mage, store, read_unit_reference, resolve_unit); + read_reference(&td->target, store, read_unit_reference, resolve_unit); + + if (store->version < CURSETYPE_VERSION) { + int id1, id2; + id1 = store->r_int(store); + id2 = store->r_int(store); + assert(id2 == 0); + td->vigour = store->r_flt(store); + td->duration = store->r_int(store); + td->effect = store->r_int(store); + td->men = store->r_int(store); + td->type = ct_find(oldcursename(id1)); + } else { + store->r_tok_buf(store, zText, sizeof(zText)); + td->type = ct_find(zText); + td->vigour = store->r_flt(store); + td->duration = store->r_int(store); + if (store->version < CURSEFLOAT_VERSION) { + td->effect = (double)store->r_int(store); + } else { + td->effect = store->r_flt(store); + } + td->men = store->r_int(store); + } + return AT_READ_OK; +} + +trigger_type tt_createcurse = { + "createcurse", + createcurse_init, + createcurse_free, + createcurse_handle, + createcurse_write, + createcurse_read +}; + +trigger *trigger_createcurse(struct unit * mage, struct unit * target, + const curse_type * ct, double vigour, int duration, double effect, int men) +{ + trigger *t = t_new(&tt_createcurse); + createcurse_data *td = (createcurse_data *) t->data.v; + td->mage = mage; + td->target = target; + td->type = ct; + td->vigour = vigour; + td->duration = duration; + td->effect = effect; + td->men = men; + return t; +} diff --git a/core/src/triggers/createcurse.h b/core/src/triggers/createcurse.h new file mode 100644 index 000000000..eaa3b35e6 --- /dev/null +++ b/core/src/triggers/createcurse.h @@ -0,0 +1,42 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef CREATECURSE_H +#define CREATECURSE_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct curse_type; + struct trigger_type; + struct trigger; + struct region; + struct faction; + struct unit; + + extern struct trigger_type tt_createcurse; + + extern struct trigger *trigger_createcurse(struct unit *mage, + struct unit *target, const struct curse_type *ct, double vigour, + int duration, double effect, int men); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/createunit.c b/core/src/triggers/createunit.c new file mode 100644 index 000000000..45671735a --- /dev/null +++ b/core/src/triggers/createunit.c @@ -0,0 +1,128 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "createunit.h" + +/* kernel includes */ +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* ansi includes */ +#include +#include +#include + +#include +/*** + ** restore a mage that was turned into a toad + **/ + +typedef struct createunit_data { + struct region *r; + struct faction *f; + const struct race *race; + int number; +} createunit_data; + +static void createunit_init(trigger * t) +{ + t->data.v = calloc(sizeof(createunit_data), 1); +} + +static void createunit_free(trigger * t) +{ + free(t->data.v); +} + +static int createunit_handle(trigger * t, void *data) +{ + /* call an event handler on createunit. + * data.v -> ( variant event, int timer ) + */ + createunit_data *td = (createunit_data *) t->data.v; + if (td->r != NULL && td->f != NULL) { + create_unit(td->r, td->f, td->number, td->race, 0, NULL, NULL); + } else { + log_error("could not perform createunit::handle()\n"); + } + unused(data); + return 0; +} + +static void createunit_write(const trigger * t, struct storage *store) +{ + createunit_data *td = (createunit_data *) t->data.v; + write_faction_reference(td->f, store); + write_region_reference(td->r, store); + write_race_reference(td->race, store); + store->w_int(store, td->number); +} + +static int createunit_read(trigger * t, struct storage *store) +{ + createunit_data *td = (createunit_data *) t->data.v; + + int uc = + read_reference(&td->f, store, read_faction_reference, resolve_faction); + int rc = + read_reference(&td->r, store, read_region_reference, + RESOLVE_REGION(store->version)); + td->race = (const struct race *)read_race_reference(store).v; + + if (uc == 0 && rc == 0) { + if (!td->f || !td->r) + return AT_READ_FAIL; + } + td->number = store->r_int(store); + + return AT_READ_OK; +} + +trigger_type tt_createunit = { + "createunit", + createunit_init, + createunit_free, + createunit_handle, + createunit_write, + createunit_read +}; + +trigger *trigger_createunit(region * r, struct faction * f, + const struct race * rc, int number) +{ + trigger *t = t_new(&tt_createunit); + createunit_data *td = (createunit_data *) t->data.v; + td->r = r; + td->f = f; + td->race = rc; + td->number = number; + return t; +} diff --git a/core/src/triggers/createunit.h b/core/src/triggers/createunit.h new file mode 100644 index 000000000..bd9d02c71 --- /dev/null +++ b/core/src/triggers/createunit.h @@ -0,0 +1,40 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef CREATEUNIT_H +#define CREATEUNIT_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + struct region; + struct faction; + struct unit; + + extern struct trigger_type tt_createunit; + + extern struct trigger *trigger_createunit(struct region *r, struct faction *f, + const struct race *rc, int number); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/gate.c b/core/src/triggers/gate.c new file mode 100644 index 000000000..4859d929c --- /dev/null +++ b/core/src/triggers/gate.c @@ -0,0 +1,116 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#include +#include +#include "gate.h" + +/* kernel includes */ +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include + +/* libc includes */ +#include + +typedef struct gate_data { + struct building *gate; + struct region *target; +} gate_data; + +static int gate_handle(trigger * t, void *data) +{ + /* call an event handler on gate. + * data.v -> ( variant event, int timer ) + */ + gate_data *gd = (gate_data *) t->data.v; + struct building *b = gd->gate; + struct region *r = gd->target; + + if (b && b->region && r) { + unit **up = &b->region->units; + while (*up) { + unit *u = *up; + if (u->building == b) + move_unit(u, r, NULL); + if (*up == u) + up = &u->next; + } + } else { + log_error("could not perform gate::handle()\n"); + return -1; + } + unused(data); + return 0; +} + +static void gate_write(const trigger * t, struct storage *store) +{ + gate_data *gd = (gate_data *) t->data.v; + building *b = gd->gate; + region *r = gd->target; + + write_building_reference(b, store); + write_region_reference(r, store); +} + +static int gate_read(trigger * t, struct storage *store) +{ + gate_data *gd = (gate_data *) t->data.v; + + int bc = + read_reference(&gd->gate, store, read_building_reference, resolve_building); + int rc = + read_reference(&gd->target, store, read_region_reference, + RESOLVE_REGION(store->version)); + + if (bc == 0 && rc == 0) { + if (!gd->gate || !gd->target) + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +static void gate_init(trigger * t) +{ + t->data.v = calloc(sizeof(gate_data), 1); +} + +static void gate_done(trigger * t) +{ + free(t->data.v); +} + +struct trigger_type tt_gate = { + "gate", + gate_init, + gate_done, + gate_handle, + gate_write, + gate_read +}; + +trigger *trigger_gate(building * b, region * target) +{ + trigger *t = t_new(&tt_gate); + gate_data *td = (gate_data *) t->data.v; + td->gate = b; + td->target = target; + return t; +} diff --git a/core/src/triggers/gate.h b/core/src/triggers/gate.h new file mode 100644 index 000000000..6e80effba --- /dev/null +++ b/core/src/triggers/gate.h @@ -0,0 +1,32 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef GATE_H +#define GATE_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + struct region; + struct building; + + extern struct trigger_type tt_gate; + + extern struct trigger *trigger_gate(struct building *b, struct region *r); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/giveitem.c b/core/src/triggers/giveitem.c new file mode 100644 index 000000000..3cdefd63e --- /dev/null +++ b/core/src/triggers/giveitem.c @@ -0,0 +1,118 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "giveitem.h" + +/* kernel includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* ansi includes */ +#include +#include +#include + +/*** + ** give an item to someone + **/ + +typedef struct giveitem_data { + struct unit *u; + const struct item_type *itype; + int number; +} giveitem_data; + +static void giveitem_init(trigger * t) +{ + t->data.v = calloc(sizeof(giveitem_data), 1); +} + +static void giveitem_free(trigger * t) +{ + free(t->data.v); +} + +static int giveitem_handle(trigger * t, void *data) +{ + /* call an event handler on giveitem. + * data.v -> ( variant event, int timer ) + */ + giveitem_data *td = (giveitem_data *) t->data.v; + if (td->u && td->u->number) { + i_change(&td->u->items, td->itype, td->number); + } else { + log_error("could not perform giveitem::handle()\n"); + } + unused(data); + return 0; +} + +static void giveitem_write(const trigger * t, struct storage *store) +{ + giveitem_data *td = (giveitem_data *) t->data.v; + write_unit_reference(td->u, store); + store->w_int(store, td->number); + store->w_tok(store, td->itype->rtype->_name[0]); +} + +static int giveitem_read(trigger * t, struct storage *store) +{ + giveitem_data *td = (giveitem_data *) t->data.v; + char zText[128]; + + int result = read_reference(&td->u, store, read_unit_reference, resolve_unit); + + td->number = store->r_int(store); + store->r_tok_buf(store, zText, sizeof(zText)); + td->itype = it_find(zText); + assert(td->itype); + + if (result == 0 && td->u == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +trigger_type tt_giveitem = { + "giveitem", + giveitem_init, + giveitem_free, + giveitem_handle, + giveitem_write, + giveitem_read +}; + +trigger *trigger_giveitem(unit * u, const item_type * itype, int number) +{ + trigger *t = t_new(&tt_giveitem); + giveitem_data *td = (giveitem_data *) t->data.v; + td->number = number; + td->u = u; + td->itype = itype; + return t; +} diff --git a/core/src/triggers/giveitem.h b/core/src/triggers/giveitem.h new file mode 100644 index 000000000..0bf1f3293 --- /dev/null +++ b/core/src/triggers/giveitem.h @@ -0,0 +1,39 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef GIVEITEM_H +#define GIVEITEM_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + struct unit; + struct item_type; + + extern struct trigger_type tt_giveitem; + + extern struct trigger *trigger_giveitem(struct unit *mage, + const struct item_type *itype, int number); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/killunit.c b/core/src/triggers/killunit.c new file mode 100644 index 000000000..aa24982de --- /dev/null +++ b/core/src/triggers/killunit.c @@ -0,0 +1,84 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "killunit.h" + +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +#include +#include +/*** + ** killunit + **/ + +static int killunit_handle(trigger * t, void *data) +{ + /* call an event handler on killunit. + * data.v -> ( variant event, int timer ) + */ + unit *u = (unit *) t->data.v; + if (u) { + /* we can't remove_unit() here, because that's what's calling us. */ + set_number(u, 0); + } + unused(data); + return 0; +} + +static void killunit_write(const trigger * t, struct storage *store) +{ + unit *u = (unit *) t->data.v; + write_unit_reference(u, store); +} + +static int killunit_read(trigger * t, struct storage *store) +{ + int result = + read_reference(&t->data.v, store, read_unit_reference, resolve_unit); + if (result == 0 && t->data.v == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +trigger_type tt_killunit = { + "killunit", + NULL, + NULL, + killunit_handle, + killunit_write, + killunit_read +}; + +trigger *trigger_killunit(unit * u) +{ + trigger *t = t_new(&tt_killunit); + t->data.v = (void *)u; + return t; +} diff --git a/core/src/triggers/killunit.h b/core/src/triggers/killunit.h new file mode 100644 index 000000000..548e85093 --- /dev/null +++ b/core/src/triggers/killunit.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef KILLUNIT_H +#define KILLUNIT_H +#ifdef __cplusplus +extern "C" { +#endif + + struct trigger_type; + struct trigger; + + struct unit; + + extern struct trigger_type tt_killunit; + extern struct trigger *trigger_killunit(struct unit *u); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/removecurse.c b/core/src/triggers/removecurse.c new file mode 100644 index 000000000..80eb6c3c6 --- /dev/null +++ b/core/src/triggers/removecurse.c @@ -0,0 +1,107 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "removecurse.h" + +/* kernel includes */ +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* ansi includes */ +#include +#include +#include + +#include + +typedef struct removecurse_data { + curse *curse; + unit *target; +} removecurse_data; + +static void removecurse_init(trigger * t) +{ + t->data.v = calloc(sizeof(removecurse_data), 1); +} + +static void removecurse_free(trigger * t) +{ + free(t->data.v); +} + +static int removecurse_handle(trigger * t, void *data) +{ + /* call an event handler on removecurse. + * data.v -> ( variant event, int timer ) + */ + removecurse_data *td = (removecurse_data *) t->data.v; + if (td->curse && td->target) { + attrib *a = a_select(td->target->attribs, td->curse, cmp_curse); + if (a) { + a_remove(&td->target->attribs, a); + } else + log_error("could not perform removecurse::handle()\n"); + } + unused(data); + return 0; +} + +static void removecurse_write(const trigger * t, struct storage *store) +{ + removecurse_data *td = (removecurse_data *) t->data.v; + store->w_tok(store, td->target ? itoa36(td->target->no) : 0); + store->w_int(store, td->curse ? td->curse->no : 0); +} + +static int removecurse_read(trigger * t, struct storage *store) +{ + removecurse_data *td = (removecurse_data *) t->data.v; + + read_reference(&td->target, store, read_unit_reference, resolve_unit); + read_reference(&td->curse, store, read_int, resolve_curse); + + return AT_READ_OK; +} + +trigger_type tt_removecurse = { + "removecurse", + removecurse_init, + removecurse_free, + removecurse_handle, + removecurse_write, + removecurse_read +}; + +trigger *trigger_removecurse(curse * c, unit * target) +{ + trigger *t = t_new(&tt_removecurse); + removecurse_data *td = (removecurse_data *) t->data.v; + td->curse = c; + td->target = target; + return t; +} diff --git a/core/src/triggers/removecurse.h b/core/src/triggers/removecurse.h new file mode 100644 index 000000000..e531d65a6 --- /dev/null +++ b/core/src/triggers/removecurse.h @@ -0,0 +1,40 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef REMOVECURSE_H +#define REMOVECURSE_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + + struct unit; + struct curse; + + extern struct trigger_type tt_removecurse; + + extern struct trigger *trigger_removecurse(struct curse *c, + struct unit *target); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/shock.c b/core/src/triggers/shock.c new file mode 100644 index 000000000..ec55ab7ff --- /dev/null +++ b/core/src/triggers/shock.c @@ -0,0 +1,145 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "shock.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +/*** + ** shock + **/ + +/* ------------------------------------------------------------- */ +/* do_shock - Schockt die Einheit, z.B. bei Verlust eines */ +/* Vertrauten. */ +/* ------------------------------------------------------------- */ + +static void do_shock(unit * u, const char *reason) +{ + int i; + + if (u->number > 0) { + /* HP - Verlust */ + u->hp = (unit_max_hp(u) * u->number) / 10; + u->hp = MAX(1, u->hp); + } + + /* Aura - Verlust */ + if (is_mage(u)) { + set_spellpoints(u, max_spellpoints(u->region, u) / 10); + } + + /* Evt. Talenttageverlust */ + for (i = 0; i != u->skill_size; ++i) + if (rng_int() % 5 == 0) { + skill *sv = u->skills + i; + int weeks = (sv->level * sv->level - sv->level) / 2; + int change = (weeks + 9) / 10; + reduce_skill(u, sv, change); + } + + /* Dies ist ein Hack, um das skillmod und familiar-Attribut beim Mage + * zu löschen wenn der Familiar getötet wird. Da sollten wir über eine + * saubere Implementation nachdenken. */ + + if (strcmp(reason, "trigger") == 0) { + remove_familiar(u); + } + if (u->faction != NULL) { + ADDMSG(&u->faction->msgs, msg_message("shock", + "mage reason", u, strdup(reason))); + } +} + +static int shock_handle(trigger * t, void *data) +{ + /* destroy the unit */ + unit *u = (unit *) t->data.v; + if (u && u->number) { + do_shock(u, "trigger"); + } + unused(data); + return 0; +} + +static void shock_write(const trigger * t, struct storage *store) +{ + unit *u = (unit *) t->data.v; + trigger *next = t->next; + while (next) { + /* make sure it is unique! */ + if (next->type == t->type && next->data.v == t->data.v) + break; + next = next->next; + } + if (next && u) { + log_error("more than one shock-attribut for %s on a unit. FIXED.\n", unitid(u)); + write_unit_reference(NULL, store); + } else { + write_unit_reference(u, store); + } +} + +static int shock_read(trigger * t, struct storage *store) +{ + int result = + read_reference(&t->data.v, store, read_unit_reference, resolve_unit); + if (result == 0 && t->data.v == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +trigger_type tt_shock = { + "shock", + NULL, + NULL, + shock_handle, + shock_write, + shock_read +}; + +trigger *trigger_shock(unit * u) +{ + trigger *t = t_new(&tt_shock); + t->data.v = (void *)u; + return t; +} diff --git a/core/src/triggers/shock.h b/core/src/triggers/shock.h new file mode 100644 index 000000000..2c0e4fb27 --- /dev/null +++ b/core/src/triggers/shock.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_TRG_SHOCK_H +#define H_TRG_SHOCK_H +#ifdef __cplusplus +extern "C" { +#endif + + struct trigger_type; + struct trigger; + + struct unit; + + extern struct trigger_type tt_shock; + extern struct trigger *trigger_shock(struct unit *u); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/timeout.c b/core/src/triggers/timeout.c new file mode 100644 index 000000000..65a1dc10f --- /dev/null +++ b/core/src/triggers/timeout.c @@ -0,0 +1,108 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "timeout.h" + +/* util includes */ +#include +#include +#include +#include + +#include +#include +/*** + ** timeout + **/ + +typedef struct timeout_data { + trigger *triggers; + int timer; + variant trigger_data; +} timeout_data; + +static void timeout_init(trigger * t) +{ + t->data.v = calloc(sizeof(timeout_data), 1); +} + +static void timeout_free(trigger * t) +{ + timeout_data *td = (timeout_data *) t->data.v; + free_triggers(td->triggers); + free(t->data.v); +} + +static int timeout_handle(trigger * t, void *data) +{ + /* call an event handler on timeout. + * data.v -> ( variant event, int timer ) + */ + timeout_data *td = (timeout_data *) t->data.v; + if (--td->timer == 0) { + handle_triggers(&td->triggers, NULL); + return -1; + } + unused(data); + return 0; +} + +static void timeout_write(const trigger * t, struct storage *store) +{ + timeout_data *td = (timeout_data *) t->data.v; + store->w_int(store, td->timer); + write_triggers(store, td->triggers); +} + +static int timeout_read(trigger * t, struct storage *store) +{ + timeout_data *td = (timeout_data *) t->data.v; + td->timer = store->r_int(store); + read_triggers(store, &td->triggers); + if (td->timer > 20) { + trigger *tr = td->triggers; + log_warning("there is a timeout lasting for another %d turns\n", td->timer); + while (tr) { + log_warning(" timeout triggers: %s\n", tr->type->name); + tr = tr->next; + } + } + if (td->triggers != NULL && td->timer > 0) + return AT_READ_OK; + return AT_READ_FAIL; +} + +trigger_type tt_timeout = { + "timeout", + timeout_init, + timeout_free, + timeout_handle, + timeout_write, + timeout_read +}; + +trigger *trigger_timeout(int time, trigger * callbacks) +{ + trigger *t = t_new(&tt_timeout); + timeout_data *td = (timeout_data *) t->data.v; + td->triggers = callbacks; + td->timer = time; + return t; +} diff --git a/core/src/triggers/timeout.h b/core/src/triggers/timeout.h new file mode 100644 index 000000000..4dc7ab07e --- /dev/null +++ b/core/src/triggers/timeout.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_TRG_TIMEOUT_H +#define H_TRG_TIMEOUT_H +#ifdef __cplusplus +extern "C" { +#endif + + struct trigger_type; + struct trigger; + + extern struct trigger_type tt_timeout; + + extern struct trigger *trigger_timeout(int time, struct trigger *callbacks); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/triggers.c b/core/src/triggers/triggers.c new file mode 100644 index 000000000..ac9ccca95 --- /dev/null +++ b/core/src/triggers/triggers.c @@ -0,0 +1,60 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include + +/* triggers includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include + +/* libc includes */ +#include + +void register_triggers(void) +{ + if (verbosity >= 2) + printf("- registering triggers\n"); + tt_register(&tt_changefaction); + tt_register(&tt_changerace); + tt_register(&tt_createcurse); + tt_register(&tt_createunit); + tt_register(&tt_gate); + tt_register(&tt_unguard); + tt_register(&tt_giveitem); + tt_register(&tt_killunit); + tt_register(&tt_removecurse); + tt_register(&tt_shock); + tt_register(&tt_unitmessage); + tt_register(&tt_timeout); + tt_register(&tt_clonedied); +} diff --git a/core/src/triggers/triggers.h b/core/src/triggers/triggers.h new file mode 100644 index 000000000..c17f75d71 --- /dev/null +++ b/core/src/triggers/triggers.h @@ -0,0 +1,30 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_TRG_TRIGGERS +#define H_TRG_TRIGGERS +#ifdef __cplusplus +extern "C" { +#endif + + void register_triggers(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/unguard.c b/core/src/triggers/unguard.c new file mode 100644 index 000000000..5cdaf63d5 --- /dev/null +++ b/core/src/triggers/unguard.c @@ -0,0 +1,75 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#include +#include +#include "unguard.h" + +/* kernel includes */ +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include + +/* libc includes */ +#include + +static int unguard_handle(trigger * t, void *data) +{ + building *b = (building *) t->data.v; + + if (b) { + fset(b, BLD_UNGUARDED); + } else { + log_error("could not perform unguard::handle()\n"); + return -1; + } + unused(data); + return 0; +} + +static void unguard_write(const trigger * t, struct storage *store) +{ + write_building_reference((building *) t->data.v, store); +} + +static int unguard_read(trigger * t, struct storage *store) +{ + int rb = + read_reference(&t->data.v, store, read_building_reference, + resolve_building); + if (rb == 0 && !t->data.v) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +struct trigger_type tt_unguard = { + "building", + NULL, + NULL, + unguard_handle, + unguard_write, + unguard_read +}; + +trigger *trigger_unguard(building * b) +{ + trigger *t = t_new(&tt_unguard); + t->data.v = (void *)b; + return t; +} diff --git a/core/src/triggers/unguard.h b/core/src/triggers/unguard.h new file mode 100644 index 000000000..691ea761b --- /dev/null +++ b/core/src/triggers/unguard.h @@ -0,0 +1,32 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef UNGUARD_H +#define UNGUARD_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + struct region; + struct building; + + extern struct trigger_type tt_unguard; + + extern struct trigger *trigger_unguard(struct building *b); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/triggers/unitmessage.c b/core/src/triggers/unitmessage.c new file mode 100644 index 000000000..6c26fe6a5 --- /dev/null +++ b/core/src/triggers/unitmessage.c @@ -0,0 +1,124 @@ +/* vi: set ts=2: ++-------------------+ Enno Rehling +| Eressea PBEM host | Christian Schlittchen +| (c) 1998 - 2008 | Katja Zedel ++-------------------+ +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ + +#include +#include +#include "unitmessage.h" + +/* kernel includes */ +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* ansi includes */ +#include +#include +#include +#include + +/*** +** give an item to someone +**/ + +typedef struct unitmessage_data { + struct unit *target; + char *string; + int type; + int level; +} unitmessage_data; + +static void unitmessage_init(trigger * t) +{ + t->data.v = calloc(sizeof(unitmessage_data), 1); +} + +static void unitmessage_free(trigger * t) +{ + unitmessage_data *sd = (unitmessage_data *) t->data.v; + free(sd->string); + free(t->data.v); +} + +static int unitmessage_handle(trigger * t, void *data) +{ + /* call an event handler on unitmessage. + * data.v -> ( variant event, int timer ) + */ + unitmessage_data *td = (unitmessage_data *) t->data.v; + if (td->target && td->target->no) { + struct faction *f = td->target->faction; + const char * str = LOC(f->locale, td->string); + /* bug found in turn 733: sometimes, alps have f*cked up messages */ + if (td->string && td->string[0]) { + addmessage(td->target->region, f, str, td->type, + td->level); + } + } + unused(data); + return 0; +} + +static void unitmessage_write(const trigger * t, struct storage *store) +{ + unitmessage_data *td = (unitmessage_data *) t->data.v; + write_unit_reference(td->target, store); + store->w_tok(store, td->string); + store->w_int(store, td->type); + store->w_int(store, td->level); +} + +static int unitmessage_read(trigger * t, struct storage *store) +{ + unitmessage_data *td = (unitmessage_data *) t->data.v; + char zText[256]; + + int result = + read_reference(&td->target, store, read_unit_reference, resolve_unit); + + td->string = store->r_tok(store); + td->type = store->r_int(store); + td->level = store->r_int(store); + td->string = strdup(zText); + + if (result == 0 && td->target == NULL) { + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +trigger_type tt_unitmessage = { + "unitmessage", + unitmessage_init, + unitmessage_free, + unitmessage_handle, + unitmessage_write, + unitmessage_read +}; + +trigger *trigger_unitmessage(unit * target, const char *string, int type, + int level) +{ + trigger *t = t_new(&tt_unitmessage); + unitmessage_data *td = (unitmessage_data *) t->data.v; + td->target = target; + td->string = strdup(string); + td->type = type; + td->level = level; + return t; +} diff --git a/core/src/triggers/unitmessage.h b/core/src/triggers/unitmessage.h new file mode 100644 index 000000000..054a65b65 --- /dev/null +++ b/core/src/triggers/unitmessage.h @@ -0,0 +1,37 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef UNITMESSAGE_H +#define UNITMESSAGE_H +#ifdef __cplusplus +extern "C" { +#endif + +/* all types we use are defined here to reduce dependencies */ + struct trigger_type; + struct trigger; + struct unit; + + extern struct trigger_type tt_unitmessage; + extern struct trigger *trigger_unitmessage(struct unit *target, + const char *string, int type, int level); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/attrib.c b/core/src/util/attrib.c new file mode 100644 index 000000000..ef7674534 --- /dev/null +++ b/core/src/util/attrib.c @@ -0,0 +1,350 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "attrib.h" + +#include "log.h" +#include "storage.h" + +#include + +#include +#include +#include + +#define MAXATHASH 61 +attrib_type *at_hash[MAXATHASH]; + +static unsigned int __at_hashkey(const char *s) +{ + int key = 0; + size_t i = strlen(s); + + while (i > 0) { + key = (s[--i] + key * 37); + } + return key & 0x7fffffff; +} + +void at_register(attrib_type * at) +{ + attrib_type *find; + + if (at->read == NULL) { + log_warning("registering non-persistent attribute %s.\n", at->name); + } + at->hashkey = __at_hashkey(at->name); + find = at_hash[at->hashkey % MAXATHASH]; + while (find && at->hashkey != find->hashkey) + find = find->nexthash; + if (find && find == at) { + log_warning("attribute '%s' was registered more than once\n", at->name); + return; + } else { + assert(!find || !"hashkey is already in use"); + } + at->nexthash = at_hash[at->hashkey % MAXATHASH]; + at_hash[at->hashkey % MAXATHASH] = at; +} + +static attrib_type *at_find(unsigned int hk) +{ + const char *translate[3][2] = { + {"zielregion", "targetregion"}, /* remapping: from 'zielregion, heute targetregion */ + {"verzaubert", "curse"}, /* remapping: früher verzaubert, jetzt curse */ + {NULL, NULL} + }; + attrib_type *find = at_hash[hk % MAXATHASH]; + while (find && hk != find->hashkey) + find = find->nexthash; + if (!find) { + int i = 0; + while (translate[i][0]) { + if (__at_hashkey(translate[i][0]) == hk) + return at_find(__at_hashkey(translate[i][1])); + ++i; + } + } + return find; +} + +attrib *a_select(attrib * a, const void *data, + bool(*compare) (const attrib *, const void *)) +{ + while (a && !compare(a, data)) + a = a->next; + return a; +} + +attrib *a_find(attrib * a, const attrib_type * at) +{ + while (a && a->type != at) + a = a->nexttype; + return a; +} + +const attrib *a_findc(const attrib * a, const attrib_type * at) +{ + while (a && a->type != at) + a = a->nexttype; + return a; +} + +static attrib *a_insert(attrib * head, attrib * a) +{ + attrib **pa = &head->next; + + assert(!(a->type->flags & ATF_UNIQUE)); + assert(head && head->type == a->type); + + while (*pa && (*pa)->type == a->type) { + pa = &(*pa)->next; + } + a->next = *pa; + return *pa = a; +} + +attrib *a_add(attrib ** pa, attrib * a) +{ + attrib *first = *pa; + assert(a->next == NULL && a->nexttype == NULL); + + if (first == NULL) + return *pa = a; + if (first->type == a->type) { + return a_insert(first, a); + } + for (;;) { + attrib *next = first->nexttype; + if (next == NULL) { + /* the type is not in the list, append it behind the last type */ + attrib **insert = &first->next; + first->nexttype = a; + while (*insert) + insert = &(*insert)->next; + *insert = a; + break; + } + if (next->type == a->type) { + return a_insert(next, a); + } + first = next; + } + return a; +} + +void a_free(attrib * a) +{ + const attrib_type *at = a->type; + if (at->finalize) + at->finalize(a); + free(a); +} + +static int a_unlink(attrib ** pa, attrib * a) +{ + attrib **pnexttype = pa; + attrib **pnext = NULL; + + assert(a != NULL); + while (*pnexttype) { + attrib *next = *pnexttype; + if (next->type == a->type) + break; + pnexttype = &next->nexttype; + pnext = &next->next; + } + if (*pnexttype && (*pnexttype)->type == a->type) { + if (*pnexttype == a) { + *pnexttype = a->next; + if (a->next != a->nexttype) { + a->next->nexttype = a->nexttype; + } + if (pnext == NULL) + return 1; + while (*pnext && (*pnext)->type != a->type) + pnext = &(*pnext)->next; + } else { + pnext = &(*pnexttype)->next; + } + while (*pnext && (*pnext)->type == a->type) { + if (*pnext == a) { + *pnext = a->next; + return 1; + } + pnext = &(*pnext)->next; + } + } + return 0; +} + +int a_remove(attrib ** pa, attrib * a) +{ + int ok; + assert(a != NULL); + ok = a_unlink(pa, a); + if (ok) + a_free(a); + return ok; +} + +void a_removeall(attrib ** pa, const attrib_type * at) +{ + attrib **pnexttype = pa; + attrib **pnext = NULL; + + while (*pnexttype) { + attrib *next = *pnexttype; + if (next->type == at) + break; + pnexttype = &next->nexttype; + pnext = &next->next; + } + if (*pnexttype && (*pnexttype)->type == at) { + attrib *a = *pnexttype; + + *pnexttype = a->nexttype; + if (pnext) { + while (*pnext && (*pnext)->type != at) + pnext = &(*pnext)->next; + *pnext = a->nexttype; + } + while (a && a->type == at) { + attrib *ra = a; + a = a->next; + a_free(ra); + } + } +} + +attrib *a_new(const attrib_type * at) +{ + attrib *a = (attrib *) calloc(1, sizeof(attrib)); + assert(at != NULL); + a->type = at; + if (at->initialize) + at->initialize(a); + return a; +} + +int a_age(attrib ** p) +{ + attrib **ap = p; + /* Attribute altern, und die Entfernung (age()==0) eines Attributs + * hat Einfluß auf den Besitzer */ + while (*ap) { + attrib *a = *ap; + if (a->type->age) { + int result = a->type->age(a); + assert(result >= 0 || !"age() returned a negative value"); + if (result == 0) { + a_remove(p, a); + continue; + } + } + ap = &a->next; + } + return (*p != NULL); +} + +static critbit_tree cb_deprecated = { 0 }; + +void at_deprecate(const char * name, int (*reader)(attrib *, void *, struct storage *)) +{ + char buffer[64]; + size_t len = strlen(name); + len = cb_new_kv(name, len, &reader, sizeof(reader), buffer); + cb_insert(&cb_deprecated, buffer, len); +} + +int a_read(struct storage *store, attrib ** attribs, void *owner) +{ + int key, retval = AT_READ_OK; + char zText[128]; + + zText[0] = 0; + key = -1; + store->r_tok_buf(store, zText, sizeof(zText)); + if (strcmp(zText, "end") == 0) + return retval; + else + key = __at_hashkey(zText); + + while (key != -1) { + int (*reader)(attrib *, void *, struct storage *) = 0; + attrib_type *at = at_find(key); + attrib * na = 0; + + if (at) { + reader = at->read; + na = a_new(at); + } else { + const void * kv; + cb_find_prefix(&cb_deprecated, zText, strlen(zText)+1, &kv, 1, 0); + if (kv) { + cb_get_kv(kv, &reader, sizeof(reader)); + } else { + fprintf(stderr, "attribute hash: %d (%s)\n", key, zText); + assert(at || !"attribute not registered"); + } + } + if (reader) { + int i = reader(na, owner, store); + if (na) { + switch (i) { + case AT_READ_OK: + a_add(attribs, na); + break; + case AT_READ_FAIL: + retval = AT_READ_FAIL; + a_free(na); + break; + default: + assert(!"invalid return value"); + break; + } + } + } else { + assert(!"fehler: keine laderoutine für attribut"); + } + + store->r_tok_buf(store, zText, sizeof(zText)); + if (!strcmp(zText, "end")) + break; + key = __at_hashkey(zText); + } + return retval; +} + +void a_write(struct storage *store, const attrib * attribs, const void *owner) +{ + const attrib *na = attribs; + + while (na) { + if (na->type->write) { + assert(na->type->hashkey || !"attribute not registered"); + store->w_tok(store, na->type->name); + na->type->write(na, owner, store); + na = na->next; + } else { + na = na->nexttype; + } + } + store->w_tok(store, "end"); +} diff --git a/core/src/util/attrib.h b/core/src/util/attrib.h new file mode 100644 index 000000000..6096a4cbe --- /dev/null +++ b/core/src/util/attrib.h @@ -0,0 +1,96 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef ATTRIB_H +#define ATTRIB_H +#ifdef __cplusplus +extern "C" { +#endif + + struct storage; + typedef void (*afun) (void); + + typedef struct attrib { + const struct attrib_type *type; + union { + afun f; + void *v; + int i; + float flt; + char c; + short s; + short sa[2]; + char ca[4]; + } data; + /* internal data, do not modify: */ + struct attrib *next; /* next attribute in the list */ + struct attrib *nexttype; /* skip to attribute of a different type */ + } attrib; + +#define ATF_UNIQUE (1<<0) /* only one per attribute-list */ +#define ATF_PRESERVE (1<<1) /* preserve order in list. append to back */ +#define ATF_USER_DEFINED (1<<2) /* use this to make udf */ + + typedef struct attrib_type { + const char *name; + void (*initialize) (struct attrib *); + void (*finalize) (struct attrib *); + int (*age) (struct attrib *); + /* age returns 0 if the attribute needs to be removed, !=0 otherwise */ + void (*write) (const struct attrib *, const void *owner, struct storage *); + int (*read) (struct attrib *, void *owner, struct storage *); /* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */ + unsigned int flags; + /* ---- internal data, do not modify: ---- */ + struct attrib_type *nexthash; + unsigned int hashkey; + } attrib_type; + + extern void at_register(attrib_type * at); + extern void at_deprecate(const char * name, int (*reader)(attrib *, void *, struct storage *)); + + extern attrib *a_select(attrib * a, const void *data, + bool(*compare) (const attrib *, const void *)); + extern attrib *a_find(attrib * a, const attrib_type * at); + extern const attrib *a_findc(const attrib * a, const attrib_type * at); + extern attrib *a_add(attrib ** pa, attrib * at); + extern int a_remove(attrib ** pa, attrib * at); + extern void a_removeall(attrib ** a, const attrib_type * at); + extern attrib *a_new(const attrib_type * at); + extern void a_free(attrib * a); + + extern int a_age(attrib ** attribs); + extern int a_read(struct storage *store, attrib ** attribs, void *owner); + extern void a_write(struct storage *store, const attrib * attribs, + const void *owner); + +#define DEFAULT_AGE NULL +#define DEFAULT_INIT NULL +#define DEFAULT_FINALIZE NULL +#define NO_WRITE NULL +#define NO_READ NULL + +#define AT_READ_OK 0 +#define AT_READ_FAIL -1 + +#define AT_AGE_REMOVE 0 /* remove the attribute after calling age() */ +#define AT_AGE_KEEP 1 /* keep the attribute for another turn */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/base36.c b/core/src/util/base36.c new file mode 100644 index 000000000..0ae284bc2 --- /dev/null +++ b/core/src/util/base36.c @@ -0,0 +1,118 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "base36.h" + +#include +#include +#include + +int atoi36(const char *str) +{ + /* cannot use strtol, because invalid strings will cause crash */ + const unsigned char *s = (const unsigned char *)str; + int i = 0, sign = 1; + assert(s); + if (!(*s)) + return 0; + + while (isxspace(*(unsigned char *)s)) + ++s; + if (*s == '-') { + sign = -1; + ++s; + } + while (isalnum(*(unsigned char *)s)) { + if (isupper(*(unsigned char *)s)) + i = i * 36 + (*s) - 'A' + 10; + else if (islower(*(unsigned char *)s)) + i = i * 36 + (*s) - 'a' + 10; + else if (isdigit(*(unsigned char *)s)) + i = i * 36 + (*s) - '0'; + else + break; + ++s; + } + if (i < 0) + return 0; + return i * sign; +} + +const char *itoab(int i, int base) +{ + static char **as = NULL; /* STATIC_RETURN: used for return, not across calls */ + char *s, *dst; + static int index = 0; /* STATIC_XCALL: used across calls */ + int neg = 0; + + if (!as) { + int j; + char *x = (char *)calloc(sizeof(char), 8 * 4); /* STATIC_LEAK: malloc in static variable */ + as = (char **)calloc(sizeof(char *), 4); + for (j = 0; j != 4; ++j) + as[j] = x + j * 8; + } + s = as[index]; + index = (index + 1) & 3; /* quick for % 4 */ + dst = s + 7; + (*dst--) = 0; + if (i != 0) { + if (i < 0) { + i = -i; + neg = 1; + } + while (i) { + int x = i % base; + i = i / base; + if (x < 10) + *(dst--) = (char)('0' + x); + else if ('a' + x - 10 == 'l') + *(dst--) = 'L'; + else + *(dst--) = (char)('a' + (x - 10)); + } + if (neg) + *(dst) = '-'; + else + ++dst; + } else + *dst = '0'; + + return dst; +} + +const char *itoa36(int i) +{ + return itoab(i, 36); +} + +const char *itoa10(int i) +{ + return itoab(i, 10); +} + +int i10toi36(int i) +{ + int r = 0; + while (i) { + r = r * 36 + i % 10; + i = i / 10; + } + return r; +} diff --git a/core/src/util/base36.h b/core/src/util/base36.h new file mode 100644 index 000000000..67663a821 --- /dev/null +++ b/core/src/util/base36.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_UTIL_BASE36 +#define H_UTIL_BASE36 +#ifdef __cplusplus +extern "C" { +#endif + + extern int atoi36(const char *s); + extern const char *itoab(int i, int base); + extern const char *itoa36(int i); + extern const char *itoa10(int i); + extern int i10toi36(int i); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/base36_test.c b/core/src/util/base36_test.c new file mode 100644 index 000000000..3d19f94df --- /dev/null +++ b/core/src/util/base36_test.c @@ -0,0 +1,32 @@ +#include +#include "base36.h" +#include +#include + +static void test_atoi36(CuTest * tc) +{ + CuAssertIntEquals(tc, 0, atoi36("0")); + CuAssertIntEquals(tc, 666, atoi36("ii")); + CuAssertIntEquals(tc, -10, atoi36("-a")); + CuAssertIntEquals(tc, -1, atoi36("-1")); + CuAssertIntEquals(tc, -10, atoi("-10")); + CuAssertIntEquals(tc, -10, atoi("-10")); +} + +static void test_itoa36(CuTest * tc) +{ + CuAssertStrEquals(tc, itoa36(0), "0"); + CuAssertStrEquals(tc, itoa10(INT_MAX), "2147483647"); + CuAssertStrEquals(tc, itoab(-1, 5), "-1"); + CuAssertStrEquals(tc, itoa36(-1), "-1"); + CuAssertStrEquals(tc, itoa36(-10), "-a"); + CuAssertStrEquals(tc, itoa36(666), "ii"); +} + +CuSuite *get_base36_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_itoa36); + SUITE_ADD_TEST(suite, test_atoi36); + return suite; +} diff --git a/core/src/util/bool.h b/core/src/util/bool.h new file mode 100644 index 000000000..0bdebc9ec --- /dev/null +++ b/core/src/util/bool.h @@ -0,0 +1,15 @@ +#if HAVE_STDBOOL_H +# include +#else +# if ! HAVE__BOOL +# ifdef __cplusplus +typedef bool _Bool; +# else +typedef unsigned char _Bool; +# endif +# endif +# define bool _Bool +# define false 0 +# define true 1 +# define __bool_true_false_are_defined 1 +#endif diff --git a/core/src/util/bsdstring.c b/core/src/util/bsdstring.c new file mode 100644 index 000000000..0b677525e --- /dev/null +++ b/core/src/util/bsdstring.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include + +#include "bsdstring.h" + +int wrptr(char **ptr, size_t * size, int bytes) +{ + assert(bytes >= 0 || !"you're not using snprintf right, maybe?"); + + if (bytes == 0) { + return 0; + } + if (bytes < 0) { + *size = 0; + return EINVAL; + } + if (bytes <= *(int *)size) { + *ptr += bytes; + *size -= bytes; + return 0; + } + + *ptr += *size; + *size = 0; + return ENAMETOOLONG; +} + +#ifndef HAVE_STRLCPY +#define HAVE_STRLCPY +size_t strlcpy(char *dst, const char *src, size_t siz) +{ /* copied from OpenBSD source code */ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) ; + } + + return (s - src - 1); /* count does not include NUL */ +} +#endif + +#ifndef HAVE_STRLCAT +#define HAVE_STRLCAT +size_t strlcat(char *dst, const char *src, size_t siz) +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (*d != '\0' && n-- != 0) + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return (dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return (dlen + (s - src)); /* count does not include NUL */ +} +#endif + +#ifndef HAVE_SLPRINTF +#define HAVE_SLPRINTF +size_t slprintf(char * dst, size_t size, const char * format, ...) +{ + va_list args; + int result; + + va_start(args, format); + result = vsnprintf(dst, size, format, args); + if (result<0 || result>=(int)size) { + dst[size-1]='\0'; + return size; + } + va_start(args, format); + va_end(args); + + return (size_t)result; +} +#endif diff --git a/core/src/util/bsdstring.h b/core/src/util/bsdstring.h new file mode 100644 index 000000000..91b89465d --- /dev/null +++ b/core/src/util/bsdstring.h @@ -0,0 +1,21 @@ +#ifndef UTIL_BSDSTRING_H +#define UTIL_BSDSTRING_H + +#include +extern int wrptr(char **ptr, size_t * size, int bytes); + +#ifndef HAVE_STRLCPY +extern size_t strlcpy(char *dst, const char *src, size_t siz); +#endif + +#ifndef HAVE_STRLCAT +extern size_t strlcat(char *dst, const char *src, size_t siz); +#endif + +#ifndef HAVE_SLPRINTF +extern size_t slprintf(char * dst, size_t size, const char * format, ...); +#endif + +#define WARN_STATIC_BUFFER() log_warning("static buffer too small in %s:%d\n", __FILE__, __LINE__) + +#endif diff --git a/core/src/util/bsdstring_test.c b/core/src/util/bsdstring_test.c new file mode 100644 index 000000000..0f9cdaeac --- /dev/null +++ b/core/src/util/bsdstring_test.c @@ -0,0 +1,68 @@ +#include +#include "bsdstring.h" +#include + +static void test_strlcat(CuTest * tc) +{ + char buffer[32]; + + memset(buffer, -2, sizeof(buffer)); + + buffer[0] = '\0'; + CuAssertIntEquals(tc, 4, strlcat(buffer, "herp", 4)); + CuAssertStrEquals(tc, "her", buffer); + + buffer[0] = '\0'; + CuAssertIntEquals(tc, 4, strlcat(buffer, "herp", 8)); + CuAssertStrEquals(tc, "herp", buffer); + CuAssertIntEquals(tc, (char)-2, buffer[5]); + + CuAssertIntEquals(tc, 8, strlcat(buffer, "derp", 8)); + CuAssertStrEquals(tc, "herpder", buffer); + CuAssertIntEquals(tc, (char)-2, buffer[8]); +} + +static void test_strlcpy(CuTest * tc) +{ + char buffer[32]; + + memset(buffer, -2, sizeof(buffer)); + + CuAssertIntEquals(tc, 4, strlcpy(buffer, "herp", 4)); + CuAssertStrEquals(tc, "her", buffer); + + CuAssertIntEquals(tc, 4, strlcpy(buffer, "herp", 8)); + CuAssertStrEquals(tc, "herp", buffer); + CuAssertIntEquals(tc, (char)-2, buffer[5]); + + CuAssertIntEquals(tc, 8, strlcpy(buffer, "herpderp", 8)); + CuAssertStrEquals(tc, "herpder", buffer); + CuAssertIntEquals(tc, (char)-2, buffer[8]); +} + +static void test_slprintf(CuTest * tc) +{ + char buffer[32]; + + memset(buffer, -2, sizeof(buffer)); + + CuAssertTrue(tc, slprintf(buffer, 4, "%s", "herpderp")>3); + CuAssertStrEquals(tc, "her", buffer); + + CuAssertIntEquals(tc, 4, slprintf(buffer, 8, "%s", "herp")); + CuAssertStrEquals(tc, "herp", buffer); + CuAssertIntEquals(tc, (char)-2, buffer[5]); + + CuAssertIntEquals(tc, 8, slprintf(buffer, 8, "%s", "herpderp")); + CuAssertStrEquals(tc, "herpder", buffer); + CuAssertIntEquals(tc, (char)-2, buffer[8]); +} + +CuSuite *get_bsdstring_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_strlcat); + SUITE_ADD_TEST(suite, test_strlcpy); + SUITE_ADD_TEST(suite, test_slprintf); + return suite; +} diff --git a/core/src/util/console.c b/core/src/util/console.c new file mode 100644 index 000000000..b850678c5 --- /dev/null +++ b/core/src/util/console.c @@ -0,0 +1,261 @@ +#include +#include "console.h" + +/* lua includes */ +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +/*** Lua Console ***/ +/* +** this macro defines a function to show the prompt and reads the +** next line for manual input +*/ +/* maximum length of an input line */ +#ifndef LUA_MAXINPUT +#define LUA_MAXINPUT 512 +#endif + +#if LUA_VERSION_NUM >= 502 +#define lua_strlen(L, idx) lua_rawlen(L, idx) +#endif + +#if defined(LUA_USE_READLINE) +#include +#include +#include +#define default_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) +#define lua_saveline(L,idx) \ + if (lua_strlen(L,idx) > 0) /* non-empty line? */ \ + add_history(lua_tostring(L, idx)); /* add it to history */ +#define lua_freeline(L,b) ((void)L, free(b)) +#else +#define default_readline(L,b,p) \ + ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ + fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ +#define lua_saveline(L,idx) { (void)L; (void)idx; } +#define lua_freeline(L,b) { (void)L; (void)b; } +#endif + +static int (*my_readline) (lua_State * l, char *, size_t, const char *prompt) = + NULL; +static int lua_readline(lua_State * l, char *b, const char *prompt) +{ + if (my_readline) { + return my_readline(l, b, LUA_MAXINPUT, prompt); + } else { + return default_readline(l, b, prompt); + } +} + +void set_readline(readline foo) +{ + my_readline = foo; +} + +#ifndef PROMPT +#define PROMPT "E> " +#endif + +#ifndef PROMPT2 +#define PROMPT2 ".. " +#endif + +/* BAD hack, all this action stuff. */ +#define STATESTACK_MAX 16 +static lua_State *state_stack[STATESTACK_MAX]; +int state_stack_top = -1; + +static const char *progname = "eressea"; + +static void lstop(lua_State * l, lua_Debug * ar) +{ + (void)ar; /* unused arg. */ + lua_sethook(l, NULL, 0, 0); + luaL_error(l, "interrupted!"); +} + +static void laction(int i) +{ + signal(i, SIG_DFL); /* if another SIGINT happens before lstop, + terminate process (default action) */ + assert(state_stack_top >= 0 && state_stack_top < STATESTACK_MAX); + lua_sethook(state_stack[state_stack_top], lstop, + LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); +} + +static void l_message(const char *pname, const char *msg) +{ + if (pname) + fprintf(stderr, "%s: ", pname); + fprintf(stderr, "%s\n", msg); + fflush(stderr); +} + +static int report(lua_State * L, int status) +{ + if (status && !lua_isnil(L, -1)) { + const char *msg = lua_tostring(L, -1); + if (msg == NULL) + msg = "(error object is not a string)"; + l_message(progname, msg); + lua_pop(L, 1); + } + return status; +} + +static int traceback(lua_State * L) +{ + lua_getglobal(L, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); /* pass error message */ + lua_pushinteger(L, 2); /* skip this function and traceback */ + lua_call(L, 2, 1); /* call debug.traceback */ + return 1; +} + +static int docall(lua_State * L, int narg, int clear) +{ + int status, pop_state = state_stack_top; + int base = lua_gettop(L) - narg; /* function index */ + lua_pushcfunction(L, traceback); /* push traceback function */ + lua_insert(L, base); /* put it under chunk and args */ + if (state_stack_top < 0 || L != state_stack[state_stack_top]) { + assert(state_stack_top + 1 < STATESTACK_MAX); + state_stack[++state_stack_top] = L; + } + signal(SIGINT, laction); + status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base); + signal(SIGINT, SIG_DFL); + state_stack_top = pop_state; + lua_remove(L, base); /* remove traceback function */ + /* force a complete garbage collection in case of errors */ + if (status != 0) + lua_gc(L, LUA_GCCOLLECT, 0); + return status; +} + +static const char *get_prompt(lua_State * L, int firstline) +{ + const char *p = NULL; + lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2"); + p = lua_tostring(L, -1); + if (p == NULL) + p = (firstline ? PROMPT : PROMPT2); + lua_pop(L, 1); /* remove global */ + return p; +} + +static int incomplete(lua_State * L, int status) +{ + if (status != LUA_ERRSYNTAX) + return 0; + if (strstr(lua_tostring(L, -1), "near `'") == NULL) + return 0; + + lua_pop(L, 1); + return 1; +} + +static int pushline(lua_State * L, int firstline) +{ + char buffer[LUA_MAXINPUT]; + char *b = buffer; + size_t l; + const char *prmt = get_prompt(L, firstline); + if (lua_readline(L, b, prmt) == 0) + return 0; /* no input */ + l = strlen(b); + if (l > 0 && b[l - 1] == '\n') /* line ends with newline? */ + b[l - 1] = '\0'; /* remove it */ + if (firstline && b[0] == '=') /* first line starts with `=' ? */ + lua_pushfstring(L, "return %s", b + 1); /* change it to `return' */ + else + lua_pushstring(L, b); + lua_freeline(L, b); + return 1; +} + +static int loadline(lua_State * L) +{ + int status; + lua_settop(L, 0); + if (!pushline(L, 1)) + return -1; /* no input */ + for (;;) { /* repeat until gets a complete line */ + status = luaL_loadbuffer(L, lua_tostring(L, 1), lua_strlen(L, 1), "=stdin"); + if (!incomplete(L, status)) + break; /* cannot try to add lines? */ + if (!pushline(L, 0)) /* no more input? */ + return -1; + lua_pushliteral(L, "\n"); /* add a new line... */ + lua_insert(L, -2); /* ...between the two lines */ + lua_concat(L, 3); /* join them */ + } + lua_saveline(L, 1); + lua_remove(L, 1); /* remove line */ + return status; +} + +static void dotty(lua_State * L) +{ + int status; + const char *oldprogname = progname; + progname = NULL; + while ((status = loadline(L)) != -1) { + if (status == 0) + status = docall(L, 0, 0); + report(L, status); + if (status == 0 && lua_gettop(L) > 0) { /* any result to print? */ + lua_getglobal(L, "print"); + lua_insert(L, 1); + if (lua_pcall(L, lua_gettop(L) - 1, 0, 0) != 0) + l_message(progname, lua_pushfstring(L, + "error calling " LUA_QL("print") " (%s)", lua_tostring(L, -1))); + } + } + lua_settop(L, 0); /* clear stack */ + fputs("\n", stdout); + fflush(stdout); + progname = oldprogname; +} + +int lua_console(lua_State * L) +{ + dotty(L); + return 0; +} + +int lua_do(lua_State * L) +{ + int status = loadline(L); + if (status != -1) { + if (status == 0) + status = docall(L, 0, 0); + report(L, status); + if (status == 0 && lua_gettop(L) > 0) { /* any result to print? */ + lua_getglobal(L, "print"); + lua_insert(L, 1); + if (lua_pcall(L, lua_gettop(L) - 1, 0, 0) != 0) + l_message(NULL, lua_pushfstring(L, "error calling `print' (%s)", + lua_tostring(L, -1))); + } + } + lua_settop(L, 0); /* clear stack */ + return 0; +} diff --git a/core/src/util/console.h b/core/src/util/console.h new file mode 100644 index 000000000..8afdeb0d0 --- /dev/null +++ b/core/src/util/console.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_LUA_CONSOLE +#define H_LUA_CONSOLE +#ifdef __cplusplus +extern "C" { +#endif + + struct lua_State; + + extern int lua_console(struct lua_State *L); + extern int lua_do(struct lua_State *L); + + typedef int (*readline) (struct lua_State *, char *, size_t, const char *); + extern void set_readline(readline foo); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/crmessage.c b/core/src/util/crmessage.c new file mode 100644 index 000000000..f02c8d25d --- /dev/null +++ b/core/src/util/crmessage.c @@ -0,0 +1,155 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + +*/ + +#include +#include "crmessage.h" + +#include "message.h" +#include "goodies.h" +#include "log.h" + +#include +#include +#include +#include + +/** type to string conversion **/ +typedef struct tsf_list { + struct tsf_list *next; + const char *name; + tostring_f fun; +} tsf_list; + +static tsf_list *tostringfs; + +static tostring_f tsf_find(const char *name) +{ + if (name != NULL) { + tsf_list *tsf; + for (tsf = tostringfs; tsf; tsf = tsf->next) { + if (!strcmp(tsf->name, name)) + return tsf->fun; + } + } + return NULL; +} + +void tsf_register(const char *name, tostring_f fun) +{ + tsf_list *tsf; + for (tsf = tostringfs; tsf; tsf = tsf->next) { + if (!strcmp(tsf->name, name)) + break; + } + if (tsf == NULL) { + tsf = malloc(sizeof(tsf_list)); + tsf->fun = fun; + tsf->name = name; + tsf->next = tostringfs; + tostringfs = tsf; + } +} + +/** crmesssage **/ +typedef struct crmessage_type { + const struct message_type *mtype; + tostring_f *renderers; + struct crmessage_type *next; +} crmessage_type; + +#define CRMAXHASH 63 +static crmessage_type *crtypes[CRMAXHASH]; + +static crmessage_type *crt_find(const struct message_type *mtype) +{ + unsigned int hash = hashstring(mtype->name) % CRMAXHASH; + crmessage_type *found = NULL; + crmessage_type *type = crtypes[hash]; + while (type) { + if (type->mtype == mtype) + found = type; + type = type->next; + } + return found; +} + +void crt_register(const struct message_type *mtype) +{ + unsigned int hash = hashstring(mtype->name) % CRMAXHASH; + crmessage_type *crt = crtypes[hash]; + while (crt && crt->mtype != mtype) { + crt = crt->next; + } + if (!crt) { + int i; + crt = malloc(sizeof(crmessage_type)); + crt->mtype = mtype; + crt->next = crtypes[hash]; + crtypes[hash] = crt; + if (mtype->nparameters > 0) { + crt->renderers = malloc(sizeof(tostring_f) * mtype->nparameters); + } else { + crt->renderers = NULL; + } + + /* can be scrapped for memory vs. speed */ + for (i = 0; i != mtype->nparameters; ++i) { + crt->renderers[i] = tsf_find(mtype->types[i]->name); + } + } +} + +int cr_render(const message * msg, char *buffer, const void *userdata) +{ + int i; + char *c = buffer; + struct crmessage_type *crt = crt_find(msg->type); + + if (crt == NULL) + return -1; + for (i = 0; i != msg->type->nparameters; ++i) { + if (crt->renderers[i] == NULL) { + log_error("No renderer for argument %s:%s of \"%s\"\n", msg->type->pnames[i], msg->type->types[i]->name, msg->type->name); + continue; /* strcpy(c, (const char*)msg->locale_string(u->faction->locale, parameters[i])); */ + } else { + if (crt->renderers[i] (msg->parameters[i], c, userdata) != 0) + continue; + } + c += strlen(c); + sprintf(c, ";%s\n", msg->type->pnames[i]); + c += strlen(c); + } + return 0; +} + +int cr_string(variant var, char *buffer, const void *userdata) +{ + sprintf(buffer, "\"%s\"", (const char *)var.v); + unused(userdata); + return 0; +} + +int cr_int(variant var, char *buffer, const void *userdata) +{ + sprintf(buffer, "%d", var.i); + unused(userdata); + return 0; +} + +int cr_ignore(variant var, char *buffer, const void *userdata) +{ + unused(var); + unused(buffer); + unused(userdata); + return -1; +} diff --git a/core/src/util/crmessage.h b/core/src/util/crmessage.h new file mode 100644 index 000000000..c56e9a85c --- /dev/null +++ b/core/src/util/crmessage.h @@ -0,0 +1,40 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_UTIL_CRMESSAGE +#define H_UTIL_CRMESSAGE + +#include "variant.h" +#ifdef __cplusplus +extern "C" { +#endif + + struct locale; + struct message; + struct message_type; + + typedef int (*tostring_f) (variant data, char *buffer, const void *userdata); + extern void tsf_register(const char *name, tostring_f fun); + /* registers a new type->string-function */ + + extern int cr_string(variant v, char *buffer, const void *userdata); + extern int cr_int(variant v, char *buffer, const void *userdata); + extern int cr_ignore(variant v, char *buffer, const void *userdata); + + extern void crt_register(const struct message_type *mtype); + extern int cr_render(const struct message *msg, char *buffer, + const void *userdata); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/dice.c b/core/src/util/dice.c new file mode 100644 index 000000000..6549c33a8 --- /dev/null +++ b/core/src/util/dice.c @@ -0,0 +1,97 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998 + * Enno Rehling (rehling@usa.net) + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * + * based on: + * + * Atlantis v1.0 13 September 1993 Copyright 1993 by Russell Wallace + * Atlantis v1.7 Copyright 1996 by Alex Schröder + * + * This program may not be used, modified or distributed. It may not be + * sold or used commercially without prior written permission from the + * authors. + */ + +#include +#include "rng.h" + +#include +#include +#include + +/** rolls a number of n-sided dice. + * Usage: 3d6-3d4+5 = dice(3,6)-dice(3,4)+5 */ +int dice(int count, int value) +{ + int d = 0, c; + + if (value <= 0) + return 0; /* (enno) %0 geht nicht. echt wahr. */ + if (count >= 0) + for (c = count; c > 0; c--) + d += rng_int() % value + 1; + else + for (c = count; c < 0; c++) + d -= rng_int() % value + 1; + + return d; +} + +static int term_eval(const char **sptr) +{ + const char *c = *sptr; + int m = 0, d = 0, k = 0, term = 1, multi = 1; + int state = 1; + + for (;;) { + if (isdigit(*(unsigned char *)c)) { + k = k * 10 + (*c - '0'); + } else if (*c == '+' || *c == '-' || *c == 0 || *c == '*' || *c == ')' + || *c == '(') { + if (state == 1) /* konstante k addieren */ + m += k * multi; + else if (state == 2) { /* dDk */ + int i; + if (k == 0) + k = 6; /* 3d == 3d6 */ + for (i = 0; i != d; ++i) + m += (1 + rng_int() % k) * multi; + } else + assert(!"dice_rand: illegal token"); + if (*c == '*') { + term *= m; + m = 0; + } + k = d = 0; + state = 1; + multi = (*c == '-') ? -1 : 1; + + if (*c == '(') { + ++c; + k = term_eval(&c); + } else if (*c == 0 || *c == ')') { + break; + } + } else if (*c == 'd' || *c == 'D') { + if (k == 0) + k = 1; /* d9 == 1d9 */ + assert(state == 1 || !"dice_rand: illegal token"); + d = k; + k = 0; + state = 2; + } + c++; + } + *sptr = c; + return m * term; +} + +int dice_rand(const char *s) +{ + return term_eval(&s); +} diff --git a/core/src/util/encoding.h b/core/src/util/encoding.h new file mode 100644 index 000000000..73d1038b2 --- /dev/null +++ b/core/src/util/encoding.h @@ -0,0 +1,19 @@ +#ifndef ENCODING_H +#define ENCODING_H +#ifdef __cplusplus +extern "C" { +#endif + + enum { + ENCODING_ERROR = -1, + ENCODING_NONE = 0, + ENCODING_8859_1 = 1, + ENCODING_UTF8 = 10 + }; + + int get_encoding_by_name(const char *name); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/event.c b/core/src/util/event.c new file mode 100644 index 000000000..10df24c8c --- /dev/null +++ b/core/src/util/event.c @@ -0,0 +1,266 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "event.h" + +/* util includes */ +#include "attrib.h" +#include "log.h" +#include "storage.h" + +/* libc includes */ +#include +#include +#include +#include + +void write_triggers(struct storage *store, const trigger * t) +{ + while (t) { + if (t->type->write) { + store->w_tok(store, t->type->name); + t->type->write(t, store); + } + t = t->next; + } + store->w_tok(store, "end"); +} + +int read_triggers(struct storage *store, trigger ** tp) +{ + for (;;) { + trigger_type *ttype; + char zText[128]; + + store->r_tok_buf(store, zText, sizeof(zText)); + if (!strcmp(zText, "end")) + break; + ttype = tt_find(zText); + assert(ttype || !"unknown trigger-type"); + *tp = t_new(ttype); + if (ttype->read) { + int i = ttype->read(*tp, store); + switch (i) { + case AT_READ_OK: + tp = &(*tp)->next; + break; + case AT_READ_FAIL: + t_free(*tp); + *tp = NULL; + break; + default: + assert(!"invalid return value"); + break; + } + } + } + return 0; +} + +trigger *t_new(trigger_type * ttype) +{ + trigger *t = calloc(sizeof(trigger), 1); + t->type = ttype; + if (ttype->initialize) + ttype->initialize(t); + return t; +} + +void t_free(trigger * t) +{ + if (t->type->finalize) + t->type->finalize(t); +} + +void free_triggers(trigger * triggers) +{ + while (triggers) { + trigger *t = triggers; + triggers = t->next; + t_free(t); + } +} + +int handle_triggers(trigger ** triggers, void *param) +{ + trigger **tp = triggers; + while (*tp) { + trigger *t = *tp; + if (t->type->handle(t, param) != 0) { + *tp = t->next; + t_free(t); + } else + tp = &t->next; + } + return (*triggers != NULL); +} + +/*** + ** at_eventhandler + **/ + +typedef struct handler_info { + char *event; + trigger *triggers; +} handler_info; + +static void init_handler(attrib * a) +{ + a->data.v = calloc(sizeof(handler_info), 1); +} + +static void free_handler(attrib * a) +{ + handler_info *hi = (handler_info *) a->data.v; + free_triggers(hi->triggers); + free(hi->event); + free(hi); +} + +static void +write_handler(const attrib * a, const void *owner, struct storage *store) +{ + handler_info *hi = (handler_info *) a->data.v; + store->w_tok(store, hi->event); + write_triggers(store, hi->triggers); +} + +static int read_handler(attrib * a, void *owner, struct storage *store) +{ + char zText[128]; + handler_info *hi = (handler_info *) a->data.v; + + store->r_tok_buf(store, zText, sizeof(zText)); + hi->event = strdup(zText); + read_triggers(store, &hi->triggers); + if (hi->triggers != NULL) { + return AT_READ_OK; + } + return AT_READ_FAIL; +} + +attrib_type at_eventhandler = { + "eventhandler", + init_handler, + free_handler, + NULL, + write_handler, + read_handler +}; + +struct trigger **get_triggers(struct attrib *ap, const char *eventname) +{ + handler_info *td = NULL; + attrib *a = a_find(ap, &at_eventhandler); + while (a != NULL && a->type == &at_eventhandler) { + td = (handler_info *) a->data.v; + if (strcmp(td->event, eventname) == 0) { + return &td->triggers; + } + a = a->next; + } + return NULL; +} + +void add_trigger(struct attrib **ap, const char *eventname, struct trigger *t) +{ + trigger **tp; + handler_info *td = NULL; + attrib *a = a_find(*ap, &at_eventhandler); + assert(t->next == NULL); + while (a != NULL && a->type == &at_eventhandler) { + td = (handler_info *) a->data.v; + if (!strcmp(td->event, eventname)) { + break; + } + a = a->next; + } + if (a == NULL || a->type != &at_eventhandler) { + a = a_add(ap, a_new(&at_eventhandler)); + td = (handler_info *) a->data.v; + td->event = strdup(eventname); + } + tp = &td->triggers; + while (*tp) + tp = &(*tp)->next; + *tp = t; +} + +void handle_event(attrib * attribs, const char *eventname, void *data) +{ + while (attribs) { + if (attribs->type == &at_eventhandler) + break; + attribs = attribs->nexttype; + } + while (attribs && attribs->type == &at_eventhandler) { + handler_info *tl = (handler_info *) attribs->data.v; + if (!strcmp(tl->event, eventname)) { + handler_info *tl = (handler_info *) attribs->data.v; + handle_triggers(&tl->triggers, data); + break; + } + attribs = attribs->next; + } +} + +void t_add(struct trigger **tlist, struct trigger *t) +{ + while (*tlist) + tlist = &(*tlist)->next; + *tlist = t; +} + +static trigger_type *triggertypes; + +void tt_register(trigger_type * tt) +{ + tt->next = triggertypes; + triggertypes = tt; +} + +trigger_type *tt_find(const char *name) +{ + trigger_type *tt = triggertypes; + while (tt && strcmp(tt->name, name)) + tt = tt->next; + return tt; +} + +void +remove_triggers(struct attrib **ap, const char *eventname, + const trigger_type * tt) +{ + trigger **tp = get_triggers(*ap, eventname); + if (tp == NULL) + return; + while (*tp) { + /* first, remove all gate-triggers */ + trigger *t = *tp; + if (t->type == tt) { + *tp = t->next; + t_free(t); + } else + tp = &t->next; + } +} + +/*** + ** default events + **/ diff --git a/core/src/util/event.h b/core/src/util/event.h new file mode 100644 index 000000000..ef230219c --- /dev/null +++ b/core/src/util/event.h @@ -0,0 +1,84 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef EVENT_H +#define EVENT_H +#ifdef __cplusplus +extern "C" { +#endif + +#include "variant.h" + + struct attrib; + struct trigger; + struct storage; + + typedef struct trigger_type { + const char *name; + void (*initialize) (struct trigger *); + void (*finalize) (struct trigger *); + int (*handle) (struct trigger *, void *); + void (*write) (const struct trigger *, struct storage * store); + int (*read) (struct trigger *, struct storage * store); + + struct trigger_type *next; + } trigger_type; + + extern trigger_type *tt_find(const char *name); + extern void tt_register(trigger_type * ttype); + + typedef struct trigger { + struct trigger_type *type; + struct trigger *next; + + variant data; + } trigger; + + typedef struct event_arg { + const char *type; + variant data; + } event_arg; + + extern trigger *t_new(trigger_type * ttype); + extern void t_free(trigger * t); + extern void t_add(trigger ** tlist, trigger * t); +/** add and handle triggers **/ + +/* add a trigger to a list of attributes */ + extern void add_trigger(struct attrib **ap, const char *eventname, + struct trigger *t); + extern void remove_triggers(struct attrib **ap, const char *eventname, + const trigger_type * tt); + extern struct trigger **get_triggers(struct attrib *ap, + const char *eventname); +/* calls handle() for each of these. e.g. used in timeout */ + extern void handle_event(struct attrib *attribs, const char *eventname, + void *data); + +/* functions for making complex triggers: */ + extern void free_triggers(trigger * triggers); /* release all these triggers */ + extern void write_triggers(struct storage *store, const trigger * t); + extern int read_triggers(struct storage *store, trigger ** tp); + extern int handle_triggers(trigger ** triggers, void *data); + + extern struct attrib_type at_eventhandler; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/filereader.c b/core/src/util/filereader.c new file mode 100644 index 000000000..12ef3add4 --- /dev/null +++ b/core/src/util/filereader.c @@ -0,0 +1,328 @@ +#include +#include "filereader.h" + +#include +#include + +#include + +#include +#include + +#define COMMENT_CHAR ';' +#define CONTINUE_CHAR '\\' +#define MAXLINE 4096*16 +static char lbuf[MAXLINE]; +static char fbuf[MAXLINE]; + +static void unicode_warning(const char *bp) +{ + log_warning("invalid sequence in UTF-8 string: %s\n", bp); +} + +INLINE_FUNCTION int eatwhite(const char *ptr, size_t * total_size) +{ + int ret = 0; + + *total_size = 0; + + while (*ptr) { + ucs4_t ucs; + size_t size = 0; + ret = unicode_utf8_to_ucs4(&ucs, ptr, &size); + if (ret != 0) + break; + if (!iswxspace((wint_t) ucs)) + break; + *total_size += size; + ptr += size; + } + return ret; +} + +static const char *getbuf_latin1(FILE * F) +{ + bool cont = false; + char quote = 0; + bool comment = false; + char *cp = fbuf; + char *tail = lbuf + MAXLINE - 2; + + tail[1] = '@'; /* if this gets overwritten by fgets then the line was very long. */ + do { + const char *bp = fgets(lbuf, MAXLINE, F); + + if (bp == NULL) + return NULL; + while (*bp && isxspace(*(unsigned char *)bp)) + ++bp; /* eatwhite */ + + comment = (bool) (comment && cont); + quote = (bool) (quote && cont); + + if (tail[1] == 0) { + /* we read he maximum number of bytes! */ + if (tail[0] != '\n') { + /* it wasn't enough space to finish the line, eat the rest */ + for (;;) { + tail[1] = '@'; + bp = fgets(lbuf, MAXLINE, F); + if (bp == NULL) + return NULL; + if (tail[1]) { + /* read enough this time to end the line */ + break; + } + } + comment = false; + cont = false; + bp = NULL; + continue; + } else { + tail[1] = '@'; + } + } + cont = false; + while (*bp && cp < fbuf + MAXLINE) { + int c = *(unsigned char *)bp; + + if (c == '\n' || c == '\r') { + /* line breaks, shmine breaks */ + break; + } + if (c == COMMENT_CHAR && !quote) { + /* comment begins. we need to keep going, to look for CONTINUE_CHAR */ + comment = true; + ++bp; + continue; + } + if (!comment && (c == '"' || c == '\'')) { + if (quote == c) { + quote = 0; + if (cp < fbuf + MAXLINE) + *cp++ = *bp; + ++bp; + continue; + } else if (!quote) { + quote = *bp++; + if (cp < fbuf + MAXLINE) + *cp++ = quote; + continue; + } + } + + if (iscntrl(c)) { + if (!comment && cp < fbuf + MAXLINE) { + *cp++ = isxspace(c) ? ' ' : '?'; + } + ++bp; + continue; + } else if (isxspace(c)) { + if (!quote) { + ++bp; + while (*bp && isxspace(*(unsigned char *)bp)) + ++bp; /* eatwhite */ + if (!comment && *bp && *bp != COMMENT_CHAR && cp < fbuf + MAXLINE) + *(cp++) = ' '; + } else if (!comment && cp + 1 <= fbuf + MAXLINE) { + *(cp++) = *(bp++); + } else { + ++bp; + } + continue; + } else if (c == CONTINUE_CHAR) { + const char *end = ++bp; + while (*end && isxspace(*(unsigned char *)end)) + ++end; /* eatwhite */ + if (*end == '\0') { + bp = end; + cont = true; + continue; + } + if (comment) { + ++bp; + continue; + } + } else if (comment) { + ++bp; + continue; + } + + if (c < 0x80) { + if (cp + 1 <= fbuf + MAXLINE) { + *(cp++) = *(bp++); + } + } else { + char inbuf = (char)c; + size_t inbytes = 1; + size_t outbytes = MAXLINE - (cp - fbuf); + int ret = unicode_latin1_to_utf8(cp, &outbytes, &inbuf, &inbytes); + if (ret > 0) + cp += ret; + else { + log_error("input data was not iso-8859-1! assuming utf-8\n"); + return NULL; + } + + ++bp; + continue; + } + } + if (cp == fbuf + MAXLINE) { + --cp; + } + *cp = 0; + } while (cont || cp == fbuf); + return fbuf; +} + +static const char *getbuf_utf8(FILE * F) +{ + bool cont = false; + char quote = 0; + bool comment = false; + char *cp = fbuf; + char *tail = lbuf + MAXLINE - 2; + + tail[1] = '@'; /* if this gets overwritten by fgets then the line was very long. */ + do { + const char *bp = fgets(lbuf, MAXLINE, F); + size_t white; + if (bp == NULL) { + return NULL; + } + + eatwhite(bp, &white); /* decoding errors will get caught later on, don't have to check */ + bp += white; + + comment = (bool) (comment && cont); + quote = (bool) (quote && cont); + + if (tail[1] == 0) { + /* we read the maximum number of bytes! */ + if (tail[0] != '\n') { + /* it wasn't enough space to finish the line, eat the rest */ + for (;;) { + tail[1] = '@'; + bp = fgets(lbuf, MAXLINE, F); + if (bp == NULL) + return NULL; + if (tail[1]) { + /* read enough this time to end the line */ + break; + } + } + comment = false; + cont = false; + bp = NULL; + continue; + } else { + tail[1] = '@'; + } + } + cont = false; + while (*bp && cp < fbuf + MAXLINE) { + ucs4_t ucs; + size_t size; + int ret; + + if (!quote) { + while (*bp == COMMENT_CHAR) { + /* comment begins. we need to keep going, to look for CONTINUE_CHAR */ + comment = true; + ++bp; + } + } + + if (*bp == '\n' || *bp == '\r') { + /* line breaks, shmine breaks */ + break; + } + + if (*bp == '"' || *bp == '\'') { + if (quote == *bp) { + quote = 0; + if (!comment && cp < fbuf + MAXLINE) + *cp++ = *bp; + ++bp; + continue; + } else if (!quote) { + quote = *bp++; + if (!comment && cp < fbuf + MAXLINE) + *cp++ = quote; + continue; + } + } + + ret = unicode_utf8_to_ucs4(&ucs, bp, &size); + + if (ret != 0) { + unicode_warning(bp); + break; + } + + if (iswxspace((wint_t) ucs)) { + if (!quote) { + bp += size; + ret = eatwhite(bp, &size); + bp += size; + if (!comment && *bp && *bp != COMMENT_CHAR && cp < fbuf + MAXLINE) + *(cp++) = ' '; + if (ret != 0) { + unicode_warning(bp); + break; + } + } else if (!comment) { + if (cp + size <= fbuf + MAXLINE) { + while (size--) { + *(cp++) = *(bp++); + } + } else + bp += size; + } else { + bp += size; + } + } else if (iswcntrl((wint_t) ucs)) { + if (!comment && cp < fbuf + MAXLINE) { + *cp++ = '?'; + } + bp += size; + } else { + if (*bp == CONTINUE_CHAR) { + const char *end; + eatwhite(bp + 1, &white); + end = bp + 1 + white; + if (*end == '\0') { + bp = end; + cont = true; + continue; + } + if (!comment && cp < fbuf + MAXLINE) + *cp++ = *bp++; + else + ++bp; + } else { + if (!comment && cp + size <= fbuf + MAXLINE) { + while (size--) { + *(cp++) = *(bp++); + } + } else { + bp += size; + } + } + } + } + if (cp == fbuf + MAXLINE) { + --cp; + } + *cp = 0; + } while (cont || cp == fbuf); + return fbuf; +} + +const char *getbuf(FILE * F, int encoding) +{ + if (encoding == XML_CHAR_ENCODING_UTF8) + return getbuf_utf8(F); + return getbuf_latin1(F); +} diff --git a/core/src/util/filereader.h b/core/src/util/filereader.h new file mode 100644 index 000000000..995d9f88f --- /dev/null +++ b/core/src/util/filereader.h @@ -0,0 +1,21 @@ +/* vi: set ts=2: +* +-------------------+ Christian Schlittchen +* | | Enno Rehling +* | Eressea PBEM host | Katja Zedel +* | (c) 1998 - 2005 | +* | | This program may not be used, modified or distributed +* +-------------------+ without prior permission by the authors of Eressea. +* +*/ +#ifndef UTIL_FILEREADER_H +#define UTIL_FILEREADER_H +#ifdef __cplusplus +extern "C" { +#endif + + const char *getbuf(FILE *, int encoding); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/functions.c b/core/src/util/functions.c new file mode 100644 index 000000000..3de812b7f --- /dev/null +++ b/core/src/util/functions.c @@ -0,0 +1,50 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "functions.h" + +#include + +/* libc includes */ +#include +#include +#include + +static critbit_tree cb_functions; + +pf_generic get_function(const char *name) +{ + const void * matches; + pf_generic result; + if (cb_find_prefix(&cb_functions, name, strlen(name)+1, &matches, 1, 0)) { + cb_get_kv(matches, &result, sizeof(result)); + return result; + } + return NULL; +} + +void register_function(pf_generic fun, const char *name) +{ + char buffer[64]; + size_t len = strlen(name); + + assert(len + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef FUNCTIONS_H +#define FUNCTIONS_H +#ifdef __cplusplus +extern "C" { +#endif + + typedef void (*pf_generic) (void); + + extern pf_generic get_function(const char *name); + extern void register_function(pf_generic fun, const char *name); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/functions_test.c b/core/src/util/functions_test.c new file mode 100644 index 000000000..cb229993d --- /dev/null +++ b/core/src/util/functions_test.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include "functions.h" + + +static void test_functions(CuTest * tc) +{ + pf_generic fun; + + fun = get_function("herpderp"); + CuAssertTrue(tc, !fun); + register_function((pf_generic)test_functions, "herpderp"); + fun = get_function("herpderp"); + CuAssertTrue(tc, fun==(pf_generic)test_functions); +} + +CuSuite *get_functions_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_functions); + return suite; +} diff --git a/core/src/util/goodies.c b/core/src/util/goodies.c new file mode 100644 index 000000000..a4c02a32b --- /dev/null +++ b/core/src/util/goodies.c @@ -0,0 +1,137 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "goodies.h" + +#include "unicode.h" + +/* libc includes */ +#include +#include +#include + +/* Simple Integer-Liste */ + +int *intlist_init(void) +{ + return (calloc(1, sizeof(int))); +} + +int *intlist_add(int *i_p, int i) +{ + i_p[0]++; + i_p = realloc(i_p, (i_p[0] + 1) * sizeof(int)); + + i_p[i_p[0]] = i; + return (i_p); +} + +int *intlist_find(int *i_p, int fi) +{ + int i; + + for (i = 1; i <= i_p[0]; i++) { + if (i_p[i] == fi) + return (&i_p[i]); + } + return NULL; +} + +char *set_string(char **s, const char *neu) +{ + if (neu == NULL) { + free(*s); + *s = NULL; + } else if (*s == NULL) { + *s = malloc(strlen(neu) + 1); + strcpy(*s, neu); + } else { + *s = realloc(*s, strlen(neu) + 1); + strcpy(*s, neu); + } + return *s; +} + +static int spc_email_isvalid(const char *address) +{ + int count = 0; + const char *c, *domain; + static const char *rfc822_specials = "()<>@,;:\\\"[]"; /* STATIC_CONST: contains a constant value */ + + /* first we validate the name portion (name@domain) */ + for (c = address; *c; c++) { + if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) { + while (*++c) { + if (*c == '\"') + break; + if (*c == '\\' && (*++c == ' ')) + continue; + if (*c <= ' ' || *c >= 127) + return 0; + } + if (!*c++) + return 0; + if (*c == '@') + break; + if (*c != '.') + return 0; + continue; + } + if (*c == '@') + break; + if (*c <= ' ' || *c >= 127) + return 0; + if (strchr(rfc822_specials, *c)) + return 0; + } + if (c == address || *(c - 1) == '.') + return 0; + + /* next we validate the domain portion (name@domain) */ + if (!*(domain = ++c)) + return 0; + do { + if (*c == '.') { + if (c == domain || *(c - 1) == '.') + return 0; + count++; + } + if (*c <= ' ' || *c >= 127) + return 0; + if (strchr(rfc822_specials, *c)) + return 0; + } while (*++c); + + return (count >= 1); +} + +int set_email(char **pemail, const char *newmail) +{ + if (newmail && *newmail) { + if (spc_email_isvalid(newmail) <= 0) + return -1; + } + if (*pemail) + free(*pemail); + *pemail = 0; + if (newmail) { + *pemail = strdup(newmail); + } + return 0; +} diff --git a/core/src/util/goodies.h b/core/src/util/goodies.h new file mode 100644 index 000000000..3f4725bc7 --- /dev/null +++ b/core/src/util/goodies.h @@ -0,0 +1,64 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef GOODIES_H +#define GOODIES_H +#ifdef __cplusplus +extern "C" { +#endif + + extern char *set_string(char **s, const char *neu); + extern int set_email(char **pemail, const char *newmail); + + extern int *intlist_init(void); + extern int *intlist_add(int *i_p, int i); + extern int *intlist_find(int *i_p, int i); + +#ifdef HAVE_INLINE +# include "strings.c" +#else + extern unsigned int hashstring(const char *s); + extern const char *escape_string(const char *str, char *buffer, + unsigned int len); + extern unsigned int jenkins_hash(unsigned int a); + extern unsigned int wang_hash(unsigned int a); +#endif + +/* benchmark for units: + * JENKINS_HASH: 5.25 misses/hit (with good cache behavior) + * WANG_HASH: 5.33 misses/hit (with good cache behavior) + * KNUTH_HASH: 1.93 misses/hit (with bad cache behavior) + * CF_HASH: fucking awful! + */ +#define KNUTH_HASH1(a, m) ((a) % m) +#define KNUTH_HASH2(a, m) (m - 2 - a % (m-2)) +#define CF_HASH1(a, m) ((a) % m) +#define CF_HASH2(a, m) (8 - ((a) & 7)) +#define JENKINS_HASH1(a, m) (jenkins_hash(a) % m) +#define JENKINS_HASH2(a, m) 1 +#define WANG_HASH1(a, m) (wang_hash(a) % m) +#define WANG_HASH2(a, m) 1 + +#define HASH1 JENKINS_HASH1 +#define HASH2 JENKINS_HASH2 + +#define SWAP_VARS(T, a, b) { T x = a; a = b; b = x; } +#ifdef __cplusplus +} +#endif +#endif /* GOODIES_H */ diff --git a/core/src/util/language.c b/core/src/util/language.c new file mode 100644 index 000000000..d50c55d6d --- /dev/null +++ b/core/src/util/language.c @@ -0,0 +1,256 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "language.h" +#include "language_struct.h" + +#include "log.h" +#include "goodies.h" +#include "umlaut.h" + +#include +#include +#include +#include + +/** importing **/ + +locale *default_locale; +locale *locales; + +unsigned int locale_index(const locale * lang) +{ + assert(lang); + return lang->index; +} + +locale *find_locale(const char *name) +{ + unsigned int hkey = hashstring(name); + locale *l = locales; + while (l && l->hashkey != hkey) + l = l->next; + return l; +} + +static unsigned int nextlocaleindex = 0; + +locale *make_locale(const char *name) +{ + unsigned int hkey = hashstring(name); + locale *l = (locale *) calloc(sizeof(locale), 1); + locale **lp = &locales; + + if (!locales) { + nextlocaleindex = 0; + } + + while (*lp && (*lp)->hashkey != hkey) + lp = &(*lp)->next; + if (*lp) { + return *lp; + } + + l->hashkey = hkey; + l->name = strdup(name); + l->next = NULL; + l->index = nextlocaleindex++; + assert(nextlocaleindex <= MAXLOCALES); + *lp = l; + if (default_locale == NULL) + default_locale = l; + return l; +} + +/** creates a list of locales + * This function takes a comma-delimited list of locale-names and creates + * the locales using the make_locale function (useful for ini-files). + * For maximum performance, locales should be created in order of popularity. + */ +void make_locales(const char *str) +{ + const char *tok = str; + while (*tok) { + char zText[32]; + while (*tok && *tok != ',') + ++tok; + strncpy(zText, str, tok - str); + zText[tok - str] = 0; + make_locale(zText); + if (*tok) { + str = ++tok; + } + } +} + +const char *locale_getstring(const locale * lang, const char *key) +{ + unsigned int hkey = hashstring(key); + unsigned int id = hkey & (SMAXHASH - 1); + const struct locale_str *find; + + assert(lang); + if (key == NULL || *key == 0) + return NULL; + find = lang->strings[id]; + while (find) { + if (find->hashkey == hkey) { + if (find->nexthash == NULL) { + /* if this is the only entry with this hash, fine. */ + assert(strcmp(key, find->key) == 0); + return find->str; + } + if (strcmp(key, find->key) == 0) { + return find->str; + } + } + find = find->nexthash; + } + return NULL; +} + +const char *locale_string(const locale * lang, const char *key) +{ + assert(lang); + + if (key != NULL) { + unsigned int hkey = hashstring(key); + unsigned int id = hkey & (SMAXHASH - 1); + struct locale_str *find; + + if (*key == 0) + return NULL; + find = lang->strings[id]; + while (find) { + if (find->hashkey == hkey) { + if (find->nexthash == NULL) { + /* if this is the only entry with this hash, fine. */ + assert(strcmp(key, find->key) == 0); + break; + } + if (strcmp(key, find->key) == 0) + break; + } + find = find->nexthash; + } + if (!find) { + log_warning("missing translation for \"%s\" in locale %s\n", key, lang->name); + if (lang->fallback) { + return locale_string(lang->fallback, key); + } + return 0; + } + return find->str; + } + return NULL; +} + +void locale_setstring(locale * lang, const char *key, const char *value) +{ + unsigned int hkey = hashstring(key); + unsigned int id = hkey & (SMAXHASH - 1); + struct locale_str *find; + if (!lang) { + lang = default_locale; + } + assert(lang); + find = lang->strings[id]; + while (find) { + if (find->hashkey == hkey && strcmp(key, find->key) == 0) + break; + find = find->nexthash; + } + if (!find) { + find = calloc(1, sizeof(struct locale_str)); + find->nexthash = lang->strings[id]; + lang->strings[id] = find; + find->hashkey = hkey; + find->key = strdup(key); + find->str = strdup(value); + } else { + if (strcmp(find->str, value) != 0) { + log_error("duplicate translation '%s' for key %s\n", value, key); + } + assert(!strcmp(find->str, value) || !"duplicate string for key"); + } +} + +const char *locale_name(const locale * lang) +{ + assert(lang); + return lang->name; +} + +char *mkname_buf(const char *space, const char *name, char *buffer) +{ + if (space && *space) { + sprintf(buffer, "%s::%s", space, name); + } else { + strcpy(buffer, name); + } + return buffer; +} + +const char *mkname(const char *space, const char *name) +{ + static char zBuffer[128]; /* STATIC_RESULT: used for return, not across calls */ + return mkname_buf(space, name, zBuffer); +} + +locale *nextlocale(const struct locale * lang) +{ + return lang->next; +} + +typedef struct lstr { + void * tokens[UT_MAX]; +} lstr; + +static lstr lstrs[MAXLOCALES]; + +void ** get_translations(const struct locale *lang, int index) +{ + assert(lang); + assert(lang->index < MAXLOCALES + || "you have to increase MAXLOCALES and recompile"); + if (lang->index < MAXLOCALES) { + return lstrs[lang->index].tokens + index; + } + return lstrs[0].tokens + index; +} + +void free_locales(void) +{ + while (locales) { + int i; + locale * next = locales->next; + + for (i=0; i!=SMAXHASH; ++i) { + while (locales->strings[i]) { + struct locale_str * strings = locales->strings[i]; + free(strings->key); + free(strings->str); + locales->strings[i] = strings->nexthash; + free(strings); + } + } + free(locales); + locales = next; + } +} \ No newline at end of file diff --git a/core/src/util/language.h b/core/src/util/language.h new file mode 100644 index 000000000..eb7c81986 --- /dev/null +++ b/core/src/util/language.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef MY_LOCALE_H +#define MY_LOCALE_H +#ifdef __cplusplus +extern "C" { +#endif + +#define MAXLOCALES 3 + + struct locale; + +/** managing multiple locales: **/ + extern struct locale *find_locale(const char *name); + extern struct locale *make_locale(const char *key); + extern void free_locales(void); + + /** operations on locales: **/ + extern void locale_setstring(struct locale *lang, const char *key, + const char *value); + extern const char *locale_getstring(const struct locale *lang, + const char *key); + extern const char *locale_string(const struct locale *lang, const char *key); /* does fallback */ + extern unsigned int locale_index(const struct locale *lang); + extern const char *locale_name(const struct locale *lang); + + extern const char *mkname(const char *namespc, const char *key); + extern char *mkname_buf(const char *namespc, const char *key, char *buffer); + + extern void make_locales(const char *str); + +#define LOC(lang, s) (lang?locale_string(lang, s):s) + + extern struct locale *default_locale; + extern struct locale *locales; + extern struct locale *nextlocale(const struct locale *lang); + + enum { + UT_PARAMS, + UT_KEYWORDS, + UT_SKILLS, + UT_RACES, + UT_OPTIONS, + UT_DIRECTIONS, + UT_ARCHETYPES, + UT_MAGIC, + UT_TERRAINS, + UT_SPECDIR, + UT_MAX + }; + + void ** get_translations(const struct locale *lang, int index); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/language_struct.h b/core/src/util/language_struct.h new file mode 100644 index 000000000..fa79fdb57 --- /dev/null +++ b/core/src/util/language_struct.h @@ -0,0 +1,28 @@ +#ifndef CLASS_LANGUAGE_STRUCT +#define CLASS_LANGUAGE_STRUCT + +/* This file should not be included by anything in the server. If you + * feel that you need to include it, it's a sure sign that you're trying to + * do something BAD. */ + +#define SMAXHASH 2048 +typedef struct locale_str { + unsigned int hashkey; + struct locale_str *nexthash; + char *str; + char *key; +} locale_str; + +typedef struct locale { + unsigned int index; + struct locale *next; + unsigned int hashkey; + const char *name; + struct locale_str *strings[SMAXHASH]; + struct locale *fallback; +} locale; + +extern locale *default_locale; +extern locale *locales; + +#endif diff --git a/core/src/util/listbox.c b/core/src/util/listbox.c new file mode 100644 index 000000000..ab3dd9eb9 --- /dev/null +++ b/core/src/util/listbox.c @@ -0,0 +1,215 @@ +/* vi: set ts=2: +* +-------------------+ Christian Schlittchen +* | | Enno Rehling +* | Eressea PBEM host | Katja Zedel +* | (c) 1998 - 2006 | +* | | This program may not be used, modified or distributed +* +-------------------+ without prior permission by the authors of Eressea. +* +*/ + +/* wenn platform.h nicht vor curses included wird, kompiliert es unter windows nicht */ +#include +#include +#include + +#include "listbox.h" +#include "gmtool_structs.h" + +#include + +#include +#include + +void +insert_selection(list_selection ** p_sel, list_selection * prev, + const char *str, void *payload) +{ + list_selection *sel = calloc(sizeof(list_selection), 1); + sel->str = strdup(str); + sel->data = payload; + if (*p_sel) { + list_selection *s; + sel->next = *p_sel; + sel->prev = sel->next->prev; + sel->next->prev = sel; + if (sel->prev) { + sel->prev->next = sel; + sel->index = sel->prev->index + 1; + } + for (s = sel->next; s; s = s->next) { + s->index = s->prev->index + 1; + } + *p_sel = sel; + } else { + *p_sel = sel; + sel->prev = prev; + if (prev) + sel->index = prev->index + 1; + } +} + +list_selection **push_selection(list_selection ** p_sel, char *str, + void *payload) +{ + list_selection *sel = calloc(sizeof(list_selection), 1); + list_selection *prev = NULL; + sel->str = str; + sel->data = payload; + while (*p_sel) { + prev = *p_sel; + p_sel = &prev->next; + } + *p_sel = sel; + if (prev) { + sel->prev = prev; + sel->index = prev->index + 1; + } + return p_sel; +} + +#define SX (getmaxx(stdscr)) +#define SY (getmaxy(stdscr)) + +list_selection *do_selection(list_selection * sel, const char *title, + void (*perform) (list_selection *, void *), void *data) +{ + WINDOW *wn; + bool update = true; + list_selection *s; + list_selection *top = sel; + list_selection *current = top; + int i; + int height = 0, width = (int)strlen(title) + 8; + for (s = sel; s; s = s->next) { + if ((int)strlen(s->str) > width) { + width = (int)strlen(s->str); + } + ++height; + if (verbosity >= 5) { + log_printf(stdout, "s %s w %d h %d\n", s->str, width, height); + } + } + if (height == 0 || width == 0) + return 0; + if (width + 3 > SX) + width = SX - 4; + if (height + 2 > SY) + height = SY - 2; + + if (verbosity >= 5) + log_printf(stdout, "w %d h %d\n", width, height); + + wn = + newwin(height + 2, width + 4, (SY - height - 2) / 2, (SX - width - 4) / 2); + + for (;;) { + int input; + if (update) { + for (s = top; s != NULL && top->index + height != s->index; s = s->next) { + i = s->index - top->index; + wmove(wn, i + 1, 4); + waddnstr(wn, s->str, -1); + wclrtoeol(wn); + } + wclrtobot(wn); + wxborder(wn); + mvwprintw(wn, 0, 2, "[ %s ]", title); + update = false; + } + i = current->index - top->index; + wattron(wn, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + wmove(wn, i + 1, 2); + waddstr(wn, "->"); + wmove(wn, i + 1, 4); + waddnstr(wn, current->str, width - 2); + wattroff(wn, A_BOLD | COLOR_PAIR(COLOR_YELLOW)); + + wrefresh(wn); + + input = getch(); + + wmove(wn, i + 1, 2); + waddstr(wn, " "); + wmove(wn, i + 1, 4); + waddnstr(wn, current->str, width); + + switch (input) { + case KEY_NPAGE: + for (i = 0; i != height / 2; ++i) { + if (current->next) { + current = current->next; + if (current->index - height >= top->index) { + top = current; + update = true; + } + } + } + break; + case KEY_PPAGE: + for (i = 0; i != height / 2; ++i) { + if (current->prev) { + if (current == top) { + top = sel; + while (top->index + height < current->index) + top = top->next; + update = true; + } + current = current->prev; + } + } + break; + case KEY_DOWN: + if (current->next) { + current = current->next; + if (current->index - height >= top->index) { + top = current; + update = true; + } + } + break; + case KEY_UP: + if (current->prev) { + if (current == top) { + top = sel; + while (top->index + height < current->index) + top = top->next; + update = true; + } + current = current->prev; + } + break; + case 27: + case 'q': + delwin(wn); + return NULL; + case 10: + case 13: + if (perform) + perform(current, data); + else { + delwin(wn); + return current; + } + break; + default: + s = current->next; + if (s == NULL) + s = top; + while (s != current) { + if (tolower(s->str[0]) == tolower(input)) { + current = s; + update = true; + } else { + s = s->next; + if (s == NULL) + s = top; + } + } + if (current->index - height >= top->index) { + top = current; + update = true; + } + } + } +} diff --git a/core/src/util/listbox.h b/core/src/util/listbox.h new file mode 100644 index 000000000..4c2d74428 --- /dev/null +++ b/core/src/util/listbox.h @@ -0,0 +1,20 @@ +#ifndef CURSES_LISTBOX +#define CURSES_LISTBOX + +typedef struct list_selection { + struct list_selection *next; + struct list_selection *prev; + int index; + char *str; + void *data; +} list_selection; + +extern struct list_selection *do_selection(struct list_selection *sel, + const char *title, void (*perform) (struct list_selection *, void *), + void *data); +extern struct list_selection **push_selection(struct list_selection **p_sel, + char *str, void *payload); +extern void insert_selection(struct list_selection **p_sel, + struct list_selection *prev, const char *str, void *payload); + +#endif /* CURSES_LISTBOX */ diff --git a/core/src/util/lists.c b/core/src/util/lists.c new file mode 100644 index 000000000..912699bc7 --- /dev/null +++ b/core/src/util/lists.c @@ -0,0 +1,122 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include + +#include +#include "lists.h" + +typedef struct void_list { + struct void_list *next; + void *data; +} void_list; + +void addlist(void *l1, void *p1) +{ + + /* add entry p to the end of list l */ + + void_list **l; + void_list *p, *q; + + l = (void_list **) l1; + p = (void_list *) p1; + assert(p->next == 0); + + if (*l) { + for (q = *l; q->next; q = q->next) + assert(q); + q->next = p; + } else + *l = p; +} + +static void choplist(void *a, void *b) +{ + void_list **l = (void_list **) a, *p = (void_list *) b; + /* remove entry p from list l - when called, a pointer to p must be + * kept in order to use (and free) p; if omitted, this will be a + * memory leak */ + + void_list **q; + + for (q = l; *q; q = &((*q)->next)) { + if (*q == p) { + *q = p->next; + p->next = 0; + break; + } + } +} + +void translist(void *l1, void *l2, void *p) +{ + + /* remove entry p from list l1 and add it at the end of list l2 */ + + choplist(l1, p); + addlist(l2, p); +} + +void insertlist(void_list ** l, void_list * p) +{ + + /* insert entry p at the beginning of list l */ + + p->next = *l; + *l = p; + +} + +void removelist(void *l, void *p) +{ + + /* remove entry p from list l; free p */ + + choplist(l, p); + free(p); +} + +void freelist(void *p1) +{ + + /* remove all entries following and including entry p from a listlist */ + + void_list *p, *p2; + + p = (void_list *) p1; + + while (p) { + p2 = p->next; + free(p); + p = p2; + } +} + +unsigned int listlen(void *l) +{ + + /* count entries p in list l */ + + unsigned int i; + void_list *p; + + for (p = (void_list *) l, i = 0; p; p = p->next, i++) ; + return i; +} diff --git a/core/src/util/lists.h b/core/src/util/lists.h new file mode 100644 index 000000000..b12edd316 --- /dev/null +++ b/core/src/util/lists.h @@ -0,0 +1,42 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef LISTS_H +#define LISTS_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + + void addlist(void *l1, void *p1); + void translist(void *l1, void *l2, void *p); +#ifndef MALLOCDBG + void freelist(void *p1); + void removelist(void *l, void *p); +#else +#define freelist(p) { while (p) { void * p2 = p->next; free(p); p = p2; } } +#define removelist(l,p) { choplist(l, p); free(p); } +#endif + + unsigned int listlen(void *l); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/log.c b/core/src/util/log.c new file mode 100644 index 000000000..0c14a6bd7 --- /dev/null +++ b/core/src/util/log.c @@ -0,0 +1,311 @@ +/* vi: set ts=2: ++-------------------+ Christian Schlittchen +| | Enno Rehling +| Eressea PBEM host | Katja Zedel +| (c) 1998 - 2003 | Henning Peters +| | Ingo Wilken ++-------------------+ Stefan Reich + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ +#include +#include "log.h" +#include "unicode.h" + +#include +#include +#include +#include +#include +#include + +/* TODO: set from external function */ +int log_flags = LOG_FLUSH | LOG_CPERROR | LOG_CPWARNING | LOG_CPDEBUG; +int log_stderr = LOG_FLUSH | LOG_CPERROR | LOG_CPWARNING; + +#ifdef STDIO_CP +static int stdio_codepage = STDIO_CP; +#else +static int stdio_codepage = 0; +#endif +static FILE *logfile; + +#define MAXLENGTH 4096 /* because I am lazy, CP437 output is limited to this many chars */ +#define LOG_MAXBACKUPS 5 +void log_flush(void) +{ + fflush(logfile); +} + +void log_puts(const char *str) +{ + fflush(stdout); + if (logfile) { + fputs(str, logfile); + } +} + +static int +cp_convert(const char *format, char *buffer, size_t length, int codepage) +{ + /* when console output on MSDOS, convert to codepage */ + const char *input = format; + char *pos = buffer; + + while (pos + 1 < buffer + length && *input) { + size_t length = 0; + int result = 0; + if (codepage == 437) { + result = unicode_utf8_to_cp437(pos, input, &length); + } else if (codepage == 1252) { + result = unicode_utf8_to_cp1252(pos, input, &length); + } + if (result != 0) { + *pos = 0; /* just in case caller ignores our return value */ + return result; + } + ++pos; + input += length; + } + *pos = 0; + return 0; +} + +void log_rotate(const char *filename, int maxindex) +{ + int n; + if (access(filename, R_OK)==0) { + char buffer[2][MAX_PATH]; + int src = 1; + assert(strlen(filename) +| | Enno Rehling +| Eressea PBEM host | Katja Zedel +| (c) 1998 - 2003 | Henning Peters +| | Ingo Wilken ++-------------------+ Stefan Reich + +This program may not be used, modified or distributed +without prior permission by the authors of Eressea. +*/ +#ifndef H_UTIL_LOG +#define H_UTIL_LOG +#ifdef __cplusplus +extern "C" { +#endif + + extern void log_open(const char *filename); + extern void log_close(void); + extern void log_flush(void); + + /* use macros above instead of these: */ + extern void log_warning(const char *format, ...); + extern void log_error(const char *format, ...); + extern void log_debug(const char *format, ...); + extern void log_info(const char *format, ...); + extern void log_printf(FILE * ios, const char *format, ...); + +#define LOG_FLUSH 0x01 +#define LOG_CPWARNING 0x02 +#define LOG_CPERROR 0x04 +#define LOG_CPDEBUG 0x08 +#define LOG_CPINFO 0x10 + + extern int log_flags; + extern int log_stderr; +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/message.c b/core/src/util/message.c new file mode 100644 index 000000000..3476ac5a6 --- /dev/null +++ b/core/src/util/message.c @@ -0,0 +1,221 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + +*/ + +#include +#include "message.h" + +#include "goodies.h" +#include "log.h" +#include "quicklist.h" + +/* libc includes */ +#include +#include +#include +#include +#include + +void (*msg_log_create) (const struct message * msg) = 0; + +const char *mt_name(const message_type * mtype) +{ + return mtype->name; +} + +message_type *mt_new(const char *name, const char *args[]) +{ + int i, nparameters = 0; + message_type *mtype = (message_type *) malloc(sizeof(message_type)); + + assert(name != NULL); + if (name == NULL) { + log_error("Trying to create message_type with name=0x0\n"); + return NULL; + } + if (args != NULL) + for (nparameters = 0; args[nparameters]; ++nparameters) ; + + mtype->name = strdup(name); + mtype->nparameters = nparameters; + if (nparameters > 0) { + mtype->pnames = (const char **)malloc(sizeof(char *) * nparameters); + mtype->types = (const arg_type **)malloc(sizeof(arg_type *) * nparameters); + } else { + mtype->pnames = NULL; + mtype->types = NULL; + } + if (args != NULL) + for (i = 0; args[i]; ++i) { + const char *x = args[i]; + const char *spos = strchr(x, ':'); + if (spos == NULL) { + mtype->pnames[i] = strdup(x); + mtype->types[i] = NULL; + } else { + char *cp = strncpy((char *)malloc(spos - x + 1), x, spos - x); + cp[spos - x] = '\0'; + mtype->pnames[i] = cp; + mtype->types[i] = find_argtype(spos + 1); + if (mtype->types[i] == NULL) { + log_error("unknown argument type %s for message type %s\n", spos + 1, mtype->name); + } + assert(mtype->types[i]); + } + } + return mtype; +} + +message_type *mt_new_va(const char *name, ...) +{ + const char *args[16]; + int i = 0; + va_list marker; + + va_start(marker, name); + for (;;) { + const char *c = va_arg(marker, const char *); + args[i++] = c; + if (c == NULL) + break; + } + va_end(marker); + return mt_new(name, args); +} + +arg_type *argtypes = NULL; + +void +register_argtype(const char *name, void (*free_arg) (variant), + variant(*copy_arg) (variant), variant_type type) +{ + arg_type *atype = (arg_type *) malloc(sizeof(arg_type)); + atype->name = name; + atype->next = argtypes; + atype->release = free_arg; + atype->copy = copy_arg; + atype->vtype = type; + argtypes = atype; +} + +const arg_type *find_argtype(const char *name) +{ + arg_type *atype = argtypes; + while (atype != NULL) { + if (strcmp(atype->name, name) == 0) + return atype; + atype = atype->next; + } + return NULL; +} + +static variant copy_arg(const arg_type * atype, variant data) +{ + assert(atype != NULL); + if (atype->copy == NULL) + return data; + return atype->copy(data); +} + +static void free_arg(const arg_type * atype, variant data) +{ + assert(atype != NULL); + if (atype->release) + atype->release(data); +} + +message *msg_create(const struct message_type *mtype, variant args[]) +{ + int i; + message *msg = (message *) malloc(sizeof(message)); + + assert(mtype != NULL); + if (mtype == NULL) { + log_error("Trying to create message with type=0x0\n"); + return NULL; + } + msg->type = mtype; + msg->parameters = (variant *) calloc(mtype->nparameters, sizeof(variant)); + msg->refcount = 1; + for (i = 0; i != mtype->nparameters; ++i) { + msg->parameters[i] = copy_arg(mtype->types[i], args[i]); + } + if (msg_log_create) + msg_log_create(msg); + return msg; +} + +#define MT_MAXHASH 1021 +static quicklist *messagetypes[MT_MAXHASH]; + +const message_type *mt_find(const char *name) +{ + unsigned int hash = hashstring(name) % MT_MAXHASH; + quicklist *ql = messagetypes[hash]; + int qi; + + for (qi = 0; ql; ql_advance(&ql, &qi, 1)) { + message_type *data = (message_type *) ql_get(ql, qi); + if (strcmp(data->name, name) == 0) { + return data; + } + } + return 0; +} + +static unsigned int mt_id(const message_type * mtype) +{ + unsigned int key = 0; + size_t i = strlen(mtype->name); + + while (i > 0) { + key = (mtype->name[--i] + key * 37); + } + return key % 0x7FFFFFFF; +} + +const message_type *mt_register(message_type * type) +{ + unsigned int hash = hashstring(type->name) % MT_MAXHASH; + quicklist **qlp = messagetypes + hash; + + if (ql_set_insert(qlp, type) == 0) { + type->key = mt_id(type); + } + return type; +} + +void msg_free(message * msg) +{ + int i; + assert(msg->refcount == 0); + for (i = 0; i != msg->type->nparameters; ++i) { + free_arg(msg->type->types[i], msg->parameters[i]); + } + free((void *)msg->parameters); + free(msg); +} + +void msg_release(struct message *msg) +{ + assert(msg->refcount > 0); + if (--msg->refcount > 0) + return; + msg_free(msg); +} + +struct message *msg_addref(struct message *msg) +{ + assert(msg->refcount > 0); + ++msg->refcount; + return msg; +} diff --git a/core/src/util/message.h b/core/src/util/message.h new file mode 100644 index 000000000..b5fb81185 --- /dev/null +++ b/core/src/util/message.h @@ -0,0 +1,73 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#ifndef UTIL_MESSAGE_H +#define UTIL_MESSAGE_H + +#include "variant.h" + +#ifdef __cplusplus +extern "C" { +#endif + + struct locale; + + typedef struct arg_type { + struct arg_type *next; + variant_type vtype; + const char *name; + void (*release) (variant); + variant(*copy) (variant); + } arg_type; + + typedef struct message_type { + unsigned int key; + const char *name; + int nparameters; + const char **pnames; + const struct arg_type **types; + } message_type; + + typedef struct message { + const struct message_type *type; + variant *parameters; + int refcount; + } message; + + extern struct message_type *mt_new(const char *name, const char **args); + extern struct message_type *mt_new_va(const char *name, ...); + /* mt_new("simple_sentence", "subject:string", "predicate:string", + * "object:string", "lang:locale", NULL); */ + + extern struct message *msg_create(const struct message_type *type, + variant args[]); + /* msg_create(&mt_simplesentence, "enno", "eats", "chocolate", &locale_de); + * parameters must be in the same order as they were for mt_new! */ + + extern void msg_release(struct message *msg); + extern struct message *msg_addref(struct message *msg); + + extern const char *mt_name(const struct message_type *mtype); + +/** message_type registry (optional): **/ + extern const struct message_type *mt_register(struct message_type *); + extern const struct message_type *mt_find(const char *); + + extern void register_argtype(const char *name, void (*free_arg) (variant), + variant(*copy_arg) (variant), variant_type); + extern const struct arg_type *find_argtype(const char *name); + + extern void (*msg_log_create) (const struct message * msg); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/nrmessage.c b/core/src/util/nrmessage.c new file mode 100644 index 000000000..6acd14663 --- /dev/null +++ b/core/src/util/nrmessage.c @@ -0,0 +1,177 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + +*/ + +#include +#include "nrmessage.h" +#include "nrmessage_struct.h" + +/* util includes */ +#include "bsdstring.h" +#include "log.h" +#include "message.h" +#include "language.h" +#include "translation.h" +#include "goodies.h" + +/* libc includes */ +#include +#include +#include + +#define NRT_MAXHASH 1021 +static nrmessage_type *nrtypes[NRT_MAXHASH]; + +const char *nrt_string(const struct nrmessage_type *type) +{ + return type->string; +} + +nrmessage_type *nrt_find(const struct locale * lang, + const struct message_type * mtype) +{ + nrmessage_type *found = NULL; + unsigned int hash = hashstring(mtype->name) % NRT_MAXHASH; + nrmessage_type *type = nrtypes[hash]; + while (type) { + if (type->mtype == mtype) { + if (found == NULL) + found = type; + else if (type->lang == NULL) + found = type; + if (lang == type->lang) { + found = type; + break; + } + } + type = type->next; + } + if (!found) { + log_warning("could not find nr-type %s for locale %s\n", mtype->name, locale_name(lang)); + } + if (lang && found && found->lang != lang) { + log_warning("could not find nr-type %s for locale %s, using %s\n", mtype->name, locale_name(lang), locale_name(found->lang)); + } + return found; +} + +nrsection *sections; + +const nrsection *section_find(const char *name) +{ + nrsection **mcp = §ions; + if (name == NULL) + return NULL; + for (; *mcp; mcp = &(*mcp)->next) { + nrsection *mc = *mcp; + if (!strcmp(mc->name, name)) + break; + } + return *mcp; +} + +const nrsection *section_add(const char *name) +{ + nrsection **mcp = §ions; + if (name == NULL) + return NULL; + for (; *mcp; mcp = &(*mcp)->next) { + nrsection *mc = *mcp; + if (!strcmp(mc->name, name)) + break; + } + if (!*mcp) { + nrsection *mc = calloc(sizeof(nrsection), 1); + mc->name = strdup(name); + *mcp = mc; + } + return *mcp; +} + +void +nrt_register(const struct message_type *mtype, const struct locale *lang, + const char *string, int level, const char *section) +{ + unsigned int hash = hashstring(mtype->name) % NRT_MAXHASH; + nrmessage_type *nrt = nrtypes[hash]; + while (nrt && (nrt->lang != lang || nrt->mtype != mtype)) { + nrt = nrt->next; + } + if (nrt) { + log_error("duplicate message-type %s\n", mtype->name); + assert(!nrt || !"trying to register same nr-type twice"); + } else { + int i; + char zNames[256]; + char *c = zNames; + nrt = malloc(sizeof(nrmessage_type)); + nrt->lang = lang; + nrt->mtype = mtype; + nrt->next = nrtypes[hash]; + nrt->level = level; + if (section) { + const nrsection *s = section_find(section); + if (s == NULL) { + s = section_add(section); + } + nrt->section = s->name; + } else + nrt->section = NULL; + nrtypes[hash] = nrt; + assert(string && *string); + nrt->string = strdup(string); + *c = '\0'; + for (i = 0; i != mtype->nparameters; ++i) { + if (i != 0) + *c++ = ' '; + c += strlen(strcpy(c, mtype->pnames[i])); + } + nrt->vars = strdup(zNames); + } +} + +size_t +nr_render(const struct message *msg, const struct locale *lang, char *buffer, + size_t size, const void *userdata) +{ + struct nrmessage_type *nrt = nrt_find(lang, msg->type); + + if (nrt) { + const char *m = + translate(nrt->string, userdata, nrt->vars, msg->parameters); + if (m) { + return strlcpy((char *)buffer, m, size); + } else { + log_error("Couldn't render message %s\n", nrt->mtype->name); + } + } + if (size > 0 && buffer) + buffer[0] = 0; + return 0; +} + +int nr_level(const struct message *msg) +{ + nrmessage_type *nrt = nrt_find(NULL, msg->type); + return nrt ? nrt->level : 0; +} + +const char *nr_section(const struct message *msg) +{ + nrmessage_type *nrt = nrt_find(default_locale, msg->type); + return nrt ? nrt->section : NULL; +} + +const char *nrt_section(const nrmessage_type * nrt) +{ + return nrt ? nrt->section : NULL; +} diff --git a/core/src/util/nrmessage.h b/core/src/util/nrmessage.h new file mode 100644 index 000000000..1c8781d41 --- /dev/null +++ b/core/src/util/nrmessage.h @@ -0,0 +1,55 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_UTIL_NRMESSAGE +#define H_UTIL_NRMESSAGE +#ifdef __cplusplus +extern "C" { +#endif + + struct locale; + struct message; + struct message_type; + struct nrmessage_type; + + typedef struct nrsection { + char *name; + struct nrsection *next; + } nrsection; + + extern nrsection *sections; + + extern void nrt_register(const struct message_type *mtype, + const struct locale *lang, const char *script, + int level, const char *section); + extern struct nrmessage_type *nrt_find(const struct locale *, + const struct message_type *); + extern const char *nrt_string(const struct nrmessage_type *type); + extern const char *nrt_section(const struct nrmessage_type *mt); + + extern size_t nr_render(const struct message *msg, const struct locale *lang, + char *buffer, size_t size, const void *userdata); + extern int nr_level(const struct message *msg); + extern const char *nr_section(const struct message *msg); + +/* before: + * fogblock;movement:0;de;{unit} konnte von {region} nicht nach {$dir direction} ausreisen, der Nebel war zu dicht. + * after: + * fogblock:movement:0 + * $unit($unit) konnte von $region($region) nicht nach $direction($direction) ausreisen, der Nebel war zu dicht. + * unit:unit region:region direction:int + */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/nrmessage_struct.h b/core/src/util/nrmessage_struct.h new file mode 100644 index 000000000..6a3a1d9b0 --- /dev/null +++ b/core/src/util/nrmessage_struct.h @@ -0,0 +1,20 @@ +#ifndef CLASS_NRMESSAGE_STRUCT +#define CLASS_NRMESSAGE_STRUCT + +/* This file should not be included by anything in the server. If you + * feel that you need to include it, it's a sure sign that you're trying to + * do something BAD. */ + +typedef struct nrmessage_type { + const struct message_type *mtype; + const struct locale *lang; + const char *string; + const char *vars; + struct nrmessage_type *next; + int level; + const char *section; +} nrmessage_type; + +extern nrmessage_type *get_nrmessagetypes(void); + +#endif diff --git a/core/src/util/os.c b/core/src/util/os.c new file mode 100644 index 000000000..5a7220e7d --- /dev/null +++ b/core/src/util/os.c @@ -0,0 +1,18 @@ +#include "os.h" + +#if defined(WIN32) +#include +#else /* WIN32 */ +#include +#include +#endif /* WIN32 */ +int os_mkdir(const char *path, int mode) +{ + +#ifdef WIN32 + mode = mode; + return _mkdir(path); +#else /* POSIX is our last hope */ + return mkdir(path, (mode_t) mode); +#endif /* WIN32 */ +} diff --git a/core/src/util/os.h b/core/src/util/os.h new file mode 100644 index 000000000..cf4bb06ea --- /dev/null +++ b/core/src/util/os.h @@ -0,0 +1,29 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef _OS_H +#define _OS_H +#ifdef __cplusplus +extern "C" { +#endif + + extern int os_mkdir(const char *path, int mode); +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/parser.c b/core/src/util/parser.c new file mode 100644 index 000000000..3634ba541 --- /dev/null +++ b/core/src/util/parser.c @@ -0,0 +1,192 @@ +#include +#include "parser.h" +#include "unicode.h" +#include "log.h" + +#include +#include +#include + +#define SPACE_REPLACEMENT '~' +#define ESCAPE_CHAR '\\' +#define MAXTOKENSIZE 8192 + +typedef struct parser_state { + const char *current_token; + char *current_cmd; + struct parser_state *next; +} parser_state; + +static parser_state *state; + +static int eatwhitespace_c(const char **str_p) +{ + int ret = 0; + ucs4_t ucs; + size_t len; + const char *str = *str_p; + + /* skip over potential whitespace */ + for (;;) { + unsigned char utf8_character = (unsigned char)*str; + if (~utf8_character & 0x80) { + if (!iswxspace(utf8_character)) + break; + ++str; + } else { + ret = unicode_utf8_to_ucs4(&ucs, str, &len); + if (ret != 0) { + log_warning("illegal character sequence in UTF8 string: %s\n", str); + break; + } + if (!iswxspace((wint_t) ucs)) + break; + str += len; + } + } + *str_p = str; + return ret; +} + +void init_tokens_str(const char *initstr, char *cmd) +{ + if (state == NULL) { + state = malloc(sizeof(parser_state)); + } else if (state->current_cmd) + free(state->current_cmd); + state->current_cmd = cmd; + state->current_token = initstr; +} + +void parser_pushstate(void) +{ + parser_state *new_state = malloc(sizeof(parser_state)); + new_state->current_cmd = NULL; + new_state->current_token = NULL; + new_state->next = state; + state = new_state; +} + +void parser_popstate(void) +{ + parser_state *new_state = state->next; + if (state->current_cmd != NULL) + free(state->current_cmd); + free(state); + state = new_state; +} + +bool parser_end(void) +{ + eatwhitespace_c(&state->current_token); + return *state->current_token == 0; +} + +void skip_token(void) +{ + char quotechar = 0; + eatwhitespace_c(&state->current_token); + + while (*state->current_token) { + ucs4_t ucs; + size_t len; + + unsigned char utf8_character = (unsigned char)state->current_token[0]; + if (~utf8_character & 0x80) { + ucs = utf8_character; + ++state->current_token; + } else { + int ret = unicode_utf8_to_ucs4(&ucs, state->current_token, &len); + if (ret == 0) { + state->current_token += len; + } else { + log_warning("illegal character sequence in UTF8 string: %s\n", state->current_token); + } + } + if (iswxspace((wint_t) ucs) && quotechar == 0) { + return; + } else { + switch (utf8_character) { + case '"': + case '\'': + if (utf8_character == quotechar) + return; + quotechar = utf8_character; + break; + case ESCAPE_CHAR: + ++state->current_token; + break; + } + } + } +} + +const char *parse_token(const char **str) +{ + static char lbuf[MAXTOKENSIZE]; /* STATIC_RESULT: used for return, not across calls */ + char *cursor = lbuf; + char quotechar = 0; + bool escape = false; + const char *ctoken = *str; + + assert(ctoken); + + eatwhitespace_c(&ctoken); + while (*ctoken && cursor - lbuf < MAXTOKENSIZE - 1) { + ucs4_t ucs; + size_t len; + bool copy = false; + + unsigned char utf8_character = *(unsigned char *)ctoken; + if (~utf8_character & 0x80) { + ucs = utf8_character; + len = 1; + } else { + int ret = unicode_utf8_to_ucs4(&ucs, ctoken, &len); + if (ret != 0) { + log_warning("illegal character sequence in UTF8 string: %s\n", ctoken); + break; + } + } + if (escape) { + copy = true; + escape = false; + } else if (iswxspace((wint_t) ucs)) { + if (quotechar == 0) + break; + copy = true; + } else if (utf8_character == '"' || utf8_character == '\'') { + if (utf8_character == quotechar) { + ++ctoken; + break; + } else if (quotechar == 0) { + quotechar = utf8_character; + ++ctoken; + } else { + *cursor++ = *ctoken++; + } + } else if (utf8_character == SPACE_REPLACEMENT) { + *cursor++ = ' '; + ++ctoken; + } else if (utf8_character == ESCAPE_CHAR) { + escape = true; + ++ctoken; + } else { + copy = true; + } + if (copy) { + memcpy(cursor, ctoken, len); + cursor += len; + ctoken += len; + } + } + + *cursor = '\0'; + *str = ctoken; + return lbuf; +} + +const char *getstrtoken(void) +{ + return parse_token((const char **)&state->current_token); +} diff --git a/core/src/util/parser.h b/core/src/util/parser.h new file mode 100644 index 000000000..521987174 --- /dev/null +++ b/core/src/util/parser.h @@ -0,0 +1,28 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2007 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ + +#ifndef UTIL_PARSER_H +#define UTIL_PARSER_H +#ifdef __cplusplus +extern "C" { +#endif + + extern void init_tokens_str(const char *initstr, char *cmd); /* initialize token parsing, take ownership of cmd */ + extern void skip_token(void); + extern const char *parse_token(const char **str); + extern void parser_pushstate(void); + extern void parser_popstate(void); + extern bool parser_end(void); + extern const char *getstrtoken(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/rand.c b/core/src/util/rand.c new file mode 100644 index 000000000..b26bf0c12 --- /dev/null +++ b/core/src/util/rand.c @@ -0,0 +1,69 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "rand.h" +#include "rng.h" + +#include +#include +#include +#include +#include + +#define M_PIl 3.1415926535897932384626433832795029L /* pi */ + +/* NormalRand aus python, random.py geklaut, dort ist Referenz auf +* den Algorithmus. mu = Mittelwert, sigma = Standardabweichung. +* http://de.wikipedia.org/wiki/Standardabweichung#Diskrete_Gleichverteilung.2C_W.C3.BCrfel +*/ +double normalvariate(double mu, double sigma) +{ + static const double NV_MAGICCONST = 1.7155277699214135; /* STATIC_CONST: a constant */ + double z; + for (;;) { + double u1 = rng_double(); + double u2 = 1.0 - rng_double(); + z = NV_MAGICCONST * (u1 - 0.5) / u2; + if (z * z / 4.0 <= -log(u2)) { + break; + } + } + return mu + z * sigma; +} + +int ntimespprob(int n, double p, double mod) +{ + int count = 0; + int i; + + for (i = 0; i < n && p > 0; i++) { + if (rng_double() < p) { + count++; + p += mod; + } + } + return count; +} + +bool chance(double x) +{ + if (x >= 1.0) + return true; + return rng_double() < x; +} diff --git a/core/src/util/rand.h b/core/src/util/rand.h new file mode 100644 index 000000000..9223923b9 --- /dev/null +++ b/core/src/util/rand.h @@ -0,0 +1,37 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef RAND_H +#define RAND_H +#ifdef __cplusplus +extern "C" { +#endif + + /* in dice.c: */ + extern int dice_rand(const char *str); + extern int dice(int count, int value); + + /* in rand.c: */ + extern double normalvariate(double mu, double sigma); + extern int ntimespprob(int n, double p, double mod); + extern bool chance(double x); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/resolve.c b/core/src/util/resolve.c new file mode 100644 index 000000000..dbe6d2cc2 --- /dev/null +++ b/core/src/util/resolve.c @@ -0,0 +1,93 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include +#include "resolve.h" +#include "storage.h" +#include "variant.h" + +typedef struct unresolved { + void *ptrptr; + /* address to pass to the resolve-function */ + variant data; + /* information on how to resolve the missing object */ + resolve_fun resolve; + /* function to resolve the unknown object */ +} unresolved; + +#define BLOCKSIZE 1024 +static unresolved *ur_list; +static unresolved *ur_begin; +static unresolved *ur_current; + +variant read_int(struct storage *store) +{ + variant var; + var.i = store->r_int(store); + return var; +} + +int +read_reference(void *address, storage * store, read_fun reader, + resolve_fun resolver) +{ + variant var = reader(store); + int result = resolver(var, address); + if (result != 0) { + ur_add(var, address, resolver); + } + return result; +} + +void ur_add(variant data, void *ptrptr, resolve_fun fun) +{ + if (ur_list == NULL) { + ur_list = malloc(BLOCKSIZE * sizeof(unresolved)); + ur_begin = ur_current = ur_list; + } else if (ur_current - ur_begin == BLOCKSIZE - 1) { + ur_begin = malloc(BLOCKSIZE * sizeof(unresolved)); + ur_current->data.v = ur_begin; + ur_current = ur_begin; + } + ur_current->data = data; + ur_current->resolve = fun; + ur_current->ptrptr = ptrptr; + + ++ur_current; + ur_current->resolve = NULL; + ur_current->data.v = NULL; +} + +void resolve(void) +{ + unresolved *ur = ur_list; + while (ur) { + if (ur->resolve == NULL) { + ur = ur->data.v; + free(ur_list); + ur_list = ur; + continue; + } + ur->resolve(ur->data, ur->ptrptr); + ++ur; + } + free(ur_list); + ur_list = NULL; +} diff --git a/core/src/util/resolve.h b/core/src/util/resolve.h new file mode 100644 index 000000000..64b9441a6 --- /dev/null +++ b/core/src/util/resolve.h @@ -0,0 +1,42 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef RESOLVE_H +#define RESOLVE_H + +#include "variant.h" +struct storage; + +#ifdef __cplusplus +extern "C" { +#endif + + typedef int (*resolve_fun) (variant data, void *address); + typedef variant(*read_fun) (struct storage * store); + extern int read_reference(void *address, struct storage *store, + read_fun reader, resolve_fun resolver); + + extern void ur_add(variant data, void *address, resolve_fun fun); + extern void resolve(void); + + extern variant read_int(struct storage *store); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/rng.h b/core/src/util/rng.h new file mode 100644 index 000000000..8737190c6 --- /dev/null +++ b/core/src/util/rng.h @@ -0,0 +1,45 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2005 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ +#ifndef UTIL_RNG_H +#define UTIL_RNG_H +#ifdef __cplusplus +extern "C" { +#endif + +#define RNG_MT + +#ifdef RNG_MT + /* initializes mt[N] with a seed */ + extern void init_genrand(unsigned long s); + + /* generates a random number on [0,0xffffffff]-interval */ + extern unsigned long genrand_int32(void); + + /* generates a random number on [0,1)-real-interval */ + extern double genrand_real2(void); + + /* generates a random number on [0,0x7fffffff]-interval */ + long genrand_int31(void); + +# define rng_init(seed) init_genrand(seed) +# define rng_int genrand_int31 +# define rng_double genrand_real2 +# define RNG_RAND_MAX 0x7fffffff +#else +# include +# define rng_init(seed) srand(seed) +# define rng_int rand() +# define rng_double ((rand()%RAND_MAX)/(double)RAND_MAX) +# define RNG_RAND_MAX RAND_MAX +#endif +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/sql.c b/core/src/util/sql.c new file mode 100644 index 000000000..127751aa9 --- /dev/null +++ b/core/src/util/sql.c @@ -0,0 +1,81 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "sql.h" + +#include "log.h" + +#include +#include +#include + +static FILE *sqlstream = NULL; +static char *sqlfilename = NULL; + +void sql_init(const char *filename) +{ + if (sqlfilename != NULL) + free(sqlfilename); + sqlfilename = strdup(filename); +} + +void _sql_print(const char *format, ...) +{ + if (!sqlstream && sqlfilename) { + sqlstream = fopen(sqlfilename, "wt+"); + free(sqlfilename); + sqlfilename = NULL; + } + if (sqlstream != NULL) { + va_list marker; + va_start(marker, format); + vfprintf(sqlstream, format, marker); + va_end(marker); + } +} + +void sql_done(void) +{ + if (sqlstream) + fclose(sqlstream); + if (sqlfilename) + free(sqlfilename); + sqlstream = NULL; + sqlfilename = NULL; +} + +const char *sqlquote(const char *str) +{ +#define BUFFERS 4 +#define BUFSIZE 1024 + static char sqlstring[BUFSIZE * BUFFERS]; /* STATIC_RESULT: used for return, not across calls */ + static int index = 0; /* STATIC_XCALL: used across calls */ + char *start = sqlstring + index * BUFSIZE; + char *o = start; + const char *i = str; + while (*i && o - start < BUFSIZE - 1) { + if (*i != '\'' && *i != '\"') { + *o++ = *i++; + } else + ++i; + } + *o = '\0'; + index = (index + 1) % BUFFERS; + return start; +} diff --git a/core/src/util/sql.h b/core/src/util/sql.h new file mode 100644 index 000000000..2cbc9f5a1 --- /dev/null +++ b/core/src/util/sql.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_UTIL_SQL +#define H_UTIL_SQL +#ifdef __cplusplus +extern "C" { +#endif + + extern void sql_init(const char *filename); + extern void sql_done(void); + extern const char *sqlquote(const char *str); + extern void _sql_print(const char *format, ...); + +#define sql_print(x) _sql_print x + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/storage.h b/core/src/util/storage.h new file mode 100644 index 000000000..15f5ce3ae --- /dev/null +++ b/core/src/util/storage.h @@ -0,0 +1,48 @@ +#ifndef STORAGE_H +#define STORAGE_H +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct storage { + /* separator for readable files: */ + int (*w_brk) (struct storage *); + /* integer values: */ + int (*w_int) (struct storage *, int arg); + int (*r_int) (struct storage *); + /* float values: */ + int (*w_flt) (struct storage *, float arg); + float (*r_flt) (struct storage *); + /* id values: */ + int (*w_id) (struct storage *, int arg); + int (*r_id) (struct storage *); + /* tokens that contain no whitespace: */ + int (*w_tok) (struct storage *, const char *tok); + char *(*r_tok) (struct storage *); + void (*r_tok_buf) (struct storage *, char *result, size_t size); + /* strings that need to be quoted: */ + int (*w_str) (struct storage *, const char *tok); + char *(*r_str) (struct storage *); + void (*r_str_buf) (struct storage *, char *result, size_t size); + /* binary data: */ + int (*w_bin) (struct storage *, void *arg, size_t size); + void (*r_bin) (struct storage *, void *result, size_t size); + + int (*open) (struct storage *, const char *filename, int mode); + int (*close) (struct storage *); + + int encoding; + int version; + void *userdata; + } storage; + +#define IO_READ 0x01 +#define IO_WRITE 0x02 +#define IO_BINARY 0x04 +#define IO_TEXT 0x08 +#define IO_DEFAULT IO_BINARY + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/strings.c b/core/src/util/strings.c new file mode 100644 index 000000000..7efa9c032 --- /dev/null +++ b/core/src/util/strings.c @@ -0,0 +1,92 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include + +/* libc includes */ +#include + +INLINE_FUNCTION unsigned int hashstring(const char *s) +{ + unsigned int key = 0; + while (*s) { + key = key * 37 + *s++; + } + return key & 0x7FFFFFFF; +} + +INLINE_FUNCTION const char *escape_string(const char *str, char *buffer, + unsigned int len) +{ + const char *start = strchr(str, '\"'); + if (start) { + static char s_buffer[4096]; /* STATIC_RESULT: used for return, not across calls */ + const char *p; + char *o; + size_t skip = start - str; + + if (buffer == NULL) { + buffer = s_buffer; + len = sizeof(s_buffer); + } + memcpy(buffer, str, skip); + o = buffer + skip; + p = str + skip; + do { + if (*p == '\"' || *p == '\\') { + if (len < 2) { + *o = '\0'; + break; + } + (*o++) = '\\'; + len -= 2; + } else { + if (len < 1) { + *o = '\0'; + break; + } + --len; + } + (*o++) = (*p); + } while (*p++); + return buffer; + } + return str; +} + +INLINE_FUNCTION unsigned int jenkins_hash(unsigned int a) +{ + a = (a + 0x7ed55d16) + (a << 12); + a = (a ^ 0xc761c23c) ^ (a >> 19); + a = (a + 0x165667b1) + (a << 5); + a = (a + 0xd3a2646c) ^ (a << 9); + a = (a + 0xfd7046c5) + (a << 3); + a = (a ^ 0xb55a4f09) ^ (a >> 16); + return a; +} + +INLINE_FUNCTION unsigned int wang_hash(unsigned int a) +{ + a = ~a + (a << 15); /* a = (a << 15) - a - 1; */ + a = a ^ (a >> 12); + a = a + (a << 2); + a = a ^ (a >> 4); + a = a * 2057; /* a = (a + (a << 3)) + (a << 11); */ + a = a ^ (a >> 16); + return a; +} diff --git a/core/src/util/translation.c b/core/src/util/translation.c new file mode 100644 index 000000000..a96f0914f --- /dev/null +++ b/core/src/util/translation.c @@ -0,0 +1,478 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#include + +#include "translation.h" + +#include "bsdstring.h" +#include "critbit.h" +#include "log.h" + +/* libc includes */ +#include +#include +#include +#include +#include + +/** + ** simple operand stack + **/ + +typedef struct opstack { + variant *begin; + variant *top; + int size; +} opstack; + +variant opstack_pop(opstack ** stackp) +{ + opstack *stack = *stackp; + + assert(stack); + assert(stack->top > stack->begin); + return *(--stack->top); +} + +void opstack_push(opstack ** stackp, variant data) +{ + opstack *stack = *stackp; + if (stack == NULL) { + stack = (opstack *) malloc(sizeof(opstack)); + stack->size = 1; + stack->begin = malloc(sizeof(variant) * stack->size); + stack->top = stack->begin; + *stackp = stack; + } + if (stack->top - stack->begin == stack->size) { + size_t pos = stack->top - stack->begin; + stack->size += stack->size; + stack->begin = realloc(stack->begin, sizeof(variant) * stack->size); + stack->top = stack->begin + pos; + } + *stack->top++ = data; +} + +/** + ** static buffer malloc + **/ + +#define BBUFSIZE 128*1024 +static struct { + char *begin; + char *end; + char *last; + char *current; +} buffer; + +char *balloc(size_t size) +{ + static int init = 0; /* STATIC_XCALL: used across calls */ + if (!init) { + init = 1; + buffer.current = buffer.begin = malloc(BBUFSIZE); + buffer.end = buffer.begin + BBUFSIZE; + } + if (buffer.current + size > buffer.end) { + /* out of memory! */ + return NULL; + } + buffer.last = buffer.current; + buffer.current += size; + return buffer.last; +} + +void bfree(char *c) +/* only release this memory if it was part of the last allocation + * that's a joke, but who cares. + */ +{ + if (c >= buffer.last && c < buffer.current) + buffer.current = c; +} + +void brelease(void) +{ + buffer.last = buffer.current = buffer.begin; +} + +/** + ** constant values + **/ + +typedef struct variable { + struct variable *next; + const char *symbol; + variant value; +} variable; + +static variable *variables; + +static void free_variables(void) +{ + variables = NULL; +} + +static void add_variable(const char *symbol, variant value) +{ + variable *var = (variable *) balloc(sizeof(variable)); + + var->value = value; + var->symbol = symbol; + + var->next = variables; + variables = var; +} + +static variable *find_variable(const char *symbol) +{ + variable *var = variables; + while (var) { + if (!strcmp(var->symbol, symbol)) + break; + var = var->next; + } + return var; +} + +/** + ** constant values + **/ + +static struct critbit_tree functions = { 0 }; + +static void free_functions(void) +{ + cb_clear(&functions); + functions.root = 0; +} + +void add_function(const char *symbol, evalfun parse) +{ + char buffer[64]; + size_t len = strlen(symbol); + + assert(len+1+sizeof(parse)<=sizeof(buffer)); + len = cb_new_kv(symbol, len, &parse, sizeof(parse), buffer); + cb_insert(&functions, buffer, len); +} + +static evalfun find_function(const char *symbol) +{ + const void * matches; + if (cb_find_prefix(&functions, symbol, strlen(symbol)+1, &matches, 1, 0)) { + evalfun result; + cb_get_kv(matches, &result, sizeof(result)); + return result; + } + return 0; +} + +static const char *parse(opstack **, const char *in, const void *); +/* static const char * sample = "\"enno and $bool($if($eq($i,0),\"noone else\",\"$i other people\"))\""; */ + +static const char *parse_symbol(opstack ** stack, const char *in, + const void *userdata) +/* in is the symbol name and following text, starting after the $ + * result goes on the stack + */ +{ + bool braces = false; + char symbol[32]; + char *cp = symbol; /* current position */ + + if (*in == '{') { + braces = true; + ++in; + } + while (isalnum(*in) || *in == '.') + *cp++ = *in++; + *cp = '\0'; + /* symbol will now contain the symbol name */ + if (*in == '(') { + /* it's a function we need to parse, start by reading the parameters */ + evalfun foo; + + while (*in != ')') { + in = parse(stack, ++in, userdata); /* will push the result on the stack */ + if (in == NULL) + return NULL; + } + ++in; + foo = find_function(symbol); + if (foo == NULL) { + log_error("parser does not know about \"%s\" function.\n", symbol); + return NULL; + } + foo(stack, userdata); /* will pop parameters from stack (reverse order!) and push the result */ + } else { + variable *var = find_variable(symbol); + if (braces && *in == '}') { + ++in; + } + /* it's a constant (variable is a misnomer, but heck, const was taken;)) */ + if (var == NULL) { + log_error("parser does not know about \"%s\" variable.\n", symbol); + return NULL; + } + opush(stack, var->value); + } + return in; +} + +#define TOKENSIZE 4096 +static const char *parse_string(opstack ** stack, const char *in, + const void *userdata) +{ /* (char*) -> char* */ + char *c; + char *buffer = balloc(TOKENSIZE); + size_t size = TOKENSIZE - 1; + const char *ic = in; + char *oc = buffer; + /* mode flags */ + bool f_escape = false; + bool bDone = false; + variant var; + + while (*ic && !bDone) { + if (f_escape) { + f_escape = false; + switch (*ic) { + case 'n': + if (size > 0) { + *oc++ = '\n'; + --size; + } + break; + case 't': + if (size > 0) { + *oc++ = '\t'; + --size; + } + break; + default: + if (size > 0) { + *oc++ = *ic++; + --size; + } + } + } else { + int ch = (unsigned char)(*ic); + int bytes; + + switch (ch) { + case '\\': + f_escape = true; + ++ic; + break; + case '"': + bDone = true; + ++ic; + break; + case '$': + ic = parse_symbol(stack, ++ic, userdata); + if (ic == NULL) + return NULL; + c = (char *)opop_v(stack); + bytes = (int)c?strlcpy(oc, c, size):0; + if (bytes < (int)size) + oc += bytes; + else + oc += size; + bfree(c); + break; + default: + if (size > 0) { + *oc++ = *ic++; + --size; + } else + ++ic; + } + } + } + *oc++ = '\0'; + bfree(oc); + var.v = buffer; + opush(stack, var); + return ic; +} + +static const char *parse_int(opstack ** stack, const char *in) +{ + int k = 0; + int vz = 1; + bool ok = false; + variant var; + do { + switch (*in) { + case '+': + ++in; + break; + case '-': + ++in; + vz = vz * -1; + break; + default: + ok = true; + } + } while (!ok); + while (isdigit(*(unsigned char *)in)) { + k = k * 10 + (*in++) - '0'; + } + var.i = k * vz; + opush(stack, var); + return in; +} + +static const char *parse(opstack ** stack, const char *inn, + const void *userdata) +{ + const char *b = inn; + while (*b) { + switch (*b) { + case '"': + return parse_string(stack, ++b, userdata); + break; + case '$': + return parse_symbol(stack, ++b, userdata); + break; + default: + if (isdigit(*(unsigned char *)b) || *b == '-' || *b == '+') { + return parse_int(stack, b); + } else + ++b; + } + } + log_error("could not parse \"%s\". Probably invalid message syntax.", inn); + return NULL; +} + +const char *translate(const char *format, const void *userdata, + const char *vars, variant args[]) +{ + int i = 0; + const char *ic = vars; + char symbol[32]; + char *oc = symbol; + opstack *stack = NULL; + const char *rv; + + brelease(); + free_variables(); + + assert(format); + assert(*ic == 0 || isalnum(*ic)); + while (*ic) { + *oc++ = *ic++; + if (!isalnum(*ic)) { + variant x = args[i++]; + *oc = '\0'; + oc = symbol; + add_variable(strcpy(balloc(strlen(symbol) + 1), symbol), x); + while (*ic && !isalnum(*ic)) + ++ic; + } + } + + if (format[0] == '"') { + rv = parse(&stack, format, userdata); + } else { + rv = parse_string(&stack, format, userdata); + } + if (rv != NULL) { + if (rv[0]) { + log_error("residual data after parsing: %s\n", rv); + } + rv = (const char *)opop(&stack).v; + free(stack->begin); + free(stack); + } + return rv; +} + +static void eval_lt(opstack ** stack, const void *userdata) +{ /* (int, int) -> int */ + int a = opop_i(stack); + int b = opop_i(stack); + int rval = (b < a) ? 1 : 0; + opush_i(stack, rval); + unused(userdata); +} + +static void eval_eq(opstack ** stack, const void *userdata) +{ /* (int, int) -> int */ + int a = opop_i(stack); + int b = opop_i(stack); + int rval = (a == b) ? 1 : 0; + opush_i(stack, rval); + unused(userdata); +} + +static void eval_add(opstack ** stack, const void *userdata) +{ /* (int, int) -> int */ + int a = opop_i(stack); + int b = opop_i(stack); + opush_i(stack, a + b); + unused(userdata); +} + +static void eval_isnull(opstack ** stack, const void *userdata) +{ /* (int, int) -> int */ + void *a = opop_v(stack); + opush_i(stack, (a == NULL) ? 1 : 0); + unused(userdata); +} + +static void eval_if(opstack ** stack, const void *userdata) +{ /* (int, int) -> int */ + void *a = opop_v(stack); + void *b = opop_v(stack); + int cond = opop_i(stack); + opush_v(stack, cond ? b : a); + unused(userdata); +} + +static void eval_strlen(opstack ** stack, const void *userdata) +{ /* string -> int */ + const char *c = (const char *)opop_v(stack); + opush_i(stack, c ? (int)strlen(c) : 0); + unused(userdata); +} + +#include "base36.h" +static void eval_int(opstack ** stack, const void *userdata) +{ + int i = opop_i(stack); + const char *c = itoa10(i); + size_t len = strlen(c); + variant var; + + var.v = strcpy(balloc(len + 1), c); + opush(stack, var); +} + +void translation_init(void) +{ + add_function("lt", &eval_lt); + add_function("eq", &eval_eq); + add_function("int", &eval_int); + add_function("add", &eval_add); + add_function("strlen", &eval_strlen); + add_function("if", &eval_if); + add_function("isnull", &eval_isnull); +} + +void translation_done(void) +{ + free_functions(); + free(buffer.begin); +} diff --git a/core/src/util/translation.h b/core/src/util/translation.h new file mode 100644 index 000000000..f918c3357 --- /dev/null +++ b/core/src/util/translation.h @@ -0,0 +1,46 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_UTIL_TRANSLATION +#define H_UTIL_TRANSLATION +#ifdef __cplusplus +extern "C" { +#endif + +#include "variant.h" + struct opstack; + extern void opstack_push(struct opstack **stack, variant data); +#define opush_i(stack, x) { variant localvar; localvar.i = x; opstack_push(stack, localvar); } +#define opush_v(stack, x) { variant localvar; localvar.v = x; opstack_push(stack, localvar); } +#define opush(stack, i) opstack_push(stack, i) + + extern variant opstack_pop(struct opstack **stack); +#define opop_v(stack) opstack_pop(stack).v +#define opop_i(stack) opstack_pop(stack).i +#define opop(stack) opstack_pop(stack) + + extern void translation_init(void); + extern void translation_done(void); + extern const char *translate(const char *format, const void *userdata, + const char *vars, variant args[]); + +/* eval_x functions */ + typedef void (*evalfun) (struct opstack ** stack, const void *); + extern void add_function(const char *symbol, evalfun parse); + +/* transient memory blocks */ + extern char *balloc(size_t size); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/umlaut.c b/core/src/util/umlaut.c new file mode 100644 index 000000000..256ec2230 --- /dev/null +++ b/core/src/util/umlaut.c @@ -0,0 +1,263 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include "umlaut.h" + +#include "log.h" +#include "unicode.h" + +#include +#include +#include +#include +#include + +typedef struct tref { + struct tref *nexthash; + ucs4_t ucs; + void *node; +} tref; + +#define LEAF 1 /* leaf node for a word. always matches */ +#define SHARED 2 /* at least two words share the node */ + +typedef struct tnode { + struct tref *next[NODEHASHSIZE]; + unsigned char flags; + variant id; +} tnode; + +char * transliterate(char * out, size_t size, const char * in) +{ + const char *src = in; + char *dst = out; + + --size; /* need space for a final 0-byte */ + while (*src && size) { + size_t len; + const char * p = src; + while ((p+size>src) && *src && (~*src & 0x80)) { + *dst++ = (char)tolower(*src++); + } + len = src-p; + size -= len; + while (size>0 && *src && (*src & 0x80)) { + unsigned int advance = 2; + if (src[0]=='\xc3') { + if (src[1]=='\xa4' || src[1]=='\x84') { + memcpy(dst, "ae", 2); + } else if (src[1]=='\xb6' || src[1]=='\x96') { + memcpy(dst, "oe", 2); + } else if (src[1]=='\xbc' || src[1]=='\x9c') { + memcpy(dst, "ue", 2); + } else if (src[1]=='\x9f') { + memcpy(dst, "ss", 2); + } else { + advance = 0; + } + } else if (src[0]=='\xe1') { + if (src[1]=='\xba' && src[2]=='\x9e') { + memcpy(dst, "ss", 2); + ++src; + } else { + advance = 0; + } + } else { + advance = 0; + } + + if (advance && advance<=size) { + src+=advance; + dst+=advance; + size-=advance; + } else { + ucs4_t ucs; + unicode_utf8_to_ucs4(&ucs, src, &len); + src+=len; + *dst++='?'; + --size; + } + } + } + *dst = 0; + return *src ? 0 : out; +} + +void addtoken(void ** root, const char *str, variant id) +{ + tnode * tk; + static const struct replace { /* STATIC_CONST: constant value */ + ucs4_t ucs; + const char str[3]; + } replace[] = { + /* match lower-case (!) umlauts and others to transcriptions */ + { + 228, "AE"}, /* auml */ + { + 246, "OE"}, /* ouml */ + { + 252, "UE"}, /* uuml */ + { + 223, "SS"}, /* szlig */ + { + 230, "AE"}, /* norsk */ + { + 248, "OE"}, /* norsk */ + { + 229, "AA"}, /* norsk */ + { + 0, ""} + }; + + assert(root && str); + if (!*root) { + tk = *root = calloc(1, sizeof(tnode)); + } else { + tk = *root; + } + assert(tk && tk==*root); + if (!*str) { + tk->id = id; + tk->flags |= LEAF; + } else { + tref *next; + int ret, index, i = 0; + ucs4_t ucs, lcs; + size_t len; + + ret = unicode_utf8_to_ucs4(&ucs, str, &len); + assert(ret == 0 || !"invalid utf8 string"); + lcs = ucs; + +#if NODEHASHSIZE == 8 + index = ucs & 7; +#else + index = ucs % NODEHASHSIZE; +#endif + assert(index >= 0); + next = tk->next[index]; + if (!(tk->flags & LEAF)) + tk->id = id; + while (next && next->ucs != ucs) + next = next->nexthash; + if (!next) { + tref *ref; + tnode *node = (tnode *)calloc(1, sizeof(tnode)); + + if (ucs < 'a' || ucs > 'z') { + lcs = towlower((wint_t) ucs); + } + if (ucs == lcs) { + ucs = towupper((wint_t) ucs); + } + + ref = (tref *)malloc(sizeof(tref)); + ref->ucs = ucs; + ref->node = node; + ref->nexthash = tk->next[index]; + tk->next[index] = ref; + + /* try lower/upper casing the character, and try again */ + if (ucs != lcs) { +#if NODEHASHSIZE == 8 + index = lcs & 7; +#else + index = lcs % NODEHASHSIZE; +#endif + ref = (tref *)malloc(sizeof(tref)); + ref->ucs = lcs; + ref->node = node; + ref->nexthash = tk->next[index]; + tk->next[index] = ref; + } + next = ref; + } else { + tnode * next_node = (tnode *)next->node; + next_node->flags |= SHARED; + if ((next_node->flags & LEAF) == 0) + next_node->id.v = NULL; /* why? */ + } + addtoken(&next->node, str + len, id); + while (replace[i].str[0]) { + if (lcs == replace[i].ucs) { + char zText[1024]; + memcpy(zText, replace[i].str, 3); + strcpy(zText + 2, (const char *)str + len); + addtoken(root, zText, id); + break; + } + ++i; + } + } +} + +void freetokens(void * root) +{ + tnode * node = (tnode *)root; + int i; + for (i=0;node && i!=NODEHASHSIZE;++i) { + if (node->next[i]) { + freetokens(node->next[i]->node); + } + } + free(node); +} + +int findtoken(const void * root, const char *key, variant * result) +{ + const char * str = key; + const tnode * tk = (const tnode *)root; + + if (!tk || !str || *str == 0) { + return E_TOK_NOMATCH; + } + do { + int index; + const tref *ref; + ucs4_t ucs; + size_t len; + int ret = unicode_utf8_to_ucs4(&ucs, str, &len); + + if (ret != 0) { + /* encoding is broken. youch */ + log_debug("findtoken | encoding error in '%s'\n", key); + return E_TOK_NOMATCH; + } +#if NODEHASHSIZE == 8 + index = ucs & 7; +#else + index = ucs % NODEHASHSIZE; +#endif + ref = tk->next[index]; + while (ref && ref->ucs != ucs) + ref = ref->nexthash; + str += len; + if (!ref) { + log_debug("findtoken | token not found '%s'\n", key); + return E_TOK_NOMATCH; + } + tk = ref->node; + } while (*str); + if (tk) { + *result = tk->id; + return E_TOK_SUCCESS; + } + log_debug("findtoken | token not found '%s'\n", key); + return E_TOK_NOMATCH; +} diff --git a/core/src/util/umlaut.h b/core/src/util/umlaut.h new file mode 100644 index 000000000..42997b50f --- /dev/null +++ b/core/src/util/umlaut.h @@ -0,0 +1,47 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef _UMLAUT_H +#define _UMLAUT_H + +#include "variant.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define E_TOK_NOMATCH (-1) +#define E_TOK_SUCCESS 0 +#define NODEHASHSIZE 8 + + int findtoken(const void *tk, const char *str, variant * result); + void addtoken(void **root, const char *str, variant id); + void freetokens(void *root); + + char * transliterate(char * out, size_t size, const char * in); + + typedef struct local_names { + struct local_names *next; + const struct locale *lang; + void * names; + } local_names; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/umlaut_test.c b/core/src/util/umlaut_test.c new file mode 100644 index 000000000..86b07f08e --- /dev/null +++ b/core/src/util/umlaut_test.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include "umlaut.h" + +static void test_transliterate(CuTest * tc) +{ + char buffer[32]; + + CuAssertStrEquals(tc, "", transliterate(buffer, sizeof(buffer), "")); + CuAssertStrEquals(tc, "herpderp", transliterate(buffer, sizeof(buffer), "herpderp")); + CuAssertStrEquals(tc, "herpderp", buffer); + + CuAssertStrEquals(tc, "herpderp", transliterate(buffer, sizeof(buffer), "HERPDERP")); + CuAssertStrEquals(tc, "haerpdaerp", transliterate(buffer, sizeof(buffer), "h\xc3\xa4rpd\xc3\xa4rp")); + CuAssertStrEquals(tc, "aeoeuess", transliterate(buffer, sizeof(buffer), "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f")); + CuAssertStrEquals(tc, "aeoeuess", transliterate(buffer, sizeof(buffer), "\xc3\x84\xc3\x96\xc3\x9c\xe1\xba\x9e")); + + /* handle buffer that is too small */ + CuAssertStrEquals(tc, 0, transliterate(buffer, 1, "herpderp")); + CuAssertStrEquals(tc, "", buffer); + CuAssertStrEquals(tc, 0, transliterate(buffer, 3, "herpderp")); + CuAssertStrEquals(tc, "he", buffer); + CuAssertStrEquals(tc, 0, transliterate(buffer, 3, "h\xc3\xa4rpd\xc3\xa4rp")); + CuAssertStrEquals(tc, "h?", buffer); +} + +static void test_umlaut(CuTest * tc) +{ + const char * umlauts = "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"; /* auml ouml uuml szlig nul */ + void * tokens = 0; + variant id; + int result; + + /* don't crash on an empty set */ + result = findtoken(tokens, "herpderp", &id); + CuAssertIntEquals(tc, E_TOK_NOMATCH, result); + + id.i = 1; + addtoken(&tokens, "herpderp", id); + id.i = 2; + addtoken(&tokens, "derp", id); + id.i = 3; + addtoken(&tokens, umlauts, id); + + /* we can find substrings if they are significant */ + result = findtoken(tokens, "herp", &id); + CuAssertIntEquals(tc, E_TOK_SUCCESS, result); + CuAssertIntEquals(tc, 1, id.i); + + result = findtoken(tokens, "DERP", &id); + CuAssertIntEquals(tc, E_TOK_SUCCESS, result); + CuAssertIntEquals(tc, 2, id.i); + + result = findtoken(tokens, umlauts, &id); + CuAssertIntEquals(tc, E_TOK_SUCCESS, result); + CuAssertIntEquals(tc, 3, id.i); + + /* transliteration is the real magic */ + result = findtoken(tokens, "AEoeUEss", &id); + CuAssertIntEquals(tc, E_TOK_SUCCESS, result); + CuAssertIntEquals(tc, 3, id.i); + + result = findtoken(tokens, "herp-a-derp", &id); + CuAssertIntEquals(tc, E_TOK_NOMATCH, result); + + freetokens(tokens); +} + +CuSuite *get_umlaut_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_umlaut); + SUITE_ADD_TEST(suite, test_transliterate); + return suite; +} diff --git a/core/src/util/unicode.c b/core/src/util/unicode.c new file mode 100644 index 000000000..33db457e7 --- /dev/null +++ b/core/src/util/unicode.c @@ -0,0 +1,572 @@ +/* vi: set ts=2: + * +-------------------+ Christian Schlittchen + * | | Enno Rehling + * | Eressea PBEM host | Katja Zedel + * | (c) 1998 - 2007 | + * | | This program may not be used, modified or distributed + * +-------------------+ without prior permission by the authors of Eressea. + * + */ + +#include +#include "unicode.h" + +#include +#include + +#define B00000000 0x00 +#define B10000000 0x80 +#define B11000000 0xC0 +#define B11100000 0xE0 +#define B11110000 0xF0 +#define B11111000 0xF8 +#define B11111100 0xFC +#define B11111110 0xFE + +#define B00111111 0x3F +#define B00011111 0x1F +#define B00001111 0x0F +#define B00000111 0x07 +#define B00000011 0x03 +#define B00000001 0x01 + +int unicode_utf8_tolower(utf8_t * op, size_t outlen, const utf8_t * ip) +{ + while (*ip) { + ucs4_t ucs = *ip; + ucs4_t low; + size_t size = 1; + + if (ucs & 0x80) { + int ret = unicode_utf8_to_ucs4(&ucs, ip, &size); + if (ret != 0) { + return ret; + } + } + if (size > outlen) { + return ENOMEM; + } + low = towlower((wint_t) ucs); + if (low == ucs) { + memcpy(op, ip, size); + ip += size; + op += size; + outlen -= size; + } else { + ip += size; + unicode_ucs4_to_utf8(op, &size, low); + op += size; + outlen -= size; + } + } + + if (outlen <= 0) { + return ENOMEM; + } + *op = 0; + return 0; +} + +int +unicode_latin1_to_utf8(utf8_t * dst, size_t * outlen, const char *in, + size_t * inlen) +{ + int is = (int)*inlen; + int os = (int)*outlen; + const char *ip = in; + unsigned char *out = (unsigned char *)dst, *op = out; + + while (ip - in < is) { + unsigned char c = *ip; + if (c > 0xBF) { + if (op - out >= os - 1) + break; + *op++ = 0xC3; + *op++ = c - 64; + } else if (c > 0x7F) { + if (op - out >= os - 1) + break; + *op++ = 0xC2; + *op++ = c; + } else { + if (op - out >= os) + break; + *op++ = c; + } + ++ip; + } + *outlen = op - out; + *inlen = ip - in; + return (int)*outlen; +} + +int unicode_utf8_strcasecmp(const utf8_t * a, const char *b) +{ + while (*a && *b) { + int ret; + size_t size; + ucs4_t ucsa = *a, ucsb = *b; + + if (ucsa & 0x80) { + ret = unicode_utf8_to_ucs4(&ucsa, a, &size); + if (ret != 0) + return -1; + a += size; + } else + ++a; + if (ucsb & 0x80) { + ret = unicode_utf8_to_ucs4(&ucsb, b, &size); + if (ret != 0) + return -1; + b += size; + } else + ++b; + + if (ucsb != ucsa) { + ucsb = towlower((wint_t) ucsb); + ucsa = towlower((wint_t) ucsa); + if (ucsb < ucsa) + return 1; + if (ucsb > ucsa) + return -1; + } + } + if (*b) + return -1; + if (*a) + return 1; + return 0; +} + +/* Convert a UCS-4 character to UTF-8. */ +int +unicode_ucs4_to_utf8(utf8_t * utf8_character, size_t * size, + ucs4_t ucs4_character) +{ + int utf8_bytes; + + if (ucs4_character <= 0x0000007F) { + /* 0xxxxxxx */ + utf8_bytes = 1; + utf8_character[0] = (char)ucs4_character; + } else if (ucs4_character <= 0x000007FF) { + /* 110xxxxx 10xxxxxx */ + utf8_bytes = 2; + utf8_character[0] = (char)((ucs4_character >> 6) | B11000000); + utf8_character[1] = (char)((ucs4_character & B00111111) | B10000000); + } else if (ucs4_character <= 0x0000FFFF) { + /* 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_bytes = 3; + utf8_character[0] = (char)((ucs4_character >> 12) | B11100000); + utf8_character[1] = (char)(((ucs4_character >> 6) & B00111111) | B10000000); + utf8_character[2] = (char)((ucs4_character & B00111111) | B10000000); + } else if (ucs4_character <= 0x001FFFFF) { + /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_bytes = 4; + utf8_character[0] = (char)((ucs4_character >> 18) | B11110000); + utf8_character[1] = + (char)(((ucs4_character >> 12) & B00111111) | B10000000); + utf8_character[2] = (char)(((ucs4_character >> 6) & B00111111) | B10000000); + utf8_character[3] = (char)((ucs4_character & B00111111) | B10000000); + } else if (ucs4_character <= 0x03FFFFFF) { + /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_bytes = 5; + utf8_character[0] = (char)((ucs4_character >> 24) | B11111000); + utf8_character[1] = + (char)(((ucs4_character >> 18) & B00111111) | B10000000); + utf8_character[2] = + (char)(((ucs4_character >> 12) & B00111111) | B10000000); + utf8_character[3] = (char)(((ucs4_character >> 6) & B00111111) | B10000000); + utf8_character[4] = (char)((ucs4_character & B00111111) | B10000000); + } else if (ucs4_character <= 0x7FFFFFFF) { + /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_bytes = 6; + utf8_character[0] = (char)((ucs4_character >> 30) | B11111100); + utf8_character[1] = + (char)(((ucs4_character >> 24) & B00111111) | B10000000); + utf8_character[2] = + (char)(((ucs4_character >> 18) & B00111111) | B10000000); + utf8_character[3] = + (char)(((ucs4_character >> 12) & B00111111) | B10000000); + utf8_character[4] = (char)(((ucs4_character >> 6) & B00111111) | B10000000); + utf8_character[5] = (char)((ucs4_character & B00111111) | B10000000); + } else { + return EILSEQ; + } + + *size = utf8_bytes; + + return 0; +} + +/* Convert a UTF-8 encoded character to UCS-4. */ +int +unicode_utf8_to_ucs4(ucs4_t * ucs4_character, const utf8_t * utf8_string, + size_t * length) +{ + utf8_t utf8_character = utf8_string[0]; + + /* Is the character in the ASCII range? If so, just copy it to the + output. */ + if (~utf8_character & 0x80) { + *ucs4_character = utf8_character; + *length = 1; + } else if ((utf8_character & 0xE0) == 0xC0) { + /* A two-byte UTF-8 sequence. Make sure the other byte is good. */ + if (utf8_string[1] != '\0' && (utf8_string[1] & 0xC0) != 0x80) { + return EILSEQ; + } + + *ucs4_character = + ((utf8_string[1] & 0x3F) << 0) + ((utf8_character & 0x1F) << 6); + *length = 2; + } else if ((utf8_character & 0xF0) == 0xE0) { + /* A three-byte UTF-8 sequence. Make sure the other bytes are + good. */ + if ((utf8_string[1] != '\0') && + (utf8_string[1] & 0xC0) != 0x80 && + (utf8_string[2] != '\0') && (utf8_string[2] & 0xC0) != 0x80) { + return EILSEQ; + } + + *ucs4_character = + ((utf8_string[2] & 0x3F) << 0) + + ((utf8_string[1] & 0x3F) << 6) + ((utf8_character & 0x0F) << 12); + *length = 3; + } else if ((utf8_character & 0xF8) == 0xF0) { + /* A four-byte UTF-8 sequence. Make sure the other bytes are + good. */ + if ((utf8_string[1] != '\0') && + (utf8_string[1] & 0xC0) != 0x80 && + (utf8_string[2] != '\0') && + (utf8_string[2] & 0xC0) != 0x80 && + (utf8_string[3] != '\0') && (utf8_string[3] & 0xC0) != 0x80) { + return EILSEQ; + } + + *ucs4_character = + ((utf8_string[3] & 0x3F) << 0) + + ((utf8_string[2] & 0x3F) << 6) + + ((utf8_string[1] & 0x3F) << 12) + ((utf8_character & 0x07) << 18); + *length = 4; + } else if ((utf8_character & 0xFC) == 0xF8) { + /* A five-byte UTF-8 sequence. Make sure the other bytes are + good. */ + if ((utf8_string[1] != '\0') && + (utf8_string[1] & 0xC0) != 0x80 && + (utf8_string[2] != '\0') && + (utf8_string[2] & 0xC0) != 0x80 && + (utf8_string[3] != '\0') && + (utf8_string[3] & 0xC0) != 0x80 && + (utf8_string[4] != '\0') && (utf8_string[4] & 0xC0) != 0x80) { + return EILSEQ; + } + + *ucs4_character = + ((utf8_string[4] & 0x3F) << 0) + + ((utf8_string[3] & 0x3F) << 6) + + ((utf8_string[2] & 0x3F) << 12) + + ((utf8_string[1] & 0x3F) << 18) + ((utf8_character & 0x03) << 24); + *length = 5; + } else if ((utf8_character & 0xFE) == 0xFC) { + /* A six-byte UTF-8 sequence. Make sure the other bytes are + good. */ + if ((utf8_string[1] != '\0') && + (utf8_string[1] & 0xC0) != 0x80 && + (utf8_string[2] != '\0') && + (utf8_string[2] & 0xC0) != 0x80 && + (utf8_string[3] != '\0') && + (utf8_string[3] & 0xC0) != 0x80 && + (utf8_string[4] != '\0') && + (utf8_string[4] & 0xC0) != 0x80 && + (utf8_string[5] != '\0') && (utf8_string[5] & 0xC0) != 0x80) { + return EILSEQ; + } + + *ucs4_character = + ((utf8_string[5] & 0x3F) << 0) + + ((utf8_string[4] & 0x3F) << 6) + + ((utf8_string[3] & 0x3F) << 12) + + ((utf8_string[2] & 0x3F) << 18) + + ((utf8_string[1] & 0x3F) << 24) + ((utf8_character & 0x01) << 30); + *length = 6; + } else { + return EILSEQ; + } + + return 0; +} + +/** Convert a UTF-8 encoded character to CP437. */ +int +unicode_utf8_to_cp437(char *cp_character, const utf8_t * utf8_string, + size_t * length) +{ + ucs4_t ucs4_character; + int result; + + result = unicode_utf8_to_ucs4(&ucs4_character, utf8_string, length); + if (result != 0) { + /* pass decoding characters upstream */ + return result; + } + + if (ucs4_character < 0x7F) { + *cp_character = (char)ucs4_character; + } else { + struct { + ucs4_t ucs4; + unsigned char cp437; + } xref[160] = { + { + 0x00A0, 255}, { + 0x00A1, 173}, { + 0x00A2, 155}, { + 0x00A3, 156}, { + 0x00A5, 157}, { + 0x00A7, 21}, { + 0x00AA, 166}, { + 0x00AB, 174}, { + 0x00AC, 170}, { + 0x00B0, 248}, { + 0x00B1, 241}, { + 0x00B2, 253}, { + 0x00B5, 230}, { + 0x00B6, 20}, { + 0x00B7, 250}, { + 0x00BA, 167}, { + 0x00BB, 175}, { + 0x00BC, 172}, { + 0x00BD, 171}, { + 0x00BF, 168}, { + 0x00C4, 142}, { + 0x00C5, 143}, { + 0x00C6, 146}, { + 0x00C7, 128}, { + 0x00C9, 144}, { + 0x00D1, 165}, { + 0x00D6, 153}, { + 0x00DC, 154}, { + 0x00DF, 225}, { + 0x00E0, 133}, { + 0x00E1, 160}, { + 0x00E2, 131}, { + 0x00E4, 132}, { + 0x00E5, 134}, { + 0x00E6, 145}, { + 0x00E7, 135}, { + 0x00E8, 138}, { + 0x00E9, 130}, { + 0x00EA, 136}, { + 0x00EB, 137}, { + 0x00EC, 141}, { + 0x00ED, 161}, { + 0x00EE, 140}, { + 0x00EF, 139}, { + 0x00F1, 164}, { + 0x00F2, 149}, { + 0x00F3, 162}, { + 0x00F4, 147}, { + 0x00F6, 148}, { + 0x00F7, 246}, { + 0x00F9, 151}, { + 0x00FA, 163}, { + 0x00FB, 150}, { + 0x00FC, 129}, { + 0x00FF, 152}, { + 0x0192, 159}, { + 0x0393, 226}, { + 0x0398, 233}, { + 0x03A3, 228}, { + 0x03A6, 232}, { + 0x03A9, 234}, { + 0x03B1, 224}, { + 0x03B4, 235}, { + 0x03B5, 238}, { + 0x03C0, 227}, { + 0x03C3, 229}, { + 0x03C4, 231}, { + 0x03C6, 237}, { + 0x2022, 7}, { + 0x203C, 19}, { + 0x207F, 252}, { + 0x20A7, 158}, { + 0x2190, 27}, { + 0x2191, 24}, { + 0x2192, 26}, { + 0x2193, 25}, { + 0x2194, 29}, { + 0x2195, 18}, { + 0x21A8, 23}, { + 0x2219, 249}, { + 0x221A, 251}, { + 0x221E, 236}, { + 0x221F, 28}, { + 0x2229, 239}, { + 0x2248, 247}, { + 0x2261, 240}, { + 0x2264, 243}, { + 0x2265, 242}, { + 0x2302, 127}, { + 0x2310, 169}, { + 0x2320, 244}, { + 0x2321, 245}, { + 0x2500, 196}, { + 0x2502, 179}, { + 0x250C, 218}, { + 0x2510, 191}, { + 0x2514, 192}, { + 0x2518, 217}, { + 0x251C, 195}, { + 0x2524, 180}, { + 0x252C, 194}, { + 0x2534, 193}, { + 0x253C, 197}, { + 0x2550, 205}, { + 0x2551, 186}, { + 0x2552, 213}, { + 0x2553, 214}, { + 0x2554, 201}, { + 0x2555, 184}, { + 0x2556, 183}, { + 0x2557, 187}, { + 0x2558, 212}, { + 0x2559, 211}, { + 0x255A, 200}, { + 0x255B, 190}, { + 0x255C, 189}, { + 0x255D, 188}, { + 0x255E, 198}, { + 0x255F, 199}, { + 0x2560, 204}, { + 0x2561, 181}, { + 0x2562, 182}, { + 0x2563, 185}, { + 0x2564, 209}, { + 0x2565, 210}, { + 0x2566, 203}, { + 0x2567, 207}, { + 0x2568, 208}, { + 0x2569, 202}, { + 0x256A, 216}, { + 0x256B, 215}, { + 0x256C, 206}, { + 0x2580, 223}, { + 0x2584, 220}, { + 0x2588, 219}, { + 0x258C, 221}, { + 0x2590, 222}, { + 0x2591, 176}, { + 0x2592, 177}, { + 0x2593, 178}, { + 0x25A0, 254}, { + 0x25AC, 22}, { + 0x25B2, 30}, { + 0x25BA, 16}, { + 0x25BC, 31}, { + 0x25C4, 17}, { + 0x25CB, 9}, { + 0x25D8, 8}, { + 0x25D9, 10}, { + 0x263A, 1}, { + 0x263B, 2}, { + 0x263C, 15}, { + 0x2640, 12}, { + 0x2642, 11}, { + 0x2660, 6}, { + 0x2663, 5}, { + 0x2665, 3}, { + 0x2666, 4}, { + 0x266A, 13}, { + 0x266B, 14} + }; + int l = 0, r = 160; + while (l != r) { + int m = (l + r) / 2; + if (xref[m].ucs4 == ucs4_character) { + *cp_character = (char)xref[m].cp437; + break; + } else if (xref[m].ucs4 < ucs4_character) + l = m; + else + r = m; + } + if (l == r) { + *cp_character = '?'; + } + } + return 0; +} + +/** Convert a UTF-8 encoded character to CP1252. */ +int +unicode_utf8_to_cp1252(char *cp_character, const utf8_t * utf8_string, + size_t * length) +{ + ucs4_t ucs4_character; + int result; + + result = unicode_utf8_to_ucs4(&ucs4_character, utf8_string, length); + if (result != 0) { + /* pass decoding characters upstream */ + return result; + } + + if (ucs4_character <= 0x7F || ucs4_character >= 0xA0) { + *cp_character = (char)ucs4_character; + } else { + struct { + ucs4_t ucs4; + unsigned char cp; + } xref[] = { + { + 0x20ac, 0x80}, { + 0x0081, 0x81}, { + 0x201a, 0x82}, { + 0x0192, 0x83}, { + 0x201e, 0x84}, { + 0x2026, 0x85}, { + 0x2020, 0x86}, { + 0x2021, 0x87}, { + 0x02c6, 0x88}, { + 0x2030, 0x89}, { + 0x0160, 0x8a}, { + 0x2039, 0x8b}, { + 0x0152, 0x8c}, { + 0x008d, 0x8d}, { + 0x017d, 0x8e}, { + 0x008f, 0x8f}, { + 0x0090, 0x90}, { + 0x2018, 0x91}, { + 0x2019, 0x92}, { + 0x201c, 0x93}, { + 0x201d, 0x94}, { + 0x2022, 0x95}, { + 0x2013, 0x96}, { + 0x2014, 0x97}, { + 0x02dc, 0x98}, { + 0x2122, 0x99}, { + 0x0161, 0x9a}, { + 0x203a, 0x9b}, { + 0x0153, 0x9c}, { + 0x009d, 0x9d}, { + 0x017e, 0x9e}, { + 0x0178, 0x9f} + }; + int l = 0, r = sizeof(xref) / sizeof(xref[0]); + while (l != r) { + int m = (l + r) / 2; + if (xref[m].ucs4 == ucs4_character) { + *cp_character = (char)xref[m].cp; + break; + } else if (xref[m].ucs4 < ucs4_character) + l = m; + else + r = m; + } + if (l == r) { + *cp_character = '?'; + } + } + return 0; +} diff --git a/core/src/util/unicode.h b/core/src/util/unicode.h new file mode 100644 index 000000000..ad0dbd3ae --- /dev/null +++ b/core/src/util/unicode.h @@ -0,0 +1,48 @@ +/* +Copyright (c) 1998-2010, Enno Rehling + Katja Zedel + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef _UNICODE_H +#define _UNICODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#define USE_UNICODE + typedef unsigned long ucs4_t; + typedef char utf8_t; + + extern int unicode_utf8_to_cp437(char *result, const utf8_t * utf8_string, + size_t * length); + extern int unicode_utf8_to_cp1252(char *result, const utf8_t * utf8_string, + size_t * length); + extern int unicode_utf8_to_ucs4(ucs4_t * result, const utf8_t * utf8_string, + size_t * length); + extern int unicode_ucs4_to_utf8(utf8_t * result, size_t * size, + ucs4_t ucs4_character); + extern int unicode_utf8_strcasecmp(const utf8_t * a, const utf8_t * b); + extern int unicode_latin1_to_utf8(utf8_t * out, size_t * outlen, + const char *in, size_t * inlen); + extern int unicode_utf8_tolower(utf8_t * out, size_t outlen, + const utf8_t * in); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/variant.h b/core/src/util/variant.h new file mode 100644 index 000000000..b65ee0868 --- /dev/null +++ b/core/src/util/variant.h @@ -0,0 +1,25 @@ +#ifndef STRUCT_VARIANT_H +#define STRUCT_VARIANT_H +#ifdef __cplusplus +extern "C" { +#endif + + typedef union variant { + void *v; + int i; + char c; + short s; + short sa[2]; + char ca[4]; + float f; + } variant; + + typedef enum variant_type { + VAR_NONE, VAR_INT, VAR_VOIDPTR, VAR_CHAR, VAR_SHORT, VAR_SHORTA, VAR_CHARA, + VAR_FLOAT + } variant_type; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/src/util/xml.c b/core/src/util/xml.c new file mode 100644 index 000000000..31a1bb542 --- /dev/null +++ b/core/src/util/xml.c @@ -0,0 +1,136 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ +#include +#include "xml.h" + +/* util includes */ +#include "log.h" + +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include + +const xmlChar *xml_i(double number) +{ + static char buffer[128]; + snprintf(buffer, sizeof(buffer), "%.0f", number); + return (const xmlChar *)buffer; +} + +int xml_ivalue(xmlNodePtr node, const char *name, int dflt) +{ + int i = dflt; + xmlChar *propValue = xmlGetProp(node, BAD_CAST name); + if (propValue != NULL) { + i = atoi((const char *)propValue); + xmlFree(propValue); + } + return i; +} + +bool xml_bvalue(xmlNodePtr node, const char *name, bool dflt) +{ + bool result = dflt; + xmlChar *propValue = xmlGetProp(node, BAD_CAST name); + if (propValue != NULL) { + if (strcmp((const char *)propValue, "no") == 0) + result = false; + else if (strcmp((const char *)propValue, "yes") == 0) + result = true; + else if (strcmp((const char *)propValue, "false") == 0) + result = false; + else if (strcmp((const char *)propValue, "true") == 0) + result = true; + else if (strcmp((const char *)propValue, "1") == 0) { + log_warning("bool value is '1': %s::%s\n", node->name, name); + result = true; + } else if (strcmp((const char *)propValue, "0") == 0) { + log_warning("bool value is '0': %s::%s\n", node->name, name); + result = false; + } + xmlFree(propValue); + } + return result; +} + +double xml_fvalue(xmlNodePtr node, const char *name, double dflt) +{ + double result = dflt; + xmlChar *propValue = xmlGetProp(node, BAD_CAST name); + if (propValue != NULL) { + result = atof((const char *)propValue); + xmlFree(propValue); + } + return result; +} + +/* new xml functions */ +/* libxml includes */ +#include +#include +#include + +typedef struct xml_reader { + struct xml_reader *next; + xml_callback callback; +} xml_reader; + +static xml_reader *xmlReaders; + +void xml_register_callback(xml_callback callback) +{ + xml_reader *reader = (xml_reader *) malloc(sizeof(xml_reader)); + xml_reader **insert = &xmlReaders; + reader->callback = callback; + reader->next = NULL; + + while (*insert) + insert = &(*insert)->next; + *insert = reader; +} + +int read_xml(const char *filename, const char *catalog) +{ + xml_reader *reader = xmlReaders; + xmlDocPtr doc; + + if (catalog) { + xmlLoadCatalog(catalog); + } +#ifdef XML_PARSE_XINCLUDE + doc = xmlReadFile(filename, NULL, XML_PARSE_XINCLUDE); +#else + doc = xmlParseFile(filename); +#endif + if (doc == NULL) { + log_error("could not open %s\n", filename); + return -1; + } + + xmlXIncludeProcess(doc); + + while (reader != NULL) { + int i = reader->callback(doc); + if (i != 0) { + return i; + } + reader = reader->next; + } + xmlFreeDoc(doc); + return 0; +} diff --git a/core/src/util/xml.h b/core/src/util/xml.h new file mode 100644 index 000000000..42b9178bb --- /dev/null +++ b/core/src/util/xml.h @@ -0,0 +1,35 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2003 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#ifndef H_UTIL_XML +#define H_UTIL_XML + +#ifdef __cplusplus +extern "C" { +#endif + + /* new xml functions: */ +#include + + typedef int (*xml_callback) (xmlDocPtr); + extern void xml_register_callback(xml_callback callback); + extern int read_xml(const char *filename, const char *catalog); + extern double xml_fvalue(xmlNodePtr node, const char *name, double dflt); + extern int xml_ivalue(xmlNodePtr node, const char *name, int dflt); + extern bool xml_bvalue(xmlNodePtr node, const char *name, bool dflt); + + const xmlChar *xml_i(double number); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/core/tools/atoi36.c b/core/tools/atoi36.c new file mode 100644 index 000000000..b93fab3a4 --- /dev/null +++ b/core/tools/atoi36.c @@ -0,0 +1,36 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2001 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. +*/ + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int i = 1, reverse = 0; + if (strstr(argv[0], "itoa36")) + reverse = 1; + if (argc > 1) { + if (strcmp(argv[1], "-r") == 0) { + i = 2; + reverse = 1; + } + } + for (; i != argc; ++i) { + if (reverse) { + printf("%s -> %s\n", argv[i], itoa36(atoi(argv[i]))); + } else + printf("%s -> %d\n", argv[i], atoi36(argv[i])); + } + return 0; +} diff --git a/core/tools/gethash.c b/core/tools/gethash.c new file mode 100644 index 000000000..75054b11e --- /dev/null +++ b/core/tools/gethash.c @@ -0,0 +1,24 @@ +#include +#include +#include + +int main(int argc, char **argv) +{ + char key[4]; + char code[4]; + char result[4]; + int a, i, rot; + for (a = 1; a < argc; ++a) { + const char *str = argv[a]; + size_t len = strlen(str); + str = str + len - 6; + memcpy(key, str, 3); + memcpy(code, str + 3, 3); + result[3] = key[3] = code[3] = 0; + rot = atoi(key); + for (i = 0; i != 3; ++i) + result[(i + rot) % 3] = ((code[i] + 10 - key[i]) % 10) + '0'; + printf("%s %s\n", argv[a], result); + } + return 0; +} diff --git a/core/tools/namegen.c b/core/tools/namegen.c new file mode 100644 index 000000000..924524179 --- /dev/null +++ b/core/tools/namegen.c @@ -0,0 +1,244 @@ +/* vi: set ts=2: + +-------------------+ Christian Schlittchen + | | Enno Rehling + | Eressea PBEM host | Katja Zedel + | (c) 1998 - 2001 | Henning Peters + | | Ingo Wilken + +-------------------+ Stefan Reich + + This program may not be used, modified or distributed + without prior permission by the authors of Eressea. + + */ + +#include +#include +#include + +static char *dwarf_syllable1[] = { + "B", "D", "F", "G", "Gl", "H", "K", "L", "M", "N", "R", "S", "T", "Th", "V", +}; + +static char *dwarf_syllable2[] = { + "a", "e", "i", "o", "oi", "u", +}; + +static char *dwarf_syllable3[] = { + "bur", "fur", "gan", "gnus", "gnar", "li", "lin", "lir", "mli", "nar", "nus", + "rin", "ran", "sin", "sil", "sur", +}; + +static char *elf_syllable1[] = { + "Al", "An", "Bal", "Bel", "Cal", "Cel", "El", "Elr", "Elv", "Eow", "Ear", "F", + "Fal", "Fel", "Fin", "G", "Gal", "Gel", "Gl", "Is", "Lan", "Leg", "Lom", + "N", "Nal", "Nel", "S", "Sal", "Sel", "T", "Tal", "Tel", "Thr", "Tin", +}; + +static char *elf_syllable2[] = { + "a", "adrie", "ara", "e", "ebri", "ele", "ere", "i", "io", "ithra", "ilma", + "il-Ga", "ili", "o", "orfi", "u", "y", +}; + +static char *elf_syllable3[] = { + "l", "las", "lad", "ldor", "ldur", "linde", "lith", "mir", "n", "nd", "ndel", + "ndil", "ndir", "nduil", "ng", "mbor", "r", "rith", "ril", "riand", "rion", + "s", "thien", "viel", "wen", "wyn", +}; + +static char *gnome_syllable1[] = { + "Aar", "An", "Ar", "As", "C", "H", "Han", "Har", "Hel", "Iir", "J", "Jan", + "Jar", "K", "L", "M", "Mar", "N", "Nik", "Os", "Ol", "P", "R", "S", "Sam", + "San", "T", "Ter", "Tom", "Ul", "V", "W", "Y", +}; + +static char *gnome_syllable2[] = { + "a", "aa", "ai", "e", "ei", "i", "o", "uo", "u", "uu", +}; + +static char *gnome_syllable3[] = { + "ron", "re", "la", "ki", "kseli", "ksi", "ku", "ja", "ta", "na", "namari", + "neli", "nika", "nikki", "nu", "nukka", "ka", "ko", "li", "kki", "rik", + "po", "to", "pekka", "rjaana", "rjatta", "rjukka", "la", "lla", "lli", "mo", + "nni", +}; + +static char *hobbit_syllable1[] = { + "B", "Ber", "Br", "D", "Der", "Dr", "F", "Fr", "G", "H", "L", "Ler", "M", + "Mer", "N", "P", "Pr", "Per", "R", "S", "T", "W", +}; + +static char *hobbit_syllable2[] = { + "a", "e", "i", "ia", "o", "oi", "u", +}; + +static char *hobbit_syllable3[] = { + "bo", "ck", "decan", "degar", "do", "doc", "go", "grin", "lba", "lbo", "lda", + "ldo", "lla", "ll", "lo", "m", "mwise", "nac", "noc", "nwise", "p", "ppin", + "pper", "tho", "to", +}; + +static char *human_syllable1[] = { + "Ab", "Ac", "Ad", "Af", "Agr", "Ast", "As", "Al", "Adw", "Adr", "Ar", "B", + "Br", "C", "Cr", "Ch", "Cad", "D", "Dr", "Dw", "Ed", "Eth", "Et", "Er", + "El", "Eow", "F", "Fr", "G", "Gr", "Gw", "Gal", "Gl", "H", "Ha", "Ib", + "Jer", "K", "Ka", "Ked", "L", "Loth", "Lar", "Leg", "M", "Mir", "N", "Nyd", + "Ol", "Oc", "On", "P", "Pr", "R", "Rh", "S", "Sev", "T", "Tr", "Th", "V", + "Y", "Z", "W", "Wic", +}; + +static char *human_syllable2[] = { + "a", "ae", "au", "ao", "are", "ale", "ali", "ay", "ardo", "e", "ei", "ea", + "eri", "era", "ela", "eli", "enda", "erra", "i", "ia", "ie", "ire", "ira", + "ila", "ili", "ira", "igo", "o", "oa", "oi", "oe", "ore", "u", "y", +}; + +static char *human_syllable3[] = { + "a", "and", "b", "bwyn", "baen", "bard", "c", "ctred", "cred", "ch", "can", + "d", "dan", "don", "der", "dric", "dfrid", "dus", "f", "g", "gord", "gan", + "l", "li", "lgrin", "lin", "lith", "lath", "loth", "ld", "ldric", "ldan", + "m", "mas", "mos", "mar", "mond", "n", "nydd", "nidd", "nnon", "nwan", + "nyth", "nad", "nn", "nnor", "nd", "p", "r", "ron", "rd", "s", "sh", "seth", + "sean", "t", "th", "tha", "tlan", "trem", "tram", "v", "vudd", "w", "wan", + "win", "wyn", "wyr", "wyr", "wyth", +}; + +static char *orc_syllable1[] = { + "B", "Er", "G", "Gr", "H", "P", "Pr", "R", "V", "Vr", "T", "Tr", "M", "Dr", +}; + +static char *orc_syllable2[] = { + "a", "i", "o", "oo", "u", "ui", +}; + +static char *orc_syllable3[] = { + "dash", "dish", "dush", "gar", "gor", "gdush", "lo", "gdish", "k", "lg", + "nak", "rag", "rbag", "rg", "rk", "ng", "nk", "rt", "ol", "urk", "shnak", + "mog", "mak", "rak", +}; + +static char *entish_syllable1[] = { + "Baum", "Wurzel", "Rinden", "Ast", "Blatt", +}; + +static char *entish_syllable2[] = { + "-", +}; + +static char *entish_syllable3[] = { + "Hüter", "Pflanzer", "Hirte", "Wächter", "Wachser", "Beschützer", +}; + +static char *cthuloid_syllable1[] = { + "Cth", "Az", "Fth", "Ts", "Xo", "Q'N", "R'L", "Ghata", "L", "Zz", "Fl", "Cl", + "S", "Y", +}; + +static char *cthuloid_syllable2[] = { + "nar", "loi", "ul", "lu", "noth", "thon", "ath", "'N", "rhy", "oth", "aza", + "agn", "oa", "og", +}; + +static char *cthuloid_syllable3[] = { + "l", "a", "u", "oa", "oggua", "oth", "ath", "aggua", "lu", "lo", "loth", + "lotha", "agn", "axl", +}; + +static char *create_random_name(race_t race) +{ + static char name[64]; + + switch (race) { + case RC_DWARF: + strcpy(name, + dwarf_syllable1[rng_int() % (sizeof(dwarf_syllable1) / + sizeof(char *))]); + strcat(name, + dwarf_syllable2[rand() % (sizeof(dwarf_syllable2) / sizeof(char *))]); + strcat(name, + dwarf_syllable3[rand() % (sizeof(dwarf_syllable3) / sizeof(char *))]); + break; + case RC_ELF: + strcpy(name, + elf_syllable1[rand() % (sizeof(elf_syllable1) / sizeof(char *))]); + strcat(name, + elf_syllable2[rand() % (sizeof(elf_syllable2) / sizeof(char *))]); + strcat(name, + elf_syllable3[rand() % (sizeof(elf_syllable3) / sizeof(char *))]); + break; +/* + case RACE_GNOME: + strcpy(name, gnome_syllable1[rand()%(sizeof(gnome_syllable1) / sizeof(char*))]); + strcat(name, gnome_syllable2[rand()%(sizeof(gnome_syllable2) / sizeof(char*))]); + strcat(name, gnome_syllable3[rand()%(sizeof(gnome_syllable3) / sizeof(char*))]); + break; +*/ + case RC_HALFLING: + strcpy(name, + hobbit_syllable1[rand() % (sizeof(hobbit_syllable1) / sizeof(char *))]); + strcat(name, + hobbit_syllable2[rand() % (sizeof(hobbit_syllable2) / sizeof(char *))]); + strcat(name, + hobbit_syllable3[rand() % (sizeof(hobbit_syllable3) / sizeof(char *))]); + break; + case RC_HUMAN: + case RC_AQUARIAN: + case RC_CAT: + strcpy(name, + human_syllable1[rand() % (sizeof(human_syllable1) / sizeof(char *))]); + strcat(name, + human_syllable2[rand() % (sizeof(human_syllable2) / sizeof(char *))]); + strcat(name, + human_syllable3[rand() % (sizeof(human_syllable3) / sizeof(char *))]); + break; + case RC_ORC: + case RC_TROLL: + case RC_GOBLIN: + strcpy(name, + orc_syllable1[rand() % (sizeof(orc_syllable1) / sizeof(char *))]); + strcat(name, + orc_syllable2[rand() % (sizeof(orc_syllable2) / sizeof(char *))]); + strcat(name, + orc_syllable3[rand() % (sizeof(orc_syllable3) / sizeof(char *))]); + break; +/* + case RC_TREEMAN: + strcpy(name, entish_syllable1[rand()%(sizeof(entish_syllable1) / sizeof(char*))]); + strcat(name, entish_syllable2[rand()%(sizeof(entish_syllable2) / sizeof(char*))]); + strcat(name, entish_syllable3[rand()%(sizeof(entish_syllable3) / sizeof(char*))]); + break; +*/ + case RC_DAEMON: + case RC_INSECT: + strcpy(name, + cthuloid_syllable1[rand() % (sizeof(cthuloid_syllable1) / + sizeof(char *))]); + strcat(name, + cthuloid_syllable2[rand() % (sizeof(cthuloid_syllable2) / + sizeof(char *))]); + strcat(name, + cthuloid_syllable3[rand() % (sizeof(cthuloid_syllable3) / + sizeof(char *))]); + break; + default: + name[0] = 0; + break; + } + + return name; +} + +int main(void) +{ + race_t race; + + for (race = 0; race < 11; race++) { + int i; + printf("%d:", (int)race); + for (i = 0; i < 20; i++) { + printf(" %s", create_random_name(race)); + } + printf("\n"); + } + + return 0; +}