diff --git a/.gitignore b/.gitignore index 308c9bba6..db818e4ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,29 @@ -build*/ +*~ +*.bak bin/ +build*/ +game-e2/data +game-e2/*.log* +game-e2/reports/ +game-e3/data/ +game-e3/*.log* +game-e3/reports/ +*.log +tags + +# Microsoft Visual Studio build artefacts +src/Debug/ +src/Release/ +src/*.vcproj.*.user Debug/ Release/ -*~ -*.user -*.pdb -*.suo -*.sdf -*.opensdf +ipch/ *.ipch +*.ncb +*.opensdf +*.pdb +*.sdf *.sh -src/*.vcproj.*.user -tags +*.suo +*.user + diff --git a/.gitmodules b/.gitmodules index d39d8b368..6c1fa8fa7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "iniparser"] path = iniparser url = git://github.com/badgerman/iniparser.git +[submodule "cJSON"] + path = cJSON + url = git://github.com/kbranigan/cJSON.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 5258613d6..4e1a35b1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 2.6) -project (arda-server C) +project (eressea-server C) enable_testing() @@ -14,6 +14,12 @@ CONFIGURE_FILE ( ${CMAKE_BINARY_DIR}/include/config.h) INCLUDE_DIRECTORIES (${CMAKE_BINARY_DIR}/include) +IF(CMAKE_COMPILER_IS_GNUCC) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -DHAVE__BOOL") +ELSE(CMAKE_COMPILER_IS_GNUCC) + MESSAGE(STATUS "Unknown compiler ${CMAKE_C_COMPILER_ID}") +ENDIF(CMAKE_COMPILER_IS_GNUCC) + find_package (Lua 5 REQUIRED) find_package (ToLua REQUIRED) find_package (LibXml2 REQUIRED) diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..573ba6503 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +all: bin/eressea + +bin: + mkdir bin + +bin/eressea: bin/Makefile + cd bin ; make + +bin/Makefile: bin + cd bin ; cmake .. + +clean: + rm -rf bin diff --git a/all.sln b/all.sln new file mode 100644 index 000000000..e7f6b465a --- /dev/null +++ b/all.sln @@ -0,0 +1,52 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kernel", "..\shared\src\kernel.vcproj", "{6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gamecode", "..\shared\src\gamecode.vcproj", "{1E8BFF9E-3044-0742-992F-C5765B80FE65}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "util", "..\shared\src\util.vcproj", "{F70CFB27-8A2F-E447-B452-4E1C590EDA6D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lua-bindings", "..\shared\src\lua-bindings.vcproj", "{75501170-51C2-E641-BA8B-EDC008184192}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "external", "..\external\external.vcproj", "{F9AE4586-8F65-486B-9666-744839E40A54}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "amalgamation-eressea", "src\eressea.vcproj", "{AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "amalgamation-example", "..\example\src\example.vcproj", "{4A17DAEE-2261-4E2C-96F6-BA4132A09551}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Debug|Win32.ActiveCfg = Debug|Win32 + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Release|Win32.ActiveCfg = Release|Win32 + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Release|Win32.Build.0 = Release|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Debug|Win32.ActiveCfg = Debug|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Release|Win32.ActiveCfg = Release|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Release|Win32.Build.0 = Release|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Debug|Win32.ActiveCfg = Debug|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Release|Win32.ActiveCfg = Release|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Release|Win32.Build.0 = Release|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Debug|Win32.ActiveCfg = Debug|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Release|Win32.ActiveCfg = Release|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Release|Win32.Build.0 = Release|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Debug|Win32.ActiveCfg = Debug|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Release|Win32.ActiveCfg = Release|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Release|Win32.Build.0 = Release|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Debug|Win32.ActiveCfg = Debug|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Debug|Win32.Build.0 = Debug|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Release|Win32.ActiveCfg = Release|Win32 + {4A17DAEE-2261-4E2C-96F6-BA4132A09551}.Debug|Win32.ActiveCfg = Debug|Win32 + {4A17DAEE-2261-4E2C-96F6-BA4132A09551}.Debug|Win32.Build.0 = Debug|Win32 + {4A17DAEE-2261-4E2C-96F6-BA4132A09551}.Release|Win32.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(SubversionScc) = preSolution + Svn-Managed = True + Manager = AnkhSVN - Subversion Support for Visual Studio + EndGlobalSection +EndGlobal diff --git a/cJSON b/cJSON new file mode 160000 index 000000000..43241a78d --- /dev/null +++ b/cJSON @@ -0,0 +1 @@ +Subproject commit 43241a78df3abc76ad7fc3d80f81e0b1db83a890 diff --git a/configure b/configure new file mode 100755 index 000000000..8bebf3813 --- /dev/null +++ b/configure @@ -0,0 +1,32 @@ +#!/bin/sh +MACHINE=`uname -m` +BIN_DIR="build-$MACHINE-Debug" +[ -z "$CC" ] && [ ! -z `which gcc` ] && CC="gcc" +[ -z "$CC" ] && [ ! -z `which tcc` ] && CC="tcc" +[ -z "$CC" ] && [ ! -z `which cc` ] && CC="cc" +[ -z "$JOBS" ] && JOBS=1 + +DISTCC=`which distcc` +if [ ! -z "$DISTCC" ] ; then +JOBS=`distcc -j` +if [ -z "$JOBS" ] ; then +JOBS=1 +elif [ $JOBS -gt 1 ] ; then +CC="distcc $CC" +MAKEOPTS=-j$JOBS +fi +fi +echo "Building with $CC and $JOBS jobs" + +mkdir -p $BIN_DIR +cd $BIN_DIR +CC="$CC" cmake .. -DCMAKE_MODULE_PATH=$PWD/../cmake/Modules -DCMAKE_BUILD_TYPE=Debug +make -j$JOBS +make test +cd .. +for GAME in game* ; do + cd $GAME + ln -sf ../$BIN_DIR/server/server + ./server -e run_tests + cd .. +done diff --git a/doc/bugs.txt b/doc/bugs.txt new file mode 100644 index 000000000..f2b5a0a0a --- /dev/null +++ b/doc/bugs.txt @@ -0,0 +1,923 @@ +BUGS + +? > nicht untersucht, fraglich +- > bestätigt, aber unerledigt ++ > erledigt +(!) kritische Bugs, die unbedingt vor der nächsten Auswertung repariert werden müssen + ++ Für alle nur den kurzen Report. War Fehler in einem logischen Ausdruck + in creport.c + ++ URSPRUNG hat eine kranke Semantik + (enno) repariert. Nach der alten Routine konnte man seine Koordinaten durch Neusetzen ermitteln! + ++ Die neuen Kampfmeldungen könnten Kosmetik brauchen, à la: Einheit(en) + (corwin) Das Gröbste verbessert. Könnte aber noch mehr Arbeit brauchen. + ++ im CR fehlen die Botschaften + (enno) Hatte Sören nicht gemacht. ist im standard-cr jetzt drin. + ++ Orks können die Vermehrungsmeldungen nicht separat abschalten + (Henning) MSG_ORKVERMEHRUNG eingebaut, ist ML_INFO, kann also mit REPORT ORK WARN + abgestellt werden + ++ Talenttage gibt's für jedermann, der kämpft + (enno) Nur noch für die, die min. 2 Runden lang kämpfen. + ++ "Einheiten vor der x. Runde" wird einmal zuviel angezeigt. + (enno) War ein Fehler in der Berechnung von side::alive + ++ Karawansereien wirken sich nicht auf die Anzeige der Luxusgüter aus. + ++ FL_LEFTSHIP wurde fälschlicherweise gespeichert. + ++ Fehler im Handel, von Thorsten beobachtet. Einheiten verkaufen, ohne etwas zu haben. + Vermutung: Zwei gleichzeitig verkaufende Einheiten bedienne sich beide aus dem Pool. + (enno) Das auch, aber außerdem wurden die Güter dem falschen abgezogen. + ++ Kapitäne ohne Wahrnehmung durchbrechen Nebelwand + (katja) Auch Kapitäne müssen nun Wahrnehmung haben + ++ Mantel der Unverwundbarkeit wirkt nicht. + (corwin) survival_chance() gab einen Prozentwert zurück, gebraucht + wurde aber ein Wert von 0-1. Funktioniert die Rüstungsfunktion? + ++ Absturz in set_resource() bei Kräuterreservierung. + (corwin) Hektischer Bugfix (|| resource == NORESOURCE). + ++ Bug in hp_status (Cast/Klammerung) + ++ Ausdauerlernen und HP richtig verwurstet. + ++ Luftschiff + (enno) Eine Alraune wurde statt des Windbeutel verwendet. Auch, wenn man keine hatte. + ++ Fehler in den expandxxx routinen! + (enno) statt addlist(xxxorders) wurde addlist(&xxxorders) gemacht. + ++ Angabe 'erschoepft' bei Personen, die Ausdauer lernen. + (corwin) HP-Gewinn beim Ausdauer lernen war inkorrekt. + (corwin) Fehler gefunden, ich war blind. + ++ SCHWERER BUG: Orks bekommen falsche Lerntage + (enno) u->n statt u->no benutzt. + (enno) change_skill statt set_skill benutzt. + ++ SCHWERER BUG: Im Kampf gehen Talentwerte kaputt + (enno) set_number statt scale_unit + ++ SCHWERER BUG: Fliehende Einheiten ohne Talente + (enno) schlicht und einfach vergessen + ++ Trankbeschreibungen stimmen nicht mit Kräutern überein + (corwin) Beschreibungen waren schon bei den alten Tränken falsch. + ++ Ostfriesische Burgen konnten nicht gebaut werden + (enno) Fehler, soweit im Server, behoben: + wenn eine einheit MACHE X 123 sagt, und es gibt ein Gebäude 123, + egal welchen Typs, dann baut sie Gebäude 123 weiter. Auch wenn + sie selbst in einem anderen steht. + ++ in den Kampfbeschreibungen ist ein Umbruch zuviel. CR klappt nicht. + (enno) repariert. in addmessage() niemals \n benutzen! + ++ im Handel (sell) werden die Steuern nicht vom Verdienst abgezogen. + (martin+enno) repariert. Der spieler bezahlt. + ++ Einheiten leben nach dem Kampf noch weiter + (enno) u->number auf 0 setzen. + ++ herbs-pool hatte eine macke + (enno) use_pooled klappte nicht, weil NULL übergeben wird. + Das könte den Thorsten-Bug erklären. + ++ Einheiten von angegriffenen klagen über kampferschöpfte Personen + (enno) flag setzen. + ++ Übergabe von 4. Magier an Elfenpartei klappt nicht. (Partei 10) + (corwin) u statt u2 in givemen() + (enno) in giveunit() auch. und daran ist es auch gescheitert. + ++ Antimagiezone sollte erst in der darauffolgenden Runde wirken. + (NEW-Flag setzen.) + (corwin) NEW-Flag wird gesetzt und in spell_active() abgefragt. + ++ move_monster() - warning + (enno) Variable d wurde nicht initialisiert. + ++ Rundungsfehler in scale_number, wenn Einheiten-Anzahl zu groß. + (enno) repariert, noch undurchsichtiger geworden. + ++ Schiffe aus Mallorn bauen zieht keine Ressourcen aus der Region ab + (enno) repariert. Es wurde an use_pooled keine Region gegeben, + Test auf Mallorn fehlte völlig. + ++ Zauber brauchen Holz als Komponenten. + (enno) Mallorn kann das Holz notfalls ersetzen. + ++ Elfenparteien konnten keinen 4. Magier übergeben bekommen. + (enno) Es wurde bei der Quellpartei das Magiermax gefragt. + ++ Aji - die Drachentöter (29206) hat -1 Gefallene zu beklagen. + Katzeneinheit. Evt. Kollision von Heilung und 14%-Chance. + (enno) gefunden. indexüberschreitung, msvc macht's möglich. + ++ random_neighbour() - warnings + (enno) implizite typecasts von int nach direction_t + ++ fliehende können von Schiffen herunterfliehen. + (enno) änderung in fleeregion(), sollte das reparieren. + ++ Fliehende Magier verlieren evt. ihre Sprüche. + (enno) stimmt! dicker bug, wer war davon betroffen? + ++ Hitpointberechnung bei Einheitensplitting fehlerhaft. + (corwin) Gefunden. change_skill in transfermen macht Probleme, dort + ersetzt durch change_skill_transfermen. + ++ dazzle und demoralize klappten nicht. + (enno) neu implementiert. + ++ Stringzuweisungen in Mapper falsch. + ++ angegriffene einheiten können nichts tun + (enno) tritt in allen getesteten kämpfen nicht mehr auf. + ++ fliehende einheiten attackieren in seltenen Fällen ein zweites mal. + (enno) gefixt. passierte bei einheiten, die komplett (ohne fluechttlings-einheit) wegrannten. + ++ HELFE GIB verhindert Einsatz von Dumpfbackbrot + (corwin) Geändert, relevant ist jetzt nur noch der HELFE-Status des + Gebers. + ++ Meldung über fehlenden Zug irritiert Neueinsteiger + (corwin) Geändert, Neueinsteiger bekommen die Meldung nicht mehr + ++ Meldungen zum Abtreiben fehlen. + (corwin) f->dh in drifting_ships wird nie gesetzt. + ++ Formatierungsfehler im Spionagebericht. + ++ Ents entstehen nicht. + ++ Vogelfreund als Einheit sichtbar (NR) + (corwin) Alle U_SPELL-Einheiten werden angezeigt? Wer hat da wieder + gefummelt, ohne zu testen... Auch Befehle kann man ihnen geben, + klasse Sache, bei ner Antimagiezone. + (enno) hihi. wäre ein cooles feature fürs neue magsystem + (corwin) Brachialfix in readorders() und rpunit(). + ++ Verteidigung mit halbem Waffentalent gegen Fernwaffen funktioniert + nicht. + (corwin) Fehler lokalisiert, switch/case in hits(). Wie soll es sein? + ++ Es wurde vergessen, die Kampfwirkung der Antimagiezone an die + veränderte Stärke anzupassen. + (corwin) Gefixt, Durchbruchschance jetzt wieder normal. + ++ Katapulte ohne Talent Katapultbedienung benutzt. + (enno) Waffenauswahl war verhunzt. + ++ Überlange Zeilen werden in der Zugvorlage umgebrochen. + (corwin) In der Zugvorlage wird nicht mehr umgebrochen. + ++ Heilung funktioniert nicht. (Wyrm vollständig geheilt.) + (corwin) Nicht reproduzierbar. Ursache unbekannt. + (corwin) Fehler in battle.c, Hitpointberechnung fehlerhaft, + wenn keine Toten in Einheit. + +? HP-Berechnung bei neu hinzurekrutierten scheint fehlerhaft. + (corwin) In Testspiel nicht reproduzierbar, scheint ok. + ++ Taktik funktioniert nicht korrekt, Werte werden nirgends verglichen. + (corwin) Taktikvergleich war nirgends implementiert. + ++ Drachen heilen nicht. + ++ Kampfmessages sind verwirrend. + (corwin) Kampfmeldungen aussagekräftiger gemacht. + ++ Schüler müssen Lehrer kontaktieren. + (corwin) Abfrage rausgenommen. Kein Sinn erkennbar. + ++ Handeln-Talent wurde negativ. + (corwin) Gefunden. Fehler in der Poolbehandlung in sell(). + +? Deadlock bei Kampf in Carwin. + (enno) war evtl. ein Problem mit der Alliiertenmatrix? + ++ Aus einem Kampf geflohene Einheiten bekommen keinerlei Kampferfahrung, + auch wenn sie vorher aktiv gekämpft haben. + (katja) Kampferfahrung ist eh abgeschafft + +? Probleme beim Übergeben von Personen mit unterschiedlichem + Ausdauer-Talent. + ! was für ein problem? ! + +- Einige Kampftexte grammatisch falsch. (Plural einsetzen... andere + Fälle.) + ++ die Tavernen tun nichts + (enno) Neue Regeln fuer Tavernen, alte Tavernen konvertiert zu neuen. + ++ Semikolon in Strings führen zu Ärger. + [BESCHREIBE EINHEIT "Schlaue Leute; Sie sind nicht dumm"]. + Generell kann man das Parsen noch verbessern. + (enno) komplett neuer parser + ++ Umlaute an einigen Stellen noch fehlend. (Bla lst sich auf.) + (enno) repariert + ++ Adresslisten sind nicht in Ordnung, es erscheinen Parteien, + die man nur parteigetarnt sieht. + (Faroul) sollte schon einige Zeit gefixt sein in spunit + +? Fernkämpfer bekommen keine Kriegsbeute. + (enno) das ist doch sicher nicht mehr so. + ++ BEWACHE geht nicht (Marco Zielinski) + (enno) fehler gefunden, es fehlte ein ! + ++ Parteien bekommen Meldungen, die nichts mit ihnen zu tun haben. + (corwin) Fehler in givesilver, alter buf verwendet. + +- Fehler mit Pferden in plan_monsters. Ich weiß aber nicht, + wozu der Code gut sein soll, habe es markiert. (enno) + +? magres wird nicht beachtet. + (katja) wo? + +? Immer noch erschöpfte Einheiten. + (enno) lag vielleicht an der kampf-hp-aufteilung, die wir aber im juli 00 gefixt haben? + +? Heilung funktioniert nicht. + +? Negative Handelsvolumina führen zu Absturz in expandselling. + (corwin) Quickfix fängt negative Mengen dort ab. Ursache für die + negativen Mengen noch nicht gefunden. Zusammenhang mit + VERKAUFE ALLES vermutet. + ! (enno) ! hatten wir das nicht repariert? + ++ Fehlende Anführungszeichnen bei BENENNE führen zu Stringüberlauf. + (enno) gefixt, mit komplett neuem parser. + +- r->demand fällt auf 0. assertion in readgame springt an. + (corwin) Brachialfix beim Einlesen. Ursache nicht gefunden. + +- flutwellen löschen bauern, silber und sonstige regionswerte nicht. + +- flutwellen vernichten schiffe, aber nicht die besatzung + +- Übergaben können an Einheiten erfolgen, die man nicht sieht. + Auf diese Weise können Tarner identifiziert werden. (In dogive() + fixen!?) auf kontaktiere prüfen, dann erlauben + ++ Betrete Schiff sollte vor Attackiere kommen, um att,betrete,nach + zu verhindern. + (corwin) Reihenfolge unverändert, aber trotzdem gefixt in + building/shipowner. + ++ Formatierungsproblem: Zentrierte lange Strings beginnen manchmal + schon in Spalte 0. Vermutlich Rundungsproblem. + (enno) in zukunft sollen eh alle bei spalte 0 anfangen. + ++ addmessage( ,"", ) erzeugt eine Zeile mit einem Space "^ $" + (enno) addmessage fliegt eh raus + ++ Magieresistenz funktioniert nicht. + (corwin) Wieder eingebaut. + ++ Drachen greifen trotz WARTEN an. + (corwin) Hoffentlich gefixt, Verwechslung thisorder, lastorder + ++ Ablegen in Nachbarregionen funktioniert nicht. + (corwin) Gefixt. + ++ Kapitäne und Burgherren stehen in der Zugvorlage nicht an der + richtigen Stelle. + (enno) eingebaut. + ++ Im Gletscher ist zuviel Eisen. Menge begrenzen. + (corwin) Max 80 Eisen pro Gletscherregion. + ++ Nebelwälle? + (corwin) Nebelwall-Anzeige in describe() war Unfug, ebenso + is_fogwalled(). + ++ Absturz bei destroy_ship durch zuviel Schaden. + ++ reale Koordinaten in den Fehlermeldungen in sail() + (katja) durch regionid() ersetzt + ++ Beschriftung der Meriankarten falsch (Beschriftung oben verschoben) + (enno) sollte gefixt sein. + ++ Man sieht Botschaften in Regionen, die nur vom Leuchtturm gesehen + werden. + (corwin) Abfrage in die Reportgenerierung eingebaut. + ++ CR bei vorigem übersehen. + (enno) gefixt. + ++ Flüchtende Drachen hinterlassen Drachenkopf und -blut. + (corwin) Hoffentlich gefixt. + +? Eine Partei in einer Kampfregion erhielt keinen Kampfbericht. + ++ Flüchtende Einheiten verlassen Burgen und Gebäude evt. nicht. + (corwin) Fliehende Einheiten verlassen auf jeden Fall Gebäude und + Schiffe. + ++ von Turmregionen erhält man keine Durchreiseereignisse + (katja) gefixt + ++ Im CR ist erkennbar, ob eine Region Mallornregion ist. + (corwin) Mallorn nur noch = 1, wenn auch Bäume da sind. + ++ Magieresistenz für Drachen falsch. + (corwin) magicresistance() muß natürlich auf alles wirken. + ++ "Maentel" ohne Umlaut wird nicht erkannt. + (corwin) In Tabelle eingefügt. + ++ Illusionen werden als Migranten gezählt. + (corwin) count_migrants prüfte nur auf nonplayer(), nicht auf + Illusionen. + ++ Werden Kämpfer auf Schiffen in einen Kampf gezogen bleiben + die Schiffe unbeschädigt. + (corwin) Damage-Routine berücksichtigte nur Kapitäne. + ++ Adressen von Parteien, die man nur in einer Turmregion oder + Durchreiseregion sieht, stehen im NR aber nicht im CR + ++ Von Migranten und Untoten erzeugte TEMP-Einheiten haben den falschen + Typ/Rasse. + (corwin) u->race statt u->faction->race + ++ material- und silberpool in den cr mit aufnehmen + (corwin) Neues Tag ;Optionen mit f->options + ++ Flüchtende Einheiten werden evt in mehrere Kämpfe verwickelt, wenn die + Region, in die sie flüchten, später ausgewertet wird. + (enno) sollte längst gefixt sein. + ++ Schiffe, die in der selben Region an- wie ablegen, haben die kueste + falsch gesetzt.und kriegen keine durchreiseinfos. + (corwin) Zumindest die Küstengeschichte ist gefixt. (Auch das hatte + ich übrigens schonmal gemacht.) + +? Schiffe, die in der selben Region an- wie ablegen, haben die kueste + falsch gesetzt.und kriegen keine durchreiseinfos. + (corwin) Küste gefixt. Durchreiseinfos müßten imho kommen. + ++ bugreport isilpetz, teleport funktioniert nicht, obwohl kontaktiert wurde. + (corwin) Gefixt. Isilpetz'Partei muß in TELEPORT_HACK extra + berücksichtigt werden. + ++ Ein Kampf, der 1+1 Kampfrunden dauert, gibt keine Kampferfahrung. + (corwin) Geändert. + ++ Mapper - Einheit ins Clip nehmen geht nicht. (Es wird eine scheinbar + ausgewürfelte Einheit genommen.) + (corwin) atoi36-Umstellung vergessen. Gefixt. + ++ von Migranten rekrutierte Temp-Einheiten erscheinen als eine andere Rasse als sie sind. + (enno) gefixt. + ++ mehrere Ponnukis + (enno) gefixt. + +? artefakte werden auf der flucht nicht mitgenommen - bugreport 26.oct.99 stefan reich + (corwin) Alte Flucht rausgenommen. + (enno) stefan sagt, es sei schon wieder passiert. + (corwin) War nur in der alten Flucht überhaupt berücksichtigt. + 0 Gewicht-Items werden jetzt wie Pferde behandelt. (Ein + Exemplar pro Person wird bei Flucht mitgenommen.) + + Dies bedeutet, daß Flüchtlinge immer z.B. einen MdU + mitnehmen, wenn in der Einheit vorhanden. Das ist eigentlich + nicht gut, aber nicht ohne großen Aufwand anders zu lösen. + ++ Merian-X-Koordinaten sind manchmal verschoben. + (corwin) Rundungsfehler bei der Kartenerstellung. Bitte nochmal + im Spiel nachgucken, ob Fix ok. + (enno) Merian ist rausgeflogen + ++ Diebstahl kann durch TEMP-Machen verhindert werden + (enno) Reihenfolge des shash in destroy_unit falsch. + +- Durch den selben trick (TEMP-Übergabe) kann man sich vor + spionage schützen + ++ Parteien aus Leuchtturmregionen ersheinne nicht in der Adressliste. + (enno) ich glaub, ich hab's. oder doch nicht? + ++ Bauernblut hilft auch fremden Dämoneneinheiten, + (enno) Auswirkungen kann man vernachlässigen. niemand hat + >100 Dämonen aus zwei parteien in einer region mit <100 bauern. + ++ potion-effects werden in scale_number nicht berücksichtigt + (enno) repariert, skalieren jetzt genau wie skills. + +- assert in battle.c:4958 wird getriggert. Schlecht reproduzierbar. + (enno) ein paar asserts zusätzlich eingebaut, falls es nochmal passiert. + ++ ; terminiert einlesen der Befehlsdatei. + (enno) repariert. 'comment' wurde nie wieder false. + ++ fix_irace ignoriert dämonen + (enno) repariert. + ++ strassen gingen verloren + (enno) read/write reparatur-routine. + ++ doppeltes ;; im CR + (enno) doppelt gemacht, in sprintf und im fprintf. + ++ Trollstärke-Gürtel + (enno) wurde nicht um SCALEWEIGHT skaliert, brachte nur 5 GE. + ++ kampfresultat in Sun Lake City. funktioniert Magieschutz nicht? + +- komische leerzeilen in kampfabrechnungen: +> Schauspieler (pg2d) verlor 9 Personen, 0 überlebten und 1 flohen nach Heredon +> (-3,5). +> Händler (uk53) verlor 1 Personen, 0 überlebten und 0 flohen nach Lykubet +> (-1,3). +> +> Schattenluchse (umnb) verlor 18 Personen, 0 überlebten und 0 flohen nach +> Sturmhafen (-2,5). +> Baumeister (f0o7) verlor 1 Personen, 0 überlebten und 0 flohen nach Heredon +> (-3,5). + +? insekten, geht der handel in wüsten und sümpfen? + ++ Meer-Vertraute können in Nichtküstenländern erscheinen. (wegen 3% in + select_famaliar()) + (corwin) Gefixt. + ++ Magielernen bei Vertrauten allgemein machen, da Goblin-Vertraute sonst + keine Magie lernen können. + +? kostenlose Talente lernen in Akademie geht nciht, wenn akademie nicht + finanziert und zwar nichtmal 30 tage + (corwin) Ist das wirklich so? Ich finde im Source keine Stelle, + die das bewirken könnte. + ++ Handelsgüterpreis wurde beim kauf zu spät erhöht + +- astraler nebel taucht in messages mit koordinaten auf. + +? Mein Alchemist (gxd) hatte letzte Woche noch 2 Bauernlieb. EIn BENUTZE + Bauernlieb brachte mir die Fehlermeldung "Gouldie Horrn (gxd) in + Wywipozos (-1,0): 'benutze bauernlieb' - Die Einheit besitzt den Trank + nicht." ein und diese Woche hat er gar keine dieser Tränke mehr. + (Seine anderen Tränke waren davon nicht betroffen). + (corwin) Wann soll das passiert sein? Diese Runde hatte er kein + BENUTZE. + (enno) das mußt du den fragen, der den bugreport gemacht hat. + ++ Leuchturm funktioniert nicht für Regionen, die in der + Regionsreihenfolge vor dem Leuchturm kommen - kein Report + (enno) repariert. + +? Probleme bei sp_charmingsong: Auflösung scheint nicht zu + funktionieren, wenn in der gleichen Runde die alte Partei gelöscht + wird. + ++ Jeder Verkauf reduzierte die Verkaufspreise. + (enno) Fehler beseitigt. + ++ Bauern wandern in und aus orkifierte Regionen. + (corwin) Unterbunden. + ++ Falsche Rundung beim Verhungern. + (corwin) Gefixt. + ++ HP von Eisengolems werden bei Mache nicht runterskaliert. + (corwin) gefixt + +? flüchtlinge verdoppeln sich + (enno) da fehlte ein scale_number in der routine. + ++ luftschiff-landungen (edelmayer, 13.6.00) + (corwin) Gelandete Luftschiffe können in Zukunft beliebig ablegen, + ebenso startende. + ++ schiffe aus mallorn (stanka, 13.6.00) + (corwin) Gefixt, fehlende Abfrage in continue_ship() + ++ bei handel wird ein gut zuviel bezahlt (ashdurbuk, 13.6.00) + (enno) gefixt + +? Adressliste sollte gescramblet werden + (corwin) Implementiert + (enno) sortiern wäre besser + ++ Statistik: Lohn auf Ozean für Nichtmeermenschen weglassen + (corwin) Implementiert + ++ Karavelle in Sumpf + (corwin) Hoffentlich gefixt. + ++ Mehrfachnennungen in Kampfauslösung + (corwin) Gefixt + ++ eisengolems ziehen ihr verbrauchtes eisen aus dem pool ab + (enno) repariert + ++ schiffbau mit mallorn geht nicht + (enno) es gab garkiene sonderbehandlung. ist drin, aber ohne poolnutzung + +? HELFE xyz NICHT funktioniert nicht. + (enno) Es muß doch HELFE xyz ALLES NICHT heißen. + +? HELFE GIB reicht scheinbar, um eine Burg betreten zu können. + +- Eisengolems übergeben nichts an andere (Bugreport Thomas Stankats, 3.7.) + +- bug bei goblinklau (Jens Meirose, 9.7.00) + +- fehlende zaubersyntax im cr (stefan götz, e-client, 9.7.00) + ++ trank der wahrheit - beschreibung passt nicht zu zutatenliste (ashdurbuk, 3.7.00) + (corwin) Beschreibung geändert. + +? bug bei beklauen (ilia, 40.6.00) + (enno) diebstahl aus dem pool ging bei reservierungen schief. neu: use_all(), get_all() + +- "Keine Gräber" (Hjalmar Bruns, 27.6.00) - trotz schlacht keine gräber gefunden + ++ "Burgenbau" (christian decomain, 26.6.00) - nicht neue größe, sondern alte zählt für das talent + (corwin) Gefixt. + +- "Antimagiekristalle" (stefan schubert, 26.6.00) - trotz antimagie finden zauber statt + ++ Im Astralraum kann man arbeiten + (corwin) maxworkingspeasants gab minimal 1 zurück. Das ausgebaut. Ich + hoffe ich habe alle DivByZero-Möglichkeiten, die dadurch entstehen + gefunden. + ++ Wölfe verschwinden nach der Schlacht nicht. + (corwin) Fehler in at_unitdissolve, gefixt. + ++ Sortierung der Adressliste nach Parteinummer funktioniert nicht. + (enno) war ein falscher cast, schätze ich. + +- Vertraute nehmen keine Gegenstände an. + ++ Mallorn für Schiffsreparatur geht nicht (Thomas Stanka, 14/8/00) + (enno) copy&paste bug + +- Mallorn für Schiffbau geht noicht (Zemsauer 9/8/00 + ++ Drachen bewachen Ozeane (10/8/00) + (corwin) geändert + +- Doku zu Reserviere sollte auf GIB ALLES angepasst werden + ++ zauber auf temp-einheiten (Sven Howard, 13.8.00) + (corwin) Für die Tyb-Zauber gefixt. Gibt es noch mehr? + ++ BE's Taktiker half den NK nicht im Kampf. + (enno) Es wurde HELFE BEWACHE statt HELFE KAEMPFE überprüft. + +- Gabe des Chaos, Negativwirkung hört nicht mehr auf (Kollakowski, 24.8.00 ; Emmler, 19.8.00) + +- Meldung, Runen des Schutzes, ist buggy (Weinzinger, 23.8.00) + +- FOLGE EINHEIT geht nicht (Dagmar Müller, 23.8.00) + +- FAHRE mit Reiter hat nicht funktioniert (Enno, 20.8.00) + +- Sägewerk funktioniert nicht (Andreas Beer, 15.8.00) + +- Mallorn für Leuchttürme verbauen? (Florian Lang, 15.8.00) + ++ Silberspenden an die eigene Partei? (Stean Reich, 20.8.00) + (corwin) Fehlendes spender->faction != bedürftiger->faction in + get_food(). + +? Xandril vermutet, man könne mit Nahkampfwaffen aus der 2. Reihe kämpfen. Vage Behauptung. + ++ GIB 0 EINHEIT geht nicht (Alke Rehling, 21.8.00; Alex Mielke, 21.8.00) + (corwin) Das war überhaupt nicht implementiert. Quickhack eingebaut. + ++ Neue Katapultregel funktionert nicht! Datenfile 201, Kampf in Rudkepaso (-8,24). + (enno) nachladen war kaputt. + +- Tempeinheiten profitieren nicht von Uni + +- Sinkende Schiffe erzeugen keine Globalmeldung, damit sieht man die + nicht! + +== Aus PLAN übertragen: == + +- Alp funktioniert nicht, Suchroutine wird nirgends aufgerufen und + enthält TODOs. + +- teil-verhungern: + "0;live" kommt im Template nicht vor. + +- Monsterauseinanderlaufen klappt nicht. + +- cansee() fehlt in vielen Zaubern. + +- Schiffeversenken gibt kaputte Meldung + Cef'az (c142) in Dór'Aglar (18,-16) versenkt die X~Lc^H~@~[c^H^X~Gc^Hm + (17). + (Das Schiff war die Cir'mor (1142), Langboot) + +- Botschaft an Einheit gibt als Meldung nur + 'Eine Botschaft von xyz: ""' + +- Durch übergeben kann man Silber vor Beklaue schützen + +- Aufgelöste Zauber (z.B. Magischer Pfad) sollten eine Meldung in der + Region geben. ('Die Straße ist verschwunden') + +- Meldungen zuhauf unvollständig, Regionsangabe oder Einheitenkennung + fehlt. Sehr uneinheitlich. + ++ Zauberliste für Dämonen wird nicht aktualisiert + (enno) gefixt. Erst nach randomevents() berechnen. + +- NUMMER PARTEI ändert auch die Nummer der befehlenden Einheit + ++ Eigene Einheiten halfen nicht, wenn man attackiert hatte und gleichzeitig wurde. + (enno) Test in join_allies auf eigene Partei fehlte. + +- Einheit auf Schiff flieht, kommt aber nicht in Nachbarregion an (Marc Korevar, 14.09.00, Runde 207->208) + +? KÄMPFE HELFE NICHT geht nicht. + (corwin) gefixt? + ++ Mehrere Kräuterbeutel im CR + (enno) 2 wurde ausgegeben, wenn mehr als ein Kraut vorhanden. + +? Benennen fremder Schiffe ging nicht (Enno) + (corwin) gefixt? + ++ Anzeige von Zitadellen als "Burg" + ++ Bau von Gebäuden klappt nicht. "Baut für 0 an ..." + (enno) Bei want=0 wurde nix gebaut. Ich dooof. + ++ Traumschlößchen sind erkennbar. + (enno) gefixt. + +- Einheiten können nach Kampf noch lange Befele ausführen. (Wenninger, 30.09.) + +? Karawanserei funktioniert nicht, Größe stimmt nicht + (enno) Max. Größenpunkte der Karawanserei waren 50 statt 10 + ++ NUMMER PARTEI ändert die Einhietennummer (G. Grundhöfer, 5.10.) + (enno) ein break im switch fehlte + +? Boot mit 4% Schaden hat 40 Kapazität? (M.Bank, 5.10.) + (enno) ist ein Rundungsfehler in shipcapacity(). Änderung noch nicht getestet. + +- Sturmmeldungen fehlen (M. Bank, 5.10.) + +- Durch Sturm unterwegs stark beschädigte Schiffe melden, das sie nicht ablegen können. (M. Bank, 5.10.) + +- Silberspende mri->bierbrauer falschrum? (S. Reich, 5.10.) + ++ FUZZY wirkt nicht auf PARTEI-Befehl (Mangar, 5.10.) + (enno) Ich denke, da habe ich jetzt eine wirklich gute Lösung gefunden. + +? Steinverbrauch beim Burgenbau falsch (Kirche des Morgenstern, 5.10.) + (stefan) lag wohl an + ++ Hohe Patzerchance Draig-Magier (Knoche, 5.10.) + (enno) Er hat auf maximaler Stufe, T 13, gezaubert. + ++ BENENNE FREMDES geht nicht mit base36 (wuzel, 5.10.) + (enno) Repariert, nicht schwer. + ++ Hafenbau > 25 (Klüstenhof, 5.10.) + (stefan, enno) falscher eintrag im buildingtype + +? Zombies gehen trotz massiver Katapultangriffe nicht kaputt (Meirose, Reich, et al, 5.10.) + (enno) In normalen Kämpfen mit Skeletten sieht das okay aus. + +? Boot wird mehr als 100% repariert (Wachtendorf, 5.10.) + (stefan) fehler in build + ++ gebäude werden doppelt bezahlt (Mueller, Rehling, et al, 5.10.) + (enno) heftiger bug, führt zu neuauswertung. flag wurde nicht getestet + ++ einstürzendes gebäude führt zu einheiten mit 0 personen (o. eckstein, 5.10.) + (enno) remove_empty_units() einfach noch einmal vor der reportgenerierung machen. + +? Vermishcung zweier Züge? (Necron, 16.10.) + ++ chaosregionen auf Insel der Partei o8 (Kirche des morgenstern, 16.10.) + ++ Überflüssige Gruppen löschen (Sassmanshausen 16.10.) + (Stefan) g->members werden jetzt wieder runtergezählt. + +? Steingolems vermehren sich, statt zu verschwinden (Emmler, 16.10.) + (enno) sollte sich mit der neuen production erledigt haben. + +- feuerwand bug, nicht erschienen (wolfgang haupt 16.10.) + ++ mallorn beim neuen gebäudeschiffs-bau (Stanka, 16.10.) + (enno) ja, das klappt nicht mehr + ++ keine Meldungen im CR (diverse, 16.10.) + (enno) corwin hat das beim abschaltender messagelevel verbockt. + +- messagelevel kaputt, warnings-pointer ist verhunzt (auswertung) + +- bekannte Zauber werden erneut angezeigt. + ++ Steinbruch hat verbraucht 5 Steine statt 5 Holz (ullrich, 30.10.) + (stefan) gefixed + +- Luxusgut-Preise fallen (diverse, 30.10.) + (corwin) Auswirkungen gefixt, aber Bug vermutlich noch da. + ++ Gewichtsberechnung geht nicht (diverse, 30.10.) + (corwin) Eine wichtige Zeile in weight() ist vergessen worden. + ++ Lokalisierung in Kampf und Produktion falsch (diverse, 30.10.) + (enno) repariert + ++ Luxusgut-Preise im CR falsch (diverse, 30.10.) + (stefan) Verkaufte Güter wurden mit Preis=0 statt negativem + Grundpreis angegeben. + ++ Materialpool funktioniert nicht bei Tränken (Katja, 30.10.) + (enno) in welchem kontext? GIB, BENUTZE, MACHE? + (katja) BENUTZE + (enno) gefixed. + ++ Alchemisten ohne Produktion (Katja, 30.10.) + (stefan) i_change fehlte in create_potion() + ++ Schrottige Meldungen bei MACHE Trank (diverse, 30.10.) + (stefan) create_potion wurde mit zuwenig Parametern aufgerufen + ++ Zuviele Kräuterbeutel (Katja, 30.10.) + (enno) repariert, fehler in report_item. + ++ Schmiede funktioniert nicht (diverse, 30.10.) + (enno) Schmiede funktioniert wieder. Aufruf von init_buildings() fehlte. + ++ Zauber verbrauchen keine Aura (diverse, 30.10.) + (enno) res_changeaura, res_changepremaura, res_changehp u.a. + waren not implemented. erledigt. + ++ Zauber erzeugen keine Gegenstände (diverse, 30.10.) + +? Wald/Ebenen-Kräuter wechseln nicht, wenn sich das Terrain ändert + (wuzel, 30.10) + ++ Waldkräuter nicht korrekt initialisiert, in Wäldern sind jetzt + Ebenenkräuter (wuzel, 30.10.) + (enno) rterrain() statt mainterrain() benutzt. + (enno) gefixt, korrektur() erledigt das nächste woche. + ++ Verkaufte Luxusgüter werden nicht abgezogen, sondern dazugezählt + (mbank, 30.10) + (enno) gefixt, das i_change war falsch. (copy&paste) + +- Gewürze wurde nicht erkannt (stanka, 30.10.) + (enno) Mehrzahl von Gewürz ist Gewürz. + Ich habe einen String für spices eingebaut, aber der wird noch nicht verwendet, + da außer den korrekten Namen der Items noch keine aliase verarbeitet werden. + (enno) am besten wären standardisierte abkürzungen. ich hab da was im kopf. + ++ doppelte Silberbeutel (diverse, 30.10.) + (enno) die alte routine war noch drin, gefixt. + ++ Abzug von Luxusgütern immer noch falsch: Güter aus dem Pool + werden bei Verkauf von der Einheit gezogen, assert in + i_change triggert. + (enno) Umstellung auf new_use_pooled() und kleiner speedup. + +- Silberspenden werden gemacht, obwohl eigentlich unnötig gewesen. + (katja) warscheinlich am Anfang der Runde nicht genug Geld für die + Gebäudeversorgung, dann helfen die aliierten aus + ++ Gespenster aus der Unterwelt (w9kL) erzielte 1026 Treffer und tötete 0 Gegner. + (enno) scheisse. waffenloser kampf. fixed. + ++ 42 Kämpfer aus Rukh-R (1ftp) feuern ihr Katapult ab: + (enno) ...und sie haben nur 42 katapulte. alle in der gleichen runde ist zuviel. + (enno) war nur kosmetisch + +- Parteien aus Durchreiseregionen erschienen nicht in den ADressen + +- Transporteure (2jws) arbeitet in Twerken (-7,8) für einen Lohn von 70 + statt 65 Silber. + ++ Illaun-Zauber Schoene/Schlechte Träume wirkt zu kurz: beginnt erst in der + darauffolgenden runde, endet aber evtl. schon in der aktuellen. + (katja) irgendwer hatte bei Schöne Träume die Dauer total verhuzt. + ++ Schattendämonen können reservieren (P. Horner, 15.09.00) + (katja) repariert + ++ Tunnelwürmer können Straßenbau statt Steinbau (H. Steinbeißer, 16.09.00) + ++ Vertraute nehmen nichts an (O. Eckstein, 17.09.00) + (katja) ist gefixt + +- FAHRE funktioniert nicht (G. Großberger, 19.09.00) + (enno) ist bei uns auch schonmal schiefgegangen, kann was dran sein. + +? Bei "Verwünschung" sieht das Opfer der Verwünschung den Zaubernden. + (enno) ich glaub, das hatte ich gefixt. + +? "Schlägerzwerg (id84) in Kitpad (-1,2): 'KAEMPFE HELFE NICHT' - unbekannter + Kampfstatus." + (corwin) Kann nicht sein. Ist das noch aktuell? + +- Einheit auf Schiff flieht nicht in benachbarte Region (M. Korevaar, 24.09.00) + +- Astraler Ausgang funktioniert nicth (Synes Elischka, 29.09.2000) + +- Leuchtturmeinsturz macht Einheiten mit 0 Personen (O. Eckstein, 05.10.2000) + * Krath (hqk), 0 Elfen, "lerne wahrnehmung"; In eine dunkle Robe gehüllter + Elf - er vertritt hier seinen Herrn und Meister. + +? Bergwerk stürzt trotz genug Silber ein (M. Müller, 05.10.2000) + (enno) Die Routine ist seit dem überarbeitet worden. + ++ Zauber "Runen des Schutzes" nicht base36 (F. Wolkwitz, 04.11.2000) + Maehrlin (39x) in Amandor (0,23): 'zaubere Runen~des~Schutzes Schiff 1bq' - + Schiff 1 wurde nicht gefunden. + (katja) #define FULL_BASE36 war vergessen worden + ++ Schmieden funktioiniern nicht (O. Eckstein, 05.11.2000) + (enno) Beispiel überprüft, er muss sich verrechnet haben. + +- Übergabe klappte mit Pferd, aber nicht mit Silber? I. Schröder, 05.11.2000) + +- einheit waru mcht zu wenig Kettenhemden (A. Neumann, 05.11.2000) + (enno) evtl. nicht oben in der schmiede gestanden? trotzdem zu wenig + +- einhiet buf erzeugt 5 statt 6 schwertern (dwalin, 6.09.2000) + (enno) die hat sich bestimmt verechnet. + ++ kostenlose kostenpflichtige talente mit lehrer (A. Klauer, 06.11.2000) + (stefan) Anteiliges Lernen betrifft jetzt alle Effekte mit. + +- hegg hat genug Material, kann aber nicht produzieren (P. Burkert, 06.11.00) + ++ Kein Anlegen in neuen Regionstypen möglich (Joern Stampehl, 09.11.00) + (corwin) Neue Terraintypen fehlten in coast_allowed komplett. + +o Leuchttürme schauen mindestens eine Region zu weit. (Größe 10 + Leuchtturm 3 Regionen.) + (stefan) Bei mir nicht. Zu beachten ist, daß man halt 2 Regionen weit sieht, + und DANN noch die Nachbarregionen drangehängt werden. + ++ 'cannot resolve action' vereinzelt + (enno) die alten actios sind weg, damit erledigt sich das wohl. + ++ timeout auf eine Vertrautenlöschung + (enno) Das ist dank neuer events wohl auch repariert? + ++ Museums'gebäude' können umbenannt werden + (corwin) Generische Gebäude (bt_generic) können bis zur Implementation + eines besseren Mechanismus nicht mehr umbenannt werden. + ++ Benennen fremder Burgen klappt nicht. + (corwin) Bei der Umstrukturierung der Gebäude vergessen worden. + ++ Benennen fremder Schiffe und Gebäude ist nicht an Locales angepaßt. + (corwin) Wird nicht mehr nahnd des Namens, sondern mit einem Flag geprüft. + ++ Bauernfressen und Vertreiben trotz Vernichtung der Untoten + (corwin) In der Reihenfolge vor die Zufallsereignisse gelegt. + ++ Straßen über 100% fertig + (corwin) Fehler in build_road() + ++ Verfolgendes Schiff fährt in falsche Richtung + (corwin) u->wants bei Verfolgungen nicht mit 0 initialisiert. + ++ (katja) diverse Zauberfehlschläge, obwohl Aura übertragen wurde (u.a. + Segne Steinkreis) weil VOR allen Zaubern, also auch vor dem + Auratransver, eine cancast Überprüfung war, die zu dem Zeitpunkt + natürlich fehlschlug. + +- memoryleak in curseid? + ++ Fliehende Einheiten nehmen kein Silber mehr mit (Katja, 28.12.2000) + ++ Magiebegabte Vertraute haben keine Zauber mehr + +- lmsreward funktioiniert nicht (curse wird nicht gesetzt) + +- benutze item funktioniert nicht. itype->use ist überall 0x0 + +- auch nichtpoolbare Resourcen wie Aura, HP, perm Aura sind im pool + +- lernen in akademie bringt 80 statt 70 Tage, ausser bei Magie, da + funktionierts? + (Zhardamon, irq 20.2.01) diff --git a/doc/changes.txt b/doc/changes.txt new file mode 100644 index 000000000..c04460958 --- /dev/null +++ b/doc/changes.txt @@ -0,0 +1,49 @@ +Konzepte: +- Astralraum abschaffen + = done (modules.astralspace). + = done (Zauber entfernt). +- Regionsbesitz und Moral +- Allianzen +- GIB abstellen + = done + +Talente: +- Talentlimit 10 + Rassenbonus + = done +- Ausdauer wirkt sich nicht auf Trefferpunkte aus + = done (rules.stamina) +- Wahrnehmung und Tarnung abschalten + = done + +Kampf: +- Regionen belagern +- Allianz ersetzt HELFE KAEMPFE +- Reduktion auf 1 Kampfrunde + = done (rules.combat_turns) +- neue Trefferchanceberechnung +- neue Beuteregelung +- Gegnerlimits + +Gegenstände: +- Waffen mit höherem Schaden. +- RdU und AdwS überarbeiten + +Diverse: +- neue Terraintypen (weniger Bauern, knappere Ressourcen) +- limitiertes Rekrutieren von Migranten erlauben +- Parteitarnung fällt weg + = done (disable TARNEN) +- Monster entfernen + +Ökonomie: +- UNTERHALTEN abstellen + = done (Befehl & Skill) +- TREIBEN abstellen + = done (Befehl & Skill) +- ARBEITEN abstellen + = done (Befehl) +- LEHREN abstellen + = done (Befehl) +- VERKAUFEN abstellen. + = done (Befehl) +- Steuern für den Regionsbesitzer diff --git a/doc/coding.txt b/doc/coding.txt new file mode 100644 index 000000000..95952bc96 --- /dev/null +++ b/doc/coding.txt @@ -0,0 +1,90 @@ +Was ist das hier? +Dieses File ist eine Sammlung von kleinen Artikeln zum Code - +Designgedanken, hauptsächlich, keine Anleitungen. + +- Die Variable buf +wird an zu vielen Stellen im Source benutzt. Können wir zumindest in +Zukunft versuchen, auf sie zu verzichten? Mir ist nie sehr wohl, wen ich +die irgendwo sehe. + +- Bibliotheken, module +Jedes Verzeichnis in common/ erzeugt eine Bibliothek. Je nach Bedarf +muss aber nicht jeder Server die ganze Bibliothek linken (die ist eher +was für den mapper), sondern kann die Files auch einzeln linken. Jedes +modul, item, attribut, usw. sollte eine Funktion zur Verfügung stellen, +die seine typen initialisiert, also it_register respektive at_register +o.ä. aufruft. Jede Biliothek hat ein File, das ihren Namen trägt (z.b. +items.txt) mit einer eigenen init_-Funktion, die alle init_funktionen +der enthaltenen objekte enthält. (init_items, init_attributes, usw). +Neue Files hinzufügen heißt also bitte, die Registrierung in den +"bibliotheks-file" zu machen. + +- KI +Beim Anblick des enums mit Rassen-Flags sieht man schnell, das die in +mehrere Kategorien fallen. Eine davon ist die KI-Steuerung, und könnte +man die vielleicht getrennt von den anderen speichern? + +- Worüber man nachdenken sollte, ehe man etwas neues hinzufügt +90% aller Erweiterungen sind eigentlich optional. Ein Eressea ist z.B. +auch ohne die Rasse Troll denkbar. Leider ist die rasse Troll an derart +viele Stellen hartgecodet, das man Eressea ohne Trolle nie übersetzen +könnte (Im Kampfsystem bekommen z.B. Trolle -1 beim Reitenbonus). Immer +drüber nachdenken, ob man hartcoden muß, oder ob es auch einen anderen +Weg gibt (in diesem Fall z.B. ein at_skillmod attribut an der Rasse +Troll). + +- RC_SPELL +Es gibt einen Zauber (Ferne Vision) der Einheiten vom Typ RC_SPELL +erzeugt, aber 36 Stellen, an denen auf diesen Typ abgetestet werden muß +(vielleicht sogar mehr?). Können wir das mal auf einen curse umstellen? +Am besten in Kombination mit einem allgemeinen "diese Einheit/Region +soll in den Report der PArtei x", das man dann auch für Spionage usw. +benutzen kann, und das im Fall von Antimagie oder Zauberende durch einen +Trigger am entsprechenden curse mit zerlegt wird? + +- Wie komplex macht man einen Curse? +siehe vorangegangener Absatz. Generell gilt hier: Lieber zwei +vielseitige Dinge machen, als ein unflexibles - der curse sollte +lediglich der container seiin, der die wirkung aufrechterhält (das +attribut an der region überwacht, und per trigger-funktion bei ende des +curse oder antimagie entfernt). die eigentliche wirkung kann man in ein +separates attribut stecken, dann ist sie auch in anderen kontexten als +zauberei verwendbar (gebaüde oder items mit der gleichen wirkung, z.b.). +Tests sollten so wenig wie möglich auf einen curse gehen (in fact, +eigentlich nur bei der antimagie) sondern immer auf die wirkung (das +attribut). + +- wie benenne ich Sourcedateien? +lang drüber nachgedacht, bin ich zum schluss gekommen: kleinbuchstaben, +keien unterstriche. Es kann sich nie jemand merken, ob testplayer oder +test_player jetzt richtig ist, und wir kommen sicher selten in die +situation, das wir zwischen opium_bringen.c und opi_umbringen.c +unterscheiden müssen. + +- wie benenne ich variablen? +da gilt das gleiceh wie bei den files, mit einer ausnahme: +typspezifikation, also zum beispiel at_ für attributstypen, mit einem +unterstrich. dann ist auch klar, was at_work ist: ein attribut, das was +mit arbeit zu tun hat, keine boolean-variable die sagt ob man auf der +arbeit ist. + +- faction::units +Die Variable funktioniert und kann benutzt werden. folgendes: +for (r=regions;r;r=r->next) for (u=r->units;u;u=u->next) if (u->faction==f) {} +schreibt sich viel einfacher so: +for (u=f->units;u;u=u->nextF) {} +und ja, es wird garantiert, das das funktioiniert, und regionsreihenfolge +einhalten tut es auch. weshalb wahlloses erzeugen von einheiten per calloc und +ohne createunit() aufruf schon seit längerem ein NoNo ist. + +- buffer length +Namen von attributen, hashcodes für items, usw. sollten kurz sein. +schliesslich landen sie im Datenfile. Eine Funktion, die sie einlädt, +sollte mit 32 byte speicherbedarf rechnen. + +- FL_MARK und FL_DH: +Der unterschied zwischen diesen beiden Flags ist: +FL_DH sollte man vor der Benutzung auf einen Wert setzen, den man coraussetzt +(man kriegt keinen wert garanteirt). +FL_MARK ist immer 0. jede routine die es setzt, muss es am ende wieder auf 0 +zurücksetzen. code-beispiel ist z.b. die SORTIERE-funktion. diff --git a/doc/directories.txt b/doc/directories.txt new file mode 100644 index 000000000..1f9d3c1fe --- /dev/null +++ b/doc/directories.txt @@ -0,0 +1,28 @@ +src/util + 1. Was ist hier drin? + Hier drin steckt Code, der nicht nur für Eressea zu gebrauchen ist. Wann immer möglich, sollte man versuchen, Code hier unterzubringen. Code in diesem Verzeichnis verwendet config.h, aber unter keinen Umständen eressea.h oder anderen code aus src/common. + 2. Beispiele: + attrib, event, base36, rand +src/common + 1. Was ist hier drin? + Der Kern des Spiels. Funktionen, ohne die ein Eressea-Spiel nicht existieren kann. Zum Beispiel Kampfsystem, Magiesystem, item-typen (aber möglichst wenige items). Je allgemeiner gehalten der Code ist, desto weniger muß in common, desto mehr kann in "pluggable" Modulen untergebracht werden. + Bei Code im Kern des Spiels sollte man beachten, das eine Abfrage auf einen konkreten item-typ immer zu einer Abhängigkeit mit Code führt, der eigentlich nicht dem Kern zuzurechnen ist. Wenn im Kampf z.B. eine Spezialwaffe erfragt wird, dann kann man kein Eressea-Spiel erzeugen, ohne diese Waffe mit zu linken, selbst wenn man sie nicht benutzt. Besser ist es in diesem Fall, wiederverwendbare attribute oder itemtype-flags zu benutzen. Analog gilt das für alle anderen typen: rassen, gebäude, schiffe, Regionstypen, Zauber. + 2. Beispiele: + battle, economy, magic (vormals newmagic), item (allerdings ohne konkret items zu enthalten), build, building, ship +src/common/gamecode + 1. Was ist hier drin? + Code, der nur die Auswertung und den Ablauf betrifft, aber keine statischen Tools wie z.B. den Mapper. + 2. Beispiele: + Hier ist die Ökonomie drin, Zufallsereignisse, Reportgenerierung. +src/common/modules + 1. Was ist hier drin? + "Pluggable" Module, wie Questen, Spezial-Planes, Events. Alles, was dem Spiel Leben einhaucht, ohne im Kern verankert zu sein. + 2. Beispiele: + arena, xmas2000, museum, score (?). + 3. Style + Ein Modul sollte aus main.c heraus initialisiert werden können. Ideal ist, wenn ein Aufruf von init_() genügt, um es zu aktivieren. Das sorgt z.B. beim Weihnachtsmann-Modul für die Erzeugung der Plane und der Units, der Registrierung der nötigen Items und Attribute, und mehr muß nicht getan werden. + Darf keine Funktionen aus gamecode() benutzen, oder sollte selber im gamecode landen. +src/triggers +src/items +src/spells + diff --git a/doc/new-source.gif b/doc/new-source.gif new file mode 100644 index 000000000..fa301e149 Binary files /dev/null and b/doc/new-source.gif differ diff --git a/doc/spells_uebersicht.txt b/doc/spells_uebersicht.txt new file mode 100644 index 000000000..d8e1bcd25 --- /dev/null +++ b/doc/spells_uebersicht.txt @@ -0,0 +1,185 @@ +Stufe, Name, Kosten, Auswirkung +Kosten: *L = pro Level, P = Permanente Aura, M = Materialien (zB Holz), + B = Bauern, $ = Silber, H = Hitpoints +(K) = Kampfzauber, (+K) = Prekampfzauber, (K+) = Postkampfzauber + + +Druide/Gwyrrd (38 Sprüche): +--------------------------- + 1 Segen der Erde 1*L Bauern verdienen 1 Silber mehr + 1 Viehheilung 1*L Magier verdient 50$*L + 1 Erschaffe Steingolems 2*L,M Magische Strassen/Burgenbauer + 2 Erschaffe Eisengolems 2*L,M Magische Rüstungsbauer + 2 Hainzauber 2*L,$ Erschafft Bäume + 3 Rostregen 2*L Vernichtet Eisenwaffen + 3 Firuns Fell 2*L Insekten können auf Gletscher + 3 Hagel (K) 1*L verursacht leichten Schaden + 3 Bergwächter 3*L Verhindert Eisenabbau + 4 Magischer Pfad 1*L,M Magische Strasse + 4 Windschild (+K) 2*L behindert Schützen + 4 B... Wasserelementar 1*L Schiffe +1 Bewegung und keine Abdrift + 5 Heilung (K+) 1*L heilt bis zu 11 Personen pro Stufe + 5 Wirbelwind (+K) 15 behindert alle Schützen zu 50% + 5 Astralschutzgeister (+K) 5*L behindert Kampfzauber + 5 E... magischen Kräuterb. 30,1P,M Artefakt + 6 Meditation 2 überträgt Aura + 6 B... einen Erdelementar 25,M Beschädigt alle Gebäude einer Region + 6 B... Sturmelementar 6*L verdoppelt Schiffsbewegung + 6 E... des wahren Sehens 50,$ Artefakt + 6 E... der Unsichtbarkeit 50,$ Artefakt + 7 Heimstein 50,1P verbessert Burg dauerhaft + 7 Wolfsgeheul (+K) 2*L ruft Wölfe für den Kampf + 8 Blick des Basilisken (K) 1*L entfernt Gegner aus Kampf + 8 Starkes Tor und ... (+K) 2*L verbessert Schutz der Burg + 8 Geister bannen 6*L Antimagie + 9 Weg der Bäume 3*L Stufe*5 GE aus Wald in Astralebene + 9 Sog des Lebens 2*L Stufe*5 GE aus Astralebene in Wald + 9 Heiliger Boden 80,3P +10 Erwecke Ents 6*L Erschafft Einheit +10 Vertrauten binden 100,5P Vertrauter +11 Segne Steinkreis 350,5P Wandelt Steinkreis zum gesegneten St. +12 Rindenhaut (+K) 4*L Verbessert Rüstung +13 B... Hitzeelementar 600 halbiert Fruchtbarkeit der Region +14 Nebel der Verwirrung 8*L Bewegung wird zufällig (nur Wald+Ozean) +15 Mahlstrom 200,M Ozeanfeld beschädigt Schiffe schwer +16 Wurzeln der Magie 250,10P,M Wald wird permanent Mallornwald +17 T... die Ebene der Hitze 800 stärker als L13, kann Region wandeln + + +Chaos/Draig (27 Sprüche): +------------------------- + 1 Verwünschung 1*L nur Showeffekt + 1 Kleine Flüche 1*L Magier verdient 50$*L + 2 Feuerball (K) 1*L Schaden + 3 Gabe des Chaos 2*L Magicboost + 4 Mächte des Todes 5*L Erschafft Untote + 5 Blutrausch (+K) 5*L,B Verbessert Kampfkraft + 5 Chaosfluch 4*L behindert Zauberer + 6 Rosthauch (K) 2*L zerstört Eisenwaffen + 6 E... des wahren Sehens 50,$ Artefakt + 6 E... der Unsichtbarkeit 50,$ Artefakt + 7 Machtübertragung 2 überträgt Aura + 7 Feuerwand 6*L Wall, macht Schaden + 7 Fluch der Pestilenz 30,B erzeugt Pest + 8 Wahnsinn des Krieges (+K) 3*L,B verwirbelt gegnerische Reihen + 8 Beschwöre Schattendämonen 3*L erzeugt Einheit + 9 E... der Trollstärke 20,1P Artefakt + 9 Astraler Riss 35,M Magier der Region verlieren Aura + 9 Astrales Chaos (+K) 6*L behindert Kampfzauber +10 Feuerteufel 50,M vernichtet Wälder +10 Pentagramm 10*L Antimagie +10 Unheilige Kraft 8*L,M Transformiert Untote +11 Todeswolke 40,H alle Personen der Region verlieren HP +11 Drachenruf 80,M lockt Drachen an +12 Beschwöre Schattenmeister 7*L erzeugt Einheit +12 E... ein Flammenschwert 150,M Artefakt +13 Vertrauten rufen 100,5P Vertrauter +14 Chaossog 150,B nächste Runde Tor zur Astralebene + + +Traum/Illaun (27 Sprüche): +-------------------------- + 1 Traumsenden 1*L nur Showeffekt + 1 Wahrsagen 1*L Magier verdient 50$*L + 1 Schattenritter (+K) 1*L Illusionen, fangen Angriffe ab + 2 Grauen der Schlacht (+K) 1*L verleitet Gegner zur Flucht + 2 Seelenfrieden 3*L,M reduziert deathcount der Region + 3 Traumschlößchen 1*L Illusion einer Burg + 3 Traum der Magie 2 Auratransfer + 3 Gestaltwandlung 1*L Einheit erscheint als andere Rasse + 4 Traumlesen 8 Magier erhält Regionsbericht + 4 Schwere Glieder (+K) 4*L -2 Def, 50% Chance dass keine Att + 5 Wiederbelebung (K+) 1*L holt Tote zurück + 5 Traumbilder analysieren 5*L analysiert Sprüche auf Einheiten + 6 Schlechter Schlaf 18 vermindert Lernrate in einer Region + 6 E... des wahren Sehens 50,$ Artefakt + 6 E... der Unsichtbarkeit 50,$ Artefakt + 7 Schlaf (K) 1*L -2 Def, kein Att, solange bis getroffen + 7 Irrlichter 2*L Bewegung wird zufällig? + 7 Traumdeuten 20 spioniert Einheit aus + 8 Schöne Träume 8*L zeitweilig Bonus auf alle Talente + 8 Traumbilder entwirren 6*L Antimagie + 9 Vertrauten rufen 100,5P Vertrauter + 9 Seelenkopie 100,20P+ Reserveklon +10 Schlechte Träume 9*L zeitweilig -L/4 Talentstufen +11 Tod des Geistes (K) 2*L vermindert Talente, töten bei 0 Tagen +12 Süße Träume 30 Vermehrung wie bei Orks +14 Erschaffe ein Traumauge 10P,M Artefakt +15 Alp 350,5P,M Monster, sucht Opfer => -2 auf Talente +16 Schleier der Verwirrung 7*L mehrere Regionen: Bewegung wird zufällig + + +Barde/Cerddor (33 Sprüche): +--------------------------- + 1 Friedenslied (+K) 2 Magier kann vor Angriff fliehen + 1 Gaukelleien 1*L Magier verdient 50$*L + 2 Lied der Heilung (K+) 1*L heilt Verwundete + 2 Hohes Lied der Gaukelei 2*L Unterhaltungsmaximum zeitweilig doppelt + 3 Regentanz 1*L Bauern verdienen 1 Silber mehr + 3 Gesang der Furcht (K) 1*L Gegner versuchen zu fliehen + 4 Gesang des Werbens 2*L rekrutiert Bauern + 4 Gesang der Verwirrung (+K) 2*L verwirbelt gegnerische Reihen + 4 Plappermaul 10 spioniert Einheit aus + 5 Heldengesang (+K) 2*L Krieger widerstehen einigen Effekten + 5 Gesang des Auratransfers 2 überträgt Aura + 5 G... Lebens analysieren 10 analysiert Zauber auf Einheit + 5 Bannlied (+K) 5*L behindert Kampfzauber + 6 Monster friedlich stimmen 15 verhindert Monsterangriffe + 6 Lied der Verführung 12 Einheit schenkt Magier 50% Besitz + 6 E... des wahren Sehens 50,$ Artefakt + 6 E... der Unsichtbarkeit 50,$ Artefakt + 7 Schaler Wein 4*L,M,$ Opfer vergißt bis 60 Talenttage + 7 Aushorchen 4,$ Magier erhält Regionsbericht + 7 Kriegsgesang (+K) 5*L verbessert Kampfkraft + 8 Gesang der Angst 5*L Malus auf Att und Def + 8 L... Ortes analysieren 3*L analysiert Zauber auf Objekten/Regionen + 8 Lebenslied festigen 5*L Antimagie + 9 Ritual der Aufnahme 3*L,1P*L L Personen können Partei wechseln + 9 Vertrauten rufen 100,5P Vertrauter +10 Mob aufwiegeln 4*L Erzeugt Einheit +10 Gesang des wachen Geistes 2*L verbessert Magieresistenz in Region +11 Gesang der Melancholie 40 kein Unterhaltunssilber verfügbar +11 Miriams flinke Finger 20,1P,$ Artefakt +12 G... schwachen Geistes 2*L vermindert Magieresistenz in Region +12 G... der Friedfertigkeit 20*L verhindert Kämpfe +13 Gesang der Versklavung 40 Opfer wechselt zeitweilig Partei +15 Aufruhr beschwichtigen 30 Löst Mob (L10) auf + + +Astral/Tybied (32 Sprüche): +--------------------------- + 1 Magie analysieren 1*L analysiert Zauber + 1 Schleieraura 1*L verbirgt Ausrüstung einer Einheit + 1 Wunderdoktor 1*L Magier verdient 50$*L + 2 Schutz vor Magie (+K) 3*L behindert Kampfzauber + 2 Astraler Blick 1*L sieht Einheiten in Astralebene + 3 Schutzzauber 5*L verbessert Magieresistenz einer Einheit + 3 Beute bewahren (K+) 1*L vermindert Zerstörung von Gegenständen + 4 Astraler Weg 2*L schiebt (L-3)*15 GE in Astralebene + 4 Astraler Ausgang 2*L schiebt (L-3)*15 GE in Realität + 5 Auratransfer 1 transferiert Aura, auch andere Gebiete + 5 Schockwelle (K) 1*L verhindert nächste Attacke + 5 Astrale Schwächezone 3*L Antimagiezone + 5 E... des wahren Sehens 50,$ Artefakt + 6 Astraler Ruf 2*L zieht (L-3)*15 GE in Astralebene + 6 Ruf der Realität 2*L zieht (L-3)*15 GE in Realität + 6 Stehle Aura 2*L entzieht anderem Magier Aura + 6 Luftschiff 10,M läßt Boot/Langboot fliegen + 6 E... der Unsichtbarkeit 50,$ Artefakt + 7 E... Antimagiekristall 50,$ Item, das Antimagiezone erzeugt + 7 Magiefresser 3*L Antimagie + 8 Runen des Schutzes 20 +20% Magieresistenz bei Burgen/Schiffen + 8 Schild des Fisches (+K) 4*L vermindert Schaden + 9 Beschleunigung (+K) 5*L verdoppelt Attacken + 9 E... einen Ring der Macht 100,$ Artefakt +10 Blick in die Realität 40 sieht Region aus Astralebene +10 E... Negativen Gewichts 30,1P,$ Artefakt +11 Zeitdehnung 5*L Doppelte Bewegung und Angriffe +12 Rüstschild (+K) 4*L verbessert Rüstung +12 Vertrauten rufen 100,5P Vertrauter +13 Belebtes Gestein 10,5P,M Versetzt Burg samt Besatzung +14 Störe Astrale Integrität 140 +15 Opfere Kraft 100 transferiert permanente Aura + + + diff --git a/doc/synonyme-rassen b/doc/synonyme-rassen new file mode 100644 index 000000000..47a216ba3 --- /dev/null +++ b/doc/synonyme-rassen @@ -0,0 +1,84 @@ + +Elfen +----- + +Waldelfen +Hochelfen +Grauelfen +Meerelfen +Auelfen +Dunkelelfen +Sylphen + + +Halblinge +--------- + +Kender + + +Meermenschen +------------ + +Nymphen +Meervolk +Echsen + + +Zentauren +--------- + +Satyre + + +Zwerge +------ + +Duergar + + +Katzen +------ + +Tiger +Löwen +Panter +Wolf +Rakshasa + + + +Trolle +------ + +Olog-Hai +Riesen +Oger + +Orks +---- + +Uruks +Gnolle +Hobgoblin +Troglodyte + + +Insekten +-------- + + +Dämonen +------- + +Teufel + + + +Goblins +------- + +Kobolde +Gnome +Wichtel + diff --git a/doc/todo.txt b/doc/todo.txt new file mode 100644 index 000000000..b573a08c2 --- /dev/null +++ b/doc/todo.txt @@ -0,0 +1,239 @@ +TODO, FEATURES, usw: + +- alte Artefakte sollten auch permanente Aura kosten + +- Traumauge auf 5 PA senken. + +- news.silly: + - Statistik der Regionen mit den meisten Gräbern + - Wahl zur unbeliebtesten Region der Welt (Wo Bauern am wenigsten gern sein + wollen). + +- NEW_ITEMS: + - SILVERWEIGHT, silber nicth doppelt berechnen. + + +- Monster sollten die Runde nach dem Auftauchen nicht angreifen können. + +- Alchemie: Mehr als einen Trank/Runde brauen + Tränke aufwerte/billiger machen + +- items, die man selber benennen kann. + namen werden reserviert für eine partei, jede partei nur endlich viele. + derartige items wiegen nichts, können frei gehandelt werden + spezielle option, um solche items an beliebige einheiten zu übergeben + (amulett des treffens, u.ä.) + +- Tränke aufwerten, + +- Gebäude für Dämonen? + +- Parteiwissen + +- Mails "von Einheiten an Einheiten". + +- Neue Schiffstypen + +- Meditation / Trance? + langer Befehl, bringt doppelte Regeneration, braucht Heiligen Hain + oder Magierturm? + +- Linear Stufenabhängige Zauber ausbauen, ist wohl zu verwirrend, das + ein Zauber Stufenabhängig ist aber nicht in der Stufe variiert werden + kann. + +- parteigetarnte alliierte angreifbar machen. siehe mail an e-kom, enno, 29.8.00 + +- Anmeldungen direkt in den mapper integrieren + ++ HELFE-Gruppen + + +- Wiederanmeldungen im Wochenbericht von Erstanmeldungen trennen + +- Nichtmeermenschen Piraterie einfacher machen. (20 Lerntage auf See? + Geringer Arbeitslohn?) + +- battle.c: evtl. kann man sich b->fighters sparen? + +- struct building: ein paar der variablen können auch attribute sein, siehe header + ++ battle.c: b->see_matrix = calloc((max_fac_no+1), sizeof(boolean *)); + (enno) die see_matrix ist als speichermethode ziemlich ineffizient, + da es eine sparse matrix ist. besser so machen wie in cansee() + +- battle.c: Drüber nachdenken, ob struct battle wirklich dynamisch angelegt werden sollte. + (enno) nein, eigentlich sollte sie das nicht... + +- mehr Geländeunterschiede. Vorschläge: + - Sumpf: bewegung -1 + ++ potion effects werden bei transfermen nicht skaliert. + spätestens im item-source wird das gefixt. + +- Durch übergeben kann man Silber vor Beklaue schützen + +- Botschaft an Einheit gibt als Meldung nur + 'Eine Botschaft von xyz: ""' + +- Schiffeversenken gibt kaputte Meldung + Cef'az (c142) in Dór'Aglar (18,-16) versenkt die X~Lc^H~@~[c^H^X~Gc^Hm + (17). + (Das Schiff war die Cir'mor (1142), Langboot) + +- Monsterauseinanderlaufen klappt nicht. + ++ cansee() fehlt in vielen Zaubern. + ++ parser hat neuerdings ein umlautproblem (unter windows) (Öl->l) + ++ gräber verwittern + +- kampfzauber im cr anzeigen. + +- umlautfix fixen. + ++ Frubilaszat (Lhmc) findet 0 Blasenmorcheln. + ++ schiffsfix fixen. + ++ handel vereinfachen. + +? Durch Fahre/Transportiere können Nichtschwimmer/Nichtflieger über + Meerengen, wenn ein Schwimmer/Flieger sie transportiert. Rast auf + dem Meer ist für sie aber nicht möglich. Evt. kann man da tricksen, + muß überprüft werden. + +- Transportkapazität sollte in race[]. + ++ Lehre-Meldungen: + skill-Nummern werden nirgends definiert. + (enno) da muß eine liste her, in einem rules.cr + (enno) oder besser, wir geben die namen aus. + +- Fehler-beschreibungen sind inkonsistent: + > "Dusei-Endlager (cawk) ist mindestens gleich gut wie wir.";error + aber + > 65;errno + (statt "Die Lernkosten können nicht bezahlt werden") + +- Unterhalt-Zahlungen: + Es fehlt Unterscheidung zwischen Bezahlung und nicht-bezahlung, + sowie der Betrag. + (enno) ja, ist schwer, wegen der komplexen message. + ++ Luxusgut-Ein-/Verkauf: + Warum auf 2 Messages aufspalten ? + (enno) warum nicht. + (corwin) Weil nur noch ne uninteressante Message mehr? + +? casualties-Messages nicht definiert. (Was soll die machen?) + (enno) + - Die sollen in render.c eine Spezialbehandlung bekommen. + + Quickfix: Eine einfache Ausgabe für den NR. + ++ hilfen für ehmv/vorlage im CR (NUMMER, TEMP) + +- Werte mit 0 wieder in den CR aufnehmen (angefangen) + ++ lerntage -> lernpunkte (doku) + + +- Eine Konstruktion der Form + + new_message(...,"%s:bla",buf) funktioniert nicht. Es werden + Nonsens-Strings ausgegeben: + + Nummer 5r94 (5r94) in Syvanrylfed (4,4) produziert 85 Schwerter aus + Optionen: AUSWERTUNG ZUGVORLAGE SILBERPOOL STATISTIK MERIAN ADRESSEN + + Wird da evt. nur der Zeiger auf Buf gespeichert? Oha, dann haben + wir bei vielen Messages ein Problem. + + -> (enno) stimmt. Der übergebene String darf nicht mehr verändert werden, + strdup() ist dein freund. Um das anders zu machen, könnte man in + new_message() einstrdup() machen, wenn der typ %s gefuden wird. + + -> strings sollten allerdings eh selten sein, da ja meist locale-dependent. + ausnahmen sind eigentlich nur namen. + + -> passiert bei allen Zauberfehlermeldungen + +- Einordnung der Messages klappt scheinbar nicht, es stehen auch + Produktionsmessages unter Meldungen und Fehler. + + -> Die Einordnung ist erst später dazugekommen, und wird noch kaum + irgendwo benutzt + ++ Unterscheidung unitid(mage), "Jemand" in der Ausgabe ist z.B. + beim Zauber Erwecke Ents fehlerhaft. (Die zaubernde Partei bekommt + ein 'Jemand ...' in den Regionsmessages.) Fehler wohl erst beim + Rendering: v_mage_de bekommt i=0 obwohl bei allen Erzeugern ein + mage angegeben wird. + (katja) scheint zu funktionieren + +Implementationslücken: + +Es fehlen noch Sprüche für + +- Tybied (20): + Stufe 13 + Artefakte zerstören (ist noch unsinnig, da es keine Artefakte mit + mehr als 1 Aura gibt) + + zusätzlich wäre schön: + niedrigstufiger Combatspell + Stufe 11 Non-Combat + Stufe 12 Non-Combat + + +- Illaun(15): + Stufe 1 + Stufe 13 + Stufe 14 + + Für diese Zauber fehlen noch Ideen + + zusätzlich wäre schön: + Stufe 2 Non-Combat + Stufe 7 Non-Combat + + +- Gwyrrd(27): + Stufe 11 + Stufe 12 + + Hier sind noch einige nicht weiter ausgearbeitete Zauberideen + vorhanden + +- Cerddor(26): + Stufe 14 + + Hier sind noch einige nicht weiter ausgearbeitete Zauberideen + vorhanden, aber alles eher niedrigstufige + + +- Draig(18): + Stufe 1 + Stufe 13 + Wahnsinn (Einheit, negativ) + + Hier fehlt nur die Implementation + + zusätzlich wäre schön: + Stufe 2 Non-Combat + +Dann gibt es für jedes Gebiet zu jeder Stufe kleiner 15 mindestens einen +normalen Spruch. + + + Kristall des Schutzes + Der Kristall erhöht im Kampf die für die Berechnung der + Magieresistenz angesetzte Stufe von 500 Personen (wenn + Besitzer ein Magier) oder 200 Personen (wenn Besitzer kein + Magier) der eigenen Partei (oder, wenn alle Personen der eigenen + geschützt sind, von Alliierten) um 25%. Der Besitzer muß kein + Magier sein. Implementation über ein Flag, welches vor dem Kampf + gesetzt wird. Sollte teuer sein, da Wirkung ziemlich heftig. + + diff --git a/doc/triggers.txt b/doc/triggers.txt new file mode 100644 index 000000000..5470a5d5e --- /dev/null +++ b/doc/triggers.txt @@ -0,0 +1,111 @@ +==-------------------== +New Style Eventhandling +==-------------------== + + Zweck des ganzen ist es, möglichst frei Reaktionen auf Ereignisse zu + implementieren. Dazu muß natürlich defineirt sein, was so ein Ereignis + ist. Sowohl die Eriegnisse als auch das, was daraufhin geschieht, muß + möglichst flexibel erweiterbar sein. außerdem sollen solche + Ereigniss-Behandlungen zur Laufzeit defineirt werden können. Die möglichen + Events kann man nur zur Compilezeit definieren, und auch die Klassen von + Behandlungsroutinen, aber festzulegen wer wann auf was reagiert sollte + durch einen Spruch, das auftauchen eines Drachen, einen Event oder den Tod + einer Einheit ausgelöst werden können, und beliebiges anderes. + +Wie's geht: + + Wenn durch den Code ein Event "event" ausgelöst wird, dann wird für das + betroffene Objekt X die Funktion handle_event(X->attribs, "event") + aufgerufen. Ja, der Event ist ein String. + + handle_event(a, e) sucht in der Liste der attribute nach einem + at_eventhandler Objekt. So ein at_eventhandler verwaltet eine Liste von + Trigger-Objekten trigger_list mit Daten und handle() Funktion, die im Fall + des Events aufgerufen wird. Für jeden event-typ (string) gibt es ein + solches at_eventhandler Attribut in der Attributliste, das mehrere + Trigger-Funktionen beinhalten kann. Ich glaube, die hat Ingo in seinem + Ansatz "action" getauft. + + Wurde ein Passendes gefunden, dann wird der Reihe nach jeder Trigger + ausgeführt. + + Das ganze wird im Datenfile sogar ziemlich lesbar, wie man hier an diesem + Magier sieht: + eventhandler destroy killunit LeL end + Hier ist ein eventhandler, der im falle eines "destroy" Events auch die + Einheit LeL killt (LeL ist der Vertraute des Magiers). + +Neue Trigger-Typen machen: + + Neue Trigger zu definieren ist ziemlich leicht, und ich habe schonmal ein + paar flexible vordefiniert. Sie sollten möglichst im Verzeichnis triggers/ + landen. Dran denken, das jeder in Eressea verwendete trigger-typ mit + tt_register() angemeldet werden muß. Das passiert in der Datei + eressea/triggers.c + + Dabei lohnt es sich, die trigger etwas genereller zu mchen. Für viele von + ihnen sollte man resolve.[hc] verstanden haben, da man das zum Speichern + von Referenzen auf Parteien, Einheiten, usw. benötigt. + +Trigger aktivieren: + + Der Trigger sollte jeweils in der Attributliste des Objektes landen, + dessen Existnez für die Ausführung nötig ist. z.B. der Trigger zum Töten + des Familiars beim Magier, der zum übergeben eines item an eine person die + ein gebäude betritt, in das Gebäude. + + Beispiel: Wenn die verzauberte Burg b zerstört wird, soll der zaubernde + Magier einen Schock erleiden: + add_trigger(&b->attribs, "destroy", trigger_shock(mage)); + Steht die Burg jedoch nach 10 Runden noch an ihrem Fleck, bekommt er einen + Schatz von 100 Silber: + trigger * ttreasure = trigger_giveitem(mage, &i_silver, 100); + trigger * ttimer = trigger_timetrigger(10, ttreasure); + add_trigger(&b-attribs, "timer", ttimer); + Wie man sieht, kann ein trigger einen anderen auslösen, und mit etwas + Geschick kann man ganze Ketten von Ereignissen hinbekommen, die wieder + neue Trigger setzen, usw. + +Bisher definierte Events: (NI=Not Implemented) + + {building|faction|unit|ship}:"destroy" - Das Objekt verschwindet. + {building|faction|unit|ship|region}:"timer" - einmal pro Runde in + eressea.c::ageing() + {building}"enter" - Gebäude oder Schiff wird betreten (NI) + +Bisher definierte trigger: + + - timeout: meta-trigger, aktiviert eine liste von triggern nach einer + zeitspanne. + - changerace: ändert race/irace für eine einheit + - giveitem: gibt items eines typs an eine einheit. + - killunit: tötet die angegebene einhiet. + - shock: schockt einen magier. + - changefaction + - removecurse + +adaptierte alte trigger: + + - famililars: + familiar: on "destroy" shock(mage) + mage: on "destroy" killunit(familiar) + - toad: + mage: on "timer" timeout([changerace(), giveitem(toadslime)]) + - summondragon: + region: on "timer" timeout([createunit()]) + - magicboost: + mage: on "timer" timeout(createcurse()) + - charm: + target: on "timer" changefaction(target) + new faction: on "destroy" destroy(target) + +problems to be solved: + + - propagation of triggers/attributes in general + - was, wenn ein removecurse(c) ausgefuehrt werden soll, aber der curse + sich propagiert hat? dafür waere wohl ein forwarding-graph ganz geeignet. + (spells:5066, alp) + +TODO: + + - fprintf/fscanf nochmal checken. diff --git a/eressea.sln b/eressea.sln new file mode 100644 index 000000000..481c4854b --- /dev/null +++ b/eressea.sln @@ -0,0 +1,53 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "eressea", "src\eressea.vcxproj", "{AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "meropis", "..\meropis\src\meropis.vcxproj", "{52764450-41CA-11E0-B2EC-3136E0D72085}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "..\example\src\example.vcxproj", "{4A17DAEE-2261-4E2C-96F6-BA4132A09551}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kernel", "..\shared\src\kernel.vcxproj", "{6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gamecode", "..\shared\src\gamecode.vcxproj", "{1E8BFF9E-3044-0742-992F-C5765B80FE65}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "util", "..\shared\src\util.vcxproj", "{F70CFB27-8A2F-E447-B452-4E1C590EDA6D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lua-bindings", "..\shared\src\lua-bindings.vcxproj", "{75501170-51C2-E641-BA8B-EDC008184192}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "external", "..\external\external.vcxproj", "{F9AE4586-8F65-486B-9666-744839E40A54}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Debug|Win32.ActiveCfg = Debug|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Debug|Win32.Build.0 = Debug|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Release|Win32.ActiveCfg = Release|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Release|Win32.Build.0 = Release|Win32 + {52764450-41CA-11E0-B2EC-3136E0D72085}.Debug|Win32.ActiveCfg = Debug|Win32 + {52764450-41CA-11E0-B2EC-3136E0D72085}.Release|Win32.ActiveCfg = Release|Win32 + {4A17DAEE-2261-4E2C-96F6-BA4132A09551}.Debug|Win32.ActiveCfg = Debug|Win32 + {4A17DAEE-2261-4E2C-96F6-BA4132A09551}.Debug|Win32.Build.0 = Debug|Win32 + {4A17DAEE-2261-4E2C-96F6-BA4132A09551}.Release|Win32.ActiveCfg = Release|Win32 + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Debug|Win32.ActiveCfg = Debug|Win32 + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Release|Win32.ActiveCfg = Release|Win32 + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Release|Win32.Build.0 = Release|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Debug|Win32.ActiveCfg = Debug|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Release|Win32.ActiveCfg = Release|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Release|Win32.Build.0 = Release|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Debug|Win32.ActiveCfg = Debug|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Release|Win32.ActiveCfg = Release|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Release|Win32.Build.0 = Release|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Debug|Win32.ActiveCfg = Debug|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Release|Win32.ActiveCfg = Release|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Release|Win32.Build.0 = Release|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Debug|Win32.ActiveCfg = Debug|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Release|Win32.ActiveCfg = Release|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/eressea_vs2009.sln b/eressea_vs2009.sln new file mode 100644 index 000000000..27611d68c --- /dev/null +++ b/eressea_vs2009.sln @@ -0,0 +1,47 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kernel", "..\shared\src\kernel.vcproj", "{6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gamecode", "..\shared\src\gamecode.vcproj", "{1E8BFF9E-3044-0742-992F-C5765B80FE65}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "util", "..\shared\src\util.vcproj", "{F70CFB27-8A2F-E447-B452-4E1C590EDA6D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lua-bindings", "..\shared\src\lua-bindings.vcproj", "{75501170-51C2-E641-BA8B-EDC008184192}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "external", "..\external\external.vcproj", "{F9AE4586-8F65-486B-9666-744839E40A54}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "amalgamation-eressea", "src\eressea.vcproj", "{AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Debug|Win32.ActiveCfg = Debug|Win32 + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Release|Win32.ActiveCfg = Release|Win32 + {6F104C0A-DDF5-A34B-A89C-0DC278DCEF6D}.Release|Win32.Build.0 = Release|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Debug|Win32.ActiveCfg = Debug|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Release|Win32.ActiveCfg = Release|Win32 + {1E8BFF9E-3044-0742-992F-C5765B80FE65}.Release|Win32.Build.0 = Release|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Debug|Win32.ActiveCfg = Debug|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Release|Win32.ActiveCfg = Release|Win32 + {F70CFB27-8A2F-E447-B452-4E1C590EDA6D}.Release|Win32.Build.0 = Release|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Debug|Win32.ActiveCfg = Debug|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Release|Win32.ActiveCfg = Release|Win32 + {75501170-51C2-E641-BA8B-EDC008184192}.Release|Win32.Build.0 = Release|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Debug|Win32.ActiveCfg = Debug|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Release|Win32.ActiveCfg = Release|Win32 + {F9AE4586-8F65-486B-9666-744839E40A54}.Release|Win32.Build.0 = Release|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Debug|Win32.ActiveCfg = Debug|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Debug|Win32.Build.0 = Debug|Win32 + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387}.Release|Win32.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(SubversionScc) = preSolution + Svn-Managed = True + Manager = AnkhSVN - Subversion Support for Visual Studio + EndGlobalSection +EndGlobal diff --git a/game-e2/eressea.ini b/game-e2/eressea.ini new file mode 100644 index 000000000..8e4eff430 --- /dev/null +++ b/game-e2/eressea.ini @@ -0,0 +1,17 @@ +[eressea] +base = . +load = setup.lua +report = reports +verbose = 0 +lomem = 0 +debug = 0 +memcheck = 0 +locales = de,en + +[config] +game = eressea +source_dir = .. +maxnmrs = 10 + +[editor] +color = 1 diff --git a/game-e2/setup.lua b/game-e2/setup.lua new file mode 100644 index 000000000..a32fa437f --- /dev/null +++ b/game-e2/setup.lua @@ -0,0 +1,15 @@ +local srcpath = config.source_dir +local respath = srcpath .. '/res' +local paths = { + 'scripts/?.lua', + 'eressea/scripts/?.lua', + 'lunit/?.lua' +} + +for idx, path in pairs(paths) do + package.path = srcpath .. '/' .. path .. ';' .. package.path +end + +read_xml(respath..'/config-eressea.xml', respath..'/catalog-eressea.xml') + +require "init" diff --git a/game-e3/eressea.ini b/game-e3/eressea.ini new file mode 100644 index 000000000..efc181a02 --- /dev/null +++ b/game-e3/eressea.ini @@ -0,0 +1,17 @@ +[eressea] +base = . +load = setup.lua +report = reports +verbose = 0 +lomem = 0 +debug = 0 +memcheck = 0 +locales = de,en + +[config] +game = e3a +source_dir = .. +maxnmrs = 20 + +[editor] +color = 1 diff --git a/game-e3/setup.lua b/game-e3/setup.lua new file mode 100644 index 000000000..8fb4074e2 --- /dev/null +++ b/game-e3/setup.lua @@ -0,0 +1,15 @@ +local srcpath = config.source_dir +local respath = srcpath .. '/res' +local paths = { + 'scripts/?.lua', + 'eressea/scripts/?.lua', + 'lunit/?.lua' +} + +for idx, path in pairs(paths) do + package.path = srcpath .. '/' .. path .. ';' .. package.path +end + +read_xml(respath..'/config-e3a.xml', respath..'/catalog-e3a.xml') + +require "init" diff --git a/res/buildings/castle-2.xml b/res/buildings/castle-2.xml new file mode 100644 index 000000000..5f48e73ee --- /dev/null +++ b/res/buildings/castle-2.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/catalog-e3a.xml b/res/catalog-e3a.xml new file mode 100644 index 000000000..77e0f1210 --- /dev/null +++ b/res/catalog-e3a.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/res/catalog-eressea.xml b/res/catalog-eressea.xml new file mode 100644 index 000000000..77e0f1210 --- /dev/null +++ b/res/catalog-eressea.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/res/config-e3a.xml b/res/config-e3a.xml new file mode 100644 index 000000000..07a3b8eab --- /dev/null +++ b/res/config-e3a.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eressea-server@eressea.de + eressea-server@eressea.de + + + Bitte denke daran, deine Befehle mit dem Betreff + E3 BEFEHLE an eressea-server@eressea.de zu senden. + Remember to send your orders to + eressea-server@eressea.de with the subject E3 ORDERS. + + + E3 BEFEHLE + E3 ORDERS + + + diff --git a/res/config-eressea.xml b/res/config-eressea.xml new file mode 100644 index 000000000..d06464759 --- /dev/null +++ b/res/config-eressea.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eressea-server@eressea.de + eressea-server@eressea.de + + + Bitte denke daran, deine Befehle mit dem Betreff + ERESSEA BEFEHLE an eressea-server@eressea.de zu senden. + Remember to send your orders to + eressea-server@eressea.de with the subject ERESSEA ORDERS. + + + ERESSEA BEFEHLE + ERESSEA ORDERS + + + diff --git a/res/directions.xml b/res/directions.xml new file mode 100644 index 000000000..0e2e0b5fe --- /dev/null +++ b/res/directions.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/e3a/armor.xml b/res/e3a/armor.xml new file mode 100644 index 000000000..fe1d7fb45 --- /dev/null +++ b/res/e3a/armor.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/e3a/armor/chainmail.xml b/res/e3a/armor/chainmail.xml new file mode 100644 index 000000000..39743d754 --- /dev/null +++ b/res/e3a/armor/chainmail.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/e3a/armor/laenmail.xml b/res/e3a/armor/laenmail.xml new file mode 100644 index 000000000..d3b1dd533 --- /dev/null +++ b/res/e3a/armor/laenmail.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/e3a/armor/laenshield.xml b/res/e3a/armor/laenshield.xml new file mode 100644 index 000000000..1ce6216b2 --- /dev/null +++ b/res/e3a/armor/laenshield.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/e3a/armor/plate.xml b/res/e3a/armor/plate.xml new file mode 100644 index 000000000..550e83173 --- /dev/null +++ b/res/e3a/armor/plate.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/e3a/armor/rustychainmail.xml b/res/e3a/armor/rustychainmail.xml new file mode 100644 index 000000000..ac2628c62 --- /dev/null +++ b/res/e3a/armor/rustychainmail.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/e3a/armor/rustyshield.xml b/res/e3a/armor/rustyshield.xml new file mode 100644 index 000000000..2becae180 --- /dev/null +++ b/res/e3a/armor/rustyshield.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/e3a/armor/scale.xml b/res/e3a/armor/scale.xml new file mode 100644 index 000000000..809d08bca --- /dev/null +++ b/res/e3a/armor/scale.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/e3a/armor/shield.xml b/res/e3a/armor/shield.xml new file mode 100644 index 000000000..9b4ad9174 --- /dev/null +++ b/res/e3a/armor/shield.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/res/e3a/armor/towershield.xml b/res/e3a/armor/towershield.xml new file mode 100644 index 000000000..5a7edcabe --- /dev/null +++ b/res/e3a/armor/towershield.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/res/e3a/buildings.xml b/res/e3a/buildings.xml new file mode 100644 index 000000000..f87987ac1 --- /dev/null +++ b/res/e3a/buildings.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/equipment.xml b/res/e3a/equipment.xml new file mode 100644 index 000000000..bbd2ac5b7 --- /dev/null +++ b/res/e3a/equipment.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/items.xml b/res/e3a/items.xml new file mode 100644 index 000000000..706bd0d67 --- /dev/null +++ b/res/e3a/items.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/luxuries.xml b/res/e3a/luxuries.xml new file mode 100644 index 000000000..6c5094a19 --- /dev/null +++ b/res/e3a/luxuries.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/messages.xml b/res/e3a/messages.xml new file mode 100644 index 000000000..af781cfa5 --- /dev/null +++ b/res/e3a/messages.xml @@ -0,0 +1,10 @@ + + + + + + + "$if($isnull($mage),"Ein unentdeckter Magier",$unit($mage)) führt einen sonderbaren Tanz auf. Kurz darauf beginnt es zu regnen." + "$if($isnull($mage),"an unseen magician",$unit($mage)) dances a strange dance. Shortly after, rain begins to fall on the fields." + + diff --git a/res/e3a/races.xml b/res/e3a/races.xml new file mode 100644 index 000000000..3c28a2319 --- /dev/null +++ b/res/e3a/races.xml @@ -0,0 +1,927 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/resources.xml b/res/e3a/resources.xml new file mode 100644 index 000000000..bd71f8c5d --- /dev/null +++ b/res/e3a/resources.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/res/e3a/resources/iron.xml b/res/e3a/resources/iron.xml new file mode 100644 index 000000000..3856c045d --- /dev/null +++ b/res/e3a/resources/iron.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/e3a/resources/mallornseed.xml b/res/e3a/resources/mallornseed.xml new file mode 100644 index 000000000..4a348c212 --- /dev/null +++ b/res/e3a/resources/mallornseed.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/e3a/resources/seed.xml b/res/e3a/resources/seed.xml new file mode 100644 index 000000000..21652f364 --- /dev/null +++ b/res/e3a/resources/seed.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/e3a/resources/stone.xml b/res/e3a/resources/stone.xml new file mode 100644 index 000000000..854003c86 --- /dev/null +++ b/res/e3a/resources/stone.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/e3a/shipnames.xml b/res/e3a/shipnames.xml new file mode 100644 index 000000000..15e261ef0 --- /dev/null +++ b/res/e3a/shipnames.xml @@ -0,0 +1,108 @@ + + + + ein Einbaum + a canoe + + + ein Floß + a raft + + + ein Kutter + a cutter + + + eine Barke + a barge + + + + eine Königsbarke + a royal barge + + + ein Katamaran + a catamaran + + + + eine Kogge + a cog + + + eine Karavelle + a caravel + + + + eine Fregatte + a frigate + + + eine Galeone + a galleon + + + + ein Drachenschiff + a dragonship + + + eine Trireme + a trireme + + + + Einbaum + canoe + + + Floß + raft + + + Kutter + cutter + + + Barke + barge + + + + Königsbarke + royal barge + + + Katamaran + catamaran + + + + Kogge + cog + + + Karavelle + caravel + + + + Fregatte + frigate + + + Galeone + galleon + + + + Drachenschiff + dragonship + + + Trireme + trireme + + diff --git a/res/e3a/ships.xml b/res/e3a/ships.xml new file mode 100644 index 000000000..1174eda8e --- /dev/null +++ b/res/e3a/ships.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/spellbooks/cerddor.xml b/res/e3a/spellbooks/cerddor.xml new file mode 100644 index 000000000..1f0680352 --- /dev/null +++ b/res/e3a/spellbooks/cerddor.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/spellbooks/common.xml b/res/e3a/spellbooks/common.xml new file mode 100644 index 000000000..67f914623 --- /dev/null +++ b/res/e3a/spellbooks/common.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/spellbooks/draig.xml b/res/e3a/spellbooks/draig.xml new file mode 100644 index 000000000..e75363c71 --- /dev/null +++ b/res/e3a/spellbooks/draig.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/spellbooks/gray.xml b/res/e3a/spellbooks/gray.xml new file mode 100644 index 000000000..217bdb8b6 --- /dev/null +++ b/res/e3a/spellbooks/gray.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/spellbooks/gwyrrd.xml b/res/e3a/spellbooks/gwyrrd.xml new file mode 100644 index 000000000..4de9d8da8 --- /dev/null +++ b/res/e3a/spellbooks/gwyrrd.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/spellbooks/illaun.xml b/res/e3a/spellbooks/illaun.xml new file mode 100644 index 000000000..020705d2f --- /dev/null +++ b/res/e3a/spellbooks/illaun.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/spells.xml b/res/e3a/spells.xml new file mode 100644 index 000000000..23ce5d1a3 --- /dev/null +++ b/res/e3a/spells.xml @@ -0,0 +1,684 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/strings.xml b/res/e3a/strings.xml new file mode 100644 index 000000000..3b368a28e --- /dev/null +++ b/res/e3a/strings.xml @@ -0,0 +1,344 @@ + + + + + + + des dritten Zeitalters + the third age + + + + Packeis + fast ice + + + %s + %s + + + + Urkunden + Certificates + + + Nußhälften + nut halves + + + Flaschengeister + bottle demons + + + Pandoras Urkunde für Halbling ehrenhalber, weiblich + Pandora's Certificate + + + Heilige Nußhälfte + piece of a holy nut + + + Flaschengeist Zhardamon + bottle demon Zhardamon + + + + Repetierarmbrust + repeating crossbow + + + Repetierarmbrüste + repeating crossbows + + + + Gerüst + scaffolding + + + Wachstube + guard house + + + Wachturm + guard tower + + + Wache + watch + + + Marktplatz + marketplace + + + + + aufständische + rebellious + + + wütende + mad + + + zornige + angry + + + unruhige + agitated + + + gleichgültige + indifferent + + + entspannte + relaxed + + + zufriedene + content + + + glückliche + happy + + + treue + loyal + + + ergebene + devoted + + + unterwürfige + subservient + + + + Streitross + charger + + + Streitrösser + chargers + + + Turmschild + tower shield + + + Turmschilde + tower shields + + + + + Braue Siebenmeilentee + brew seven mile tea + + + Braue Wasser des Lebens + brew water of life + + + Braue Schaffenstrunk + brew busy beer + + + Braue Wundsalbe + brew ointment + + + Braue Bauernblut + brew peasant blood + + + Braue Pferdeglück + brew horsepower potion + + + Braue Elixier der Macht + brew elixir of power + + + Braue Heiltrank + brew healing potion + + + + + + Durch dieses uralte Tanzritual ruft der Zauberkundige + die Kräfte des Lebens und der Fruchtbarkeit an. Die darauf folgenden + Regenfälle begünstigen das Wachstum und erhöhen die Ernteerträge + einiger Bauern der Region bis der Regen wieder nachlässt. + This ancient rite calls upon the + forces of life and fertility. For the next few weeks, + the peasant's harvest will be extraordinary good. + (OBS: this spell has been changed and needs a new translation). + + + Für dieses Ernteritual sendet der Druide seine arkane Energie entlang + der astralen Kraftlinien der gesamten Umgebung, um selbst weit entfernte + Naturgeister zu erreichen. Die Beschwörung dieser Naturgeister ist eine + hohe Kunst, die höchste Konzentration und vor allem viel Erfahrung + erfordert. Die Bauern werden nach und nach von den besseren Ernten + profitieren und ihre Ersparnisse steigern können. + This ritual increases the output of the local farms. + Peasants in the region produce an extra silverpiece. The stronger the + druid's spell is, the longer the effect will last. + (OBS: this spell has been changed and needs a new translation). + + + In den dunkleren Gassen gibt es sie, + die Flüche und Verhexungen auf Bestellung. Aber + auch Gegenzauber hat der Jünger des Draigs + natürlich im Angebot. Ob nun der Sohn des + Nachbarn in einen Liebesbann gezogen werden soll + oder die Nebenbuhlerin Pickel und Warzen + bekommen soll, niemand gibt gerne zu, zu solchen + Mitteln gegriffen zu haben. Für diese + Dienstleistung streicht der Magier 25 Silber pro + Stufe ein. + In the dark alleys you can find those + who sell curses and hexes on demand - but you + can buy the apropriate counterspells from the + followers of Draig as well. May it be a love + spell for the son of a neighbour or a wart in + the face of a rival. For offering these + services, the sorcerer charges 25 silver pieces. + per level. + + + Personne n'interprète aussi bien les + rêves que les mages d'Illaun. Ils sont également + versés dans l'utilisation des objets utilisés + pour prédire le futur comme les boules de + cristal, les cartes de tarot ou les lignes de la + main. Un mentaliste peut gagner 25 écus par + niveau et par semaine en proposant ses services + aux paysans. + Niemand kann so gut die Träume deuten + wie ein Magier des Illaun. Auch die Kunst der + Wahrsagerei, des Kartenlegens und des Handlesens + sind ihm geläufig. Dafür zahlen ihm die Bauern + 25 Silber pro Stufe. + No one can read dreams as well as the + mages of Illaun. Furthermore, they are also + familiar with all other common means of + foretelling the future like crystal balls, tarot + cards or palms. A mentalist can earn 25 silver + pieces per level and week for offering these + services to peasants. + + + Wenn einem der Alchemist nicht weiterhelfen kann, geht man zu dem + gelehrten Tybiedmagier. Seine Tränke und Tinkturen helfen gegen + alles, was man sonst nicht bekommen kann. Ob nun die kryptische + Formel unter dem Holzschuh des untreuen Ehemannes wirklich geholfen + hat - nun, der des Lesens nicht mächtige Bauer wird es nie wissen. + Dem Magier hilft es auf jeden Fall... beim Füllen seines + Geldbeutels. 25 Silber pro Stufe lassen sich so in einer Woche + verdienen. + If the local alchemist could not help you, you should visit a + scholar of Tybied. His potions and tinctures may help when nothing + else does. If the cryptic formula under the wooden shoes of the + unfaithful husband really helped? - well, the peasant, who isn't + capable of reading, will never know. At least it helped the magician... + to fill his purse. In one week he can earn 25 silver per level that + way. + + + Cerddormagier sind _die_ Gaukler unter + den Magiern, sie lieben es das Volk zu unterhalten und + im Mittelpunkt zu stehen. Schon Anfänger lernen die + kleinen Kunststücke und magischen Tricks, mit denen man + das Volk locken und verführen kann, den Geldbeutel ganz + weit zu öffnen, und am Ende der Woche wird der Gaukler + 25 Silber pro Stufe verdient haben. + The mages of Cerddor truly are the + bards of the wizards; they love to use their sorcery to + entertain the crowds and to be the center of attention. + Even the apprentices study those little magic tricks, + which attract and fascinate the people and thus ensnare + them into leaving a few coins or more for the artist. By + the end of the week, the bard will have earned 25 silver + per level. + + + Die Fähigkeiten der Gwyrrd-Magier in + der Viehzucht und Heilung sind bei den Bauern sehr + begehrt. Gerade auf Märkten sind ihre Dienste häufig sehr + gefragt. Manch einer mag auch sein Talent dazu nutzen, + ein Tier für einen besseren Preis zu verkaufen. Pro + Stufe kann der Magier so 25 Silber verdienen. + The abilities of the mages of Gwyrrd + concerning the breeding and healing of cattle are highly + appreciated among the peasants. Especially at the + markets, their services are demanded frequently. Some of + them also use their talents to sell an animal at a + higher price. A magician can earn 25 silver pieces per level + in this way. + + + Time is one of the first mysteries every magician tries to solve. If he succeeds, he can focus all his energies on his magical studies as the ways between dormatory, library and magician tower now pass much faster. To keep his heart in tune the magician uses a special self made tey. Some mages even share it with up to 10 people + Die Zeit ist eines der ersten Geheimnisse, die jeder Magier zu erkunden versucht. Gelingt ihm dies, kann er alle seine Energie auf das Studium der Magie verwenden, die Wege zwischen Dormitorium, Bibliothek und Magierturm schafft er nun viel schneller. Um sein Herz im Takt zu halten verwendet er einen speziellen selbstgemachten Tee. Manche Magier teilen diesen Tee mit bis zu 10 Personen. + + + One of the most strange spells enables the magician to withdraw a little life energy from his surroundings to produce the so called water of life. The juice of some selected herbs is used to conserve the energies of life. As the magician is protected by his magical abilities, he is the only one who can touch the juice without taking damage. This is necessary because the juice must be carried around from place to place to avoid damaging one region by taking too much energy at once. + In einem der seltsamsten Zauber kann der Magier seiner Umgebung ein klein wenig der Lebensenergie entziehen um das sogenannte Wasser des Lebens herstellen. Als Basis dient ihm hierbei der Saft aus einigen ausgesuchten Kräutern, welcher die Lebensenergie speichern kann. Da der Magier aufgrund seiner magischen Fähigkeiten immun ist, ist er der einzige, der den Saft berühren kann, ohne Schaden zu nehmen. Sodenn muß er diesen eine Woche lang von Ort zu Ort tragen, damit er sich mit Lebensenergie vollsaugt, ohne einem einzelnen Ort soviel zu entfernen, daß er Schaden nimmt. + + + Busybeer is another application for the knowledge about time a magician accumulates. Even though manual labour is only seldom done by mages, they still show interest in increasing the efficiency of their minions that do this work for them. Mornac the wise was the first to discover how to produce a potion that would enable 10 minions to do the work of 20, thus freeing the other 10 for different experiments. + Ein weiteres Anwendungsgebiet des Wissens über die Zeit welches ein Magier ansammelt stellt der Schaffenstrunk dar. Auch wenn körperliche Arbeiten eher selten von Magiern ausgeführt werden, so haben diese doch ein Interesse daran, die Effizienz ihrer Untergebenen bei solchen Arbeiten zu steigern. Mornac der Weise war der erste, der entdeckte, daß man einen Trunk herstellen kann, durch den 10 Untergebene die arbeit von 20 erledigen können, wodurch 10 für andere Experimente weiterverwendet werden konnten. + + + In the aftermath of battle it is vital to heal your own troops. This can be done by a healer as well as by a magician. In contrast to a healer, the magician can fullfill his treatment already before the battle by binding his magical powers into a potent salve. This salve can be stored and only needs to be applied to the wounds after the battle. + Nach einem harten Kampf sollte man sich heilen lassen. Diese Möglichkeit bietet der Magier ebenso wie der Heiler. Im Gegensatz zum Heiler ist der Magier jedoch in der Lage, seine Behandlung bereits vor dem Kampf durchzuführen, indem er seine Heilkräfte in eine magische Salbe bindet, welche gelagert werden kann und nach dem Kampf nur aufgetragen werden muß. + + + One of the most dangerous and best guarded secrets of all mages is the knowledge about the power of death. Even though most of them would not openly admit it, and it is at least partially forbidden in most countries, each of them studies death sooner or later. When they do, they quickly find out that there is another plane of existance, the home of the demons. Only blood can quelch the thirst of those, when they switch from their home to our world. But experienced mages will find out that the demons bloodwine can be deluted when apropiate herbs are included, making it enough for 100 instead of 10 demons. As the demons may not know about that, the magician has to secretly sacrifice one of his freed minions. + Zu den gefährlichsten und geheimsten Wissen der Magier zählt das Wissen über die Macht des Todes. Auch wenn die meisten es nicht zugeben, so fasziniert dieses Thema jeden Magier. Früher oder später beschäftigen sich alle mit diesem, teiweise verbotenen, Gebiet. Sodann werden sie feststellen, das es noch eine weitere Ebene der Existenz gibt, in der die Dämonen beheimatet sind. Nur Blut allein vermag den Hunger dieser Wesen zu befriedigen, wenn sie ihre Ebene verlassen und unsere betreten. Erfahrene Magier werden jedoch feststellen, dass man den Blutwein, den die Dämonen zu sich nehmen strecken kann, so daß davon 100 anstatt nur 10 Dämonen satt werden. Da die Dämonen davon jedoch nichts wissen dürfen, muß der Magier selbst klammheimlich einen seiner urplötzlich verfügbar gewordenen Untergebenen opfern. + + + Even though mages live in strict cellibate they know a lot about creating certain longings. Peasants keep asking them for this love potion or that. But the penality for bewitching a peasant is death, so the mages offer their services only to farmers for their breeding stock. In an elaborate ritual, which only serves to hide the simplicity of the procedure, the magician draws the fluids from certains plants. while doing so, he calls upon the spirits of fertility which of course only listen when he talkes to them. Now the farmer knows that any attempt to draw the fluids himself will only result in a useless waste of resorces. Finally, the magician hands the vial with the fluids to the farmer, who pours it into his horses drinking water. + Obwohl für Magier das Gebiet der Liebe Tabu ist und sie im strengen Zölibat leben, haben sie ein großes Wissen darüber, wie man gewisse Bedürfnisse weckt, weshalb sie immer wieder von Dorfbewohnerinnen und Dorfbewohnern nach entsprechenden Zaubern gefragt werden. Da die Verzauberung eines Bewohners jedoch streng verboten ist, bieten sie ihre Dienste nur für die Züchter an. In einem Aufwendigen Ritual, welches jedoch nur dazu dient zu verschleiern, wie einfach dies eigentlich ist, vermischt der Magier vor den Augen des Züchters einige Pflanzensäfte. Dabei ruft er die Geister an die dem Pferdezüchter das Glück bescheren sollen, um klarzumachen, das diese nur mit ihm sprechen und jeglicher Versuch des Züchters, selbst die Kräuter zu mischen nur eine unbrauchbare Pampe produzieren würde. Anschließend überreicht der Magier dem Züchter eine Phiole, die dieser in die Tränke seiner Pferde entleeren muß. + + + Just like with the knowledge about death, the peasants feel uncomfortable with the knowledge about monsters. A few warriors though, who have already faced these creatures in combat, foud that the monsters blood had en invigourating effect on them. There is talk about some warriors, who bathed in the blood of the slain monsters to take up their strenght. But this effect ends soon, and only occurs with fresh blood. As no one has time to quickly slay a wyrm before attacking his neighbors, a way had to be found to make the effect last longer. After lots of experiments that cost the life of lots of good warriors who had to constantly bring in fresh dragon blood, Manasouf the black finally found a way. Originally a closely guarded secret, the recipe is now knows in all lands. First, the hardened dragon blood needs to be melted in hot tin. After that, the magician binds the spirit of the dragon to its blood once again. It can not fnd eternal rest until the last bit of blood has been used. + Ebenso wie das Wissen über den Tod ist das Wissen über gewisse Monster bei der abergläubigen Bevölkerung nicht gerne gesehen. Einige wenige Krieger jedoch, die diesen Kreaturen schon mal im Kampf gegenüberstanden haben entdeckt, daß deren Blut eine belebende Wirkung auf sie hatte. So solle es schon Kriger gegeben haben, die im Blut der erschlagenen Monster badeten, um deren Stärke in sich aufzunehmen. Diese Wirkung verfliegt jedoch rasch, und wirkt nur bei frischen Blut. Da niemand vor dem Kampf gegen seinen Nachbarn die Zeit hat, schnell noch einen Wyrm zu erschlagen musste ein Weg gefunden werden, die Wirkung haltbar zu machen. Manasouf dem schwarzen gelang dies nach zahlreichen Experimenten, die das Leben vieler guter Männer kosteten, welche ständig neues Drachenblut für seine Versuche beschaffen mussten. Ursprünglich ein streng gehütetes Geheimnis, ist das Rezept inzwischen im ganzen Land bekannt. Zunächst muß geronnene Drachenblut muß in einem Tiegel wieder verflüssigt werden. Anschließend wird der Geist des erschlagenen Drachen in der Geisterebene wieder an sein Blut gebunden, und kann solange nicht in frieden ruhen, bis das letzte bisschen seines Blutes verbraucht wurde. + + + Some mages research deth's secrets until they can bring the dead back to life. But those who are brought back are often only shadows of ther former self and turn against their erstwhile friends. But those mages that study life and its iteraction with death find a possibility, to bring the deceased back as their original selves. A drawback is that this is only possible in the very first minutes after the death. As even mages can not be everywhere at the same time, a way had to be found to give this ability to helpers. All healers who tried to learn this from the mages failed, though, until one of those healers was backstabbingly killed. In the moment of his death he used the knowledge gained and was able to have his murderer executed the following day. The potion he designed has to be blessed by a magician before usage at any given time. This potion gives 4 people (or 1 person 4 times) a 50% chance to survive an otherwise deadly wound. It is used automatically by the victom. + Manche Magier erforschen den Tod, bis sie verstorbene wieder ins Leben zurück bringen können. Diese sind jedoch meist bösartig und nur noch Schatten ihres früheren selbst. Diejenigen jedoch, die sich intensiv mit dem Leben und seiner Kombination mit dem Tod beschäftigen finden eine Möglichkeit, verstorbene in ihrer wahren Gestallt zurück zu rufen. Dies ist allerdings nur wenige Minuten nach dem Tod möglich. Da selbst Magier nicht überall gleichzeitig sein können, musste ein Weg gefunden werden, diese Fähigkeit auf andere zu übertragen. Alle Versuche, dies feldschern beizubringen scheiterten jedoch, bis einer dieser Felschner von einem Widersacher hinterrücks ermordet wurde. Im Moment seines Todes wandte er sein erworbenes Wissen an und konnte tags darauf den Übeltäter wegen Mordes hinrichten lassen. Der von ihm entwickelte magische Trank muß jedoch von einem der Magie des Lebens kundigen gesegnet werden, um seine volle Wirkung zu entfalten. 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. + + + + + + Vertrauten binden + Bind Familiar + + + + + 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. + + + + diff --git a/res/e3a/terrains.xml b/res/e3a/terrains.xml new file mode 100644 index 000000000..f473fd500 --- /dev/null +++ b/res/e3a/terrains.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/weapons.xml b/res/e3a/weapons.xml new file mode 100644 index 000000000..421c60305 --- /dev/null +++ b/res/e3a/weapons.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/weapons/crossbow.xml b/res/e3a/weapons/crossbow.xml new file mode 100644 index 000000000..57535a070 --- /dev/null +++ b/res/e3a/weapons/crossbow.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/res/e3a/weapons/greatbow.xml b/res/e3a/weapons/greatbow.xml new file mode 100644 index 000000000..ba91f54de --- /dev/null +++ b/res/e3a/weapons/greatbow.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/e3a/weapons/greatsword.xml b/res/e3a/weapons/greatsword.xml new file mode 100644 index 000000000..5fc1c9042 --- /dev/null +++ b/res/e3a/weapons/greatsword.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/res/e3a/weapons/halberd.xml b/res/e3a/weapons/halberd.xml new file mode 100644 index 000000000..edb933701 --- /dev/null +++ b/res/e3a/weapons/halberd.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/res/e3a/weapons/laensword.xml b/res/e3a/weapons/laensword.xml new file mode 100644 index 000000000..13ebbf03e --- /dev/null +++ b/res/e3a/weapons/laensword.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/res/e3a/weapons/mallorncrossbow.xml b/res/e3a/weapons/mallorncrossbow.xml new file mode 100644 index 000000000..920ae553f --- /dev/null +++ b/res/e3a/weapons/mallorncrossbow.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/res/e3a/weapons/mallornlance.xml b/res/e3a/weapons/mallornlance.xml new file mode 100644 index 000000000..6355f5780 --- /dev/null +++ b/res/e3a/weapons/mallornlance.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/res/e3a/weapons/rustygreatsword.xml b/res/e3a/weapons/rustygreatsword.xml new file mode 100644 index 000000000..818fac553 --- /dev/null +++ b/res/e3a/weapons/rustygreatsword.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/res/e3a/weapons/rustyhalberd.xml b/res/e3a/weapons/rustyhalberd.xml new file mode 100644 index 000000000..bab849b49 --- /dev/null +++ b/res/e3a/weapons/rustyhalberd.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/res/eressea/artrewards.xml b/res/eressea/artrewards.xml new file mode 100644 index 000000000..6cb836c52 --- /dev/null +++ b/res/eressea/artrewards.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/buildings.xml b/res/eressea/buildings.xml new file mode 100644 index 000000000..64d65cff0 --- /dev/null +++ b/res/eressea/buildings.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/eressea/equipment.xml b/res/eressea/equipment.xml new file mode 100644 index 000000000..6e2f25b95 --- /dev/null +++ b/res/eressea/equipment.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/items.xml b/res/eressea/items.xml new file mode 100644 index 000000000..a1efc9620 --- /dev/null +++ b/res/eressea/items.xml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/races.xml b/res/eressea/races.xml new file mode 100644 index 000000000..d34d2bb28 --- /dev/null +++ b/res/eressea/races.xml @@ -0,0 +1,1373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/spellbooks/cerddor.xml b/res/eressea/spellbooks/cerddor.xml new file mode 100644 index 000000000..0609b0141 --- /dev/null +++ b/res/eressea/spellbooks/cerddor.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/spellbooks/draig.xml b/res/eressea/spellbooks/draig.xml new file mode 100644 index 000000000..b31905875 --- /dev/null +++ b/res/eressea/spellbooks/draig.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/spellbooks/gray.xml b/res/eressea/spellbooks/gray.xml new file mode 100644 index 000000000..bdb751b15 --- /dev/null +++ b/res/eressea/spellbooks/gray.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/spellbooks/gwyrrd.xml b/res/eressea/spellbooks/gwyrrd.xml new file mode 100644 index 000000000..68d0c4ff0 --- /dev/null +++ b/res/eressea/spellbooks/gwyrrd.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/spellbooks/illaun.xml b/res/eressea/spellbooks/illaun.xml new file mode 100644 index 000000000..a0d2caf52 --- /dev/null +++ b/res/eressea/spellbooks/illaun.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/spellbooks/tybied.xml b/res/eressea/spellbooks/tybied.xml new file mode 100644 index 000000000..791180c19 --- /dev/null +++ b/res/eressea/spellbooks/tybied.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/spellinfo.xml b/res/eressea/spellinfo.xml new file mode 100644 index 000000000..ce2e82635 --- /dev/null +++ b/res/eressea/spellinfo.xml @@ -0,0 +1,113 @@ + + + + + Dieses uralte Tanzritual ruft die + Kräfte des Lebens und der Fruchtbarkeit. Die Erträge der + Bauern werden für einige Wochen deutlich besser + ausfallen. + This ancient rite calls upon the + forces of life and fertility. For the next few weeks, + the peasant's harvest will be extraordinary good. + + + Dieses Ernteritual verbessert die Erträge der + arbeitenden Bauern in der Region um ein Silberstück. Je mehr Kraft der + Druide investiert, desto länger wirkt der Zauber. + This ritual increases the output of the local farms. + Peasants in the region produce an extra silverpiece. The stronger the + druid's spell is, the longer the effect will last. + + + Wenn einem der Alchemist nicht weiterhelfen kann, geht man zu dem + gelehrten Tybiedmagier. Seine Tränke und Tinkturen helfen gegen + alles, was man sonst nicht bekommen kann. Ob nun die kryptische + Formel unter dem Holzschuh des untreuen Ehemannes wirklich geholfen + hat - nun, der des Lesens nicht mächtige Bauer wird es nie wissen. + Dem Magier hilft es auf jeden Fall... beim Füllen seines + Geldbeutels. 50 Silber pro Stufe lassen sich so in einer Woche + verdienen. + If the local alchemist could not help you, you should visit a + scholar of Tybied. His potions and tinctures may help when nothing + else does. If the cryptic formula under the wooden shoes of the + unfaithful husband really helped? - well, the peasant, who isn't + capable of reading, will never know. At least it helped the magician... + to fill his purse. In one week he can earn 50 silver per level that + way. + + + Cerddormagier sind _die_ Gaukler unter + den Magiern, sie lieben es das Volk zu unterhalten und + im Mittelpunkt zu stehen. Schon Anfänger lernen die + kleinen Kunststücke und magischen Tricks, mit denen man + das Volk locken und verführen kann, den Geldbeutel ganz + weit zu öffnen, und am Ende der Woche wird der Gaukler + 50 Silber pro Stufe verdient haben. + The mages of Cerddor truly are the + bards of the wizards; they love to use their sorcery to + entertain the crowds and to be the center of attention. + Even the apprentices study those little magic tricks, + which attract and fascinate the people and thus ensnare + them into leaving a few coins or more for the artist. By + the end of the week, the bard will have earned 50 silver + per level. + + + Die Fähigkeiten der Gwyrrd-Magier in + der Viehzucht und Heilung sind bei den Bauern sehr + begehrt. Gerade auf Märkten sind ihre Dienste häufig sehr + gefragt. Manch einer mag auch sein Talent dazu nutzen, + ein Tier für einen besseren Preis zu verkaufen. Pro + Stufe kann der Magier so 50 Silber verdienen. + The abilities of the mages of Gwyrrd + concerning the breeding and healing of cattle are highly + appreciated among the peasants. Especially at the + markets, their services are demanded frequently. Some of + them also use their talents to sell an animal at a + higher price. A magician can earn 50 silver pieces per level + in this way. + + + In den dunkleren Gassen gibt es sie, + die Flüche und Verhexungen auf Bestellung. Aber + auch Gegenzauber hat der Jünger des Draigs + natürlich im Angebot. Ob nun der Sohn des + Nachbarn in einen Liebesbann gezogen werden soll + oder die Nebenbuhlerin Pickel und Warzen + bekommen soll, niemand gibt gerne zu, zu solchen + Mitteln gegriffen zu haben. Für diese + Dienstleistung streicht der Magier 50 Silber pro + Stufe ein. + In the dark alleys you can find those + who sell curses and hexes on demand - but you + can buy the apropriate counterspells from the + followers of Draig as well. May it be a love + spell for the son of a neighbour or a wart in + the face of a rival. For offering these + services, the sorcerer charges 50 silver pieces + per level. + + + Personne n'interprète aussi bien les + rêves que les mages d'Illaun. Ils sont également + versés dans l'utilisation des objets utilisés + pour prédire le futur comme les boules de + cristal, les cartes de tarot ou les lignes de la + main. Un mentaliste peut gagner 50 écus par + niveau et par semaine en proposant ses services + aux paysans. + Niemand kann so gut die Träume deuten + wie ein Magier des Illaun. Auch die Kunst der + Wahrsagerei, des Kartenlegens und des Handlesens + sind ihm geläufig. Dafür zahlen ihm die Bauern + 50 Silber pro Stufe. + No one can read dreams as well as the + mages of Illaun. Furthermore, they are also + familiar with all other common means of + foretelling the future like crystal balls, tarot + cards or palms. A mentalist can earn 50 silver + pieces per level and week for offering these + services to peasants. + + + diff --git a/res/eressea/spells.xml b/res/eressea/spells.xml new file mode 100644 index 000000000..6b8fec30c --- /dev/null +++ b/res/eressea/spells.xml @@ -0,0 +1,646 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/eressea/strings.xml b/res/eressea/strings.xml new file mode 100644 index 000000000..0f7a7141d --- /dev/null +++ b/res/eressea/strings.xml @@ -0,0 +1,453 @@ + + + + + + Aufzeichung des Vortrags von Selen Ard'Ragorn in Bar'Glingal: + 'Es heiss, dieser Spruch wäre wohl in den Spelunken der Westgassen + entstanden, doch es kann genausogut in jedem andern verrufenen + Viertel gewesen sein. Seine wichtigste Zutat ist etwa ein Fass + schlechtesten Weines, je billiger und ungesunder, desto + wirkungsvoller wird die Essenz. Die Kunst, diesen Wein in pure + Essenz zu destillieren, die weitaus anspruchsvoller als das einfache + Rezeptmischen eines Alchemisten ist, und diese dergestalt zu binden + und konservieren, das sie sich nicht gleich wieder verflüchtigt, wie + es ihre Natur wäre, ja, dies ist etwas, das nur ein Meister des + Cerddor vollbringen kann. Nun besitzt Ihr eine kleine Phiola mit + einer rubinrotschimmernden - nun, nicht flüssig, doch auch nicht + ganz Dunst - nennen wir es einfach nur Elixier. Doch nicht dies ist + die wahre Herausforderung, sodann muss, da sich ihre Wirkung leicht + verflüchtigt, diese innerhalb weniger Tage unbemerkt in das Getränk + des Opfers geträufelt werden. Ihr Meister der Betöhrung und + Verführung, hier nun könnt Ihr Eure ganze Kunst unter Beweis + stellen. Doch gebt Acht, nicht unbedacht selbst von dem Elixier zu + kosten, denn wer einmal gekostet hat, der kann vom Weine nicht mehr + lassen, und er säuft sicherlich eine volle Woche lang. Jedoch nicht + die Verführung zum Trunke ist die wahre Gefahr, die dem Elixier + innewohnt, sondern das der Trunkenheit so sicher ein gar + fürchterliches Leid des Kopfes folgen wird, wie der Tag auf die + Nacht folgt. Und er wird gar sicherlich von seiner besten Fähigkeit + einige Tage bis hin zu den Studien zweier Wochen vergessen haben. + Noch ein Wort der Warnung: Dieses ist sehr aufwendig, und so Ihr + noch weitere Zauber in der selben Woche wirken wollt, so werden sie Euch + schwerer fallen.' + + + + Mit diesem Spruch kann der Traumweber + versuchen, die Verzauberungen einer einzelnen + Einheit 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 mentalist can + attempt to detect enchantments on a target unit. + He will get an idea of the effect of all spells + that don't exceed his own abilities. If a spell + is stronger, it takes a little luck for a + successful analysis. + + + + + Traumbilder analysieren + Analyse Dreams + + + Schaler Wein + Hangover + + + + + Schneemann + snowman + + + Schneemänner + snowmen + + + + + + Keine Informationen über diesen Schiffstyp verfügbar. + No Information available for this type of ship. + + + Der Sumpfgasballon besteht aus einem großen + Weidenkorb, welcher Platz + für maximal 5 Personen oder 500 Gewichtseinheiten bietet, und einer + großen, mit Sumpfgas gefüllten Wyrmblase. Bei guten Winden kann sich + der Ballon zwei Regionen pro Woche fortbewegen. Das Führen eines + Ballons ist nicht einfach, und der Kapitän muss mindestens ein + Segeltalent von 6 besitzen. Diese neue Entwicklung auf Eressea wird + ausschließlich für den Xontormia-Expreß hergestellt und die Baupläne + sind streng geheim. So ist es auch bisher noch niemandem gelungen, ein + Exemplar nachzubauen. + + + + + Benutzt der Kapitän des Schiffes diesen Talisman, so wird allen an Bord befindlichen Mallornsamen ihre magisch Energie entzogen, und das Schiff kann mit dieser Energie bis zu zwei Wochen lang fliegen. + + + Eine Geburtstagstorte mit 10 Kerzen. Herzlichen Glückwunsch, Eressea! + A birthday cake with 10 candles. Happy Birthday, Eressea! + + + Keine Informationen. + No Information available. + + + Dieses Fluggerät aus der Schmiede der Zwerge von Celeband galt wie die + 'Ebene der Herausforderung' seit Urzeiten als verschollen, ja man + zweifelte seine Existenz an. Die Sage überliefert, das derjenige, der + sie auf der Spitze des Turmes seiner Gesinnung benutzt, als einziger + die 'Ebene der Herausforderungen' verlassen kann. + + + Glückwunsch, mein Kind. Du bist im Besitz des mächtigsten + Artefaktes Eresseas. Ein Fluch, sagt man, liege auf ihm, denn + niemand hat es bisher lange sein Eigen genannt... + + + Kleines trockenes Dauergebäck, m od. s; - u. -es, - u. -e + + + So wisse denn, dass das Auge des Drachen den Weg zur Herausforderung + aufzeigt. Doch die Überlieferung sagt, das nur der Unschuldige und + Ungewappnete es benutzen kann. Sie sagt auch, daß er einen Beutel mit + einem Betrag von bis zu zweitausend Silber mit sich führen soll, + jedoch nicht mehr als einem Fünftel der Stärke seines Volkes + entsprechend - dem Torwächter zum Geschenke als Beweis seiner + asketischen Gesinnung. Die 5 scheidet ganz aus. + + + (Prunus dulcis) [...] Die Nüsse existieren in zwei Varianten, süß und + bitter. Süße Mandeln sind der bekannte eßbare Typ, der in Form von + Nüssen gegessen, beim Kochen verwandt oder zu Mandelöl und Mandelmehl + verarbeitet wird. + + + A tasty fruit. + Frucht aus der Gattung Malus (ca. 25 Arten), gehört + zur Familie der + Rosengewächse. Die am häufigsten kultivierte Baumfrucht. Der Apfel + gehört zu den fleischigen Früchten, in dem der gereifte Fruchtknoten + und + das umgebende Gewebe fleischig und eßbar werden. Die Apfelblüte der + meisten Varianten erfordert Kreuzbestäubung zur Befruchtung. Form und + Größe des Apfels bei der Ernte variieren abhängig von kulturellen und + umweltbedingten Einflüssen in Größe, Form, Farbe und Geschmack, sind + jedoch nichtsdestotrotz üblicherweise rund, zwischen 50 und 100mm im + Durchmesser und weisen röt- oder gelbliche Farbtöne auf. + + + Nuß, im umgangssprachlichen Sinne alle trockenen, + hartschaligen + Früchte oder Samen, die eine Schale besitzen, die sich leicht + vom inneren, eßbaren Kern entfernen läßt. In der botanischen + Terminologie beschränkt sich die Bezeichnung Nuß auf eine + einsamige Frucht, die aus einem Fruchtknoten (Ovarium) + entstanden ist, dessen äußere Wände sich verholzt haben und der + sich nicht öffnet, um seinen Samen zu entlassen. Solche echten + Nüsse können eßbar, aber auch ungenießbar sein. Bekannte + Beispiele sind Eicheln, Bucheckern, Kastanien und Haselnüsse. + Beispiele für Früchte oder Samen, die vom Volksmund fälschlich + als Nüsse bezeichnet werden, sind Mandeln und Walnüsse: Im + botanischen Sinne sind dies Steinfrüchte, denen die fleischige + äußere Schale entfernt wurde. Andere Beispiele für unechte + Nüsse sind Erdnüsse - in Hülsen eingeschlossene Samen - sowie + Roßkastanien und Paranüsse, bei denen es sich um von Kapseln + umhüllte Samen handelt. + + + Zwerge schufen diesen wunderschönen Ring aus Weissgold und Platin. Die + Oberfläche ist so glatt, dass man nur bei genauem Hinsehen entdeckt, + dass hier eigentlich zwei Metalle ineinander verarbeitet worden sind. + In der Innenseite des Rings ist eine Gravur zu lesen: "Wildente, 3. + Woche Eiswind Jahr 8". + + + Dieses Brautkleid ist mit Abstand das schönste, was je jemand + irgendwie irgendwo gesehen hat. Auch wenn nur Wildente und Jadee das + finden müssten, wird jeder Bewohner Eresseas dies neidlos bestätigen. + Das sehr stilvolle Kleid lässt die zarten Schultern seiner Trägerin + frei und liegt am Oberkörper eng an. Dies betont atemberaubend die + zarten Kurven der Braut. Der Rock fällt leicht ausgestellt den ganzen + langen Weg an den Beinen herunter Richtung Boden, wo er sich in einer + sehr stilvollen Schleppe ergiesst. + + + Dieser Ring ist ein wahres Meisterwerk. Obwohl er sehr gross ist + (weil auch sein Träger sehr gross ist), wirkt er filigran. Weissgold + und Platin verschmelzen in diesem Ring zu einer Einheit, die die + Schönheit der einzelnen Elemente nur noch unterstreich. In der + Innenseite des Rings ist eine Gravur zu lesen: 'Jadee, 3. Woche + Eiswind Jahr 8'. + + + Hach! Sieht der Mann beeindruckend aus in diesem Frack! Und so + ordentlich! Und so ernst! Und so beeindruckend! Es fällt ein + wenig schwer, sich auf den Bräutigam zu konzentrieren, weil das + Brautkleid noch daneben strahlt, aber der Anzug des Bräutigams ist + auf jeden Fall so, wie er sein soll und sieht toll aus und sehr + geschmackvoll. + + + Orange nose, black hat, frosty character. A snowman. + He'll make a fine guard if you use him in a cold place. (USE 1 + snowman) + Rübennase, schwarzer Hut, kaltes Herz. Ein Schneemann. Er gibt + einen prima Wachmann ab, wenn er in einem Gletscher belebt wird + (BENUTZE 1 Schneemann). + + + These items stay frozen all year round. There seem to be bits of + ice in them - in the right hands, these might put an eye out! + Ein Schneeball. Es scheinen kleine Eissplitter darin zu sein. In + den richtigen Händen können sie sicher weh tun. + + + This badge pronounces its wearer an official visitor to the + region of Muschelplateau. + Dieses Abzeichen identifiziert die Partei seines Trägers offiziell + als einen Besucher der Botschafterregion 'Muschelplateau'. + + + Dieser wunderschoen geschmueckte Baum entfaltet in den Wintermonaten eine magische Wirkung auf den ganzen Wald. + In the winter months, this beautifully decorated tree has a magical effect on the entire forest. + + + Dieser magische Staub ist aus einem im Winter vom Himmel gefallenen + Stern gewonnen worden, und ihm werden aphrodisiakische Eigenschaften + nachgesagt. + This vial of dust, made from the remains of a star that fell from + the sky one cold winter night, is said to have aphrodisiacal powers. + + + + Ring der Levitation + ring of levitation + + + Ringe der Levitation + rings of levitation + + + Geburtstagstorte + birthday cake + + + Geburtstagstorten + birthday cakes + + + Muschel + seashell + + + Muscheln + seashells + + + + Die ersten beiden Züge mußt du abgeben, sonst wird deine + Partei sofort wieder gelöscht, um Karteileichen zu vermeiden. + If you fail to send in orders for one of the first two turns, your faction will be erased from the game to reduce the number of inactive players in Eressea. + + + Mit der ersten Auswertung bekommst du einen + Computerreport, den du mit vielen Tools wie z.B. Magellan benutzen kannst. Wenn du ihn + weiterhin bekommen willst, gib einer deiner Einheiten den Befehl OPTION + COMPUTER. + With the first two turns, you will get a computer report + (CR). It can be used with some tools like Magellan. If you want to continue + getting it after the second turn, please make one of your units give the + order OPTION COMPUTER. + + + ARBEITEN + WORK + + + Tempel + temple + + + Seeschlangenkopf + + + Seeschlangenköpfe + + + + + Pavillion + pavilion + + + + Portal + portal + + + + Jadees Hochzeitsring + Jadee's wedding ring + + + + Jadees Hochzeitsringe + Jadee's wedding rings + + + + Wildentes Hochzeitsring + Wildente's wedding ring + + + + Wildentes Hochzeitsringe + Wildente's wedding rings + + + + Hochzeitskleid + wedding dress + + + + Hochzeitskleider + wedding dresses + + + + Frack + tuxedo + + + + Fräcke + tuxedos + + + + Ring + ring + + + + Ringe + rings + + + + + + Horn des Tanzes + horn of dancing + + + Hörner des Tanzes + horns of dancing + + + Miniatur einer Akademie der Künste + academy of arts in a box + + + Miniaturen einer Akademie der Künste + academies of arts in a box + + + Miniatur einer Skulptur + art sculpture in a box + + + Miniaturen einer Skulptur + art sculptures in a box + + + Gefangener Windgeist + trapped air elemental + + + Gefangene Windgeister + trapped air elementals + + + Auratrank + aura potion + + + Auratränke + aura potions + + + Dudelsack der Furcht + bagpipe of fear + + + Dudelsäcke der Furcht + bagpipes of fear + + + + + + Auge des Dämon + eye of the demon + oeil du démon + + + Augen des Dämon + eyes of the demon + oeil du démon + + + Schwinge des Greifen + wing of the gryphon + aile du griffon + + + Schwingen des Greifen + wings of the gryphon + ailes du griffon + + + + + Adamantium + adamantium + + + Adamantium + adamantium + + + Adamantium + adamantium + + + + Adamantiumaxt + adamantium axe + + + Adamantiumäxte + adamantium axes + + + + Adamantiumrüstung + adamantium plate + + + Adamantiumrüstungen + adamantium plates + + + diff --git a/res/eressea/terrains.xml b/res/eressea/terrains.xml new file mode 100644 index 000000000..8b2ce7b1c --- /dev/null +++ b/res/eressea/terrains.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/names-dragons.xml b/res/names-dragons.xml new file mode 100644 index 000000000..1b0abf4c1 --- /dev/null +++ b/res/names-dragons.xml @@ -0,0 +1,95 @@ + + + + + der Weise + + + der Allwissende + + + der Mächtige + + + die Ehrwürdige + + + die Listige + + + der Grüne + + + die Strafende + + + der Sehende + + + der Reisende + + + die Wissende + + + der Goldene + + + der Graue + + + der Steinerne + + + die Alte + + + die Mächtige + + + die Goldene + + + der Grausame + + + der Sanddrache + + + der Durstige + + + die Verzehrende + + + die Grüne + + + die Rote + + + der Furchtlose + + + der Allmächtige + + + der Weitblickende + + + der Weiße + + + die Glänzende + + + der Wissende + + + die Unbarmherzige + + + die Schöne + + + diff --git a/res/names-ghouls.xml b/res/names-ghouls.xml new file mode 100644 index 000000000..474156c55 --- /dev/null +++ b/res/names-ghouls.xml @@ -0,0 +1,115 @@ + + + + + Faulende + + + Angsteinflößende + + + Leise + + + Kinderfressende + + + Menschenfressende + + + Wahnsinnige + + + Brutale + + + Schwarze + + + Dunkle + + + Fürchterliche + + + Grauenhafte + + + Furchtbare + + + Entsetzliche + + + Schauderhafte + + + Schreckliche + + + Düstere + + + Schaurige + + + + Ghoule + + + Kreaturen + + + Verlorene + + + Erschlagene + + + Verdammte + + + Schlurfende Ghoule + + + + der Nacht + + + der Schatten + + + der Finsternis + + + des Bösen + + + der Erschlagenen + + + der Verfluchten + + + der Ruhelosen + + + aus dem Nebel + + + aus dem Dunkel + + + der Tiefe + + + in Ketten + + + aus dem Totenreich + + + aus der Unterwelt + + + diff --git a/res/names-skeletons.xml b/res/names-skeletons.xml new file mode 100644 index 000000000..47554d6ae --- /dev/null +++ b/res/names-skeletons.xml @@ -0,0 +1,121 @@ + + + + + Klapperige + + + Stöhnende + + + Schwarzknochige + + + Schwarzgewandete + + + Angsteinflößende + + + Heulende + + + Wartende + + + Grauenvolle + + + Schwarze + + + Dunkle + + + Fürchterliche + + + Grauenhafte + + + Furchtbare + + + Entsetzliche + + + Schauderhafte + + + Schreckliche + + + Düstere + + + Schaurige + + + Erbarmungslose + + + + Skelette + + + Kreaturen + + + Krieger + + + Kämpfer + + + Rächer + + + + der Nacht + + + der Schatten + + + der Finsternis + + + des Bösen + + + der Erschlagenen + + + der Verfluchten + + + der Gefolterten + + + der Ruhelosen + + + aus dem Nebel + + + aus dem Dunkel + + + der Tiefe + + + in Ketten + + + aus dem Totenreich + + + aus der Unterwelt + + + diff --git a/res/names-undead.xml b/res/names-undead.xml new file mode 100644 index 000000000..4623b9710 --- /dev/null +++ b/res/names-undead.xml @@ -0,0 +1,156 @@ + + + + Grausige + + + Stöhnende + + + Schlurfende + + + Schwarzgewandete + + + Faulende + + + Angsteinflößende + + + Heulende + + + Wartende + + + Grauenvolle + + + Schwarze + + + Dunkle + + + Fürchterliche + + + Grauenhafte + + + Furchtbare + + + Entsetzliche + + + Schauderhafte + + + Schreckliche + + + Gespenstische + + + Ekelhafte + + + Düstere + + + Schaurige + + + Erbarmungslose + + + Hungrige + + + + Geister + + + Phantome + + + Vampire + + + Zombies + + + Gespenster + + + Kreaturen + + + Gestalten + + + Schemen + + + Monster + + + Krieger + + + Ghule + + + Kopflose + + + Irrlichter + + + + der Nacht + + + der Schatten + + + der Finsternis + + + des Bösen + + + der Erschlagenen + + + der Verfluchten + + + der Gefolterten + + + der Ruhelosen + + + aus dem Nebel + + + aus dem Dunkel + + + der Tiefe + + + in Ketten + + + aus dem Totenreich + + + aus der Unterwelt + + + diff --git a/res/names-zombies.xml b/res/names-zombies.xml new file mode 100644 index 000000000..d494b9380 --- /dev/null +++ b/res/names-zombies.xml @@ -0,0 +1,108 @@ + + + + Faulende + + + Zerschlagene + + + Gefolterte + + + Angsteinflößende + + + Leise Schlurfende + + + Kinderfressende + + + Schwarze + + + Dunkle + + + Fürchterliche + + + Grauenhafte + + + Furchtbare + + + Entsetzliche + + + Schauderhafte + + + Schreckliche + + + Düstere + + + Schaurige + + + + Zombies + + + Kreaturen + + + Verlorene + + + Erschlagene + + + Verdammte + + + + der Nacht + + + der Schatten + + + der Finsternis + + + des Bösen + + + der Erschlagenen + + + der Verfluchten + + + der Ruhelosen + + + aus dem Nebel + + + aus dem Dunkel + + + der Tiefe + + + in Ketten + + + aus dem Totenreich + + + aus der Unterwelt + + + diff --git a/res/races/dragon.xml b/res/races/dragon.xml new file mode 100644 index 000000000..ecb7e04da --- /dev/null +++ b/res/races/dragon.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/res/races/goblin-2.xml b/res/races/goblin-2.xml new file mode 100644 index 000000000..7e23e28cf --- /dev/null +++ b/res/races/goblin-2.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/races/goblin-3.xml b/res/races/goblin-3.xml new file mode 100644 index 000000000..845b69c20 --- /dev/null +++ b/res/races/goblin-3.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/races/wyrm.xml b/res/races/wyrm.xml new file mode 100644 index 000000000..8569f4c19 --- /dev/null +++ b/res/races/wyrm.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/races/youngdragon.xml b/res/races/youngdragon.xml new file mode 100644 index 000000000..a853d874b --- /dev/null +++ b/res/races/youngdragon.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/scripts/e3a/build.lua b/scripts/e3a/build.lua new file mode 100644 index 000000000..8ea2693b8 --- /dev/null +++ b/scripts/e3a/build.lua @@ -0,0 +1,194 @@ +function size() + return 16 +end + +function make_island(pl, x, y, a, b) + if b==nil then b = a/3 end + local nx, ny = plane.normalize(pl, x, y) + gmtool.make_island(nx, ny, a, b) +end + +function make_block(pl, x, y, r) + local nx, ny = plane.normalize(pl, x, y) + gmtool.make_block(nx, ny, r) +end + +function find(email) + for f in factions() do if f.email==email then return f end end + return nil +end + +function give_item(email, id, uname, iname) + f = find(email) + for u in f.units do + u.id=atoi36(id) + u.name=uname + u:add_item(iname, 1) + break + end +end + +function give_items() + give_item("hongeldongel@web.de", "boss", "Drollitz", "rpg_item_1") + give_item("zangerl.helmut@chello.at", "holb", "Holbard", "rpg_item_2") + give_item("r.lang@chello.at", "brtL", "Bertl", "rpg_item_2") + give_item("schlaustauch@gmx.de", "bert", "Bertram", "rpg_item_3") +end + +function island(pl, x, y, r) + make_block(pl, x, y, r) + make_island(pl, x+r/2+2, y+r/2, size() * 3) + make_island(pl, x-r-2, y+r/2, size() * 3) + make_island(pl, x-r/2-2, y-r/2, size() * 3) + make_island(pl, x+r+2, y-r/2, size() * 3) + make_island(pl, x+r/2+2, y-r-2, size() * 3) + make_island(pl, x-r/2-2, y+r+2, size() * 3) +end + +function cross(pl, x, y, r) + make_block(pl, x-r, y+r*2, r) + make_block(pl, x+r*4/3, y, r) + make_block(pl, x-r*4/3, y, r) + make_block(pl, x+r, y-r*2, r) + + make_island(pl, x, y, size() * 3) + make_island(pl, x, y-r*4/3, size() * 3) + make_island(pl, x, y+r*4/3, size() * 3) + make_island(pl, x+r*4/3, y-r*4/3, size() * 3) + make_island(pl, x-r*4/3, y+r*4/3, size() * 3) +end + +function clean() + for r in regions() do + if r.terrain=="ocean" then + -- print(r) + region.destroy(r) + end + end +end + +function count() + local i = 0 + for f in factions() do i = i + 1 end + print(i) +end + +function line(pl) + local m = 0 + local i = 0 + local x, y = plane.normalize(pl, 0, i) + local r = get_region(x, y) + while true do + if r==nil then + if m==0 and (i>=0 or i<-10) then + local s = size() + gmtool.make_island(x, y, s*3, s) + else + gmtool.make_block(x, y, 6) + end + r = get_region(x, y) + if r==nil then + r = region.create(x, y, "ocean") + end + m = 1 - m + end + i = r.y + 1 + x, y = plane.normalize(pl, 0, i) + r = get_region(x, y) + if r~=nil and r.y==0 then break end + end +end + +function build(pl) + local d = 28 + local h = 20 + line(pl) + island(pl, d+15, -6, 11) + island(pl, -d, -h-10, 11) + cross(pl, -d, h-10, 6) + island(pl, d, 2*h, 11) +end + +function fill(pl, w, h) + local x, y + for x=0,w do + for y=0,h do + local nx, ny = plane.normalize(pl, x, y) + local r = get_region(nx, ny) + if r==nil then + r = region.create(nx, ny, "ocean") + end + end + end +end + +function seed() + local input = io.open(config.basepath .. "/parteien.txt") + for f in factions() do + if f.race=="vampunicorn" then + local str = input:read("*line") + if str==nil then break end + local race, lang, email = str:match("([^ ]*) ([^ ]*) ([^ ]*)") + f.race = race:lower() + f.options = f.options + 4096 + f.email = email + f.locale = lang + for u in f.units do + u.race = race:lower() + u.hp = u.hp_max + local b = building.create(u.region, "castle") + if lang=="de" then + u.name = "Entdecker" + b.name = "Heimat" + else + u.name = "Explorer" + b.name = "Home" + end + b.size = 10 + u.building = b + end + end + end + for r in regions() do + r:set_resource("sapling", r:get_resource("tree")/4) + r:set_resource("seed", 0) + end + update_owners() +end + +function select() + for f in factions() do + if f.email=="enno@eressea.de" then + for u in f.units do + gmtool.select(u.region, true) + u.number = 0 + end + end + end +end + +function justWords(str) + local t = {} + local function helper(word) table.insert(t, word) return "" end + if not str:gsub("%w+", helper):find"%S" then return t end +end + +function rebuild() + free_game() + local w = 110 + local h = 80 + local pl = plane.create(0, -w/2, -h/2, w+1, h+1) + build(pl) + fill(pl, w, h) + write_map("export.cr") +end + +function testwelt() + free_game() + local w = 10 + local h = 10 + local pl = plane.create(0, -w/2, -h/2, w+1, h+1) + gmtool.make_island(0, 0, 30, 3) + fill(pl, w, h) + write_map("export.cr") +end diff --git a/scripts/e3a/frost.lua b/scripts/e3a/frost.lua new file mode 100644 index 000000000..f60d557fb --- /dev/null +++ b/scripts/e3a/frost.lua @@ -0,0 +1,52 @@ +module('frost', package.seeall) + +local function is_winter(turn) + local season = get_season(turn) + return season == "calendar::winter" +end + +local function is_spring(turn) + local season = get_season(turn) + return season == "calendar::spring" +end + +local function freeze(r, chance) + for i, rn in ipairs(r.adj) do + -- each region has a chance to freeze + if rn.terrain=="ocean" and (chance>=100 or math.fmod(rng_int(), 100)=100 or math.fmod(rng_int(), 100) 500 then + local n, markets = collect_markets(r) + + if n>0 then + local give + if r.luxury~=nil then + give = {} + local numlux = p / trade + for x = 1, numlux do + local m = 1+math.fmod(rng_int(), n) + u = markets[m] + if give[u] then + give[u] = give[u] + 1 + else + give[u] = 1 + end + end + + for u, v in pairs(give) do + u:add_item(r.luxury, v) + end + end + + if r.herb~=nil then + give = {} + local numherb = p / 500 + for x = 1, numherb do + local m = 1+math.fmod(rng_int(), n) + u = markets[m] + if give[u] then + give[u] = give[u] + 1 + else + give[u] = 1 + end + end + + for u, v in pairs(give) do + u:add_item(r.herb, v) + end + end + end + end +end + +local function markets() + local r + for r in regions() do + market_action(r) + end +end + +-- add_proc(markets, "Markets", "Bauernwanderung") diff --git a/scripts/e3a/modules.lua b/scripts/e3a/modules.lua new file mode 100644 index 000000000..178527e74 --- /dev/null +++ b/scripts/e3a/modules.lua @@ -0,0 +1,17 @@ +require "spells" +require "e3a.xmas2009" +require "e3a.rules" +require "e3a.markets" + +local srcpath = config.source_dir +tests = { + srcpath .. '/eressea/scripts/tests/common.lua', + srcpath .. '/eressea/scripts/tests/spells.lua', +-- srcpath .. '/eressea/scripts/tests/bson.lua', +-- srcpath .. '/eressea/scripts/tests/attrib.lua', + srcpath .. '/scripts/tests/spells.lua', + srcpath .. '/scripts/tests/castles.lua', + srcpath .. '/scripts/tests/morale.lua', + srcpath .. '/scripts/tests/e3a.lua', + srcpath .. '/scripts/tests/stealth.lua', +} diff --git a/scripts/e3a/rules.lua b/scripts/e3a/rules.lua new file mode 100644 index 000000000..2d137d2d0 --- /dev/null +++ b/scripts/e3a/rules.lua @@ -0,0 +1,77 @@ +-- when appending to this, make sure the item has a canuse-function! +local goblin_denied = " plate lance mallornlance greatbow axe greatsword halberd rustyaxe rustyhalberd towershield scale " +function item_canuse(u, iname) + local race = u.race + if race=="goblin" then + if string.find(goblin_denied, " " .. iname .. " ") then + return false + end + end + if iname=="rep_crossbow" then + -- only dwarves and halflings allowed to use towershield + return race=="dwarf" or race=="halfling" + end + if iname=="scale" then + -- only dwarves and halflings can use scale + return race=="dwarf" or race=="halfling" + end + if iname=="towershield" then + -- only dwarves allowed to use towershield + return race=="dwarf" + end + if iname=="greatbow" then + -- only elves use greatbow + return race=="elf" + end + return true +end + +function building_protection(b, u) + return 1 +end + +function building_taxes(b, blevel) + btype = b.type + if btype=="castle" then + return blevel * 0.01 + elseif btype=="watch" then + return blevel * 0.005 + end + return 0.0 +end + +-- the "raindance" spell +function raindance(r, mage, level, force) + if (create_curse(mage, r, "blessedharvest", force, 1+force*2, 100 * force)) then + -- slightly crooked way of reporting an action to everyone in the region + local msg = message.create("raindance_effect") + msg:set_unit("mage", mage) + if (msg:report_action(r, mage, 3)) then + local msg2 = message.create("raindance_effect") + msg2:set_unit("mage", nil) + msg2:report_action(r, mage, 4) + end + end + return level +end + +-- the "blessed harvest" spell +function blessedharvest(r, mage, level, force) + if create_curse(mage, r, "blessedharvest", force, 1+force*2, 50 * force) then + -- slightly crooked way of reporting an action to everyone in the region + local msg = message.create("harvest_effect") + msg:set_unit("mage", mage) + if (msg:report_action(r, mage, 3)) then + local msg2 = message.create("harvest_effect") + msg2:set_unit("mage", nil) + msg2:report_action(r, mage, 4) + end + for idx, rn in ipairs(r.adj) do + -- nur landregionen haben moral>=0 + if r.morale>=0 then + create_curse(mage, r, "blessedharvest", force, force*2, 50 * force) + end + end + end + return level +end diff --git a/scripts/e3a/xmas2009.lua b/scripts/e3a/xmas2009.lua new file mode 100644 index 000000000..7e634d918 --- /dev/null +++ b/scripts/e3a/xmas2009.lua @@ -0,0 +1,26 @@ +function xmas2009() + if not get_key("xm09") then +-- print("Es weihnachtet sehr (2009)") + set_key("xm09", true) + for f in factions() do + f:add_item("xmastree", 1) + local msg = message.create("msg_event") + msg:set_string("string", translate("santa2006")) + msg:send_faction(f) + end + end + return 0 +end + +function use_xmastree(u, amount) + if u.region.herb~=nil then + local trees = u.region:get_resource("tree") + u.region:set_resource("tree", 10+trees) + u:use_pooled("xmastree", amount) + local msg = message.create("usepotion") + msg:set_unit("unit", u) + msg:set_resource("potion", "xmastree") + msg:send_region(u.region) + return 0 + end +end diff --git a/scripts/eressea/adamant.lua b/scripts/eressea/adamant.lua new file mode 100644 index 000000000..003403c13 --- /dev/null +++ b/scripts/eressea/adamant.lua @@ -0,0 +1,78 @@ +-- adamant gifts and setup for tunnels + +-- use only once to hand out some items to existing factions +function adamant_gifts() + for f in factions() do + local i = math.fmod(test.rng_int(), 2) + if i==0 then + f:add_item("adamantium", 1) + f:add_item("adamantiumplate", 1) + else + f:add_item("adamantium", 3) + f:add_item("adamantiumaxe", 1) + end + end +end + +function adamant_seeds() + for r in regions() do + if r:get_key("tnnL") then + print("1 ", r:get_resource("adamantium"), r) + test.adamantium_island(r) + print("2 ", r:get_resource("adamantium")) + end + end +end + +-- create a fixed path to a specific region +local function create_path(from, to) + local param = tostring(to.uid) + local b = building.create(from, "portal") + b.name = "Weltentor" + b.size = 1 + b:add_action("tunnel_action", param) +end + +-- create a wonky tunnel wth more than one exit +local function create_tunnel(from, param) + local b = building.create(from, "portal") + b.name = "Weltentor" + b.size = 1 + b:add_action("tunnel_action", param) +end + +-- make a tunnel from the cursor to the first selected region +function mktunnel() + local from = gmtool.get_cursor() + local to = gmtool.get_selection()() + if to~=nil then + region.create(from.x, from.y, "glacier") + create_tunnel(from, to) + gmtool.select(to, 0) + gmtool.highlight(to, 1) + end +end + +-- turn all selected regions into targets for a wonky tunnel ("tnnL") +function mkanchors() + for r in gmtool.get_selection() do + if not r:get_key("tnnL") then + r:set_key("tnnL", true) + if r:get_flag(0) then + -- RF_CHAOTIC gets removed + r:set_flag(0, false) + end + r:set_resource("peasant", r:get_resource("peasant") + 1) + end + end +end + +-- region.create and prepare all hell-regions to become wonky gates +function mkgates() + for r in regions() do + if r.plane_id==0 and r.terrain=="hell" then + create_tunnel(r, "tnnL") + region.create(r.x, r.y, "glacier") + end + end +end diff --git a/scripts/eressea/alp.lua b/scripts/eressea/alp.lua new file mode 100644 index 000000000..1f33c8681 --- /dev/null +++ b/scripts/eressea/alp.lua @@ -0,0 +1,37 @@ +require "callbacks" +require "dumptable" + +local function trigger_alp_destroyed(alp, event) + m = message.create("alp_destroyed") + m:set_region("region", alp.region) + m:send_faction(alp.faction) +end + +local function trigger_alp_dissolve(u, event, attr) + local alp = attr.alp + attr.alp.number = 0 -- kills the alp +end + +local function init_alp(attr) + -- dumptable(attr) + eventbus.register(attr.alp, "destroy", trigger_alp_destroyed) + eventbus.register(attr.mage, "destroy", trigger_alp_dissolve, attr) + eventbus.register(attr.target, "destroy", trigger_alp_dissolve, attr) +end + +callbacks["init_alp"] = init_alp + +-- Spell: summon alp +function summon_alp(r, mage, level, force, params) + local alp = unit.create(mage.faction, r, 1, "alp") + local target = params[1] + alp:set_skill("stealth", 7) + alp.status = 5 -- FLEE + attr = attrib.create(alp, { ['name'] = 'alp', ['target'] = target, ['alp'] = alp, ['mage'] = mage }) + init_alp(attr) + msg = message.create("summon_alp_effect") + m:set_unit("mage", mage) + m:set_unit("alp", alp) + m:set_unit("target", target) + m:send_faction(mage.faction) +end diff --git a/scripts/eressea/embassy.lua b/scripts/eressea/embassy.lua new file mode 100644 index 000000000..8648d600e --- /dev/null +++ b/scripts/eressea/embassy.lua @@ -0,0 +1,30 @@ +function use_seashell(u, amount) +-- Muschelplateau... + local r = get_region(165,30) + local visit = u.faction.objects:get("embassy_muschel") + if visit~=nil and u.region~= r then + local turns = get_turn() - visit + local msg = message.create("msg_event") + msg:set_string("string", u.name .. "(" .. itoa36(u.id) .. ") erzählt den Bewohnern von " .. u.region.name .. " von Muschelplateau, das die Partei " .. u.faction.name .. " vor " .. turns .. " Wochen besucht hat." ) + msg:send_region(u.region) + return 0 + end + return -4 +end + +function update_embassies() +-- Muschelplateau + local r = get_region(165,30) + if r~=nil then + local u + for u in r.units do + if u.faction.objects:get("embassy_muschel")==nil then + if (u.faction:add_item("seashell", 1)>0) then + print(u.faction) + u.faction.objects:set("embassy_muschel", get_turn()) + end + end + end + end +end + diff --git a/scripts/eressea/ents.lua b/scripts/eressea/ents.lua new file mode 100644 index 000000000..60532442d --- /dev/null +++ b/scripts/eressea/ents.lua @@ -0,0 +1,32 @@ + +local function create_ents(r, number) + local f = get_faction(0) + if f~=nil and number>0 then + u = add_unit(f, r) + u.number = number + u.name = "Wütende Ents" + u:set_skill("perception", 2) + + msg = message.create("entrise") + msg:set_region("region", r) + msg:send_region(r) + return u + end + return nil +end + +function spawn_ents() + local r + for r in regions() do + if r:get_flag(0) then -- RF_CHAOTIC + if r.terrain == "plain" and r:get_resource("tree")==0 then + if math.random(3)==1 then + u = create_ents(r, math.random(30)) + if u ~= nil then + r:set_resource("tree", u.number) + end + end + end + end + end +end diff --git a/scripts/eressea/eternath.lua b/scripts/eressea/eternath.lua new file mode 100644 index 000000000..c421d3cce --- /dev/null +++ b/scripts/eressea/eternath.lua @@ -0,0 +1,18 @@ +require "gates" + +local function eternath_exchange(b1, b2, size) + local units1 = gate_units(b1, size) + local units2 = gate_units(b2, size) + + gate_travel(b2, units1) + gate_travel(b1, units2) +end + +function eternathgate_action(b) + if eternathgate == nil then + eternathgate = b + else + eternath_exchange(eternathgate, b, 10) + end + return 1 +end diff --git a/scripts/eressea/igjaruk.lua b/scripts/eressea/igjaruk.lua new file mode 100644 index 000000000..155e83283 --- /dev/null +++ b/scripts/eressea/igjaruk.lua @@ -0,0 +1,59 @@ +function teleport_all(map, grave) + print("- teleporting all quest members to the grave") + local index + local r + for index, r in pairs(map) do + local u + for u in r.units do + u.region = grave + print (" .teleported " .. u.name) + grave:add_notice("Ein Portal öffnet sich, und " .. u.name .. " erscheint in " .. grave.name) + end + end +end + +function wyrm() + print("- running the wyrm quest") + local grave = get_region(-9995,4) + local plane = get_plane_id("arena") + local map = {} + local mapsize = 0 + local r + + for r in regions() do + if r.plane_id==plane then + mapsize=mapsize+1 + map[mapsize] = r + end + end + + local u + for u in grave.units do + if u.faction.id~=atoi36("rr") then + teleport_all(map, grave) + break + end + end + + local index + local r + for index, r in pairs(map) do + if r~=grave then + if (math.fmod(r.x,2)==math.fmod(get_turn(),2)) then + r:add_notice("Eine Botschaft von Igjarjuk, Herr der Wyrme: 'Die Zeit des Wartens ist beinahe vorrüber. Euer Fürst kehrt aus dem Grabe zurück.'") + else + r:add_notice("Eine Botschaft von Gwaewar, Herr der Greife: 'Das Ende naht. Igjarjuk ist aus seinem Grab auferstanden. Eilt, noch ist die Welt zu retten!'") + end + end + end + + local gryph=get_unit(atoi36("gfd4")) + local igjar=get_unit(atoi36("igjr")) + if grave~=nil and gryph~=nil and igjar~=nil then + gryph.region=grave + igjar.region=grave + grave:add_notice("Eine Botschaft von Gwaewar, Herr der Greife: 'Ihr, die Ihr die Strapazen der letzten Jahre überstanden habt: Lasst nicht zu, dass Igjarjuk wieder in die Welt der Lebenden zurückkehrt. Vernichtet das Auge - jetzt und hier!'") + grave:add_notice("Eine Botschaft von Igjarjuk, Herr der Wyrme: 'Gwaewar, Du wirst dereinst an Deinem Glauben an das Gute in den Sterblichen verrecken... So wie ich es einst tat. Der Krieg ist die einzige Sprache die sie verstehen, und derjenige, der mir hilft, wird ihn gewinnen.'") + end + +end diff --git a/scripts/eressea/items.lua b/scripts/eressea/items.lua new file mode 100644 index 000000000..8f6ca2f8a --- /dev/null +++ b/scripts/eressea/items.lua @@ -0,0 +1,19 @@ +function use_ring_of_levitation(u, amount) + if u.ship~=nil and amount>0 then + local mallorn = 0 + for u2 in u.region.units do + if u2.ship==u.ship then + local i = u2:get_item("mallornseed") + if i>0 then + u2:use_pooled("mallornseed", i) + u2:use_pooled("seed", i) + mallorn = mallorn + i + end + end + end + if mallorn>0 then + levitate_ship(u.ship, u, mallorn, 2) + end + end + return 0 +end diff --git a/scripts/eressea/main.lua b/scripts/eressea/main.lua new file mode 100644 index 000000000..33544da09 --- /dev/null +++ b/scripts/eressea/main.lua @@ -0,0 +1,63 @@ +require "multis" + +function apply_fixes() + local turn = get_turn() + if config.game=="eressea" and turn>654 and turn<662 then + print("Fixing familiars") + fix_familiars() + end +end + +function process(orders) + local confirmed_multis = { } + local suspected_multis = { } + + if open_game(get_turn())~=0 then + print("could not read game") + return -1 + end + apply_fixes() + init_summary() + + -- kill multi-players (external script) + kill_multis(confirmed_multis, false) + mark_multis(suspected_multis, false) + + -- run the turn: + if read_orders(orders) ~= 0 then + print("could not read " .. orders) + return -1 + end + + plan_monsters() + + if nmr_check(config.maxnmrs or 80)~=0 then + return -1 + end + + process_orders() + + -- create new monsters: + spawn_dragons() + spawn_undead() + spawn_braineaters(0.25) + spawn_ents() + + -- post-turn updates: + update_xmas2006() + update_embassies() + update_guards() + update_scores() + + local localechange = { de = { "ii" } } + change_locales(localechange) + + write_files(config.locales) + + file = "" .. get_turn() .. ".dat" + if eressea.write_game(file)~=0 then + print("could not write game") + return -1 + end + return 0 +end diff --git a/scripts/eressea/modules.lua b/scripts/eressea/modules.lua new file mode 100644 index 000000000..1e27c889b --- /dev/null +++ b/scripts/eressea/modules.lua @@ -0,0 +1,25 @@ +require "spells" +require "gates" +require "eressea.alp" +require "eressea.eternath" +require "eressea.wedding-jadee" +require "eressea.ponnuki" +require "eressea.items" +require "eressea.rules" +-- require "eressea.10years" +require "eressea.xmas2004" +require "eressea.xmas2005" +require "eressea.xmas2006" +require "eressea.embassy" +require "eressea.tunnels" +require "eressea.ents" + +local srcpath = config.source_dir +tests = { + srcpath .. '/eressea/scripts/tests/common.lua', + srcpath .. '/eressea/scripts/tests/spells.lua', +-- srcpath .. '/eressea/scripts/tests/bson.lua', + srcpath .. '/scripts/tests/spells.lua', + srcpath .. '/scripts/tests/spells-e2.lua', + srcpath .. '/scripts/tests/eressea.lua', +} diff --git a/scripts/eressea/ponnuki.lua b/scripts/eressea/ponnuki.lua new file mode 100644 index 000000000..95d121cc5 --- /dev/null +++ b/scripts/eressea/ponnuki.lua @@ -0,0 +1,42 @@ +function ponnuki_brain(u) + jokes = { + "Ein Bummerang ist, wenn man ihn wegwirft und er kommt nicht wieder, dann war's keiner.", + + "Merke: Mit Schwabenwitzen soll man ganz sparsam sein.", + + "Was bekommt man, wenn man Katzen und Elfen kreuzt? Elfen ohne Rheuma.", + + "Was bekommt man, wenn man Insekten und Katzen kreuzt? Tiger, die Crisan benutzen." + } + local i = math.random(table.getn(jokes)) + u.region:add_notice(jokes[i]) + local d = math.random(6) + r = u.region:next(d-1) + u:clear_orders() + directions = { "NW", "NO", "O", "SO", "SW", "W" } + u:add_order("NACH " .. directions[d]) +end + +local function init_ponnuki(home) + local f = get_faction(0) + local u = get_unit(atoi36("ponn")) + if u == nil then + u = add_unit(f, home) + u.id = atoi36("ponn") + u.name = "Ponnuki" + u.info = "Go, Ponnuki, Go!" + u.race = "illusion" + u:set_racename("Ritter von Go") + end + if u.faction==f then + set_unit_brain(u, ponnuki_brain) + end +end + +-- initialize other scripts +local magrathea = get_region(-67, -5) +if magrathea~=nil and init_ponnuki~=nil then + init_ponnuki(magrathea) + return +end + diff --git a/scripts/eressea/rules.lua b/scripts/eressea/rules.lua new file mode 100644 index 000000000..8e2137a13 --- /dev/null +++ b/scripts/eressea/rules.lua @@ -0,0 +1,8 @@ +function item_canuse(u, iname) + -- local race = u.race + -- if iname=="greatbow" then + -- -- only elves use greatbow + -- return race=="elf" + -- end + return true +end diff --git a/scripts/eressea/tunnels.lua b/scripts/eressea/tunnels.lua new file mode 100644 index 000000000..2c96be14a --- /dev/null +++ b/scripts/eressea/tunnels.lua @@ -0,0 +1,62 @@ +local function tunnel_travelers(b) + local units = nil + for u in b.units do + if units==nil then + units = {} + end + units[u] = u + end + return units +end + +targets = nil +ntargets = 0 + +local function get_target(param) + -- print("finding targets: " .. param) + if targets == nil then + targets = {} + local r + for r in regions() do + if r:get_key(param) then + if (r:get_flag(0)) then + r:set_flag(0, false) + end + if (r.terrain=="ocean") then + r = region.create(r.x, r.y, "plain") + end + targets[ntargets] = r + ntargets = ntargets + 1 + -- print("target: " .. tostring(r)) + end + end + end + if ntargets==0 then + return nil + end + local rn = math.fmod(rng_int(), ntargets) + return targets[rn] +end + +-- export, will be called from lc_age() +function tunnel_action(b, param) + local r = nil + if tonumber(param)~=nil then + r = get_region_by_id(tonumber(param)) + end + local units = tunnel_travelers(b) + if units~=nil then + print("Tunnel from " .. tostring(b) .. " [" .. param .. "]") + for key, u in pairs(units) do + local rto = r + if r==nil then + rto = get_target(param) + end + if rto~=nil then + u.region = rto + print(" - teleported " .. tostring(u) .. " to " .. tostring(rto)) + end + end + end + return 1 -- return 0 to destroy +end diff --git a/scripts/eressea/wedding-jadee.lua b/scripts/eressea/wedding-jadee.lua new file mode 100644 index 000000000..f7a671c7a --- /dev/null +++ b/scripts/eressea/wedding-jadee.lua @@ -0,0 +1,47 @@ +-- this script contains the action functions for the two portals +-- used on the jadee/wildente wedding island. the two _action functions +-- are used as age() functions for a building_action with b:addaction("name") + +if gate_travel==nil then + loadscript("gates.lua") +end + +hellgate = nil +peacegate = nil + +local function wedding_travellers(b) + local units = {} + + for u in b.units do + if u:get_flag("wdgt") then + units[u] = u + end + end + return units +end + +local function wedding_exchange(b1, b2) + local units1 = wedding_travellers(b1) + local units2 = wedding_travellers(b2) + + gate_travel(b2, units1) + gate_travel(b1, units2) +end + +function hellgate_action(b) + if hellgate == nil then + hellgate = b + else + wedding_exchange(hellgate, b) + end + return 1 +end + +function peacegate_action(b) + if peacegate == nil then + peacegate = b + else + wedding_exchange(peacegate, b) + end + return 1 +end diff --git a/scripts/eressea/xmas2004.lua b/scripts/eressea/xmas2004.lua new file mode 100644 index 000000000..92913c183 --- /dev/null +++ b/scripts/eressea/xmas2004.lua @@ -0,0 +1,25 @@ +function use_snowman(u, amount) + if u.region.terrain == "glacier" then + local man = unit.create(u.faction, u.region) + man.race = "snowman" + man.number = amount + u:add_item("snowman", -amount) + return 0 + end + return -4 +end + +function xmas2004() + if get_gamename() == "Eressea" then + if not get_key("xm04") then + print("Es weihnachtet sehr (2004)") + set_key("xm04", true) + for f in factions() do + f:add_item("speedsail", 1) + f:add_notice("santa2004") + end + end + end +end + +-- xmas2004() diff --git a/scripts/eressea/xmas2005.lua b/scripts/eressea/xmas2005.lua new file mode 100644 index 000000000..cde00f65b --- /dev/null +++ b/scripts/eressea/xmas2005.lua @@ -0,0 +1,31 @@ +function usepotion_message(u, potion) + msg = message.create("usepotion") + msg:set_unit("unit", u) + msg:set_resource("potion", potion) + return msg +end + +function use_stardust(u, amount) + local p = u.region:get_resource("peasant") + p = math.ceil(1.5 * p) + u.region:set_resource("peasant", p) + local msg = usepotion_message(u, "stardust") + msg:send_region(u.region) + u:use_pooled("stardust", amount) + return 0 +end + +function xmas2005() + if get_gamename() == "Eressea" then + if not get_flag("xm05") then + print("Es weihnachtet sehr (2005)") + set_flag("xm05", true) + for f in factions() do + f:add_item("stardust", 1) + f:add_notice("santa2005") + end + end + end +end + +-- xmas2005() diff --git a/scripts/eressea/xmas2006.lua b/scripts/eressea/xmas2006.lua new file mode 100644 index 000000000..fa3ec251b --- /dev/null +++ b/scripts/eressea/xmas2006.lua @@ -0,0 +1,54 @@ +function use_xmastree(u, amount) + u.region:set_key("xm06", true) + u:use_pooled("xmastree", amount) + local msg = message.create("usepotion") + msg:set_unit("unit", u) + msg:set_resource("potion", "xmastree") + msg:send_region(u.region) + return 0 +end + +function update_xmas2006() + local turn = get_turn() + local season = get_season(turn) + if season == "calendar::winter" then + print("it is " .. season .. ", the christmas trees do their magic") + local msg = message.create("xmastree_effect") + for r in regions() do + if r:get_key("xm06") then + trees = r:get_resource("tree") + if trees*0.1>=1 then + r:set_resource("tree", trees * 1.1) + msg:send_region(r) + end + if clear then + end + end + end + else + local prevseason = get_season(turn-1) + if prevseason == "calendar::winter" then + -- we celebrate knut and kick out the trees. + for r in regions() do + if r:get_key("xm06") then + r:set_key("xm06", false) + end + end + end + end +end + +function xmas2006() + if get_gamename() == "Eressea" then + if not get_key("xm06") then + print("Es weihnachtet sehr (2006)") + set_key("xm06", true) + for f in factions() do + f:add_item("xmastree", 1) + f:add_notice("santa2006") + end + end + end +end + +-- xmas2006() diff --git a/scripts/tests/castles.lua b/scripts/tests/castles.lua new file mode 100644 index 000000000..c8c1bc83b --- /dev/null +++ b/scripts/tests/castles.lua @@ -0,0 +1,27 @@ +require "lunit" + +module("tests.e3.castles", package.seeall, lunit.testcase ) + +function setup() + eressea.free_game() +end + +function test_small_castles() + local r = region.create(0, 0, "plain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + local f2 = faction.create("noreply@eressea.de", "halfling", "de") + local u2 = unit.create(f2, r, 1) + u1:add_item("money", 10000) + + local b = building.create(r, "castle") + u2.building = b + u1.building = b + + b.owner = u2 + assert_equal("site", b:get_typename(7)) + assert_equal("fortification", b:get_typename(8)) + b.owner = u1 + assert_equal("site", b:get_typename(9)) + assert_equal("fortification", b:get_typename(10)) +end diff --git a/scripts/tests/e3a.lua b/scripts/tests/e3a.lua new file mode 100644 index 000000000..5bef245c4 --- /dev/null +++ b/scripts/tests/e3a.lua @@ -0,0 +1,661 @@ +require "lunit" + +module("tests.e3.e3features", package.seeall, lunit.testcase) + +function setup() + eressea.free_game() + eressea.settings.set("rules.economy.food", "4") +end + +function test_no_stealth() + local r = region.create(0,0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + + u:set_skill("stealth", 1) + assert_equal(-1, u:get_skill("stealth")) + u:clear_orders() + u:add_order("LERNEN TARNUNG") + process_orders() + assert_equal(-1, u:get_skill("stealth")) +end + +--[[ +function test_analyze_magic() + local r1 = region.create(0,0, "plain") + local r2 = region.create(1,0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + + local u = unit.create(f, r2, 1) + + u.race = "elf" + u:set_skill("magic", 6) + u.magic = "gwyrrd" + u.aura = 60 + u:add_spell("analyze_magic") + u:clear_orders() + u:add_order("Zaubere stufe 2 'Magie analysieren' REGION 1,0") + process_orders() +end +]]-- + +function test_seecast() + local r = region.create(0,0, "plain") + for i = 1,10 do + -- this prevents storms (only high seas have storms) + region.create(i, 1, "plain") + end + for i = 1,10 do + region.create(i, 0, "ocean") + end + local f = faction.create("noreply@eressea.de", "human", "de") + local s1 = ship.create(r, "cutter") + local u1 = unit.create(f, r, 2) + u1:set_skill("sailing", 3) + u1:add_item("money", 1000) + u1.ship = s1 + local u2 = unit.create(f, r, 1) + u2.race = "elf" + u2:set_skill("magic", 6) + u2.magic = "gwyrrd" + u2.aura = 60 + u2.ship = s1 + u2:add_spell("stormwinds") + update_owners() + u2:clear_orders() + u2:add_order("Zaubere stufe 2 'Beschwoere einen Sturmelementar' " .. itoa36(s1.id)) + u1:clear_orders() + u1:add_order("NACH O O O O") + process_orders() + assert_equal(4, u2.region.x) + + u2:clear_orders() + u2:add_order("Zaubere stufe 2 'Beschwoere einen Sturmelementar' " .. itoa36(s1.id)) + u1:clear_orders() + u1:add_order("NACH O O O O") + process_orders() + assert_equal(8, u2.region.x) +end + +local function use_tree(terrain) + local r = region.create(0,0, terrain) + local f = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f, r, 5) + r:set_resource("tree", 0) + u1:add_item("xmastree", 1) + u1:clear_orders() + u1:add_order("BENUTZEN 1 Weihnachtsbaum") + process_orders() + return r +end + +function test_xmas2009() + local r = region.create(0,0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f, r, 1) + process_orders() + xmas2009() + assert_equal("xmastree", f.items()) +end + +function test_xmastree() + local r + r = use_tree("ocean") + assert_equal(0, r:get_resource("tree")) + eressea.free_game() + r = use_tree("plain") + assert_equal(10, r:get_resource("tree")) +end + +function test_fishing() + eressea.settings.set("rules.economy.food", "0") + local r = region.create(0,0, "ocean") + local r2 = region.create(1,0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local s1 = ship.create(r, "cutter") + local u1 = unit.create(f, r, 3) + u1.ship = s1 + u1:set_skill("sailing", 10) + u1:add_item("money", 100) + u1:clear_orders() + u1:add_order("NACH O") + update_owners() + + process_orders() + assert_equal(r2, u1.region) + assert_equal(90, u1:get_item("money")) + + u1:clear_orders() + u1:add_order("NACH W") + + process_orders() + assert_equal(r, u1.region) + assert_equal(60, u1:get_item("money")) +end + +function test_ship_capacity() + eressea.settings.set("rules.ship.drifting", "0") + eressea.settings.set("rules.ship.storms", "0") + local r = region.create(0,0, "ocean") + region.create(1,0, "ocean") + local r2 = region.create(2,0, "ocean") + local f = faction.create("noreply@eressea.de", "human", "de") + local f2 = faction.create("noreply@eressea.de", "goblin", "de") + + -- u1 is at the limit and moves + local s1 = ship.create(r, "cutter") + local u1 = unit.create(f, r, 5) + u1.ship = s1 + u1:set_skill("sailing", 10) + u1:add_item("sword", 55) + u1:clear_orders() + u1:add_order("NACH O O") + + -- u2 has too many people + local s2 = ship.create(r, "cutter") + local u2 = unit.create(f, r, 6) + u2.ship = s2 + u2:set_skill("sailing", 10) + u2:clear_orders() + u2:add_order("NACH O O") + + -- u3 has goblins, they weigh 40% less + local s3 = ship.create(r, "cutter") + local u3 = unit.create(f2, r, 8) + u3.ship = s3 + u3:set_skill("sailing", 10) + u3:add_item("sword", 55) + u3:clear_orders() + u3:add_order("NACH O O") + + -- u4 has too much stuff + local s4 = ship.create(r, "cutter") + local u4 = unit.create(f, r, 5) + u4.ship = s4 + u4:set_skill("sailing", 10) + u4:add_item("sword", 56) + u4:clear_orders() + u4:add_order("NACH O O") + + update_owners() + process_orders() + if r2~=u1.region then + print(get_turn(), u1, u1.faction) + write_reports() + end + assert_equal(r2, u1.region) + assert_not_equal(r2, u2.region) + if r2~=u3.region then + print(get_turn(), u3, u3.faction) + write_reports() + end + assert_equal(r2, u3.region) + assert_not_equal(r2, u4.region) +end + +function test_owners() + local r = region.create(0, 0, "plain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + local f2 = faction.create("noreply@eressea.de", "human", "de") + local u2 = unit.create(f2, r, 1) + local u3 = unit.create(f2, r, 1) + + local b3 = building.create(r, "castle") + b3.size = 2 + u3.building = b3 + local b1 = building.create(r, "castle") + b1.size = 1 + u1.building = b1 + local b2 = building.create(r, "castle") + b2.size = 2 + u2.building = b2 + + update_owners() + assert(r.owner==u3.faction) + b1.size=3 + b2.size=3 + update_owners() + assert(r.owner==u2.faction) + b1.size=4 + update_owners() + assert(r.owner==u1.faction) +end + +function test_taxes() + local r = region.create(0, 0, "plain") + r:set_resource("peasant", 1000) + r:set_resource("money", 5000) + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:clear_orders() + u:add_order("LERNE Holzfaellen") -- do not work + local b = building.create(r, "watch") + b.size = 10 + u.building = b + update_owners() + assert_equal(1, r.morale) + process_orders() + assert_equal(1, r.morale) + assert_equal(25, u:get_item("money")) +end + +function test_leave() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + f.id = 42 + local b1 = building.create(r, "castle") + b1.size = 10 + local b2 = building.create(r, "lighthouse") + b2.size = 10 + local u = unit.create(f, r, 1) + u.building = b1 + u:add_item("money", u.number * 100) + u:clear_orders() + u:add_order("BETRETE BURG " .. itoa36(b2.id)) + process_orders() + init_reports() + write_report(u.faction) + print(u.faction) + assert_equal(b1, u.building, "region owner has left the building") -- region owners may not leave +end + +function test_market() + -- if i am the only trader around, i should be getting all the herbs from all 7 regions + local herb_multi = 500 -- from rc_herb_trade() + local r, idx + local herbnames = { 'h0', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8' } + idx = 1 + for x = -1, 1 do for y = -1, 1 do + r = region.create(x, y, "plain") + r:set_resource("peasant", herb_multi * 9 + 50) -- 10 herbs per region + r.herb = herbnames[idx] + idx = idx+1 + end end + r = get_region(0, 0) + local b = building.create(r, "market") + b.size = 10 + local f = faction.create("noreply@eressea.de", "human", "de") + f.id = 42 + local u = unit.create(f, r, 1) + u.building = b + u:add_item("money", u.number * 10000) + for i = 0, 5 do + local rn = r:next(i) + end + eressea.process.markets() + u:add_item("money", -u:get_item("money")) -- now we only have herbs + local len = 0 + for i in u.items do + len = len + 1 + end + assert_not_equal(0, len, "trader did not get any herbs") + for idx, name in pairs(herbnames) do + local n = u:get_item(name) + if n>0 then + assert_equal(10, n, 'trader did not get exaxtly 10 herbs') + end + end +end + +function test_market_gives_items() + local r + for x = -1, 1 do for y = -1, 1 do + r = region.create(x, y, "plain") + r:set_resource("peasant", 5000) + end end + r = get_region(0, 0) + local b = building.create(r, "market") + b.size = 10 + local f = faction.create("noreply@eressea.de", "human", "de") + f.id = 42 + local u = unit.create(f, r, 1) + u.building = b + u:add_item("money", u.number * 10000) + for i = 0, 5 do + local rn = r:next(i) + end + process_orders() + local len = 0 + for i in u.items do + len = len + 1 + end + assert(len>1) +end + +function test_spells() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u.race = "elf" + u:clear_orders() + u:add_item("money", 10000) + u:set_skill("magic", 5) + u:add_order("LERNE MAGIE Illaun") + process_orders() + local sp + local nums = 0 + if f.spells~=nil then + for sp in f.spells do + nums = nums + 1 + end + assert(nums>0) + for sp in u.spells do + nums = nums - 1 + end + assert(nums==0) + elseif u.spells~=nil then + for sp in u.spells do + nums = nums + 1 + end + assert(nums>0) + end +end + +function test_alliance() + local r = region.create(0, 0, "plain") + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + u1:add_item("money", u1.number * 100) + local f2 = faction.create("info@eressea.de", "human", "de") + local u2 = unit.create(f2, r, 1) + u2:add_item("money", u2.number * 100) + assert(f1.alliance==nil) + assert(f2.alliance==nil) + u1:clear_orders() + u2:clear_orders() + u1:add_order("ALLIANZ NEU pink") + u1:add_order("ALLIANZ EINLADEN " .. itoa36(f2.id)) + u2:add_order("ALLIANZ BEITRETEN pink") + process_orders() + assert(f1.alliance~=nil) + assert(f2.alliance~=nil) + assert(f2.alliance==f1.alliance) + u1:clear_orders() + u2:clear_orders() + u1:add_order("ALLIANZ KOMMANDO " .. itoa36(f2.id)) + process_orders() + assert(f1.alliance~=nil) + assert(f2.alliance~=nil) + assert(f2.alliance==f1.alliance) + for f in f1.alliance.factions do + assert_true(f.id==f1.id or f.id==f2.id) + end + u1:clear_orders() + u2:clear_orders() + u2:add_order("ALLIANZ AUSSTOSSEN " .. itoa36(f1.id)) + process_orders() + assert(f1.alliance==nil) + assert(f2.alliance~=nil) + u1:clear_orders() + u2:clear_orders() + u2:add_order("ALLIANZ NEU zing") + u1:add_order("ALLIANZ BEITRETEN zing") -- no invite! + process_orders() + assert(f1.alliance==nil) + assert(f2.alliance~=nil) + u1:clear_orders() + u2:clear_orders() + u1:add_order("ALLIANZ NEU zack") + u1:add_order("ALLIANZ EINLADEN " .. itoa36(f2.id)) + u2:add_order("ALLIANZ BEITRETEN zack") + process_orders() + assert(f1.alliance==f2.alliance) + assert(f2.alliance~=nil) +end + +function test_canoe_passes_through_land() + local f = faction.create("noreply@eressea.de", "human", "de") + local src = region.create(0, 0, "ocean") + local land = region.create(1, 0, "plain") + region.create(2, 0, "ocean") + local dst = region.create(3, 0, "ocean") + local sh = ship.create(src, "canoe") + local u1 = unit.create(f, src, 1) + local u2 = unit.create(f, src, 1) + u1.ship = sh + u2.ship = sh + u1:set_skill("sailing", 10) + u1:clear_orders() + u1:add_order("NACH O O O") + process_orders() + assert_equal(land, u2.region, "canoe did not stop at coast") + u1:add_order("NACH O O O") + process_orders() + assert_equal(dst, sh.region, "canoe could not leave coast") + assert_equal(dst, u1.region, "canoe could not leave coast") + assert_equal(dst, u2.region, "canoe could not leave coast") +end + +function test_give_50_percent_of_money() + local r = region.create(0, 0, "plain") + local u1 = unit.create(faction.create("noreply@eressea.de", "human", "de"), r, 1) + local u2 = unit.create(faction.create("noreply@eressea.de", "orc", "de"), r, 1) + u1.faction.age = 10 + u2.faction.age = 10 + u1:add_item("money", 500) + u2:add_item("money", 500) + local m1, m2 = u1:get_item("money"), u2:get_item("money") + u1:clear_orders() + u1:add_order("GIB " .. itoa36(u2.id) .. " 221 Silber") + u2:clear_orders() + u2:add_order("LERNEN Hiebwaffen") + process_orders() + assert_equal(m1, u1:get_item("money")) + assert_equal(m2, u2:get_item("money")) + + m1, m2 = u1:get_item("money"), u2:get_item("money") + u1:clear_orders() + u1:add_order("GIB " .. itoa36(u2.id) .. " 221 Silber") + u2:clear_orders() + u2:add_order("HELFEN " .. itoa36(u1.faction.id) .. " GIB") + u2:add_item("horse", 100) + u2:add_order("GIB 0 ALLES PFERD") + local h = r:get_resource("horse") + process_orders() + assert_true(r:get_resource("horse")>=h+100) + assert_equal(m1-221, u1:get_item("money")) + assert_equal(m2+110, u2:get_item("money")) +end + +function test_give_100_percent_of_items() + r = region.create(0, 0, "plain") + local u1 = unit.create(faction.create("noreply@eressea.de", "human", "de"), r, 1) + local u2 = unit.create(faction.create("noreply@eressea.de", "orc", "de"), r, 1) + u1.faction.age = 10 + u2.faction.age = 10 + u1:add_item("money", 500) + u1:add_item("log", 500) + local m1, m2 = u1:get_item("log"), u2:get_item("log") + u1:clear_orders() + u1:add_order("GIB " .. itoa36(u2.id) .. " 332 Holz") + u2:clear_orders() + u2:add_order("LERNEN Hiebwaffen") + u2:add_order("HELFEN " .. itoa36(u1.faction.id) .. " GIB") + process_orders() + assert_equal(m1-332, u1:get_item("log")) + assert_equal(m2+332, u2:get_item("log")) +end + +function test_cannot_give_person() + 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 u1 = unit.create(f1, r, 10) + local u2 = unit.create(f2, r, 10) + u1.faction.age = 10 + u2.faction.age = 10 + u1:add_item("money", 500) + u2:add_item("money", 500) + u2:clear_orders() + u2:add_order("GIB ".. itoa36(u1.id) .. " 1 PERSON") + u2:add_order("HELFE ".. itoa36(f1.id) .. " GIB") + u1:add_order("HELFE ".. itoa36(f2.id) .. " GIB") + process_orders() + assert_equal(10, u2.number) + assert_equal(10, u1.number) +end + +function test_cannot_give_unit() + 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 u1 = unit.create(f1, r, 10) + local u2 = unit.create(f2, r, 10) + u1.faction.age = 10 + u2.faction.age = 10 + u1:add_item("money", 500) + u2:add_item("money", 500) + u2:clear_orders() + u2:add_order("GIB ".. itoa36(u1.id) .. " EINHEIT") + u2:add_order("HELFE ".. itoa36(f1.id) .. " GIB") + u1:add_order("HELFE ".. itoa36(f2.id) .. " GIB") + process_orders() + assert_not_equal(u2.faction.id, u1.faction.id) +end + +function test_guard_by_owners() + -- 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) + local b = building.create(r, "castle") + b.size = 10 + u1.building = b + u1:add_item("money", 100) + + 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") + process_orders() + assert_equal(iron, u2:get_item("iron")) +end + +function test_market_action() + local f = faction.create("noreply@eressea.de", "human", "de") + local x, y, r + for x=0,2 do + for y=0,2 do + r = region.create(x, y, "plain") + r.luxury = "balm" + r.herb = "h2" + r:set_resource("peasant", 5000) + end + end + r = get_region(1, 1) + local u = unit.create(f, r, 1) + b = building.create(r, "market") + b.size = 10 + u.building = b + update_owners() + for r in regions() do + market_action(r) + end + assert_equal(35, u:get_item("balm")) + assert_equal(70, u:get_item("h2")) +end + +local function setup_packice(x, onfoot) + local f = faction.create("noreply@eressea.de", "human", "de") + local plain = region.create(0,0, "plain") + local ice = region.create(1,0, "packice") + local ocean = region.create(2,0, "ocean") + local u = unit.create(f, get_region(x, 0), 2) + if not onfoot then + local s = ship.create(u.region, "cutter") + u:set_skill("sailing", 3) + u.ship = s + end + u:add_item("money", 400) + + return u +end + +function test_no_sailing_through_packice() + local u = setup_packice(0) + u:clear_orders() + u:add_order("NACH O O") + process_orders() + assert_equal(0, u.region.x) +end + +function test_can_sail_from_packice_to_ocean() + local u = setup_packice(1) + + u:clear_orders() + u:add_order("NACH W") + process_orders() + assert_equal(1, u.region.x) + + u:clear_orders() + u:add_order("NACH O") + process_orders() + assert_equal(2, u.region.x) +end + +function test_can_sail_into_packice() + local u = setup_packice(2) + u:clear_orders() + u:add_order("NACH W W") + process_orders() + assert_equal(1, u.region.x) +end + +function test_can_walk_into_packice() + local u = setup_packice(0, true) + u:clear_orders() + u:add_order("NACH O") + process_orders() + assert_equal(1, u.region.x) +end + +function test_cannot_walk_into_ocean() + local u = setup_packice(1, true) + u:clear_orders() + u:add_order("NACH O") + process_orders() + assert_equal(1, u.region.x) +end + +function test_p2() + local f = faction.create("noreply@eressea.de", "human", "de") + local r = region.create(0, 0, "plain") + local u = unit.create(f, r, 1) + r:set_resource("tree", 0) + u:clear_orders() + u:add_order("BENUTZE 'Wasser des Lebens'") + u:add_item("p2", 1) + u:add_item("log", 10) + u:add_item("mallorn", 10) + process_orders() + assert_equal(5, r:get_resource("tree")) + assert_equal(0, u:get_item("p2")) + assert_equal(15, u:get_item("log") + u:get_item("mallorn")) +end + +function test_p2_move() + -- http://bugs.eressea.de/view.php?id=1855 + local f = faction.create("noreply@eressea.de", "human", "de") + local r = region.create(0, 0, "plain") + region.create(1, 0, "plain") + local u = unit.create(f, r, 1) + r:set_resource("tree", 0) + u:clear_orders() + u:add_order("BENUTZE 'Wasser des Lebens'") + u:add_order("NACH OST") + u:add_item("horse", 1) + u:add_item("p2", 1) + u:add_item("log", 1) + u:add_item("mallorn", 1) + process_orders() + assert_equal(1, u.region.x) + assert_equal(1, r:get_resource("tree")) +end diff --git a/scripts/tests/eressea.lua b/scripts/tests/eressea.lua new file mode 100644 index 000000000..f33726886 --- /dev/null +++ b/scripts/tests/eressea.lua @@ -0,0 +1,202 @@ +require "lunit" + +module("tests.e3.e2features", package.seeall, lunit.testcase ) + +function setup() + eressea.free_game() + eressea.settings.set("nmr.timeout", "0") + eressea.settings.set("rules.economy.food", "4") +end + +function test_learn() + eressea.settings.set("study.random_progress", "0") + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + f.age = 20 + local u = unit.create(f, r) + u:clear_orders() + u:add_order("@LERNEN Reiten") + process_orders() + assert_equal(1, u:get_skill("riding")) + process_orders() + process_orders() + assert_equal(2, u:get_skill("riding")) + process_orders() + process_orders() + process_orders() + assert_equal(3, u:get_skill("riding")) +end + +function test_teach() + eressea.settings.set("study.random_progress", "0") + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + f.age = 20 + local u = unit.create(f, r, 10) + local u2 = unit.create(f, r) + u:clear_orders() + u:add_order("@LERNEN reiten") + u2:clear_orders() + u2:add_order("LEHREN " .. itoa36(u.id)) + u2:set_skill("riding", 4) + process_orders() + assert_equal(1, u:get_skill("riding")) + process_orders() + assert_equal(2, u:get_skill("riding")) +end + +function test_rename() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r) + u:add_item("aoh", 1) + assert_equal(u:get_item("ao_healing"), 1) +end + +function DISABLE_test_alp() + 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) + u.race = "elf" + u:set_skill("magic", 10) + u:add_item("money", 3010) + u.magic = "illaun" + u.aura = 200 + u.ship = s1 + u:add_spell("summon_alp") + u:clear_orders() + u:add_order("ZAUBERE 'Alp' " .. itoa36(u2.id)) + process_orders() + print(get_turn(), f) + write_reports() +end + +function test_unit_limit_is_1500() + local r = region.create(0,0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + for i = 1,1500 do + unit.create(f, r, 1) + end + local u = unit.create(f, r, 0) + u:add_item("money", 20000) + u:clear_orders() + u:add_order("REKRUTIEREN 1") + process_orders() + assert_equal(1, u.number) +end + +function test_ship_capacity() + local r = region.create(0,0, "ocean") + region.create(1,0, "ocean") + local r2 = region.create(2,0, "ocean") + local f = faction.create("noreply@eressea.de", "human", "de") + + -- u1 is at the limit and moves + local s1 = ship.create(r, "boat") + local u1 = unit.create(f, r, 5) + u1.ship = s1 + u1:set_skill("sailing", 10) + u1:clear_orders() + u1:add_order("NACH O O") + + -- u2 has too many people + local s2 = ship.create(r, "boat") + local u2 = unit.create(f, r, 6) + u2.ship = s2 + u2:set_skill("sailing", 10) + u2:clear_orders() + u2:add_order("NACH O O") + + -- u4 has too much stuff + local s4 = ship.create(r, "boat") + local u4 = unit.create(f, r, 5) + u4.ship = s4 + u4:set_skill("sailing", 10) + u4:add_item("sword", 1) + u4:clear_orders() + u4:add_order("NACH O O") + + process_orders() + +-- print(s.region, u.region, r2) + assert_equal(r2.id, u1.region.id, "boat with 5 humans did not move") + assert_not_equal(r2.id, u2.region.id, "boat with too many people has moved") + assert_not_equal(r2.id, u4.region.id, "boat with too much cargo has moved") +end + +function test_levitate() + local r = region.create(0,0, "plain") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 2) + local s = ship.create(r, "boat") + u.ship = s + u.age = 20 + u:set_skill("sailing", 5) + u:add_item("money", 100) + u:clear_orders() + u:add_order("ARBEITE") + levitate_ship(u.ship, u, 2, 1) + assert_equal(32, u.ship.flags) + process_orders() + assert_equal(0, u.ship.flags) +end + +function test_terrains() + local terrains = { "hell", "wall1", "corridor1" } + for k,v in ipairs(terrains) do + local r = region.create(k, k, v) + assert_not_equal(nil, r) + end +end + +function test_races() + local races = { "wolf", "orc", "human", "demon" } + for k,v in ipairs(races) do + local f = faction.create("noreply@eressea.de", "human", "de") + assert_not_equal(nil, f) + end +end + +function test_can_give_person() + 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 u1 = unit.create(f1, r, 10) + local u2 = unit.create(f2, r, 10) + u1.faction.age = 10 + u2.faction.age = 10 + u1:add_item("money", 500) + u2:add_item("money", 500) + u2:clear_orders() + u2:add_order("GIB ".. itoa36(u1.id) .. " 1 PERSON") + u2:add_order("HELFE ".. itoa36(f1.id) .. " GIB") + u1:add_order("HELFE ".. itoa36(f2.id) .. " GIB") + u1:add_order("KONTAKTIERE ".. itoa36(u2.id)) + process_orders() + assert_equal(9, u2.number) + assert_equal(11, u1.number) +end + +function test_no_uruk() + local f1 = faction.create("noreply@eressea.de", "uruk", "de") + assert_equal(f1.race, "orc") +end + +function test_snowman() + local r = region.create(0, 0, "glacier") + local f = faction.create("noreply@eressea.de", "human", "de") + local u = unit.create(f, r, 1) + u:add_item("snowman", 1) + u:clear_orders() + u:add_order("BENUTZEN 1 Schneemann") + process_orders() + for u2 in r.units do + if u2.id~=u.id then + assert_equal(u2.race, "snowman") + u = nil + break + end + end + assert_equal(nil, u) +end diff --git a/scripts/tests/morale.lua b/scripts/tests/morale.lua new file mode 100644 index 000000000..ddbdb9526 --- /dev/null +++ b/scripts/tests/morale.lua @@ -0,0 +1,178 @@ +require "lunit" + +module("tests.e3.morale", package.seeall, lunit.testcase ) + +function setup() + eressea.free_game() +end + +function test_when_owner_returns_morale_drops_only_2() + local r = region.create(0, 0, "plain") + assert_equal(1, r.morale) + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + u1:add_item("money", 10000) + local b = building.create(r, "castle") + b.size = 50 + + set_turn(get_turn()+10) + f1.lastturn=get_turn() + u1.building = b + update_owners() + r.morale = 6 + u1.building = nil + process_orders() + assert_equal(5, r.morale) -- no owner, fall by 1 + u1.building = b + update_owners() + set_key("test", 42) + process_orders() + assert_equal(3, r.morale) -- new owner, fall by 2 +end + +function test_morale_alliance() + local r = region.create(0, 0, "plain") + assert_equal(1, r.morale) + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + u1:add_item("money", 10000) + local f2 = faction.create("noreply@eressea.de", "human", "de") + local u2 = unit.create(f2, r, 1) + u2:add_item("money", 10000) + local f3 = faction.create("noreply@eressea.de", "human", "de") + local u3 = unit.create(f3, r, 1) + u3:add_item("money", 10000) + + local al = alliance.create(42, "Die Antwoord") + f1.alliance = al; + f2.alliance = al; + + local b = building.create(r, "castle") + b.size = 50 + u1.building = b + u2.building = b + u3.building = b + update_owners() + r.morale = 6 + + local function run_a_turn() + process_orders() + f1.lastturn=get_turn() + f2.lastturn=get_turn() + f3.lastturn=get_turn() + end + + -- just checking everything's okay after setup. + run_a_turn() + assert_equal(6, r.morale) + + -- change owner, new owner is in the same alliance + u1.building = nil + run_a_turn() + assert_equal(4, r.morale) + + -- change owner, new owner is not in the same alliance + u2.building = nil + run_a_turn() + assert_equal(0, r.morale) +end + +function test_morale_change() + local r = region.create(0, 0, "plain") + assert_equal(1, r.morale) + local f1 = faction.create("noreply@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + u1:add_item("money", 10000) + local f2 = faction.create("noreply@eressea.de", "human", "de") + local u2 = unit.create(f2, r, 1) + u2:add_item("money", 10000) + + local AVG_STEP = 6 + local b = building.create(r, "castle") + b.size = 10 + u1.building = b + + local function run_a_turn() + process_orders() + f1.lastturn=get_turn() + f2.lastturn=get_turn() + end + + -- reinhardt-regel: nach 2*AVG_STEP ist moral mindestens einmal gestiegen. + update_owners() + assert_not_equal(r.owner, nil) + for i=1,AVG_STEP*2 do + run_a_turn() + assert_not_equal(r.owner, nil) + end + assert_not_equal(1, r.morale) + + -- regel: moral ist nie hoeher als 2 punkte ueber burgen-max. + for i=1,AVG_STEP*4 do + run_a_turn() + end + assert_equal(4, r.morale) + + -- auch mit herrscher faellt moral um 1 pro woche, wenn moral > burgstufe + r.morale = 6 + run_a_turn() + assert_equal(5, r.morale) + run_a_turn() + assert_equal(4, r.morale) + run_a_turn() + assert_equal(4, r.morale) + + -- regel: ohne herrscher fällt die moral jede woche um 1 punkt, bis sie 1 erreicht + u1.building = nil + update_owners() + run_a_turn() + assert_equal(3, r.morale) + run_a_turn() + assert_equal(2, r.morale) + run_a_turn() + assert_equal(1, r.morale) + run_a_turn() + assert_equal(1, r.morale) + + -- ohne herrscher ändert sich auch beschissene Moral nicht: + r.morale = 0 + run_a_turn() + assert_equal(0, r.morale) +end + +function test_morale_old() + local r = region.create(0, 0, "plain") + assert_equal(1, r.morale) + local f1 = faction.create("first@eressea.de", "human", "de") + local u1 = unit.create(f1, r, 1) + local f2 = faction.create("second@eressea.de", "human", "de") + local u2 = unit.create(f2, r, 1) + + local b = building.create(r, "castle") + b.size = 10 + u1.building = b + u2.building = b + update_owners() + assert_equal(1, r.morale) + r.morale = 5 + assert_equal(r.owner, u1.faction) + u1:clear_orders() + u1:add_order("GIB " .. itoa36(u2.id) .. " KOMMANDO") + process_orders() + u1:clear_orders() + assert_equal(u2.faction, r.owner) + assert_equal(3, r.morale) -- 5-MORALE_TRANSFER + for u in r.units do + if u.faction.id==u2.faction.id then + u.building = nil + end + end + update_owners() + assert_equal(r.owner, u1.faction) + assert_equal(0, r.morale) +end + +function test_no_uruk() + local f1 = faction.create("noreply@eressea.de", "uruk", "de") + assert_equal(f1.race, "orc") +end diff --git a/scripts/tests/spells-e2.lua b/scripts/tests/spells-e2.lua new file mode 100644 index 000000000..a8ff7b943 --- /dev/null +++ b/scripts/tests/spells-e2.lua @@ -0,0 +1,37 @@ +require "lunit" + +module("tests.e3.spells-e2", 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_create_magicherbbag() + u:add_spell('create_magicherbbag') + u:cast_spell('create_magicherbbag') + assert_equal(1, u:get_item("magicherbbag")) +end + +function test_create_runesword() + u:add_spell('create_runesword') + u:cast_spell('create_runesword') + assert_equal(1, u:get_item("runesword")) +end + +function test_create_firesword() + u:add_spell("create_firesword") + u:cast_spell('create_firesword', 1) + assert_equal(1, u:get_item("firesword")) +end + diff --git a/scripts/tests/spells.lua b/scripts/tests/spells.lua new file mode 100644 index 000000000..7a1cb6d86 --- /dev/null +++ b/scripts/tests/spells.lua @@ -0,0 +1,49 @@ +require "lunit" + +module("tests.e3.spells", package.seeall, lunit.testcase) + +function setup() + eressea.free_game() + eressea.settings.set("magic.fumble.enable", "0") +end + +function test_blessedharvest_lasts_n_turn() + eressea.free_game() + local r = region.create(0, 0, "plain") + local f = faction.create("noreply@eressea.de", "halfling", "de") + local u = unit.create(f, r) + local err = 0 + r:set_resource("peasant", 100) + r:set_resource("money", 0) + u:add_item("money", 1000) + u.magic = "gwyrrd" + u.race = "dwarf" + u:set_skill("magic", 20) + u.aura = 200 + err = err + u:add_spell("raindance") + err = err + u:add_spell("blessedharvest") + assert_equal(0, err) + + u:clear_orders() + local level = 5 + u:add_order("ZAUBERE STUFE " .. level .. " Regentanz") + assert_equal(0, r:get_resource("money"), 0) + + local m = 0 + local p = 100 + for i=1,level+2 do + process_orders() + local income = p * 12 + p = r:get_resource("peasant") + income = income - p * 10 + m = m + income + -- print(i, m, p, r:get_resource("money")) + if (i>level+1) then + assert_not_equal(m, r:get_resource("money")) + else + assert_equal(m, r:get_resource("money")) + end + u:clear_orders() + u:add_order("ARBEITEN") + end +end diff --git a/scripts/tests/stealth.lua b/scripts/tests/stealth.lua new file mode 100644 index 000000000..a5f8cdcb5 --- /dev/null +++ b/scripts/tests/stealth.lua @@ -0,0 +1,36 @@ +require "lunit" + +module("tests.e3.stealth", package.seeall, lunit.testcase) + +local f +local u + +function setup() + eressea.free_game() + eressea.settings.set("rules.economy.food", "4") + + local r = region.create(0,0, "plain") + f = faction.create("stealthy@eressea.de", "human", "de") + u = unit.create(f, r, 1) + f = faction.create("stealth@eressea.de", "human", "de") +end + +function test_stealth_faction_on() + u:clear_orders() + u:add_order("TARNEN PARTEI") + + eressea.settings.set("rules.stealth.faction", 1) + process_orders() + assert_not_match("Partei", report.report_unit(u, f)) + assert_match("anonym", report.report_unit(u, f)) +end + +function test_stealth_faction_off() + u:clear_orders() + u:add_order("TARNEN PARTEI") + + eressea.settings.set("rules.stealth.faction", 0) + process_orders() + assert_match("Partei", report.report_unit(u, f)) + assert_not_match("anonym", report.report_unit(u, f)) +end diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 000000000..27f316a3b --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,4 @@ + +Debug + +*.user \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8a6e78665..0eb069c74 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,7 +19,25 @@ include_directories (${BSON_INCLUDE_DIR}) include_directories (${INIPARSER_INCLUDE_DIR}) include_directories (${CURSES_INCLUDE_DIR}) -add_executable(server main.c) +set (SERVER_SRC + races/races.c + races/dragons.c + races/zombies.c + races/illusion.c + main.c + bindings.c + monsters.c + spells/combatspells.c + spells/shipcurse.c + spells/regioncurse.c + spells/buildingcurse.c + spells/alp.c + spells/unitcurse.c + spells/spells.c + curses.c +) + +add_executable(server ${SERVER_SRC}) target_link_libraries(server ${ERESSEA_LIBRARY} ${BINDINGS_LIBRARY} diff --git a/src/bindings.c b/src/bindings.c new file mode 100644 index 000000000..ca2f27c55 --- /dev/null +++ b/src/bindings.c @@ -0,0 +1,94 @@ +#include +#include +#include "spells/shipcurse.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +static int tolua_levitate_ship(lua_State * L) +{ + ship *sh = (ship *) tolua_tousertype(L, 1, 0); + unit *mage = (unit *) tolua_tousertype(L, 2, 0); + double power = (double)tolua_tonumber(L, 3, 0); + int duration = (int)tolua_tonumber(L, 4, 0); + int cno = levitate_ship(sh, mage, power, duration); + tolua_pushnumber(L, (lua_Number) cno); + return 1; +} + +extern void spawn_undead(void); +extern void spawn_dragons(void); +extern void plan_monsters(struct faction *f); + +static int tolua_planmonsters(lua_State * L) +{ + faction *f = (faction *) tolua_tousertype(L, 1, get_monsters()); + if (f) { + plan_monsters(f); + } + + return 0; +} + +static int tolua_spawn_dragons(lua_State * L) +{ + spawn_dragons(); + return 0; +} + +static int tolua_spawn_undead(lua_State * L) +{ + spawn_undead(); + return 0; +} + +static int fix_familiars(struct lua_State *L) +{ + faction *f; + for (f = factions; f; f = f->next) { + unit *u; + for (u = f->units; u; u = u->nextF) { + struct sc_mage *mage = get_mage(u); + if (mage && is_familiar(u)) { + if (mage->spellbook && mage->magietyp == M_GRAY) { + equipment *eq; + char buffer[64]; + + spellbook_clear(mage->spellbook); + free(mage->spellbook); + mage->spellbook = 0; + + snprintf(buffer, sizeof(buffer), "%s_familiar", u_race(u)->_name[0]); + eq = get_equipment(buffer); + if (eq) { + equip_unit_mask(u, eq, EQUIP_SPELLS); + } + } + } + } + } + return 0; +} + +void bind_eressea(struct lua_State *L) +{ + tolua_module(L, NULL, 0); + tolua_beginmodule(L, NULL); + { + tolua_function(L, TOLUA_CAST "levitate_ship", tolua_levitate_ship); + tolua_function(L, TOLUA_CAST "plan_monsters", tolua_planmonsters); + tolua_function(L, TOLUA_CAST "spawn_undead", tolua_spawn_undead); + tolua_function(L, TOLUA_CAST "spawn_dragons", tolua_spawn_dragons); + tolua_function(L, TOLUA_CAST "fix_familiars", &fix_familiars); + } + tolua_endmodule(L); +} diff --git a/src/curses.c b/src/curses.c new file mode 100644 index 000000000..7dd9be6ff --- /dev/null +++ b/src/curses.c @@ -0,0 +1,309 @@ +#include +#include + +#include "curses.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spells/regioncurse.h" +#include "spells/unitcurse.h" +#include "spells/shipcurse.h" +#include "spells/buildingcurse.h" + +#include +#include +#include +#include +#include +#include + +#include + +typedef struct wallcurse { + curse *buddy; + connection *wall; +} wallcurse; + +void cw_init(attrib * a) +{ + curse *c; + curse_init(a); + c = (curse *) a->data.v; + c->data.v = calloc(sizeof(wallcurse), 1); +} + +void cw_write(const attrib * a, const void *target, storage * store) +{ + connection *b = ((wallcurse *) ((curse *) a->data.v)->data.v)->wall; + curse_write(a, target, store); + store->w_int(store, b->id); +} + +typedef struct bresolve { + unsigned int id; + curse *self; +} bresolve; + +static int resolve_buddy(variant data, void *addr); + +static int cw_read(attrib * a, void *target, storage * store) +{ + bresolve *br = calloc(sizeof(bresolve), 1); + curse *c = (curse *) a->data.v; + wallcurse *wc = (wallcurse *) c->data.v; + variant var; + + curse_read(a, store, target); + br->self = c; + br->id = store->r_int(store); + + var.i = br->id; + ur_add(var, &wc->wall, resolve_borderid); + + var.v = br; + ur_add(var, &wc->buddy, resolve_buddy); + return AT_READ_OK; +} + +/* ------------------------------------------------------------- */ +/* Name: Feuerwand + * Stufe: + * Gebiet: Draig + * Kategorie: Region, negativ + * Flag: + * Kosten: SPC_LINEAR + * Aura: + * Komponenten: + * + * Wirkung: + * eine Wand aus Feuer entsteht in der angegebenen Richtung + * + * Was fuer eine Wirkung hat die? + */ + +void wall_vigour(curse * c, double delta) +{ + wallcurse *wc = (wallcurse *) c->data.v; + assert(wc->buddy->vigour == c->vigour); + wc->buddy->vigour += delta; + if (wc->buddy->vigour <= 0) { + erase_border(wc->wall); + wc->wall = NULL; + ((wallcurse *) wc->buddy->data.v)->wall = NULL; + } +} + +const curse_type ct_firewall = { + "Feuerwand", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR | NO_MERGE), + NULL, /* curseinfo */ + wall_vigour /* change_vigour */ +}; + +attrib_type at_cursewall = { + "cursewall", + cw_init, + curse_done, + curse_age, + cw_write, + cw_read, + ATF_CURSE +}; + +static int resolve_buddy(variant data, void *addr) +{ + curse *result = NULL; + bresolve *br = (bresolve *) data.v; + connection *b; + + assert(br->id > 0); + b = find_border(br->id); + + if (b && b->from && b->to) { + attrib *a = a_find(b->from->attribs, &at_cursewall); + while (a && a->data.v != br->self) { + curse *c = (curse *) a->data.v; + wallcurse *wc = (wallcurse *) c->data.v; + if (wc->wall->id == br->id) + break; + a = a->next; + } + if (!a || a->type != &at_cursewall) { + a = a_find(b->to->attribs, &at_cursewall); + while (a && a->type == &at_cursewall && a->data.v != br->self) { + curse *c = (curse *) a->data.v; + wallcurse *wc = (wallcurse *) c->data.v; + if (wc->wall->id == br->id) + break; + a = a->next; + } + } + if (a && a->type == &at_cursewall) { + curse *c = (curse *) a->data.v; + free(br); + result = c; + } + } else { + /* fail, object does not exist (but if you're still loading then + * you may want to try again later) */ + *(curse **) addr = NULL; + return -1; + } + *(curse **) addr = result; + return 0; +} + +static void wall_init(connection * b) +{ + wall_data *fd = (wall_data *) calloc(sizeof(wall_data), 1); + fd->countdown = -1; /* infinite */ + b->data.v = fd; +} + +static void wall_destroy(connection * b) +{ + free(b->data.v); +} + +static void wall_read(connection * b, storage * store) +{ + static wall_data dummy; + wall_data *fd = b->data.v ? (wall_data *) b->data.v : &dummy; + variant mno; + + if (store->version < STORAGE_VERSION) { + mno.i = store->r_int(store); + fd->mage = findunit(mno.i); + if (!fd->mage && b->data.v) { + ur_add(mno, &fd->mage, resolve_unit); + } + } else { + read_reference(&fd->mage, store, read_unit_reference, resolve_unit); + } + fd->force = store->r_int(store); + if (store->version >= NOBORDERATTRIBS_VERSION) { + fd->countdown = store->r_int(store); + } + fd->active = true; +} + +static void wall_write(const connection * b, storage * store) +{ + wall_data *fd = (wall_data *) b->data.v; + write_unit_reference(fd->mage, store); + store->w_int(store, fd->force); + store->w_int(store, fd->countdown); +} + +static int wall_age(connection * b) +{ + wall_data *fd = (wall_data *) b->data.v; + --fd->countdown; + return (fd->countdown > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +static region *wall_move(const connection * b, struct unit *u, + struct region *from, struct region *to, bool routing) +{ + wall_data *fd = (wall_data *) b->data.v; + if (!routing && fd->active) { + int hp = dice(3, fd->force) * u->number; + hp = MIN(u->hp, hp); + u->hp -= hp; + if (u->hp) { + ADDMSG(&u->faction->msgs, msg_message("firewall_damage", + "region unit", from, u)); + } else + ADDMSG(&u->faction->msgs, msg_message("firewall_death", "region unit", + from, u)); + if (u->number > u->hp) { + scale_number(u, u->hp); + u->hp = u->number; + } + } + return to; +} + +static const char *b_namefirewall(const connection * b, const region * r, + const faction * f, int gflags) +{ + const char *bname; + unused(f); + unused(r); + unused(b); + if (gflags & GF_ARTICLE) + bname = "a_firewall"; + else + bname = "firewall"; + + if (gflags & GF_PURE) + return bname; + return LOC(f->locale, mkname("border", bname)); +} + +border_type bt_firewall = { + "firewall", VAR_VOIDPTR, + b_transparent, /* transparent */ + wall_init, /* init */ + wall_destroy, /* destroy */ + wall_read, /* read */ + wall_write, /* write */ + b_blocknone, /* block */ + b_namefirewall, /* name */ + b_rvisible, /* rvisible */ + b_finvisible, /* fvisible */ + b_uinvisible, /* uvisible */ + NULL, + wall_move, + wall_age +}; + +void convert_firewall_timeouts(connection * b, attrib * a) +{ + while (a) { + if (b->type == &bt_firewall && a->type == &at_countdown) { + wall_data *fd = (wall_data *) b->data.v; + fd->countdown = a->data.i; + } + } +} + +border_type bt_wisps = { /* only here for reading old data */ + "wisps", VAR_VOIDPTR, + b_transparent, /* transparent */ + 0, /* init */ + wall_destroy, /* destroy */ + wall_read, /* read */ + 0, /* write */ + b_blocknone, /* block */ + 0, /* name */ + b_rvisible, /* rvisible */ + b_fvisible, /* fvisible */ + b_uvisible, /* uvisible */ + NULL, /* visible */ + 0 +}; + +void register_curses(void) +{ + border_convert_cb = &convert_firewall_timeouts; + at_register(&at_cursewall); + + register_bordertype(&bt_firewall); + register_bordertype(&bt_wisps); + register_bordertype(&bt_chaosgate); + + register_unitcurse(); + register_regioncurse(); + register_shipcurse(); + register_buildingcurse(); +} diff --git a/src/curses.h b/src/curses.h new file mode 100644 index 000000000..912f73da9 --- /dev/null +++ b/src/curses.h @@ -0,0 +1,28 @@ +#ifndef H_KRNL_CURSES +#define H_KRNL_CURSES +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_curses(void); + + /* für Feuerwände: in movement muß das noch explizit getestet werden. + ** besser wäre eine blcok_type::move() routine, die den effekt + ** der Bewegung auf eine struct unit anwendet. + **/ + extern struct border_type bt_chaosgate; + extern struct border_type bt_firewall; + + typedef struct wall_data { + struct unit *mage; + int force; + bool active; + int countdown; + } wall_data; + + extern const struct curse_type ct_firewall; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/eressea.vcproj b/src/eressea.vcproj new file mode 100644 index 000000000..8fda875d9 --- /dev/null +++ b/src/eressea.vcproj @@ -0,0 +1,634 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/eressea.vcxproj b/src/eressea.vcxproj new file mode 100644 index 000000000..7f591479a --- /dev/null +++ b/src/eressea.vcxproj @@ -0,0 +1,194 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + eressea + {AD80EB0B-7CB4-42F2-9C95-8CCEF68DB387} + build + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + true + $(ProjectDir)$(Configuration)\ + $(ProjectDir)$(Configuration)\ + false + + + + /MP %(AdditionalOptions) + Disabled + ../../shared/src;../../external;../../external/lua/src;../../external/tolua/include;../../external/pdcurses;..\..\external\libxml2\include;%(AdditionalIncludeDirectories) + LIBXML_STATIC;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Use + stdafx.h + EnableAllWarnings + EditAndContinue + true + + + libxml2.lib;pdcurses.lib;lua.lib;tolua.lib;ws2_32.lib;%(AdditionalDependencies) + true + Console + MachineX86 + ..\..\external\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + ../../shared/src;../../external;../../external/lua/src;../../external/tolua/include;../../external/pdcurses;..\..\external\libxml2\include;%(AdditionalIncludeDirectories) + LIBXML_STATIC;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Use + Level3 + ProgramDatabase + true + + + libxml2.lib;pdcurses.lib;lua.lib;tolua.lib;%(AdditionalDependencies) + true + Console + true + true + MachineX86 + ..\..\external\$(Configuration)\;%(AdditionalLibraryDirectories) + + + + + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + true + true + + + + + + + + Create + stdafx.h + Create + + + src/util;%(AdditionalIncludeDirectories) + + + + + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/eressea.vcxproj.filters b/src/eressea.vcxproj.filters new file mode 100644 index 000000000..3075045d1 --- /dev/null +++ b/src/eressea.vcxproj.filters @@ -0,0 +1,130 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {7607f7af-54a4-402b-9b69-049ff8332f94} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Unity Build + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + + + + \ No newline at end of file diff --git a/src/main.c b/src/main.c index 3bdeb31c7..2d07ec4b4 100644 --- a/src/main.c +++ b/src/main.c @@ -19,24 +19,27 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include -#include -#include -#include #include +#include #include #include -#include +#include +#include + +#include "races/races.h" +#include "spells/spells.h" +#include "curses.h" #include - #include #include #include +#include -static const char *luafile = 0; +static const char *logfile= "eressea.log"; +static const char *luafile = "setup.lua"; static const char *entry_point = NULL; static const char *inifile = "eressea.ini"; -static const char *logfile= "eressea.log"; static int memdebug = 0; static void parse_config(const char *filename) @@ -52,6 +55,9 @@ static void parse_config(const char *filename) /* only one value in the [editor] section */ force_color = iniparser_getint(d, "editor:color", force_color); + + /* excerpt from [config] (the rest is used in bindings.c) */ + game_name = iniparser_getstring(d, "config:game", game_name); } else { log_error("could not open configuration file %s\n", filename); } @@ -125,6 +131,11 @@ static int parse_args(int argc, char **argv, int *exitcode) case 'q': verbosity = 0; break; + case 'r': + entry_point = "run_turn"; + i = get_arg(argc, argv, 2, i, &arg, 0); + turn = atoi(arg); + break; case 'v': i = get_arg(argc, argv, 2, i, &arg, 0); verbosity = arg ? atoi(arg) : 0xff; @@ -161,20 +172,80 @@ static int parse_args(int argc, char **argv, int *exitcode) return 0; } +#if defined(HAVE_SIGACTION) && defined(HAVE_EXECINFO) +#include +#include + +static void report_segfault(int signo, siginfo_t * sinf, void *arg) +{ + void *btrace[50]; + size_t size; + int fd = fileno(stderr); + + fflush(stdout); + fputs("\n\nProgram received SIGSEGV, backtrace follows.\n", stderr); + size = backtrace(btrace, 50); + backtrace_symbols_fd(btrace, size, fd); + abort(); +} + +static int setup_signal_handler(void) +{ + struct sigaction act; + + act.sa_flags = SA_ONESHOT | SA_SIGINFO; + act.sa_sigaction = report_segfault; + sigfillset(&act.sa_mask); + return sigaction(SIGSEGV, &act, NULL); +} +#else +static int setup_signal_handler(void) +{ + return 0; +} +#endif + +#undef CRTDBG +#ifdef CRTDBG +#include +void init_crtdbg(void) +{ +#if (defined(_MSC_VER)) + int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + if (memdebug == 1) { + flags |= _CRTDBG_CHECK_ALWAYS_DF; /* expensive */ + } else if (memdebug == 2) { + flags = (flags & 0x0000FFFF) | _CRTDBG_CHECK_EVERY_16_DF; + } else if (memdebug == 3) { + flags = (flags & 0x0000FFFF) | _CRTDBG_CHECK_EVERY_128_DF; + } else if (memdebug == 4) { + flags = (flags & 0x0000FFFF) | _CRTDBG_CHECK_EVERY_1024_DF; + } + _CrtSetDbgFlag(flags); +#endif +} +#endif + void locale_init(void) { setlocale(LC_CTYPE, ""); setlocale(LC_NUMERIC, "C"); - assert(towlower(0xC4) == 0xE4); /* Ä => ä */ + if (towlower(0xC4) != 0xE4) { /* Ä => ä */ + log_error("Umlaut conversion is not working properly. Wrong locale? LANG=%s\n", + getenv("LANG")); + } } +extern void bind_eressea(struct lua_State *L); + int main(int argc, char **argv) { int err, result = 0; - lua_State * L; + lua_State *L; - log_open(logfile); + setup_signal_handler(); parse_config(inifile); + log_open(logfile); err = parse_args(argc, argv, &result); if (err) { @@ -183,17 +254,31 @@ int main(int argc, char **argv) locale_init(); +#ifdef CRTDBG + init_crtdbg(); +#endif + L = lua_init(); game_init(); + register_races(); + register_curses(); + register_spells(); + bind_eressea(L); err = eressea_run(L, luafile, entry_point); if (err) { log_error("server execution failed with code %d\n", err); return err; } +#ifdef MSPACES + malloc_stats(); +#endif game_done(); lua_done(L); log_close(); + if (global.inifile) { + iniparser_free(global.inifile); + } return 0; } diff --git a/src/monsters.c b/src/monsters.c new file mode 100644 index 000000000..14dba72af --- /dev/null +++ b/src/monsters.c @@ -0,0 +1,1017 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.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 without + * prior permission by the authors of Eressea. + * This program may not be sold or used commercially without prior written + * permission from the authors. + */ + +#include +#include + +/* 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 +#include + +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* libc includes */ +#include +#include +#include + +#define MOVECHANCE 25 /* chance fuer bewegung */ +#define DRAGON_RANGE 20 /* Max. Distanz zum nächsten Drachenziel */ +#define MAXILLUSION_TEXTS 3 + +static void reduce_weight(unit * u) +{ + int capacity, weight = 0; + item **itmp = &u->items; + int horses = get_resource(u, oldresourcetype[R_HORSE]); + + if (horses > 0) { + horses = MIN(horses, (u->number * 2)); + change_resource(u, oldresourcetype[R_HORSE], -horses); + } + + /* 0. ditch any vehicles */ + while (*itmp != NULL) { + item *itm = *itmp; + const item_type *itype = itm->type; + weight += itm->number * itype->weight; + if (itype->flags & ITF_VEHICLE) { + give_item(itm->number, itm->type, u, NULL, NULL); + } + if (*itmp == itm) + itmp = &itm->next; + } + + capacity = walkingcapacity(u); + + /* 1. get rid of anything that isn't silver or really lightweight or helpful in combat */ + for (itmp = &u->items; *itmp && capacity > 0;) { + item *itm = *itmp; + const item_type *itype = itm->type; + weight += itm->number * itype->weight; + if (weight > capacity) { + if (itype->weight >= 10 && itype->rtype->wtype == 0 + && itype->rtype->atype == 0) { + if (itype->capacity < itype->weight) { + int reduce = MIN(itm->number, -((capacity - weight) / itype->weight)); + give_item(reduce, itm->type, u, NULL, NULL); + weight -= reduce * itype->weight; + } + } + } + if (*itmp == itm) + itmp = &itm->next; + } + + for (itmp = &u->items; *itmp && weight > capacity;) { + item *itm = *itmp; + const item_type *itype = itm->type; + weight += itm->number * itype->weight; + if (itype->capacity < itype->weight) { + int reduce = MIN(itm->number, -((capacity - weight) / itype->weight)); + give_item(reduce, itm->type, u, NULL, NULL); + weight -= reduce * itype->weight; + } + if (*itmp == itm) + itmp = &itm->next; + } +} + +static order *monster_attack(unit * u, const unit * target) +{ + if (u->region != target->region) + return NULL; + if (u->faction == target->faction) + return NULL; + if (!cansee(u->faction, u->region, target, 0)) + return NULL; + if (monster_is_waiting(u)) + return NULL; + + return create_order(K_ATTACK, u->faction->locale, "%i", target->no); +} + +static order *get_money_for_dragon(region * r, unit * u, int wanted) +{ + unit *u2; + int n; + + /* attackiere bewachende einheiten */ + for (u2 = r->units; u2; u2 = u2->next) { + if (u2 != u && is_guard(u2, GUARD_TAX)) { + order *ord = monster_attack(u, u2); + if (ord) + addlist(&u->orders, ord); + } + } + + /* falls genug geld in der region ist, treiben wir steuern ein. */ + if (rmoney(r) >= wanted) { + /* 5% chance, dass der drache aus einer laune raus attackiert */ + if (chance(1.0 - u_race(u)->aggression)) { + return create_order(K_TAX, default_locale, NULL); + } + } + + /* falls der drache launisch ist, oder das regionssilber knapp, greift er alle an */ + n = 0; + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction != u->faction && cansee(u->faction, r, u2, 0)) { + int m = get_money(u2); + if (m == 0 || is_guard(u2, GUARD_TAX)) + continue; + else { + order *ord = monster_attack(u, u2); + if (ord) { + addlist(&u->orders, ord); + n += m; + } + } + } + } + + /* falls die einnahmen erreicht werden, bleibt das monster noch eine + * runde hier. */ + if (n + rmoney(r) >= wanted) { + return create_order(K_TAX, default_locale, NULL); + } + + /* wenn wir NULL zurückliefern, macht der drache was anderes, z.b. weggehen */ + return NULL; +} + +static int all_money(region * r, faction * f) +{ + unit *u; + int m; + + m = rmoney(r); + for (u = r->units; u; u = u->next) { + if (f != u->faction) { + m += get_money(u); + } + } + return m; +} + +static direction_t richest_neighbour(region * r, faction * f, int absolut) +{ + + /* m - maximum an Geld, d - Richtung, i - index, t = Geld hier */ + + double m; + double t; + direction_t d = NODIRECTION, i; + + if (absolut == 1 || rpeasants(r) == 0) { + m = (double)all_money(r, f); + } else { + m = (double)all_money(r, f) / (double)rpeasants(r); + } + + /* finde die region mit dem meisten geld */ + + for (i = 0; i != MAXDIRECTIONS; i++) { + region *rn = rconnect(r, i); + if (rn != NULL && fval(rn->terrain, LAND_REGION)) { + if (absolut == 1 || rpeasants(rn) == 0) { + t = (double)all_money(rn, f); + } else { + t = (double)all_money(rn, f) / (double)rpeasants(rn); + } + + if (t > m) { + m = t; + d = i; + } + } + } + return d; +} + +static bool room_for_race_in_region(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; + } + + if (c > (rc->splitsize * 2)) + return false; + + return true; +} + +static direction_t random_neighbour(region * r, unit * u) +{ + int i; + region *rc; + region * next[MAXDIRECTIONS]; + int rr, c = 0, c2 = 0; + + get_neighbours(r, next); + /* Nachsehen, wieviele Regionen in Frage kommen */ + + for (i = 0; i != MAXDIRECTIONS; i++) { + rc = next[i]; + if (rc && can_survive(u, rc)) { + if (room_for_race_in_region(rc, u_race(u))) { + c++; + } + c2++; + } + } + + if (c == 0) { + if (c2 == 0) { + return NODIRECTION; + } else { + c = c2; + c2 = 0; /* c2 == 0 -> room_for_race nicht beachten */ + } + } + + /* Zufällig eine auswählen */ + + rr = rng_int() % c; + + /* Durchzählen */ + + c = -1; + for (i = 0; i != MAXDIRECTIONS; i++) { + rc = next[i]; + if (rc && can_survive(u, rc)) { + if (c2 == 0) { + c++; + } else if (room_for_race_in_region(rc, u_race(u))) { + c++; + } + if (c == rr) + return (direction_t)i; + } + } + + assert(1 == 0); /* Bis hierhin sollte er niemals kommen. */ + return NODIRECTION; +} + +static direction_t treeman_neighbour(region * r) +{ + int i; + int rr; + int c = 0; + region * next[MAXDIRECTIONS]; + + get_neighbours(r, next); + /* Nachsehen, wieviele Regionen in Frage kommen */ + + for (i = 0; i != MAXDIRECTIONS; i++) { + if (next[i] && rterrain(next[i]) != T_OCEAN + && rterrain(next[i]) != T_GLACIER && rterrain(next[i]) != T_DESERT) { + ++c; + } + } + + if (c == 0) { + return NODIRECTION; + } + /* Zufällig eine auswählen */ + + rr = rng_int() % c; + + /* Durchzählen */ + + c = -1; + for (i = 0; i != MAXDIRECTIONS; i++) { + if (next[i] && rterrain(next[i]) != T_OCEAN + && rterrain(next[i]) != T_GLACIER && rterrain(next[i]) != T_DESERT) { + if (++c == rr) { + return (direction_t)i; + } + } + } + + assert(!"this should never happen"); /* Bis hierhin sollte er niemals kommen. */ + return NODIRECTION; +} + +static order *monster_move(region * r, unit * u) +{ + direction_t d = NODIRECTION; + + if (monster_is_waiting(u)) { + return NULL; + } + switch (old_race(u_race(u))) { + case RC_FIREDRAGON: + case RC_DRAGON: + case RC_WYRM: + d = richest_neighbour(r, u->faction, 1); + break; + case RC_TREEMAN: + d = treeman_neighbour(r); + break; + default: + d = random_neighbour(r, u); + break; + } + + /* falls kein geld gefunden wird, zufaellig verreisen, aber nicht in + * den ozean */ + + if (d == NODIRECTION) + return NULL; + + reduce_weight(u); + return create_order(K_MOVE, u->faction->locale, "%s", + LOC(u->faction->locale, directions[d])); +} + +static int dragon_affinity_value(region * r, unit * u) +{ + int m = all_money(r, u->faction); + + if (u_race(u) == new_race[RC_FIREDRAGON]) { + return (int)(normalvariate(m, m / 2)); + } else { + return (int)(normalvariate(m, m / 4)); + } +} + +static attrib *set_new_dragon_target(unit * u, region * r, int range) +{ + int max_affinity = 0; + region *max_region = NULL; + quicklist *ql, *rlist = regions_in_range(r, range, allowed_dragon); + int qi; + + for (qi=0, ql = rlist; ql; ql_advance(&ql, &qi, 1)) { + region *r2 = (region *)ql_get(ql, qi); + int affinity = dragon_affinity_value(r2, u); + if (affinity > max_affinity) { + max_affinity = affinity; + max_region = r2; + } + } + + ql_free(rlist); + + if (max_region && max_region != r) { + attrib *a = a_find(u->attribs, &at_targetregion); + if (!a) { + a = a_add(&u->attribs, make_targetregion(max_region)); + } else { + a->data.v = max_region; + } + return a; + } + return NULL; +} + +static order *make_movement_order(unit * u, const region * target, int moves, + bool(*allowed) (const region *, const region *)) +{ + region *r = u->region; + region **plan; + int bytes, position = 0; + char zOrder[128], *bufp = zOrder; + size_t size = sizeof(zOrder) - 1; + + if (monster_is_waiting(u)) + return NULL; + + plan = path_find(r, target, DRAGON_RANGE * 5, allowed); + if (plan == NULL) + return NULL; + + bytes = + (int)strlcpy(bufp, + (const char *)LOC(u->faction->locale, keywords[K_MOVE]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + while (position != moves && plan[position + 1]) { + region *prev = plan[position]; + region *next = plan[++position]; + direction_t dir = reldirection(prev, next); + assert(dir != NODIRECTION && dir != D_SPECIAL); + if (size > 1) { + *bufp++ = ' '; + --size; + } + bytes = + (int)strlcpy(bufp, + (const char *)LOC(u->faction->locale, directions[dir]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + *bufp = 0; + return parse_order(zOrder, u->faction->locale); +} + +#ifdef TODO_ALP +static order *monster_seeks_target(region * r, unit * u) +{ + direction_t d; + unit *target = NULL; + int dist, dist2; + direction_t i; + region *nr; + + /* Das Monster sucht ein bestimmtes Opfer. Welches, steht + * in einer Referenz/attribut + * derzeit gibt es nur den alp + */ + + switch (old_race(u_race(u))) { + case RC_ALP: + target = alp_target(u); + break; + default: + assert(!"Seeker-Monster gibt kein Ziel an"); + } + + /* TODO: prüfen, ob target überhaupt noch existiert... */ + if (!target) { + log_error("Monster '%s' hat kein Ziel!\n", unitname(u)); + return NULL; /* this is a bug workaround! remove!! */ + } + + if (r == target->region) { /* Wir haben ihn! */ + if (u_race(u) == new_race[RC_ALP]) { + alp_findet_opfer(u, r); + } else { + assert(!"Seeker-Monster hat keine Aktion fuer Ziel"); + } + return NULL; + } + + /* Simpler Ansatz: Nachbarregion mit gerinster Distanz suchen. + * Sinnvoll momentan nur bei Monstern, die sich nicht um das + * Terrain kümmern. Nebelwände & Co machen derzeit auch nix... + */ + dist2 = distance(r, target->region); + d = NODIRECTION; + for (i = 0; i < MAXDIRECTIONS; i++) { + nr = rconnect(r, i); + assert(nr); + dist = distance(nr, target->region); + if (dist < dist2) { + dist2 = dist; + d = i; + } + } + assert(d != NODIRECTION); + + return create_order(K_MOVE, u->faction->locale, "%s", + LOC(u->faction->locale, directions[d])); +} +#endif + +static void monster_attacks(unit * u) +{ + region *r = u->region; + unit *u2; + + for (u2 = r->units; u2; u2 = u2->next) { + if (cansee(u->faction, r, u2, 0) && u2->faction != u->faction + && chance(0.75)) { + order *ord = monster_attack(u, u2); + if (ord) + addlist(&u->orders, ord); + } + } +} + +static const char *random_growl(void) +{ + switch (rng_int() % 5) { + case 0: + return "Groammm"; + case 1: + return "Roaaarrrr"; + case 2: + return "Chhhhhhhhhh"; + case 3: + return "Tschrrrkk"; + case 4: + return "Schhhh"; + } + return ""; +} + +extern struct attrib_type at_direction; + +static order *monster_learn(unit * u) +{ + int c = 0; + int n; + skill *sv; + const struct locale *lang = u->faction->locale; + + /* Monster lernt ein zufälliges Talent aus allen, in denen es schon + * Lerntage hat. */ + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level > 0) + ++c; + } + + if (c == 0) + return NULL; + + n = rng_int() % c + 1; + c = 0; + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level > 0) { + if (++c == n) { + return create_order(K_STUDY, lang, "'%s'", skillname(sv->id, lang)); + } + } + } + return NULL; +} + +static bool check_overpopulated(unit * u) +{ + unit *u2; + int c = 0; + + for (u2 = u->region->units; u2; u2 = u2->next) { + if (u_race(u2) == u_race(u) && u != u2) + c += u2->number; + } + + if (c > u_race(u)->splitsize * 2) + return true; + + return false; +} + +static void recruit_dracoids(unit * dragon, int size) +{ + faction *f = dragon->faction; + region *r = dragon->region; + const struct item *weapon = NULL; + order *new_order = NULL; + unit *un = createunit(r, f, size, new_race[RC_DRACOID]); + + fset(un, UFL_ISNEW | UFL_MOVED); + + name_unit(un); + change_money(dragon, -un->number * 50); + equip_unit(un, get_equipment("recruited_dracoid")); + + setstatus(un, ST_FIGHT); + for (weapon = un->items; weapon; weapon = weapon->next) { + const weapon_type *wtype = weapon->type->rtype->wtype; + if (wtype && (wtype->flags & WTF_MISSILE)) { + setstatus(un, ST_BEHIND); + } + new_order = create_order(K_STUDY, f->locale, "'%s'", + skillname(weapon->type->rtype->wtype->skill, f->locale)); + } + + if (new_order != NULL) { + addlist(&un->orders, new_order); + } +} + +static order *plan_dragon(unit * u) +{ + attrib *ta = a_find(u->attribs, &at_targetregion); + region *r = u->region; + region *tr = NULL; + bool move = false; + order *long_order = NULL; + + reduce_weight(u); + + if (ta == NULL) { + move |= (r->land == 0 || r->land->peasants == 0); /* when no peasants, move */ + move |= (r->land == 0 || r->land->money == 0); /* when no money, move */ + } + move |= chance(0.04); /* 4% chance to change your mind */ + + if (u_race(u) == new_race[RC_WYRM] && !move) { + unit *u2; + for (u2 = r->units; u2; u2 = u2->next) { + /* wyrme sind einzelgänger */ + if (u2 == u) { + /* we do not make room for newcomers, so we don't need to look at them */ + break; + } + if (u2 != u && u_race(u2) == u_race(u) && chance(0.5)) { + move = true; + break; + } + } + } + + if (move) { + /* dragon gets bored and looks for a different place to go */ + ta = set_new_dragon_target(u, u->region, DRAGON_RANGE); + } else + ta = a_find(u->attribs, &at_targetregion); + if (ta != NULL) { + tr = (region *) ta->data.v; + if (tr == NULL || !path_exists(u->region, tr, DRAGON_RANGE, allowed_dragon)) { + ta = set_new_dragon_target(u, u->region, DRAGON_RANGE); + if (ta) + tr = findregion(ta->data.sa[0], ta->data.sa[1]); + } + } + if (tr != NULL) { + assert(long_order == NULL); + switch (old_race(u_race(u))) { + case RC_FIREDRAGON: + long_order = make_movement_order(u, tr, 4, allowed_dragon); + break; + case RC_DRAGON: + long_order = make_movement_order(u, tr, 3, allowed_dragon); + break; + case RC_WYRM: + long_order = make_movement_order(u, tr, 1, allowed_dragon); + break; + default: + break; + } + if (rng_int() % 100 < 15) { + const struct locale *lang = u->faction->locale; + /* do a growl */ + if (rname(tr, lang)) { + addlist(&u->orders, + create_order(K_MAIL, lang, "%s '%s... %s %s %s'", + LOC(lang, parameters[P_REGION]), + random_growl(), + u->number == + 1 ? "Ich rieche" : "Wir riechen", + "etwas in", rname(tr, u->faction->locale))); + } + } + } else { + /* we have no target. do we like it here, then? */ + long_order = get_money_for_dragon(u->region, u, income(u)); + if (long_order == NULL) { + /* money is gone, need a new target */ + set_new_dragon_target(u, u->region, DRAGON_RANGE); + } else if (u_race(u) != new_race[RC_FIREDRAGON]) { + /* neue dracoiden! */ + if (r->land && !fval(r->terrain, FORBIDDEN_REGION)) { + int ra = 20 + rng_int() % 100; + if (get_money(u) > ra * 50 + 100 && rng_int() % 100 < 50) { + recruit_dracoids(u, ra); + } + } + } + } + if (long_order == NULL) { + skill_t sk = SK_PERCEPTION; + /* study perception (or a random useful skill) */ + while (!skill_enabled[sk] || u_race(u)->bonus[sk] < -5) { + sk = (skill_t) (rng_int() % MAXSKILLS); + } + long_order = create_order(K_STUDY, u->faction->locale, "'%s'", + skillname(sk, u->faction->locale)); + } + return long_order; +} + +void plan_monsters(faction * f) +{ + region *r; + + assert(f); + f->lastorders = turn; + + for (r = regions; r; r = r->next) { + unit *u; + double attack_chance = MONSTERATTACK; + bool attacking = false; + + for (u = r->units; u; u = u->next) { + attrib *ta; + order *long_order = NULL; + + /* Ab hier nur noch Befehle für NPC-Einheiten. */ + if (!is_monsters(u->faction)) + continue; + + if (attack_chance > 0.0) { + if (chance(attack_chance)) + attacking = true; + attack_chance = 0.0; + } + + if (u->status > ST_BEHIND) { + setstatus(u, ST_FIGHT); + /* all monsters fight */ + } + if (skill_enabled[SK_PERCEPTION]) { + /* Monster bekommen jede Runde ein paar Tage Wahrnehmung dazu */ + /* TODO: this only works for playerrace */ + produceexp(u, SK_PERCEPTION, u->number); + } + + /* Befehle müssen jede Runde neu gegeben werden: */ + free_orders(&u->orders); + + if (attacking) { + monster_attacks(u); + } + /* units with a plan to kill get ATTACK orders: */ + ta = a_find(u->attribs, &at_hate); + if (ta && !monster_is_waiting(u)) { + unit *tu = (unit *) ta->data.v; + if (tu && tu->region == r) { + addlist(&u->orders, + create_order(K_ATTACK, u->faction->locale, "%i", tu->no)); + } else if (tu) { + tu = findunitg(ta->data.i, NULL); + if (tu != NULL) { + long_order = make_movement_order(u, tu->region, 2, allowed_walk); + } + } else + a_remove(&u->attribs, ta); + } + + /* All monsters guard the region: */ + if (!monster_is_waiting(u) && r->land) { + addlist(&u->orders, create_order(K_GUARD, u->faction->locale, NULL)); + } + + /* Einheiten mit Bewegungsplan kriegen ein NACH: */ + if (long_order == NULL) { + attrib *ta = a_find(u->attribs, &at_targetregion); + if (ta) { + if (u->region == (region *) ta->data.v) { + a_remove(&u->attribs, ta); + } + } else if (u_race(u)->flags & RCF_MOVERANDOM) { + if (rng_int() % 100 < MOVECHANCE || check_overpopulated(u)) { + long_order = monster_move(r, u); + } + } + } + + if (long_order == NULL) { + /* Einheiten, die Waffenlosen Kampf lernen könnten, lernen es um + * zu bewachen: */ + if (u_race(u)->bonus[SK_WEAPONLESS] != -99) { + if (eff_skill(u, SK_WEAPONLESS, u->region) < 1) { + long_order = + create_order(K_STUDY, f->locale, "'%s'", + skillname(SK_WEAPONLESS, f->locale)); + } + } + } + + if (long_order == NULL) { + /* Ab hier noch nicht generalisierte Spezialbehandlungen. */ + + if (!u->orders) { + handle_event(u->attribs, "ai_move", u); + } + + switch (old_race(u_race(u))) { + case RC_SEASERPENT: + long_order = create_order(K_PIRACY, f->locale, NULL); + break; +#ifdef TODO_ALP + case RC_ALP: + long_order = monster_seeks_target(r, u); + break; +#endif + case RC_FIREDRAGON: + case RC_DRAGON: + case RC_WYRM: + long_order = plan_dragon(u); + break; + default: + if (u_race(u)->flags & RCF_LEARN) { + long_order = monster_learn(u); + } + break; + } + } + if (long_order) { + addlist(&u->orders, long_order); + } + } + } + pathfinder_cleanup(); +} + +static double chaosfactor(region * r) +{ + attrib *a = a_find(r->attribs, &at_chaoscount); + if (!a) + return 0; + return ((double)a->data.i / 1000.0); +} + +static int nrand(int start, int sub) +{ + int res = 0; + + do { + if (rng_int() % 100 < start) + res++; + start -= sub; + } + while (start > 0); + + return res; +} + +/** Drachen und Seeschlangen können entstehen */ +void spawn_dragons(void) +{ + region *r; + faction *monsters = get_monsters(); + + for (r = regions; r; r = r->next) { + unit *u; + + if (fval(r->terrain, SEA_REGION) && rng_int() % 10000 < 1) { + u = createunit(r, monsters, 1, new_race[RC_SEASERPENT]); + fset(u, UFL_ISNEW | UFL_MOVED); + equip_unit(u, get_equipment("monster_seaserpent")); + } + + if ((r->terrain == newterrain(T_GLACIER) + || r->terrain == newterrain(T_SWAMP) + || r->terrain == newterrain(T_DESERT)) + && rng_int() % 10000 < (5 + 100 * chaosfactor(r))) { + if (chance(0.80)) { + u = createunit(r, monsters, nrand(60, 20) + 1, new_race[RC_FIREDRAGON]); + } else { + u = createunit(r, monsters, nrand(30, 20) + 1, new_race[RC_DRAGON]); + } + fset(u, UFL_ISNEW | UFL_MOVED); + equip_unit(u, get_equipment("monster_dragon")); + + if (verbosity >= 2) { + log_printf(stdout, "%d %s in %s.\n", u->number, + LOC(default_locale, + rc_name(u_race(u), u->number != 1)), regionname(r, NULL)); + } + + name_unit(u); + + /* add message to the region */ + ADDMSG(&r->msgs, + msg_message("sighting", "region race number", r, u_race(u), u->number)); + } + } +} + +/** Untote können entstehen */ +void spawn_undead(void) +{ + region *r; + faction *monsters = get_monsters(); + + for (r = regions; r; r = r->next) { + int unburied = deathcount(r); + static const curse_type *ctype = NULL; + + if (!ctype) + ctype = ct_find("holyground"); + if (ctype && curse_active(get_curse(r->attribs, ctype))) + continue; + + /* Chance 0.1% * chaosfactor */ + if (r->land && unburied > r->land->peasants / 20 + && rng_int() % 10000 < (100 + 100 * chaosfactor(r))) { + unit *u; + /* es ist sinnfrei, wenn irgendwo im Wald 3er-Einheiten Untote entstehen. + * Lieber sammeln lassen, bis sie mindestens 5% der Bevölkerung sind, und + * dann erst auferstehen. */ + int undead = unburied / (rng_int() % 2 + 1); + const race *rc = NULL; + int i; + if (r->age < 100) + undead = undead * r->age / 100; /* newbie-regionen kriegen weniger ab */ + + if (!undead || r->age < 20) + continue; + + switch (rng_int() % 3) { + case 0: + rc = new_race[RC_SKELETON]; + break; + case 1: + rc = new_race[RC_ZOMBIE]; + break; + default: + rc = new_race[RC_GHOUL]; + break; + } + + u = createunit(r, monsters, undead, rc); + fset(u, UFL_ISNEW | UFL_MOVED); + if ((rc == new_race[RC_SKELETON] || rc == new_race[RC_ZOMBIE]) + && rng_int() % 10 < 4) { + equip_unit(u, get_equipment("rising_undead")); + } + + for (i = 0; i < MAXSKILLS; i++) { + if (rc->bonus[i] >= 1) { + set_level(u, (skill_t) i, 1); + } + } + u->hp = unit_max_hp(u) * u->number; + + deathcounts(r, -undead); + name_unit(u); + + if (verbosity >= 2) { + log_printf(stdout, "%d %s in %s.\n", u->number, + LOC(default_locale, + rc_name(u_race(u), u->number != 1)), regionname(r, NULL)); + } + + { + message *msg = msg_message("undeadrise", "region", r); + add_message(&r->msgs, msg); + 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)) + continue; + fset(u->faction, FFL_SELECT); + add_message(&u->faction->msgs, msg); + } + msg_release(msg); + } + } else { + int i = deathcount(r); + if (i) { + /* Gräber verwittern, 3% der Untoten finden die ewige Ruhe */ + deathcounts(r, (int)(-i * 0.03)); + } + } + } +} diff --git a/src/races/Jamfile b/src/races/Jamfile new file mode 100644 index 000000000..6272f8708 --- /dev/null +++ b/src/races/Jamfile @@ -0,0 +1,20 @@ +SubDir TOP src races ; + +TargetDirectory ; +SubDirHdrs $(SUBDIR)/../gamecode ; +SubDirHdrs $(SUBDIR)/../kernel ; +SubDirHdrs $(SUBDIR)/../util ; +SubDirHdrs $(SUBDIR)/.. ; +SubDirHdrs $(SUBDIR)/../.. ; +SubDirHdrs $(XMLHDRS) ; + +SOURCES = + dragons.c + illusion.c + races.c + zombies.c + ; + +if $(BUILDTYPE) = REGULAR { +Library races : $(SOURCES) ; +} diff --git a/src/races/dragons.c b/src/races/dragons.c new file mode 100644 index 000000000..cee2709f7 --- /dev/null +++ b/src/races/dragons.c @@ -0,0 +1,49 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include + +/* kernel includes */ +#include +#include + +/* util includes */ +#include + +#define age_chance(a,b,p) (MAX(0,a-b)*p) + +#define DRAGONAGE 27 +#define WYRMAGE 68 + +void age_firedragon(unit * u) +{ + if (u->number > 0 && rng_int() % 100 < age_chance(u->age, DRAGONAGE, 1)) { + double q = (double)u->hp / (double)(unit_max_hp(u) * u->number); + u_setrace(u, new_race[RC_DRAGON]); + u->irace = NULL; + scale_number(u, 1); + u->hp = (int)(unit_max_hp(u) * u->number * q); + } +} + +void age_dragon(unit * u) +{ + if (u->number > 0 && rng_int() % 100 < age_chance(u->age, WYRMAGE, 1)) { + double q = (double)u->hp / (double)(unit_max_hp(u) * u->number); + u_setrace(u, new_race[RC_WYRM]); + u->irace = NULL; + u->hp = (int)(unit_max_hp(u) * u->number * q); + } +} diff --git a/src/races/illusion.c b/src/races/illusion.c new file mode 100644 index 000000000..f0853965a --- /dev/null +++ b/src/races/illusion.c @@ -0,0 +1,39 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include + +/* kernel includes */ +#include +#include +#include + +/* libc includes */ +#include +#include + +#define ILLUSIONMAX 6 + +void age_illusion(unit * u) +{ + if (u->faction->race != new_race[RC_ILLUSION]) { + if (u->age == ILLUSIONMAX) { + ADDMSG(&u->faction->msgs, msg_message("warnillusiondissolve", "unit", u)); + } else if (u->age > ILLUSIONMAX) { + set_number(u, 0); + ADDMSG(&u->faction->msgs, msg_message("illusiondissolve", "unit", u)); + } + } +} diff --git a/src/races/races.c b/src/races/races.c new file mode 100644 index 000000000..487f489b0 --- /dev/null +++ b/src/races/races.c @@ -0,0 +1,121 @@ +/* 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 +#include "races.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +void age_firedragon(struct unit *u); +void age_dragon(struct unit *u); +void age_illusion(struct unit *u); +void age_undead(struct unit *u); +void age_skeleton(struct unit *u); +void age_zombie(struct unit *u); +void age_ghoul(struct unit *u); + +static void oldfamiliars(unit * u) +{ + char fname[64]; + /* these familiars have no special skills. + */ + snprintf(fname, sizeof(fname), "%s_familiar", u_race(u)->_name[0]); + create_mage(u, M_GRAY); + equip_unit(u, get_equipment(fname)); +} + +static 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]; +} + +static void equip_newunits(const struct equipment *eq, struct unit *u) +{ + struct region *r = u->region; + + switch (old_race(u_race(u))) { + case RC_ELF: + set_show_item(u->faction, I_FEENSTIEFEL); + break; + case RC_GOBLIN: + set_show_item(u->faction, I_RING_OF_INVISIBILITY); + set_number(u, 10); + break; + case RC_HUMAN: + if (u->building == NULL) { + const building_type *btype = bt_find("castle"); + if (btype != NULL) { + building *b = new_building(btype, r, u->faction->locale); + b->size = 10; + u_set_building(u, b); + building_set_owner(u); + } + } + break; + case RC_CAT: + set_show_item(u->faction, I_RING_OF_INVISIBILITY); + break; + case RC_AQUARIAN: + { + ship *sh = new_ship(st_find("boat"), r, u->faction->locale); + sh->size = sh->type->construction->maxsize; + u_set_ship(u, sh); + } + break; + case RC_CENTAUR: + rsethorses(r, 250 + rng_int() % 51 + rng_int() % 51); + break; + default: + break; + } +} + +/* Die Funktionen werden über den hier registrierten Namen in races.xml + * in die jeweilige Rassendefiniton eingebunden */ +void register_races(void) +{ + /* function initfamiliar */ + register_function((pf_generic) oldfamiliars, "oldfamiliars"); + + register_function((pf_generic) allowed_dragon, "movedragon"); + + register_function((pf_generic) allowed_swim, "moveswimming"); + register_function((pf_generic) allowed_fly, "moveflying"); + register_function((pf_generic) allowed_walk, "movewalking"); + + /* function age for race->age() */ + register_function((pf_generic) age_undead, "ageundead"); + register_function((pf_generic) age_illusion, "ageillusion"); + register_function((pf_generic) age_skeleton, "ageskeleton"); + register_function((pf_generic) age_zombie, "agezombie"); + register_function((pf_generic) age_ghoul, "ageghoul"); + register_function((pf_generic) age_dragon, "agedragon"); + register_function((pf_generic) age_firedragon, "agefiredragon"); + + /* function itemdrop + * to generate battle spoils + * race->itemdrop() */ + register_function((pf_generic) equip_newunits, "equip_newunits"); +} diff --git a/src/races/races.h b/src/races/races.h new file mode 100644 index 000000000..94e5633bc --- /dev/null +++ b/src/races/races.h @@ -0,0 +1,23 @@ +/* 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_RACES +#define H_RACES + +#ifdef __cplusplus +extern "C" { +#endif + + extern void register_races(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/races/zombies.c b/src/races/zombies.c new file mode 100644 index 000000000..1aa3936be --- /dev/null +++ b/src/races/zombies.c @@ -0,0 +1,94 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include + +/* kernel includes */ +#include +#include +#include + +/* util iclude */ +#include + +/* libc includes */ +#include + +#define UNDEAD_MIN 90 /* mind. zahl vor weg gehen */ +#define UNDEAD_BREAKUP 25 /* chance dafuer */ +#define UNDEAD_BREAKUP_FRACTION (25+rng_int()%70) /* anteil der weg geht */ + +#define age_chance(a,b,p) (MAX(0,a-b)*p) + +void age_undead(unit * u) +{ + region *r = u->region; + int n = 0; + + /* untote, die einer partei angehoeren, koennen sich + * absplitten, anstatt sich zu vermehren. monster + * untote vermehren sich nur noch */ + + if (u->number > UNDEAD_MIN && !is_monsters(u->faction) + && rng_int() % 100 < UNDEAD_BREAKUP) { + int m; + unit *u2; + + n = 0; + for (m = u->number; m; m--) { + if (rng_int() % 100 < UNDEAD_BREAKUP_FRACTION) + ++n; + } + u2 = create_unit(r, get_monsters(), 0, new_race[RC_UNDEAD], 0, NULL, u); + make_undead_unit(u2); + transfermen(u, u2, u->number - n); + } +} + +void age_skeleton(unit * u) +{ + if (is_monsters(u->faction) && rng_int() % 100 < age_chance(u->age, 27, 1)) { + int n = MAX(1, u->number / 2); + double q = (double)u->hp / (double)(unit_max_hp(u) * u->number); + u_setrace(u, new_race[RC_SKELETON_LORD]); + u->irace = NULL; + scale_number(u, n); + u->hp = (int)(unit_max_hp(u) * u->number * q); + } +} + +void age_zombie(unit * u) +{ + if (is_monsters(u->faction) && rng_int() % 100 < age_chance(u->age, 27, 1)) { + int n = MAX(1, u->number / 2); + double q = (double)u->hp / (double)(unit_max_hp(u) * u->number); + u_setrace(u, new_race[RC_ZOMBIE_LORD]); + u->irace = NULL; + scale_number(u, n); + u->hp = (int)(unit_max_hp(u) * u->number * q); + } +} + +void age_ghoul(unit * u) +{ + if (is_monsters(u->faction) && rng_int() % 100 < age_chance(u->age, 27, 1)) { + int n = MAX(1, u->number / 2); + double q = (double)u->hp / (double)(unit_max_hp(u) * u->number); + u_setrace(u, new_race[RC_GHOUL_LORD]); + u->irace = NULL; + scale_number(u, n); + u->hp = (int)(unit_max_hp(u) * u->number * q); + } +} diff --git a/src/server.c b/src/server.c index a5529f07f..e69de29bb 100644 --- a/src/server.c +++ b/src/server.c @@ -1,4 +0,0 @@ -#include -#include -#include "stdafx.h" -#include "main.c" diff --git a/src/spells/alp.c b/src/spells/alp.c new file mode 100644 index 000000000..1c3da3dcf --- /dev/null +++ b/src/spells/alp.c @@ -0,0 +1,200 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include +#include "alp.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +extern const char *directions[]; + +typedef struct alp_data { + unit *mage; + unit *target; +} alp_data; + +static void alp_init(attrib * a) +{ + a->data.v = calloc(sizeof(alp_data), 1); +} + +static void alp_done(attrib * a) +{ + free(a->data.v); +} + +static int alp_verify(attrib * a) +{ + alp_data *ad = (alp_data *) a->data.v; + if (ad->mage && ad->target) + return 1; + return 0; /* remove the attribute */ +} + +static void +alp_write(const attrib * a, const void *owner, struct storage *store) +{ + alp_data *ad = (alp_data *) a->data.v; + write_unit_reference(ad->mage, store); + write_unit_reference(ad->target, store); +} + +static int alp_read(attrib * a, void *owner, struct storage *store) +{ + alp_data *ad = (alp_data *) a->data.v; + int rm = read_reference(&ad->mage, store, read_unit_reference, resolve_unit); + int rt = + read_reference(&ad->target, store, read_unit_reference, resolve_unit); + if (rt == 0 && rm == 0 && (!ad->target || !ad->mage)) { + /* the target or mage disappeared. */ + return AT_READ_FAIL; + } + return AT_READ_OK; +} + +static attrib_type at_alp = { + "alp", + alp_init, + alp_done, + alp_verify, + alp_write, + alp_read, + ATF_UNIQUE +}; + +int sp_summon_alp(struct castorder *co) +{ + unit *alp, *opfer; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + const struct race *rc = new_race[RC_ALP]; + struct faction *f = get_monsters(); + struct message *msg; + + opfer = pa->param[0]->data.u; + + /* Der Alp gehört den Monstern, darum erhält der Magier auch keine + * Regionsberichte von ihm. Er erhält aber später eine Mitteilung, + * sobald der Alp sein Opfer erreicht hat. + */ + alp = create_unit(r, f, 1, rc, 0, NULL, NULL); + set_level(alp, SK_STEALTH, 7); + setstatus(alp, ST_FLEE); /* flieht */ + + { + attrib *a = a_add(&alp->attribs, a_new(&at_alp)); + alp_data *ad = (alp_data *) a->data.v; + ad->mage = mage; + ad->target = opfer; + } + + { + /* Wenn der Alp stirbt, den Magier nachrichtigen */ + add_trigger(&alp->attribs, "destroy", trigger_unitmessage(mage, + "trigger_alp_destroy", MSG_EVENT, ML_INFO)); + /* Wenn Opfer oder Magier nicht mehr existieren, dann stirbt der Alp */ + add_trigger(&mage->attribs, "destroy", trigger_killunit(alp)); + add_trigger(&opfer->attribs, "destroy", trigger_killunit(alp)); + } + msg = msg_message("summon_alp_effect", "mage alp target", mage, alp, opfer); + r_addmessage(r, mage->faction, msg); + msg_release(msg); + + return cast_level; +} + +void alp_findet_opfer(unit * alp, region * r) +{ + curse *c; + attrib *a = a_find(alp->attribs, &at_alp); + alp_data *ad = (alp_data *) a->data.v; + unit *mage = ad->mage; + unit *opfer = ad->target; + double effect; + message *msg; + + assert(opfer); + assert(mage); + + /* Magier und Opfer Bescheid geben */ + msg = msg_message("alp_success", "target", opfer); + add_message(&mage->faction->msgs, msg); + r_addmessage(opfer->region, opfer->faction, msg); + msg_release(msg); + + /* Relations werden in destroy_unit(alp) automatisch gelöscht. + * Die Aktionen, die beim Tod des Alps ausgelöst werden sollen, + * müssen jetzt aber deaktiviert werden, sonst werden sie gleich + * beim destroy_unit(alp) ausgelöst. + */ + a_removeall(&alp->attribs, &at_eventhandler); + + /* Alp umwandeln in Curse */ + effect = -2; + c = + create_curse(mage, &opfer->attribs, ct_find("worse"), 2, 2, effect, + opfer->number); + /* solange es noch keine spezielle alp-Antimagie gibt, reagiert der + * auch auf normale */ + set_number(alp, 0); + + /* wenn der Magier stirbt, wird der Curse wieder vom Opfer genommen */ + add_trigger(&mage->attribs, "destroy", trigger_removecurse(c, opfer)); +} + +void register_alp(void) +{ + at_register(&at_alp); +} + +unit *alp_target(unit * alp) +{ + alp_data *ad; + unit *target = NULL; + + attrib *a = a_find(alp->attribs, &at_alp); + + if (a) { + ad = (alp_data *) a->data.v; + target = ad->target; + } + return target; + +} diff --git a/src/spells/alp.h b/src/spells/alp.h new file mode 100644 index 000000000..c350e7c7d --- /dev/null +++ b/src/spells/alp.h @@ -0,0 +1,53 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#ifndef ALP_H +#define ALP_H +#ifdef __cplusplus +extern "C" { +#endif + + struct castorder; + struct region; + struct unit; +/* ------------------------------------------------------------- */ +/* Name: Alp + * Stufe: 15 + * Gebiet: Illaun + * Wirkung: + * Erschafft ein kleines Monster (den Alp). Dieser bewegt sich jede + * zweite Runde auf eine Zieleinheit zu. Sobald das Ziel erreicht ist, + * verwandelt es sich in einen Curse auf die Einheit, welches -2 auf + * alle Talente bewirkt. + * + * Fähigkeiten (factypes.c): + * Der Alp hat mittlere Tarnung (T7) und exzellente Verteidigung + * (+20, 99% Magieresistenz, siehe factypes.c) + * + * TODO: Der Alp-Curse sollte sich durch besondere Antimagie (Tybied) + * entfernen lassen. + * + * (UNITSPELL | SEARCHGLOBAL | TESTRESISTANCE) + */ + + extern int sp_summon_alp(struct castorder *co); + extern void register_alp(void); + + struct unit *alp_target(struct unit *alp); + void alp_findet_opfer(struct unit *alp, struct region *r); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/spells/buildingcurse.c b/src/spells/buildingcurse.c new file mode 100644 index 000000000..266cfcf45 --- /dev/null +++ b/src/spells/buildingcurse.c @@ -0,0 +1,98 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include +#include "buildingcurse.h" + +/* kernel includes */ +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +static message *cinfo_building(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + assert(typ == TYP_BUILDING); + + if (self != 0) { /* owner or inside */ + return msg_message(mkname("curseinfo", c->type->cname), "id", c->no); + } + return msg_message(mkname("curseinfo", "buildingunknown"), "id", c->no); +} + +/* CurseInfo mit Spezialabfragen */ + +/* C_MAGICWALLS*/ +static message *cinfo_magicrunes(const void *obj, objtype_t typ, const curse * c, + int self) +{ + message *msg = NULL; + if (typ == TYP_BUILDING) { + building *b; + b = (building *) obj; + if (self != 0) { + msg = + msg_message("curseinfo::magicrunes_building", "building id", b, c->no); + } + } else if (typ == TYP_SHIP) { + ship *sh; + sh = (ship *) obj; + if (self != 0) { + msg = msg_message("curseinfo::magicrunes_ship", "ship id", sh, c->no); + } + } + + return msg; +} + +static struct curse_type ct_magicrunes = { "magicrunes", + CURSETYP_NORM, 0, M_SUMEFFECT, cinfo_magicrunes +}; + +/* Heimstein-Zauber */ +static struct curse_type ct_magicwalls = { "magicwalls", + CURSETYP_NORM, 0, NO_MERGE, cinfo_building +}; + +/* Feste Mauer - Präkampfzauber, wirkt nur 1 Runde */ +static struct curse_type ct_strongwall = { "strongwall", + CURSETYP_NORM, 0, NO_MERGE, NULL +}; + +/* Ewige Mauern-Zauber */ +static struct curse_type ct_nocostbuilding = { "nocostbuilding", + CURSETYP_NORM, CURSE_NOAGE | CURSE_ONLYONE, NO_MERGE, cinfo_building +}; + +void register_buildingcurse(void) +{ + ct_register(&ct_magicwalls); + ct_register(&ct_strongwall); + ct_register(&ct_magicrunes); + ct_register(&ct_nocostbuilding); +} diff --git a/src/spells/buildingcurse.h b/src/spells/buildingcurse.h new file mode 100644 index 000000000..a3b05dedc --- /dev/null +++ b/src/spells/buildingcurse.h @@ -0,0 +1,28 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#ifndef _BCURSE_H +#define _BCURSE_H +#ifdef __cplusplus +extern "C" { +#endif + + struct locale; + struct curse; + + extern void register_buildingcurse(void); + +#ifdef __cplusplus +} +#endif +#endif /* _BCURSE_H */ diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c new file mode 100644 index 000000000..8ec78f5df --- /dev/null +++ b/src/spells/combatspells.c @@ -0,0 +1,1783 @@ +/* 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 +#include "combatspells.h" + +/* 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 + +/* libc includes */ +#include +#include + +#define EFFECT_HEALING_SPELL 5 + +/* ------------------------------------------------------------------ */ +/* Kampfzauberfunktionen */ + +/* COMBAT */ + +static const char *spell_damage(int sp) +{ + switch (sp) { + case 0: + /* meist tödlich 20-65 HP */ + return "5d10+15"; + case 1: + /* sehr variabel 4-48 HP */ + return "4d12"; + case 2: + /* leicht verwundet 4-18 HP */ + return "2d8+2"; + case 3: + /* fast immer tödlich 30-50 HP */ + return "5d5+25"; + case 4: + /* verwundet 11-26 HP */ + return "3d6+8"; + case 5: + /* leichter Schaden */ + return "2d4"; + default: + /* schwer verwundet 14-34 HP */ + return "4d6+10"; + } +} + +static double get_force(double power, int formel) +{ + switch (formel) { + case 0: + /* (4,8,12,16,20,24,28,32,36,40,44,..) */ + return (power * 4); + case 1: + /* (15,30,45,60,75,90,105,120,135,150,165,..) */ + return (power * 15); + case 2: + /* (40,80,120,160,200,240,280,320,360,400,440,..) */ + return (power * 40); + case 3: + /* (2,8,18,32,50,72,98,128,162,200,242,..) */ + return (power * power * 2); + case 4: + /* (4,16,36,64,100,144,196,256,324,400,484,..) */ + return (power * power * 4); + case 5: + /* (10,40,90,160,250,360,490,640,810,1000,1210,1440,..) */ + return (power * power * 10); + case 6: + /* (6,24,54,96,150,216,294,384,486,600,726,864) */ + return (power * power * 6); + default: + return power; + } +} + +/* Generischer Kampfzauber */ +int sp_kampfzauber(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + troop at, dt; + message *m; + /* Immer aus der ersten Reihe nehmen */ + int force, enemies; + int killed = 0; + const char *damage; + + if (power <= 0) + return 0; + at.fighter = fi; + at.index = 0; + + switch (sp->id) { + /* lovar halbiert im Schnitt! */ + case SPL_FIREBALL: + damage = spell_damage(0); + force = lovar(get_force(power, 0)); + break; + case SPL_HAGEL: + damage = spell_damage(2); + force = lovar(get_force(power, 4)); + break; + case SPL_METEORRAIN: + damage = spell_damage(1); + force = lovar(get_force(power, 1)); + break; + default: + damage = spell_damage(10); + force = lovar(get_force(power, 10)); + } + + enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + if (enemies == 0) { + message *m = + msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + while (force > 0 && killed < enemies) { + dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + assert(dt.fighter); + --force; + killed += terminate(dt, at, AT_COMBATSPELL, damage, false); + } + + m = msg_message("battle::combatspell", "mage spell dead", + fi->unit, sp, killed); + message_all(b, m); + msg_release(m); + + return level; +} + +/* Versteinern */ +int sp_petrify(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + /* Wirkt auf erste und zweite Reihe */ + int force, enemies; + int stoned = 0; + message *m; + + force = lovar(get_force(power, 0)); + + enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + if (!enemies) { + message *m = + msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + while (force && stoned < enemies) { + troop dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + unit *du = dt.fighter->unit; + if (!is_magic_resistant(mage, du, 0)) { + /* person ans ende hinter die lebenden schieben */ + remove_troop(dt); + ++stoned; + } + --force; + } + + m = + msg_message("cast_petrify_effect", "mage spell amount", fi->unit, sp, + stoned); + message_all(b, m); + msg_release(m); + return level; +} + +/* Benommenheit: eine Runde kein Angriff */ +int sp_stun(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + message *m; + /* Aus beiden Reihen nehmen */ + int force = 0, enemies; + int stunned; + + if (power <= 0) + return 0; + + switch (sp->id) { + case SPL_SHOCKWAVE: + force = lovar(get_force(power, 1)); + break; + default: + assert(0); + } + + enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + if (!enemies) { + message *m = + msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + stunned = 0; + while (force && stunned < enemies) { + troop dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + fighter *df = dt.fighter; + unit *du = df->unit; + + --force; + if (!is_magic_resistant(mage, du, 0)) { + df->person[dt.index].flags |= FL_STUNNED; + ++stunned; + } + } + + m = msg_message("cast_stun_effect", "mage spell amount", fi->unit, sp, stunned); + message_all(b, m); + msg_release(m); + return level; +} + +/** randomly shuffle an array + * for correctness, see Donald E. Knuth, The Art of Computer Programming + */ +static void scramble_fighters(quicklist * ql) +{ + int qi, qlen = ql_length(ql); + + for (qi = 0; qi != qlen; ++qi) { + int qj = qi + (rng_int() % (qlen - qi)); + void *a = ql_get(ql, qi); + void *b = ql_replace(ql, qj, a); + ql_replace(ql, qi, b); + } +} + +/* Rosthauch */ +int sp_combatrosthauch(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + battle *b = fi->side->battle; + quicklist *ql, *fgs; + int force = lovar(power * 15); + int qi, k = 0; + + if (!count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, + SELECT_ADVANCE | SELECT_FIND)) { + message *msg = msg_message("rust_effect_0", "mage", fi->unit); + message_all(b, msg); + msg_release(msg); + return 0; + } + + fgs = fighters(b, fi->side, FIGHT_ROW, BEHIND_ROW - 1, FS_ENEMY); + scramble_fighters(fgs); + + for (qi = 0, ql = fgs; ql; ql_advance(&ql, &qi, 1)) { + fighter *df = (fighter *) ql_get(ql, qi); + + if (df->alive == 0) + continue; + if (force <= 0) + break; + + /* da n MIN(force, x), sollte force maximal auf 0 sinken */ + assert(force >= 0); + + if (df->weapons) { + int w; + for (w = 0; df->weapons[w].type != NULL; ++w) { + weapon *wp = df->weapons; + int n = MIN(force, wp->used); + if (n) { + requirement *mat = wp->type->itype->construction->materials; + bool iron = false; + while (mat && mat->number > 0) { + if (mat->rtype == oldresourcetype[R_IRON]) { + iron = true; + break; + } + mat++; + } + if (iron) { + int p; + force -= n; + wp->used -= n; + k += n; + i_change(&df->unit->items, wp->type->itype, -n); + for (p = 0; n && p != df->unit->number; ++p) { + if (df->person[p].missile == wp) { + df->person[p].missile = NULL; + --n; + } + } + for (p = 0; n && p != df->unit->number; ++p) { + if (df->person[p].melee == wp) { + df->person[p].melee = NULL; + --n; + } + } + } + } + } + } + } + ql_free(fgs); + + if (k == 0) { + /* keine Waffen mehr da, die zerstört werden könnten */ + message *msg = msg_message("rust_effect_1", "mage", fi->unit); + message_all(b, msg); + msg_release(msg); + fi->magic = 0; /* kämpft nichtmagisch weiter */ + level = 0; + } else { + message *msg = msg_message("rust_effect_2", "mage", fi->unit); + message_all(b, msg); + msg_release(msg); + } + return level; +} + +int sp_sleep(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + unit *du; + troop dt; + int force, enemies; + int k = 0; + message *m; + /* Immer aus der ersten Reihe nehmen */ + + force = lovar(co->force * 25); + enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + + if (!enemies) { + m = msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + while (force && enemies) { + dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + assert(dt.fighter); + du = dt.fighter->unit; + if (!is_magic_resistant(mage, du, 0)) { + dt.fighter->person[dt.index].flags |= FL_SLEEPING; + ++k; + --enemies; + } + --force; + } + + m = msg_message("cast_sleep_effect", "mage spell amount", fi->unit, sp, k); + message_all(b, m); + msg_release(m); + return level; +} + +int sp_speed(struct castorder * co) +{ + fighter * fi = co->magician.fig; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + int force; + int allies; + int targets = 0; + message *m; + + force = lovar(power * power * 5); + + allies = + count_allies(fi->side, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE, ALLY_ANY); + /* maximal 2*allies Versuche ein Opfer zu finden, ansonsten bestände + * die Gefahr eine Endlosschleife*/ + allies *= 2; + + while (force && allies) { + troop dt = select_ally(fi, FIGHT_ROW, BEHIND_ROW, ALLY_ANY); + fighter *df = dt.fighter; + --allies; + + if (df) { + if (df->person[dt.index].speed == 1) { + df->person[dt.index].speed++; + targets++; + --force; + } + } + } + + m = + msg_message("cast_speed_effect", "mage spell amount", fi->unit, sp, + targets); + message_all(b, m); + msg_release(m); + return 1; +} + +static skill_t random_skill(unit * u, bool weighted) +{ + int n = 0; + skill *sv; + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level > 0) { + if (weighted) + n += sv->level; + else + ++n; + } + } + + if (n == 0) + return NOSKILL; + + n = rng_int() % n; + + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->level > 0) { + if (weighted) { + if (n < (int)sv->level) + return sv->id; + n -= sv->level; + } else { + if (n == 0) + return sv->id; + --n; + } + } + } + + assert(0 == 1); /* Hier sollte er niemals ankommen. */ + return NOSKILL; +} + +/** The mind blast spell for regular folks. +* This spell temporarily reduces the skill of the victims +*/ +int sp_mindblast_temp(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + int k = 0, reset = 0, maxloss = (level + 2) / 3; + message *m; + int force = lovar(power * 25); + int enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + + if (!enemies) { + m = msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + while (force > 0 && enemies > 0) { + unit *du; + troop dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + + assert(dt.fighter); + du = dt.fighter->unit; + if (fval(du, UFL_MARK)) { + /* not this one again */ + continue; + } + + if (humanoidrace(u_race(du)) && force >= du->number) { + if (!is_magic_resistant(mage, du, 0)) { + skill_t sk = random_skill(du, true); + if (sk != NOSKILL) { + int n = 1 + rng_int() % maxloss; + attrib *a = make_skillmod(sk, SMF_ALWAYS, NULL, 0.0, n); + /* neat: you can add a whole lot of these to a unit, they stack */ + a_add(&du->attribs, a); + } + k += du->number; + } + force -= du->number; + } + fset(du, UFL_MARK); + reset = 1; + enemies -= du->number; + } + + if (reset) { + unit *u; + for (u = b->region->units; u; u = u->next) { + freset(u, UFL_MARK); + } + } + + m = msg_message("sp_mindblast_temp_effect", "mage spell amount", mage, sp, k); + message_all(b, m); + msg_release(m); + return level; +} + +/** A mind blast spell for monsters. + * This spell PERMANENTLY reduces the skill of the victims or kills them + * when they have no skills left. Not currently in use. + */ +int sp_mindblast(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + int killed = 0, k = 0, reset = 0; + message *m; + int force = lovar(power * 25); + int enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + + if (!enemies) { + m = msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + while (enemies > 0 && force > 0) { + unit *du; + troop dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + + assert(dt.fighter); + du = dt.fighter->unit; + if (fval(du, UFL_MARK)) { + /* not this one again */ + continue; + } + + if (humanoidrace(u_race(du)) && force >= du->number) { + if (!is_magic_resistant(mage, du, 0)) { + skill_t sk = random_skill(du, false); + if (sk != NOSKILL) { + skill *sv = get_skill(du, sk); + int n = 1 + rng_int() % 3; + + reduce_skill(du, sv, n); + k += du->number; + } else { + /* unit has no skill. kill it. */ + kill_troop(dt); + ++killed; + } + } + force -= du->number; + } else { + /* only works against humanoids, don't try others. but do remove them + * from 'force' once or we may never terminate. */ + fset(du, UFL_MARK); + reset = 1; + } + enemies -= du->number; + } + + if (reset) { + unit *u; + for (u = b->region->units; u; u = u->next) { + freset(u, UFL_MARK); + } + } + + m = + msg_message("sp_mindblast_effect", "mage spell amount dead", mage, sp, k, + killed); + message_all(b, m); + msg_release(m); + return level; +} + +int sp_dragonodem(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + troop dt; + troop at; + int force, enemies; + int killed = 0; + const char *damage; + + /* 11-26 HP */ + damage = spell_damage(4); + /* Jungdrache 3->54, Drache 6->216, Wyrm 12->864 Treffer */ + force = lovar(get_force(power, 6)); + + enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + + if (!enemies) { + struct message *m = + msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } else { + struct message *m; + + at.fighter = fi; + at.index = 0; + + while (force && killed < enemies) { + dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + assert(dt.fighter); + --force; + killed += terminate(dt, at, AT_COMBATSPELL, damage, false); + } + + m = + msg_message("battle::combatspell", "mage spell dead", fi->unit, sp, + killed); + message_all(b, m); + msg_release(m); + } + return level; +} + +/* Feuersturm: Betrifft sehr viele Gegner (in der Regel alle), + * macht nur vergleichsweise geringen Schaden */ +int sp_immolation(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + const spell * sp = co->sp; + battle *b = fi->side->battle; + troop at; + int force, qi, killed = 0; + const char *damage; + quicklist *fgs, *ql; + message *m; + + /* 2d4 HP */ + damage = spell_damage(5); + /* Betrifft alle Gegner */ + force = 99999; + + if (!count_enemies(b, fi, FIGHT_ROW, AVOID_ROW, SELECT_ADVANCE | SELECT_FIND)) { + message *m = + msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + at.fighter = fi; + at.index = 0; + + fgs = fighters(b, fi->side, FIGHT_ROW, AVOID_ROW, FS_ENEMY); + for (qi = 0, ql = fgs; ql; ql_advance(&ql, &qi, 1)) { + fighter *df = (fighter *) ql_get(ql, qi); + int n = df->alive - df->removed; + troop dt; + + dt.fighter = df; + while (n != 0) { + dt.index = --n; + killed += terminate(dt, at, AT_COMBATSPELL, damage, false); + if (--force == 0) + break; + } + if (force == 0) + break; + } + ql_free(fgs); + + m = + msg_message("battle::combatspell", "mage spell killed", fi->unit, sp, + killed); + message_all(b, m); + msg_release(m); + return level; +} + +int sp_drainodem(fighter * fi, int level, double power, spell * sp) +{ + battle *b = fi->side->battle; + troop dt; + troop at; + int force, enemies; + int drained = 0; + int killed = 0; + const char *damage; + message *m; + + /* 11-26 HP */ + damage = spell_damage(4); + /* Jungdrache 3->54, Drache 6->216, Wyrm 12->864 Treffer */ + force = lovar(get_force(power, 6)); + + enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + + if (!enemies) { + m = msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + at.fighter = fi; + at.index = 0; + + while (force && drained < enemies) { + dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + assert(dt.fighter); + if (hits(at, dt, NULL)) { + drain_exp(dt.fighter->unit, 90); + ++drained; + killed += terminate(dt, at, AT_COMBATSPELL, damage, false); + } + --force; + } + + m = + msg_message("cast_drainlife_effect", "mage spell amount", fi->unit, sp, + drained); + message_all(b, m); + msg_release(m); + return level; +} + +/* ------------------------------------------------------------- */ +/* PRECOMBAT */ + +int sp_shadowcall(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + battle *b = fi->side->battle; + region *r = b->region; + unit *mage = fi->unit; + attrib *a; + int force = (int)(get_force(power, 3) / 2); + unit *u; + const char *races[3] = { "shadowbat", "nightmare", "vampunicorn" }; + const race *rc = rc_find(races[rng_int() % 3]); + message *msg; + + u = create_unit(r, mage->faction, force, rc, 0, NULL, mage); + setstatus(u, ST_FIGHT); + + set_level(u, SK_WEAPONLESS, (int)(power / 2)); + set_level(u, SK_STAMINA, (int)(power / 2)); + u->hp = u->number * unit_max_hp(u); + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 0; + a->data.ca[1] = 100; + a_add(&u->attribs, a); + + make_fighter(b, u, fi->side, is_attacker(fi)); + msg = + msg_message("sp_shadowcall_effect", "mage amount race", mage, u->number, + u_race(u)); + message_all(b, msg); + msg_release(msg); + return level; +} + +int sp_wolfhowl(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + battle *b = fi->side->battle; + region *r = b->region; + unit *mage = fi->unit; + attrib *a; + message *msg; + int force = (int)(get_force(power, 3) / 2); + const race * rc = new_race[RC_WOLF]; + if (force>0) { + unit *u = + create_unit(r, mage->faction, force, rc, 0, NULL, mage); + leave(u, true); + setstatus(u, ST_FIGHT); + + set_level(u, SK_WEAPONLESS, (int)(power / 3)); + set_level(u, SK_STAMINA, (int)(power / 3)); + u->hp = u->number * unit_max_hp(u); + + if (fval(mage, UFL_ANON_FACTION)) { + fset(u, UFL_ANON_FACTION); + } + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 0; + a->data.ca[1] = 100; + a_add(&u->attribs, a); + + make_fighter(b, u, fi->side, is_attacker(fi)); + } + msg = + msg_message("sp_wolfhowl_effect", "mage amount race", mage, force, rc); + message_all(b, msg); + msg_release(msg); + + return level; +} + +int sp_shadowknights(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + unit *u; + battle *b = fi->side->battle; + region *r = b->region; + unit *mage = fi->unit; + attrib *a; + int force = MAX(1, (int)get_force(power, 3)); + message *msg; + + u = + create_unit(r, mage->faction, force, new_race[RC_SHADOWKNIGHT], 0, NULL, + mage); + setstatus(u, ST_FIGHT); + + u->hp = u->number * unit_max_hp(u); + + if (fval(mage, UFL_ANON_FACTION)) { + fset(u, UFL_ANON_FACTION); + } + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 0; + a->data.ca[1] = 100; + a_add(&u->attribs, a); + + make_fighter(b, u, fi->side, is_attacker(fi)); + + msg = msg_message("sp_shadowknights_effect", "mage", mage); + message_all(b, msg); + msg_release(msg); + + return level; +} + +int sp_strong_wall(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + battle *b = fi->side->battle; + unit *mage = fi->unit; + building *burg; + double effect; + static bool init = false; + message *msg; + static const curse_type *strongwall_ct; + if (!init) { + init = true; + strongwall_ct = ct_find("strongwall"); + } + + if (!mage->building) { + return 0; + } + burg = mage->building; + + effect = power * 0.25; + create_curse(mage, &burg->attribs, strongwall_ct, power, 1, effect, 0); + + msg = + msg_message("sp_strongwalls_effect", "mage building", mage, mage->building); + message_all(b, msg); + msg_release(msg); + + return level; +} + +/** Spells: chaosrow / song of confusion. + * German Title: 'Gesang der Verwirrung' + */ +int sp_chaosrow(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + quicklist *fgs, *ql; + message *m; + const char *mtype; + int qi, k = 0; + + if (!count_enemies(b, fi, FIGHT_ROW, NUMROWS, SELECT_ADVANCE | SELECT_FIND)) { + m = msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + if (sp->id == SPL_CHAOSROW) + power *= 40; + else + power = get_force(power, 5); + + fgs = fighters(b, fi->side, FIGHT_ROW, NUMROWS, FS_ENEMY); + scramble_fighters(fgs); + + for (qi = 0, ql = fgs; ql; ql_advance(&ql, &qi, 1)) { + fighter *df = (fighter *) ql_get(ql, qi); + int n = df->unit->number; + + if (df->alive == 0) + continue; + if (power <= 0.0) + break; + /* force sollte wegen des MAX(0,x) nicht unter 0 fallen können */ + + if (is_magic_resistant(mage, df->unit, 0)) + continue; + + if (chance(power / n)) { + int row = statusrow(df->status); + df->side->size[row] -= df->alive; + if (u_race(df->unit)->battle_flags & BF_NOBLOCK) { + df->side->nonblockers[row] -= df->alive; + } + row = FIRST_ROW + (rng_int() % (LAST_ROW - FIRST_ROW)); + switch (row) { + case FIGHT_ROW: + df->status = ST_FIGHT; + break; + case BEHIND_ROW: + df->status = ST_CHICKEN; + break; + case AVOID_ROW: + df->status = ST_AVOID; + break; + case FLEE_ROW: + df->status = ST_FLEE; + break; + default: + assert(!"unknown combatrow"); + } + assert(statusrow(df->status) == row); + df->side->size[row] += df->alive; + if (u_race(df->unit)->battle_flags & BF_NOBLOCK) { + df->side->nonblockers[row] += df->alive; + } + k += df->alive; + } + power = MAX(0, power - n); + } + ql_free(fgs); + + if (sp->id == SPL_CHAOSROW) { + mtype = (k > 0) ? "sp_chaosrow_effect_1" : "sp_chaosrow_effect_0"; + } else { + mtype = (k > 0) ? "sp_confusion_effect_1" : "sp_confusion_effect_0"; + } + m = msg_message(mtype, "mage", mage); + message_all(b, m); + msg_release(m); + return level; +} + +/* Gesang der Furcht (Kampfzauber) */ +/* Panik (Präkampfzauber) */ + +int sp_flee(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + quicklist *fgs, *ql; + int force, n, qi; + int panik = 0; + message *msg; + + switch (sp->id) { + case SPL_FLEE: + force = (int)get_force(power, 4); + break; + case SPL_SONG_OF_FEAR: + force = (int)get_force(power, 3); + break; + case SPL_AURA_OF_FEAR: + force = (int)get_force(power, 5); + break; + default: + force = (int)get_force(power, 10); + } + + if (!count_enemies(b, fi, FIGHT_ROW, AVOID_ROW, SELECT_ADVANCE | SELECT_FIND)) { + msg = msg_message("sp_flee_effect_0", "mage spell", mage, sp); + message_all(b, msg); + msg_release(msg); + return 0; + } + + fgs = fighters(b, fi->side, FIGHT_ROW, AVOID_ROW, FS_ENEMY); + scramble_fighters(fgs); + + for (qi = 0, ql = fgs; ql; ql_advance(&ql, &qi, 1)) { + fighter *df = (fighter *) ql_get(ql, qi); + + for (n = 0; n != df->alive; ++n) { + if (force < 0) + break; + + if (df->person[n].flags & FL_PANICED) { /* bei SPL_SONG_OF_FEAR möglich */ + df->person[n].attack -= 1; + --force; + ++panik; + } else if (!(df->person[n].flags & FL_COURAGE) + || !fval(u_race(df->unit), RCF_UNDEAD)) { + if (!is_magic_resistant(mage, df->unit, 0)) { + df->person[n].flags |= FL_PANICED; + ++panik; + } + --force; + } + } + } + ql_free(fgs); + + msg = msg_message("sp_flee_effect_1", "mage spell amount", mage, sp, panik); + message_all(b, msg); + msg_release(msg); + + return level; +} + +/* Heldenmut */ +int sp_hero(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + int df_bonus = 0; + int force = 0; + int allies; + int targets = 0; + message *m; + + switch (sp->id) { + case SPL_HERO: + df_bonus = (int)(power / 5); + force = MAX(1, lovar(get_force(power, 4))); + break; + + default: + df_bonus = 1; + force = MAX(1, (int)power); + } + + allies = + count_allies(fi->side, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE, ALLY_ANY); + /* maximal 2*allies Versuche ein Opfer zu finden, ansonsten bestände + * die Gefahr eine Endlosschleife*/ + allies *= 2; + + while (force && allies) { + troop dt = select_ally(fi, FIGHT_ROW, BEHIND_ROW, ALLY_ANY); + fighter *df = dt.fighter; + --allies; + + if (df) { + if (!(df->person[dt.index].flags & FL_COURAGE)) { + df->person[dt.index].defence += df_bonus; + df->person[dt.index].flags = df->person[dt.index].flags | FL_COURAGE; + targets++; + --force; + } + } + } + + m = + msg_message("cast_hero_effect", "mage spell amount", fi->unit, sp, targets); + message_all(b, m); + msg_release(m); + + return level; +} + +int sp_berserk(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + int at_bonus = 0; + int df_malus = 0; + int force = 0; + int allies = 0; + int targets = 0; + message *m; + + switch (sp->id) { + case SPL_BERSERK: + case SPL_BLOODTHIRST: + at_bonus = MAX(1, level / 3); + df_malus = 2; + force = (int)get_force(power, 2); + break; + + default: + at_bonus = 1; + df_malus = 0; + force = (int)power; + } + + allies = + count_allies(fi->side, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE, ALLY_ANY); + /* maximal 2*allies Versuche ein Opfer zu finden, ansonsten bestände + * die Gefahr eine Endlosschleife*/ + allies *= 2; + + while (force && allies) { + troop dt = select_ally(fi, FIGHT_ROW, BEHIND_ROW - 1, ALLY_ANY); + fighter *df = dt.fighter; + --allies; + + if (df) { + if (!(df->person[dt.index].flags & FL_COURAGE)) { + df->person[dt.index].attack += at_bonus; + df->person[dt.index].defence -= df_malus; + df->person[dt.index].flags = df->person[dt.index].flags | FL_COURAGE; + targets++; + --force; + } + } + } + + m = + msg_message("cast_berserk_effect", "mage spell amount", fi->unit, sp, + targets); + message_all(b, m); + msg_release(m); + return level; +} + +int sp_frighten(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + int at_malus = 0; + int df_malus = 0; + int force = 0; + int enemies = 0; + int targets = 0; + message *m; + + at_malus = MAX(1, level - 4); + df_malus = 2; + force = (int)get_force(power, 2); + + enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + if (!enemies) { + message *m = + msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + while (force && enemies) { + troop dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + fighter *df = dt.fighter; + --enemies; + + if (!df) + break; + + assert(!helping(fi->side, df->side)); + + if (df->person[dt.index].flags & FL_COURAGE) { + df->person[dt.index].flags &= ~(FL_COURAGE); + } + if (!is_magic_resistant(mage, df->unit, 0)) { + df->person[dt.index].attack -= at_malus; + df->person[dt.index].defence -= df_malus; + targets++; + } + --force; + } + + m = + msg_message("cast_frighten_effect", "mage spell amount", fi->unit, sp, + targets); + message_all(b, m); + msg_release(m); + return level; +} + +int sp_tiredsoldiers(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + int n = 0; + int force = (int)(power * power * 4); + message *m; + + if (!count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, + SELECT_ADVANCE | SELECT_FIND)) { + message *m = + msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + while (force) { + troop t = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + fighter *df = t.fighter; + + if (!df) + break; + + assert(!helping(fi->side, df->side)); + if (!(df->person[t.index].flags & FL_TIRED)) { + if (!is_magic_resistant(mage, df->unit, 0)) { + df->person[t.index].flags = df->person[t.index].flags | FL_TIRED; + df->person[t.index].defence -= 2; + ++n; + } + } + --force; + } + + m = msg_message("cast_tired_effect", "mage spell amount", fi->unit, sp, n); + message_all(b, m); + msg_release(m); + return level; +} + +int sp_windshield(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + int force, at_malus; + int enemies; + message *m; + + switch (sp->id) { + case SPL_WINDSHIELD: + force = (int)get_force(power, 4); + at_malus = level / 4; + break; + + default: + force = (int)power; + at_malus = 2; + } + enemies = count_enemies(b, fi, BEHIND_ROW, BEHIND_ROW, SELECT_ADVANCE); + if (!enemies) { + m = msg_message("battle::out_of_range", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return 0; + } + + while (force && enemies) { + troop dt = select_enemy(fi, BEHIND_ROW, BEHIND_ROW, SELECT_ADVANCE); + fighter *df = dt.fighter; + --enemies; + + if (!df) + break; + assert(!helping(fi->side, df->side)); + + if (df->person[dt.index].missile) { + /* this suxx... affects your melee weapon as well. */ + df->person[dt.index].attack -= at_malus; + --force; + } + } + + m = msg_message("cast_storm_effect", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return level; +} + +int sp_reeling_arrows(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + const spell * sp = co->sp; + battle *b = fi->side->battle; + message *m; + + b->reelarrow = true; + m = msg_message("cast_spell_effect", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + return level; +} + +/* Magier weicht dem Kampf aus. Wenn er sich bewegen kann, zieht er in + * eine Nachbarregion, wobei ein NACH berücksichtigt wird. Ansonsten + * bleibt er stehen und nimmt nicht weiter am Kampf teil. */ +int sp_denyattack(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + + const spell * sp = co->sp; + battle *b = fi->side->battle; + unit *mage = fi->unit; + region *r = b->region; + message *m; + + /* Fliehende Einheiten verlassen auf jeden Fall Gebäude und Schiffe. */ + if (!fval(r->terrain, SEA_REGION)) { + leave(mage, false); + } + /* und bewachen nicht */ + setguard(mage, GUARD_NONE); + /* irgendwie den langen befehl sperren */ + /* fset(fi, FIG_ATTACKED); */ + + /* wir tun so, als wäre die Person geflohen */ + fset(fi, FIG_NOLOOT); + fi->run.hp = mage->hp; + fi->run.number = mage->number; + /* fighter leeren */ + rmfighter(fi, mage->number); + + m = msg_message("cast_escape_effect", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + + return level; +} + +static void do_meffect(fighter * af, int typ, int effect, int duration) +{ + battle *b = af->side->battle; + meffect *me = (meffect *) malloc(sizeof(struct meffect)); + ql_push(&b->meffects, me); + me->magician = af; + me->typ = typ; + me->effect = effect; + me->duration = duration; +} + +int sp_armorshield(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + int effect; + int duration; + battle *b = fi->side->battle; + message *m = msg_message("cast_spell_effect", "mage spell", fi->unit, sp); + + message_all(b, m); + msg_release(m); + + /* gibt Rüstung +effect für duration Treffer */ + + switch (sp->id) { + case SPL_ARMORSHIELD: + effect = level / 3; + duration = (int)(20 * power * power); + break; + + default: + effect = level / 4; + duration = (int)(power * power); + } + do_meffect(fi, SHIELD_ARMOR, effect, duration); + return level; +} + +int sp_reduceshield(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + int effect; + int duration; + battle *b = fi->side->battle; + message *m = msg_message("cast_spell_effect", "mage spell", fi->unit, sp); + message_all(b, m); + msg_release(m); + + /* jeder Schaden wird um effect% reduziert bis der Schild duration + * Trefferpunkte aufgefangen hat */ + + switch (sp->id) { + case SPL_REDUCESHIELD: + effect = 50; + duration = (int)(50 * power * power); + break; + + default: + effect = level * 3; + duration = (int)get_force(power, 5); + } + do_meffect(fi, SHIELD_REDUCE, effect, duration); + return level; +} + +int sp_fumbleshield(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + const spell * sp = co->sp; + int effect; + int duration; + battle *b = fi->side->battle; + message *m = msg_message("cast_spell_effect", "mage spell", fi->unit, sp); + + message_all(b, m); + msg_release(m); + + /* der erste Zauber schlägt mit 100% fehl */ + + switch (sp->id) { + case SPL_DRAIG_FUMBLESHIELD: + case SPL_GWYRRD_FUMBLESHIELD: + case SPL_CERRDOR_FUMBLESHIELD: + case SPL_TYBIED_FUMBLESHIELD: + duration = 100; + effect = MAX(1, 25 - level); + break; + + default: + duration = 100; + effect = 10; + } + do_meffect(fi, SHIELD_BLOCK, effect, duration); + return level; +} + +/* ------------------------------------------------------------- */ +/* POSTCOMBAT */ + +static int count_healable(battle * b, fighter * df) +{ + side *s; + int healable = 0; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (helping(df->side, s)) { + healable += s->casualties; + } + } + return healable; +} + +/* wiederbeleben */ +int sp_reanimate(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + battle *b = fi->side->battle; + unit *mage = fi->unit; + int healable, j = 0; + double c = 0.50 + 0.02 * power; + double k = EFFECT_HEALING_SPELL * power; + bool use_item = get_item(mage, I_AMULET_OF_HEALING) > 0; + message *msg; + + if (use_item) { + k *= 2; + c += 0.10; + } + + healable = count_healable(b, fi); + healable = (int)MIN(k, healable); + while (healable--) { + fighter *tf = select_corpse(b, fi); + if (tf != NULL && tf->side->casualties > 0 + && u_race(tf->unit) != new_race[RC_DAEMON] + && (chance(c))) { + assert(tf->alive < tf->unit->number); + /* t.fighter->person[].hp beginnt mit t.index = 0 zu zählen, + * t.fighter->alive ist jedoch die Anzahl lebender in der Einheit, + * also sind die hp von t.fighter->alive + * t.fighter->hitpoints[t.fighter->alive-1] und der erste Tote + * oder weggelaufene ist t.fighter->hitpoints[tf->alive] */ + tf->person[tf->alive].hp = 2; + ++tf->alive; + ++tf->side->size[SUM_ROW]; + ++tf->side->size[tf->unit->status + 1]; + ++tf->side->healed; + --tf->side->casualties; + assert(tf->side->casualties >= 0); + --tf->side->dead; + assert(tf->side->dead >= 0); + ++j; + } + } + if (j <= 0) { + level = j; + } + if (use_item) { + msg = + msg_message("reanimate_effect_1", "mage amount item", mage, j, + oldresourcetype[R_AMULET_OF_HEALING]); + } else { + msg = msg_message("reanimate_effect_0", "mage amount", mage, j); + } + message_all(b, msg); + msg_release(msg); + + return level; +} + +int sp_keeploot(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + const spell * sp = co->sp; + battle *b = fi->side->battle; + message *m = msg_message("cast_spell_effect", "mage spell", fi->unit, sp); + + message_all(b, m); + msg_release(m); + + b->keeploot = (int)MAX(25, b->keeploot + 5 * power); + + return level; +} + +static int heal_fighters(quicklist * fgs, int *power, bool heal_monsters) +{ + int healhp = *power, healed = 0, qi; + quicklist *ql; + + for (qi = 0, ql = fgs; ql; ql_advance(&ql, &qi, 1)) { + fighter *df = (fighter *) ql_get(ql, qi); + + if (healhp <= 0) + break; + + /* Untote kann man nicht heilen */ + if (df->unit->number == 0 || fval(u_race(df->unit), RCF_NOHEAL)) + continue; + + /* wir heilen erstmal keine Monster */ + if (heal_monsters || playerrace(u_race(df->unit))) { + int n, hp = df->unit->hp / df->unit->number; + int rest = df->unit->hp % df->unit->number; + + for (n = 0; n < df->unit->number; n++) { + int wound = hp - df->person[n].hp; + if (rest > n) + ++wound; + + if (wound > 0 && wound < hp) { + int heal = MIN(healhp, wound); + assert(heal >= 0); + df->person[n].hp += heal; + healhp = MAX(0, healhp - heal); + ++healed; + if (healhp <= 0) + break; + } + } + } + } + + *power = healhp; + return healed; +} + +int sp_healing(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + battle *b = fi->side->battle; + unit *mage = fi->unit; + int j = 0; + int healhp = (int)power * 200; + quicklist *fgs; + message *msg; + bool use_item = get_item(mage, I_AMULET_OF_HEALING) > 0; + + /* bis zu 11 Personen pro Stufe (einen HP müssen sie ja noch + * haben, sonst wären sie tot) können geheilt werden */ + + if (use_item) { + healhp *= 2; + } + + /* gehe alle denen wir helfen der reihe nach durch, heile verwundete, + * bis zu verteilende HP aufgebraucht sind */ + + fgs = fighters(b, fi->side, FIGHT_ROW, AVOID_ROW, FS_HELP); + scramble_fighters(fgs); + j += heal_fighters(fgs, &healhp, false); + j += heal_fighters(fgs, &healhp, true); + ql_free(fgs); + + if (j <= 0) { + level = j; + } + if (use_item) { + msg = + msg_message("healing_effect_1", "mage amount item", mage, j, + oldresourcetype[R_AMULET_OF_HEALING]); + } else { + msg = msg_message("healing_effect_0", "mage amount", mage, j); + } + message_all(b, msg); + msg_release(msg); + + return level; +} + +int sp_undeadhero(struct castorder * co) +{ + fighter * fi = co->magician.fig; + int level = co->level; + double power = co->force; + battle *b = fi->side->battle; + unit *mage = fi->unit; + region *r = b->region; + quicklist *fgs, *ql; + int qi, n, undead = 0; + message *msg; + int force = (int)get_force(power, 0); + double c = 0.50 + 0.02 * power; + + /* Liste aus allen Kämpfern */ + fgs = fighters(b, fi->side, FIGHT_ROW, AVOID_ROW, FS_ENEMY | FS_HELP); + scramble_fighters(fgs); + + for (qi = 0, ql = fgs; ql; ql_advance(&ql, &qi, 1)) { + fighter *df = (fighter *) ql_get(ql, qi); + unit *du = df->unit; + + if (force <= 0) + break; + + /* keine Monster */ + if (!playerrace(u_race(du))) + continue; + + if (df->alive + df->run.number < du->number) { + int j = 0; + + /* Wieviele Untote können wir aus dieser Einheit wecken? */ + for (n = df->alive + df->run.number; n != du->number; n++) { + if (chance(c)) { + ++j; + if (--force <= 0) + break; + } + } + + if (j > 0) { + unit *u = + create_unit(r, mage->faction, 0, new_race[RC_UNDEAD], 0, du->name, + du); + + /* new units gets some stats from old unit */ + + if (du->display) { + unit_setinfo(u, du->display); + } else { + unit_setinfo(u, NULL); + } + setstatus(u, du->status); + setguard(u, GUARD_NONE); + + /* inherit stealth from magician */ + if (fval(mage, UFL_ANON_FACTION)) { + fset(u, UFL_ANON_FACTION); + } + + /* transfer dead people to new unit, set hitpoints to those of old unit */ + transfermen(du, u, j); + u->hp = u->number * unit_max_hp(du); + assert(j <= df->side->casualties); + df->side->casualties -= j; + df->side->dead -= j; + + /* counting total number of undead */ + undead += j; + } + } + } + ql_free(fgs); + + level = MIN(level, undead); + if (undead == 0) { + msg = + msg_message("summonundead_effect_0", "mage region", mage, mage->region); + } else { + msg = + msg_message("summonundead_effect_1", "mage region amount", mage, + mage->region, undead); + } + + message_all(b, msg); + msg_release(msg); + return level; +} diff --git a/src/spells/combatspells.h b/src/spells/combatspells.h new file mode 100644 index 000000000..9ff3e2e2b --- /dev/null +++ b/src/spells/combatspells.h @@ -0,0 +1,57 @@ +/* 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_COMBATSPELLS +#define H_GC_COMBATSPELLS + +#ifdef __cplusplus +extern "C" { +#endif + + struct fighter; + + /* Kampfzauber */ + extern int sp_fumbleshield(struct castorder * co); + extern int sp_shadowknights(struct castorder * co); + extern int sp_combatrosthauch(struct castorder * co); + extern int sp_kampfzauber(struct castorder * co); + extern int sp_healing(struct castorder * co); + extern int sp_keeploot(struct castorder * co); + extern int sp_reanimate(struct castorder * co); + extern int sp_chaosrow(struct castorder * co); + extern int sp_flee(struct castorder * co); + extern int sp_berserk(struct castorder * co); + extern int sp_tiredsoldiers(struct castorder * co); + extern int sp_reeling_arrows(struct castorder * co); + extern int sp_denyattack(struct castorder * co); + extern int sp_sleep(struct castorder * co); + extern int sp_windshield(struct castorder * co); + extern int sp_strong_wall(struct castorder * co); + extern int sp_petrify(struct castorder * co); + extern int sp_hero(struct castorder * co); + extern int sp_frighten(struct castorder * co); + extern int sp_mindblast(struct castorder * co); + extern int sp_mindblast_temp(struct castorder * co); + extern int sp_speed(struct castorder * co); + extern int sp_wolfhowl(struct castorder * co); + extern int sp_dragonodem(struct castorder * co); + extern int sp_reduceshield(struct castorder * co); + extern int sp_armorshield(struct castorder * co); + extern int sp_stun(struct castorder * co); + extern int sp_undeadhero(struct castorder * co); + extern int sp_shadowcall(struct castorder * co); + extern int sp_immolation(struct castorder * co); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/spells/regioncurse.c b/src/spells/regioncurse.c new file mode 100644 index 000000000..bf65656a6 --- /dev/null +++ b/src/spells/regioncurse.c @@ -0,0 +1,303 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include +#include "regioncurse.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +/* --------------------------------------------------------------------- */ +/* CurseInfo mit Spezialabfragen + */ + +/* + * godcursezone + */ +static message *cinfo_cursed_by_the_gods(const void *obj, objtype_t typ, + const curse * c, int self) +{ + region *r = (region *) obj; + + unused(typ); + unused(self); + assert(typ == TYP_REGION); + + if (fval(r->terrain, SEA_REGION)) { + return msg_message("curseinfo::godcurseocean", "id", c->no); + } + return msg_message("curseinfo::godcurse", "id", c->no); +} + +static struct curse_type ct_godcursezone = { + "godcursezone", + CURSETYP_NORM, CURSE_IMMUNE | CURSE_ISNEW, (NO_MERGE), + cinfo_cursed_by_the_gods, +}; + +/* --------------------------------------------------------------------- */ +/* + * C_GBDREAM + */ +static message *cinfo_dreamcurse(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(self); + unused(typ); + unused(obj); + assert(typ == TYP_REGION); + + if (curse_geteffect(c) > 0) { + return msg_message("curseinfo::gooddream", "id", c->no); + } + return msg_message("curseinfo::baddream", "id", c->no); +} + +static struct curse_type ct_gbdream = { + "gbdream", + CURSETYP_NORM, CURSE_ISNEW, (NO_MERGE), cinfo_dreamcurse +}; + +/* --------------------------------------------------------------------- */ +/* + * C_MAGICSTREET + * erzeugt Straßennetz + */ +static message *cinfo_magicstreet(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + unused(self); + unused(obj); + assert(typ == TYP_REGION); + + /* Warnung vor Auflösung */ + if (c->duration <= 2) { + return msg_message("curseinfo::magicstreet", "id", c->no); + } + return msg_message("curseinfo::magicstreetwarn", "id", c->no); +} + +static struct curse_type ct_magicstreet = { + "magicstreet", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR), + cinfo_magicstreet +}; + +/* --------------------------------------------------------------------- */ + +static message *cinfo_antimagiczone(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + unused(self); + unused(obj); + assert(typ == TYP_REGION); + + /* Magier spüren eine Antimagiezone */ + if (self != 0) { + return msg_message("curseinfo::antimagiczone", "id", c->no); + } + + return NULL; +} + +/* alle Magier können eine Antimagiezone wahrnehmen */ +static int +cansee_antimagiczone(const struct faction *viewer, const void *obj, objtype_t typ, + const curse * c, int self) +{ + region *r; + unit *u = NULL; + unit *mage = c->magician; + + unused(typ); + + assert(typ == TYP_REGION); + r = (region *) obj; + for (u = r->units; u; u = u->next) { + if (u->faction == viewer) { + if (u == mage) { + self = 2; + break; + } + if (is_mage(u)) { + self = 1; + } + } + } + return self; +} + +static struct curse_type ct_antimagiczone = { + "antimagiczone", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR), + cinfo_antimagiczone, NULL, NULL, NULL, cansee_antimagiczone +}; + +/* --------------------------------------------------------------------- */ +static message *cinfo_farvision(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + unused(obj); + + assert(typ == TYP_REGION); + + /* Magier spüren eine farvision */ + if (self != 0) { + return msg_message("curseinfo::farvision", "id", c->no); + } + + return 0; +} + +static struct curse_type ct_farvision = { + "farvision", + CURSETYP_NORM, 0, (NO_MERGE), + cinfo_farvision +}; + +/* --------------------------------------------------------------------- */ + +static struct curse_type ct_fogtrap = { + "fogtrap", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR), + cinfo_simple +}; + +static struct curse_type ct_maelstrom = { + "maelstrom", + CURSETYP_NORM, CURSE_ISNEW, (M_DURATION | M_VIGOUR), + cinfo_simple +}; + +static struct curse_type ct_blessedharvest = { + "blessedharvest", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR), + cinfo_simple +}; + +static struct curse_type ct_drought = { + "drought", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR), + cinfo_simple +}; + +static struct curse_type ct_badlearn = { + "badlearn", + CURSETYP_NORM, CURSE_ISNEW, (M_DURATION | M_VIGOUR), + cinfo_simple +}; + +/* Trübsal-Zauber */ +static struct curse_type ct_depression = { + "depression", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR), + cinfo_simple +}; + +/* Astralblock, auf Astralregion */ +static struct curse_type ct_astralblock = { + "astralblock", + CURSETYP_NORM, 0, NO_MERGE, + cinfo_simple +}; + +/* Unterhaltungsanteil vermehren */ +static struct curse_type ct_generous = { + "generous", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR | M_MAXEFFECT), + cinfo_simple +}; + +/* verhindert Attackiere regional */ +static struct curse_type ct_peacezone = { + "peacezone", + CURSETYP_NORM, 0, NO_MERGE, + cinfo_simple +}; + +/* erniedigt Magieresistenz von nicht-aliierten Einheiten, wirkt nur 1x +* pro Einheit */ +static struct curse_type ct_badmagicresistancezone = { + "badmagicresistancezone", + CURSETYP_NORM, 0, NO_MERGE, + cinfo_simple +}; + +/* erhöht Magieresistenz von aliierten Einheiten, wirkt nur 1x pro +* Einheit */ +static struct curse_type ct_goodmagicresistancezone = { + "goodmagicresistancezone", + CURSETYP_NORM, 0, NO_MERGE, + cinfo_simple +}; + +static struct curse_type ct_riotzone = { + "riotzone", + CURSETYP_NORM, 0, (M_DURATION), + cinfo_simple +}; + +static struct curse_type ct_holyground = { + "holyground", + CURSETYP_NORM, CURSE_NOAGE, (M_VIGOUR_ADD), + cinfo_simple +}; + +static struct curse_type ct_healing = { + "healingzone", + CURSETYP_NORM, 0, (M_VIGOUR | M_DURATION), + cinfo_simple +}; + +void register_regioncurse(void) +{ + ct_register(&ct_fogtrap); + ct_register(&ct_antimagiczone); + ct_register(&ct_farvision); + ct_register(&ct_gbdream); + ct_register(&ct_maelstrom); + ct_register(&ct_blessedharvest); + ct_register(&ct_drought); + ct_register(&ct_badlearn); + ct_register(&ct_depression); + ct_register(&ct_astralblock); + ct_register(&ct_generous); + ct_register(&ct_peacezone); + ct_register(&ct_magicstreet); + ct_register(&ct_badmagicresistancezone); + ct_register(&ct_goodmagicresistancezone); + ct_register(&ct_riotzone); + ct_register(&ct_godcursezone); + ct_register(&ct_holyground); + ct_register(&ct_healing); +} diff --git a/src/spells/regioncurse.h b/src/spells/regioncurse.h new file mode 100644 index 000000000..5e4afec5d --- /dev/null +++ b/src/spells/regioncurse.h @@ -0,0 +1,28 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#ifndef _RCURSE_H +#define _RCURSE_H +#ifdef __cplusplus +extern "C" { +#endif + + struct curse; + struct locale; + + extern void register_regioncurse(void); + +#ifdef __cplusplus +} +#endif +#endif /* _RCURSE_H */ diff --git a/src/spells/shipcurse.c b/src/spells/shipcurse.c new file mode 100644 index 000000000..39d530770 --- /dev/null +++ b/src/spells/shipcurse.c @@ -0,0 +1,160 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include +#include +#include "shipcurse.h" + +/* kernel includes */ +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +message *cinfo_ship(const void *obj, objtype_t typ, const curse * c, int self) +{ + message *msg; + + unused(typ); + unused(obj); + assert(typ == TYP_SHIP); + + if (self != 0) { /* owner or inside */ + msg = msg_message(mkname("curseinfo", c->type->cname), "id", c->no); + } else { + msg = msg_message("curseinfo::ship_unknown", "id", c->no); + } + if (msg == NULL) { + log_error("There is no curseinfo for %s.\n", c->type->cname); + } + return msg; +} + +/* CurseInfo mit Spezialabfragen */ + +/* C_SHIP_NODRIFT */ +static message *cinfo_shipnodrift(const void *obj, objtype_t typ, const curse * c, + int self) +{ + ship *sh = (ship *) obj; + + unused(typ); + assert(typ == TYP_SHIP); + + if (self != 0) { + return msg_message("curseinfo::shipnodrift_1", "ship duration id", sh, + c->duration, c->no); + } + return msg_message("curseinfo::shipnodrift_0", "ship id", sh, c->no); +} + +static struct curse_type ct_stormwind = { "stormwind", + CURSETYP_NORM, 0, NO_MERGE, cinfo_ship +}; + +static int flyingship_read(storage * store, curse * c, void *target) +{ + ship *sh = (ship *) target; + c->data.v = sh; + if (store->version < FOSS_VERSION) { + sh->flags |= SF_FLYING; + return 0; + } + assert(sh->flags & SF_FLYING); + return 0; +} + +static int flyingship_write(storage * store, const curse * c, + const void *target) +{ + const ship *sh = (const ship *)target; + assert(sh->flags & SF_FLYING); + return 0; +} + +static int flyingship_age(curse * c) +{ + ship *sh = (ship *) c->data.v; + if (sh && c->duration == 1) { + freset(sh, SF_FLYING); + return 1; + } + return 0; +} + +static struct curse_type ct_flyingship = { "flyingship", + CURSETYP_NORM, 0, NO_MERGE, cinfo_ship, NULL, flyingship_read, + flyingship_write, NULL, flyingship_age +}; + +static struct curse_type ct_nodrift = { "nodrift", + CURSETYP_NORM, 0, (M_DURATION | M_VIGOUR), cinfo_shipnodrift +}; + +static struct curse_type ct_shipspeedup = { "shipspeedup", + CURSETYP_NORM, 0, 0, cinfo_ship +}; + +curse *shipcurse_flyingship(ship * sh, unit * mage, double power, int duration) +{ + static const curse_type *ct_flyingship = NULL; + if (!ct_flyingship) { + ct_flyingship = ct_find("flyingship"); + assert(ct_flyingship); + } + if (curse_active(get_curse(sh->attribs, ct_flyingship))) { + return NULL; + } else if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { + return NULL; + } else { + /* mit C_SHIP_NODRIFT haben wir kein Problem */ + curse *c = + create_curse(mage, &sh->attribs, ct_flyingship, power, duration, 0.0, 0); + c->data.v = sh; + if (c && c->duration > 0) { + sh->flags |= SF_FLYING; + } + return c; + } +} + +int levitate_ship(ship * sh, unit * mage, double power, int duration) +{ + curse *c = shipcurse_flyingship(sh, mage, power, duration); + if (c) { + return c->no; + } + return 0; +} + +void register_shipcurse(void) +{ + ct_register(&ct_stormwind); + ct_register(&ct_flyingship); + ct_register(&ct_nodrift); + ct_register(&ct_shipspeedup); +} diff --git a/src/spells/shipcurse.h b/src/spells/shipcurse.h new file mode 100644 index 000000000..5d416bc54 --- /dev/null +++ b/src/spells/shipcurse.h @@ -0,0 +1,36 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#ifndef _SCURSE_H +#define _SCURSE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + struct locale; + struct message; + extern struct message *cinfo_ship(const void *obj, objtype_t typ, + const struct curse *c, int self); + extern void register_shipcurse(void); + extern struct curse *shipcurse_flyingship(struct ship *sh, struct unit *mage, + double power, int duration); + int levitate_ship(struct ship *sh, struct unit *mage, double power, + int duration); + +#ifdef __cplusplus +} +#endif +#endif /* _SCURSE_H */ diff --git a/src/spells/spells.c b/src/spells/spells.c new file mode 100644 index 000000000..0b3ea79ca --- /dev/null +++ b/src/spells/spells.c @@ -0,0 +1,6754 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include +#include +#include "spells.h" + +#include "../curses.h" +#include "buildingcurse.h" +#include "regioncurse.h" +#include "unitcurse.h" +#include "shipcurse.h" +#include "combatspells.h" + +/* kernel includes */ +#include +#include /* fuer lovar */ +#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 + +/* spells includes */ +#include "alp.h" + +/* 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 + +/* triggers includes */ +#include +#include +#include +#include +#include +#include +#include + +/* attributes includes */ +#include +#include +/* ----------------------------------------------------------------------- */ + +static double zero_effect = 0.0; + +attrib_type at_wdwpyramid = { + "wdwpyramid", NULL, NULL, NULL, a_writevoid, a_readvoid +}; + +/* ----------------------------------------------------------------------- */ + +static void report_spell(unit * mage, region * r, message * msg) +{ + r_addmessage(r, NULL, msg); + if (mage && mage->region != r) { + add_message(&mage->faction->msgs, msg); + } +} + +static void report_failure(unit * mage, struct order *ord) +{ + /* Fehler: "Der Zauber schlaegt fehl" */ + cmistake(mage, ord, 180, MSG_MAGIC); +} + +/* ------------------------------------------------------------- */ +/* Spruchanalyse - Ausgabe von curse->info und curse->name */ +/* ------------------------------------------------------------- */ + +static double curse_chance(const struct curse *c, double force) +{ + return 1.0 + (force - c->vigour) * 0.1; +} + +static void magicanalyse_region(region * r, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = r->attribs; a; a = a->next) { + curse *c = (curse *) a->data.v; + double probability; + int mon; + + if (!fval(a->type, ATF_CURSE)) + continue; + + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = MAX(1, mon); + found = true; + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_noage", + "mage region curse", mage, r, c->type)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_age", + "mage region curse months", mage, r, c->type, mon)); + } + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_fail", + "mage region", mage, r)); + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_nospell", + "mage region", mage, r)); + } +} + +static void magicanalyse_unit(unit * u, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = u->attribs; a; a = a->next) { + curse *c; + double probability; + int mon; + if (!fval(a->type, ATF_CURSE)) + continue; + + c = (curse *) a->data.v; + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = MAX(1, mon); + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_noage", + "mage unit curse", mage, u, c->type)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_age", + "mage unit curse months", mage, u, c->type, mon)); + } + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_fail", "mage unit", + mage, u)); + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_nospell", + "mage target", mage, u)); + } +} + +static void magicanalyse_building(building * b, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = b->attribs; a; a = a->next) { + curse *c; + double probability; + int mon; + + if (!fval(a->type, ATF_CURSE)) + continue; + + c = (curse *) a->data.v; + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = MAX(1, mon); + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_age", + "mage building curse", mage, b, c->type)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_age", + "mage building curse months", mage, b, c->type, mon)); + } + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_fail", + "mage building", mage, b)); + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_nospell", + "mage building", mage, b)); + } + +} + +static void magicanalyse_ship(ship * sh, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = sh->attribs; a; a = a->next) { + curse *c; + double probability; + int mon; + if (!fval(a->type, ATF_CURSE)) + continue; + + c = (curse *) a->data.v; + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = MAX(1, mon); + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_noage", + "mage ship curse", mage, sh, c->type)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_age", + "mage ship curse months", mage, sh, c->type, mon)); + } + } else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_fail", "mage ship", + mage, sh)); + + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_nospell", + "mage ship", mage, sh)); + } + +} + +int break_curse(attrib ** alist, int cast_level, double force, curse * c) +{ + int succ = 0; +/* attrib **a = a_find(*ap, &at_curse); */ + attrib **ap = alist; + + while (*ap && force > 0) { + curse *c1; + attrib *a = *ap; + if (!fval(a->type, ATF_CURSE)) { + do { + ap = &(*ap)->next; + } while (*ap && a->type == (*ap)->type); + continue; + } + c1 = (curse *) a->data.v; + + /* Immunitaet pruefen */ + if (c_flags(c1) & CURSE_IMMUNE) { + do { + ap = &(*ap)->next; + } while (*ap && a->type == (*ap)->type); + continue; + } + + /* Wenn kein spezieller cursetyp angegeben ist, soll die Antimagie + * auf alle Verzauberungen wirken. Ansonsten pruefe, ob der Curse vom + * richtigen Typ ist. */ + if (!c || c == c1) { + double remain = destr_curse(c1, cast_level, force); + if (remain < force) { + succ = cast_level; + force = remain; + } + if (c1->vigour <= 0) { + a_remove(alist, a); + } + } + if (*ap == a) + ap = &a->next; + } + return succ; +} + +/* ------------------------------------------------------------- */ +/* Report a spell's effect to the units in the region. +*/ + +static void +report_effect(region * r, unit * mage, message * seen, message * unseen) +{ +#if 0 + unit *u; + + /* melden, 1x pro Partei */ + freset(mage->faction, FFL_SELECT); + 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); + + /* Bei Fernzaubern sieht nur die eigene Partei den Magier */ + if (u->faction != mage->faction) { + if (r == mage->region) { + /* kein Fernzauber, pruefe, ob der Magier ueberhaupt gesehen + * wird */ + if (cansee(u->faction, r, mage, 0)) { + r_addmessage(r, u->faction, seen); + } else { + r_addmessage(r, u->faction, unseen); + } + } else { /* Fernzauber, fremde Partei sieht den Magier niemals */ + r_addmessage(r, u->faction, unseen); + } + } else { /* Partei des Magiers, sieht diesen immer */ + r_addmessage(r, u->faction, seen); + } + } + } + /* Ist niemand von der Partei des Magiers in der Region, dem Magier + * nochmal gesondert melden */ + if (!fval(mage->faction, FFL_SELECT)) { + add_message(&mage->faction->msgs, seen); + } +#else + int err = report_action(r, mage, seen, ACTION_RESET | ACTION_CANSEE); + if (err) { + report_action(r, mage, seen, ACTION_CANNOTSEE); + } +#endif +} + +/* ------------------------------------------------------------- */ +/* Die Spruchfunktionen */ +/* ------------------------------------------------------------- */ +/* Meldungen: + * + * Fehlermeldungen sollten als MSG_MAGIC, level ML_MISTAKE oder + * ML_WARN ausgegeben werden. (stehen im Kopf der Auswertung unter + * Zauberwirkungen) + + sprintf(buf, "%s in %s: 'ZAUBER %s': [hier die Fehlermeldung].", + unitname(mage), regionname(mage->region, mage->faction), sa->strings[0]); + add_message(0, mage->faction, buf, MSG_MAGIC, ML_MISTAKE); + + * Allgemein sichtbare Auswirkungen in der Region sollten als + * Regionsereignisse auch dort auftauchen. + + { + message * seen = msg_message("harvest_effect", "mage", mage); + message * unseen = msg_message("harvest_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + } + + * Meldungen an den Magier ueber Erfolg sollten, wenn sie nicht als + * Regionsereigniss auftauchen, als MSG_MAGIC level ML_INFO unter + * Zauberwirkungen gemeldet werden. Direkt dem Magier zuordnen (wie + * Botschaft an Einheit) ist derzeit nicht moeglich. + * ACHTUNG! r muss nicht die Region des Magier sein! (FARCASTING) + * + * Parameter: + * die Struct castorder *co ist in magic.h deklariert + * die Parameterliste spellparameter *pa = co->par steht dort auch. + * + */ + +/* ------------------------------------------------------------- */ +/* Name: Vertrauter + * Stufe: 10 + * + * Wirkung: + * Der Magier beschwoert einen Vertrauten, ein kleines Tier, welches + * dem Magier zu Diensten ist. Der Magier kann durch die Augen des + * Vertrauten sehen, und durch den Vertrauten zaubern, allerdings nur + * mit seiner halben Stufe. Je nach Vertrautem erhaelt der Magier + * evtl diverse Skillmodifikationen. Der Typ des Vertrauten ist + * zufaellig bestimmt, wird aber durch Magiegebiet und Rasse beeinflußt. + * "Tierische" Vertraute brauchen keinen Unterhalt. + * + * Ein paar Moeglichkeiten: + * Magieg. Rasse Besonderheiten + * Eule Tybied -/- fliegt, Auraregeneration + * Rabe Ilaun -/- fliegt + * Imp Draig -/- Magieresistenz? + * Fuchs Gwyrrd -/- Wahrnehmung + * ???? Cerddor -/- ???? (Singvogel?, Papagei?) + * Adler -/- -/- fliegt, +Wahrnehmung, =^=Adlerauge-Spruch? + * Kraehe -/- -/- fliegt, +Tarnung (weil unauffaellig) + * Delphin -/- Meerm. schwimmt + * Wolf -/- Ork + * Hund -/- Mensch kann evtl BEWACHE ausfuehren + * Ratte -/- Goblin + * Albatros -/- -/- fliegt, kann auf Ozean "landen" + * Affe -/- -/- kann evtl BEKLAUE ausfuehren + * Goblin -/- !Goblin normale Einheit + * Katze -/- !Katze normale Einheit + * Daemon -/- !Daemon normale Einheit + * + * Spezielle V. fuer Katzen, Trolle, Elfen, Daemonen, Insekten, Zwerge? + */ + +static const race *select_familiar(const race * magerace, magic_t magiegebiet) +{ + const race *retval = 0; + int rnd = rng_int() % 100; + + assert(magerace->familiars[0]); + if (rnd >= 70) { + retval = magerace->familiars[magiegebiet]; + } else { + retval = magerace->familiars[0]; + } + + if (!retval || rnd < 3) { + race_list *familiarraces = get_familiarraces(); + unsigned int maxlen = listlen(familiarraces); + if (maxlen > 0) { + race_list *rclist = familiarraces; + int index = rng_int() % maxlen; + while (index-- > 0) { + rclist = rclist->next; + } + retval = rclist->data; + } + } + + if (!retval) { + retval = magerace->familiars[0]; + } + if (!retval) { + log_error("select_familiar: No familiar (not even a default) defined for %s.\n", magerace->_name[0]); + } + return retval; +} + +/* ------------------------------------------------------------- */ +/* der Vertraue des Magiers */ + +static void make_familiar(unit * familiar, unit * mage) +{ + /* skills and spells: */ + if (u_race(familiar)->init_familiar != NULL) { + u_race(familiar)->init_familiar(familiar); + } else { + log_error("could not perform initialization for familiar %s.\n", familiar->faction->race->_name[0]); + } + + /* triggers: */ + create_newfamiliar(mage, familiar); + + /* Hitpoints nach Talenten korrigieren, sonst starten vertraute + * mit Ausdauerbonus verwundet */ + familiar->hp = unit_max_hp(familiar); +} + +static int sp_summon_familiar(castorder * co) +{ + unit *familiar; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + const race *rc; + int sk; + int dh, dh1, bytes; + message *msg; + char zText[2048], *bufp = zText; + size_t size = sizeof(zText) - 1; + + if (get_familiar(mage) != NULL) { + cmistake(mage, co->order, 199, MSG_MAGIC); + return 0; + } + rc = select_familiar(mage->faction->race, mage->faction->magiegebiet); + if (rc == NULL) { + log_error("could not find suitable familiar for %s.\n", mage->faction->race->_name[0]); + return 0; + } + + if (fval(rc, RCF_SWIM) && !fval(rc, RCF_WALK)) { + int coasts = is_coastregion(r); + int dir; + if (coasts == 0) { + cmistake(mage, co->order, 229, MSG_MAGIC); + return 0; + } + + /* In welcher benachbarten Ozeanregion soll der Familiar erscheinen? */ + coasts = rng_int() % coasts; + dh = -1; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *rn = rconnect(r, dir); + if (rn && fval(rn->terrain, SEA_REGION)) { + dh++; + if (dh == coasts) + break; + } + } + r = rconnect(r, dir); + } + + msg = msg_message("familiar_name", "unit", mage); + nr_render(msg, mage->faction->locale, zText, sizeof(zText), mage->faction); + msg_release(msg); + familiar = create_unit(r, mage->faction, 1, rc, 0, zText, mage); + setstatus(familiar, ST_FLEE); + fset(familiar, UFL_LOCKED); + make_familiar(familiar, mage); + + dh = 0; + dh1 = 0; + for (sk = 0; sk < MAXSKILLS; ++sk) { + if (skill_enabled[sk] && rc->bonus[sk] > -5) + dh++; + } + + for (sk = 0; sk < MAXSKILLS; sk++) { + if (skill_enabled[sk] && rc->bonus[sk] > -5) { + dh--; + if (dh1 == 0) { + dh1 = 1; + } else { + if (dh == 0) { + bytes = + (int)strlcpy(bufp, (const char *)LOC(mage->faction->locale, + "list_and"), size); + } else { + bytes = (int)strlcpy(bufp, (const char *)", ", size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = + (int)strlcpy(bufp, (const char *)skillname((skill_t)sk, mage->faction->locale), + size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + ADDMSG(&mage->faction->msgs, msg_message("familiar_describe", + "mage race skills", mage, rc, zText)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Zerstoere Magie + * Wirkung: + * Zerstoert alle Zauberwirkungen auf dem Objekt. Jeder gebrochene + * Zauber verbraucht c->vigour an Zauberkraft. Wird der Spruch auf + * einer geringeren Stufe gezaubert, als der Zielzauber an c->vigour + * hat, so schlaegt die Aufloesung mit einer von der Differenz abhaengigen + * Chance fehl. Auch dann wird force verbraucht, der Zauber jedoch nur + * abgeschwaecht. + * + * Flag: + * (FARCASTING|SPELLLEVEL|ONSHIPCAST|TESTCANSEE) + * */ +static int sp_destroy_magic(castorder * co) +{ + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + curse *c = NULL; + char ts[80]; + attrib **ap; + int obj; + int succ; + + /* da jeder Zauber force verbraucht und der Zauber auf alles und nicht + * nur einen Spruch wirken soll, wird die Wirkung hier verstaerkt */ + force *= 4; + + /* Objekt ermitteln */ + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + { + /* region *tr = pa->param[0]->data.r; -- farcasting! */ + region *tr = co_get_region(co); + ap = &tr->attribs; + write_regionname(tr, mage->faction, ts, sizeof(ts)); + break; + } + case SPP_TEMP: + case SPP_UNIT: + { + unit *u; + u = pa->param[0]->data.u; + ap = &u->attribs; + write_unitname(u, ts, sizeof(ts)); + break; + } + case SPP_BUILDING: + { + building *b; + b = pa->param[0]->data.b; + ap = &b->attribs; + write_buildingname(b, ts, sizeof(ts)); + break; + } + case SPP_SHIP: + { + ship *sh; + sh = pa->param[0]->data.sh; + ap = &sh->attribs; + write_shipname(sh, ts, sizeof(ts)); + break; + } + default: + return 0; + } + + succ = break_curse(ap, cast_level, force, c); + + if (succ) { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_effect", + "unit region command succ target", mage, mage->region, co->order, succ, + ts)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_noeffect", + "unit region command", mage, mage->region, co->order)); + } + + return MAX(succ, 1); +} + +/* ------------------------------------------------------------- */ +/* Name: Transferiere Aura + * Stufe: variabel + * Gebiet: alle + * Kategorie: Einheit, positiv + * Wirkung: + * Mit Hilfe dieses Zauber kann der Magier eigene Aura im Verhaeltnis + * 2:1 auf einen anderen Magier des gleichen Magiegebietes oder (nur + * bei Tybied) im Verhaeltnis 3:1 auf einen Magier eines anderen + * Magiegebietes uebertragen. + * + * Syntax: + * "ZAUBERE " + * "ui" + * Flags: + * (UNITSPELL|ONSHIPCAST) + * */ + +static int sp_transferaura(castorder * co) +{ + int aura, gain, multi = 2; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + unit *u; + sc_mage *scm_dst, *scm_src = get_mage(mage); + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + /* Wieviel Transferieren? */ + aura = pa->param[1]->data.i; + u = pa->param[0]->data.u; + scm_dst = get_mage(u); + + if (scm_dst == NULL) { + /* "Zu dieser Einheit kann ich keine Aura uebertragen." */ + cmistake(mage, co->order, 207, MSG_MAGIC); + return 0; + } else if (scm_src->magietyp == M_TYBIED) { + if (scm_src->magietyp != scm_dst->magietyp) + multi = 3; + } else if (scm_src->magietyp == M_GRAY) { + if (scm_src->magietyp != scm_dst->magietyp) + multi = 4; + } else if (scm_dst->magietyp != scm_src->magietyp) { + /* "Zu dieser Einheit kann ich keine Aura uebertragen." */ + cmistake(mage, co->order, 207, MSG_MAGIC); + return 0; + } + + if (aura < multi) { + /* "Auraangabe fehlerhaft." */ + cmistake(mage, co->order, 208, MSG_MAGIC); + return 0; + } + + gain = MIN(aura, scm_src->spellpoints) / multi; + scm_src->spellpoints -= gain * multi; + scm_dst->spellpoints += gain; + +/* sprintf(buf, "%s transferiert %d Aura auf %s", unitname(mage), + gain, unitname(u)); */ + ADDMSG(&mage->faction->msgs, msg_message("auratransfer_success", + "unit target aura", mage, u, gain)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* DRUIDE */ +/* ------------------------------------------------------------- */ +/* Name: Guenstige Winde + * Stufe: 4 + * Gebiet: Gwyrrd + * Wirkung: + * Schiffsbewegung +1, kein Abtreiben. Haelt (Stufe) Runden an. + * Kombinierbar mit "Sturmwind" (das +1 wird dadurch aber nicht + * verdoppelt), und "Luftschiff". + * + * Flags: + * (SHIPSPELL|ONSHIPCAST|SPELLLEVEL|TESTRESISTANCE) + */ + +static int sp_goodwinds(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + int duration = cast_level + 1; + spellparameter *pa = co->par; + message *m; + ship *sh; + unit *u; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + sh = pa->param[0]->data.sh; + + /* keine Probleme mit C_SHIP_SPEEDUP und C_SHIP_FLYING */ + /* NODRIFT bewirkt auch +1 Geschwindigkeit */ + create_curse(mage, &sh->attribs, ct_find("nodrift"), power, duration, + zero_effect, 0); + + /* melden, 1x pro Partei */ + freset(mage->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + m = msg_message("wind_effect", "mage ship", mage, sh); + for (u = r->units; u; u = u->next) { + if (u->ship != sh) /* nur den Schiffsbesatzungen! */ + continue; + if (!fval(u->faction, FFL_SELECT)) { + r_addmessage(r, u->faction, m); + fset(u->faction, FFL_SELECT); + } + } + if (!fval(mage->faction, FFL_SELECT)) { + r_addmessage(r, mage->faction, m); + } + msg_release(m); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Magischer Pfad + * Stufe: 4 + * Gebiet: Gwyrrd + * Wirkung: + * fuer Stufe Runden wird eine (magische) Strasse erzeugt, die wie eine + * normale Strasse wirkt. + * Im Ozean schlaegt der Spruch fehl + * + * Flags: + * (FARCASTING|SPELLLEVEL|REGIONSPELL|ONSHIPCAST|TESTRESISTANCE) + */ +static int sp_magicstreet(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + + if (!fval(r->terrain, LAND_REGION)) { + cmistake(mage, co->order, 186, MSG_MAGIC); + return 0; + } + + /* wirkt schon in der Zauberrunde! */ + create_curse(mage, &r->attribs, ct_find("magicstreet"), co->force, + co->level + 1, zero_effect, 0); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("path_effect", "mage region", mage, r); + message *unseen = msg_message("path_effect", "mage region", NULL, r); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return co->level; +} + +/* ------------------------------------------------------------- */ +/* Name: Erwecke Ents + * Stufe: 10 + * Kategorie: Beschwoerung, positiv + * Gebiet: Gwyrrd + * Wirkung: + * Verwandelt (Stufe) Baeume in eine Gruppe von Ents, die sich fuer Stufe + * Runden der Partei des Druiden anschliessen und danach wieder zu + * Baeumen werden + * Patzer: + * Monster-Ents entstehen + * + * Flags: + * (SPELLLEVEL) + */ +static int sp_summonent(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + unit *u; + attrib *a; + int ents; + + if (rtrees(r, 2) == 0) { + cmistake(mage, co->order, 204, MSG_EVENT); + /* nicht ohne baeume */ + return 0; + } + + ents = (int)MIN(power * power, rtrees(r, 2)); + + u = create_unit(r, mage->faction, ents, new_race[RC_TREEMAN], 0, NULL, mage); + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 2; /* An r->trees. */ + a->data.ca[1] = 5; /* 5% */ + a_add(&u->attribs, a); + fset(u, UFL_LOCKED); + + rsettrees(r, 2, rtrees(r, 2) - ents); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("ent_effect", "mage amount", mage, ents); + message *unseen = msg_message("ent_effect", "mage amount", NULL, ents); + report_effect(r, mage, seen, unseen); + msg_release(unseen); + msg_release(seen); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Segne Steinkreis + * Stufe: 11 + * Kategorie: Artefakt + * Gebiet: Gwyrrd + * Wirkung: + * Es werden zwei neue Gebaeude eingefuehrt: Steinkreis und Steinkreis + * (gesegnet). Ersteres kann man bauen, letzteres wird aus einem + * fertigen Steinkreis mittels des Zaubers erschaffen. + * + * Flags: + * (BUILDINGSPELL) + * + */ +static int sp_blessstonecircle(castorder * co) +{ + building *b; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *p = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (p->param[0]->flag == TARGET_NOTFOUND) + return 0; + + b = p->param[0]->data.b; + + if (b->type != bt_find("stonecircle")) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_notstonecircle", "building", b)); + return 0; + } + + if (b->size < b->type->maxsize) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_notcomplete", "building", b)); + return 0; + } + + b->type = bt_find("blessedstonecircle"); + + msg = msg_message("blessedstonecircle_effect", "mage building", mage, b); + add_message(&r->msgs, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Mahlstrom + * Stufe: 15 + * Kategorie: Region, negativ + * Gebiet: Gwyrrd + * Wirkung: + * Erzeugt auf See einen Mahlstrom fuer Stufe-Wochen. Jedes Schiff, das + * durch den Mahlstrom segelt, nimmt 0-150% Schaden. (D.h. es hat auch + * eine 1/3-Chance, ohne Federlesens zu sinken. Der Mahlstrom sollte + * aus den Nachbarregionen sichtbar sein. + * + * Flags: + * (OCEANCASTABLE | ONSHIPCAST | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_maelstrom(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + curse *c; + double power = co->force; + double effect = co->force; + int duration = (int)power + 1; + + if (!fval(r->terrain, SEA_REGION)) { + cmistake(mage, co->order, 205, MSG_MAGIC); + /* nur auf ozean */ + return 0; + } + + /* Attribut auf Region. + * Existiert schon ein curse, so wird dieser verstaerkt + * (Max(Dauer), Max(Staerke))*/ + c = create_curse(mage, &r->attribs, ct_find("maelstrom"), power, duration, effect, 0); + + /* melden, 1x pro Partei */ + if (c) { + message *seen = msg_message("maelstrom_effect", "mage", mage); + message *unseen = msg_message("maelstrom_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Wurzeln der Magie + * Stufe: 16 + * Kategorie: Region, neutral + * Gebiet: Gwyrrd + * Wirkung: + * Wandelt einen Wald permanent in eine Mallornregion + * + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_mallorn(castorder * co) +{ + region *r = co_get_region(co); + int cast_level = co->level; + unit *mage = co->magician.u; + + if (!fval(r->terrain, LAND_REGION)) { + cmistake(mage, co->order, 290, MSG_MAGIC); + return 0; + } + if (fval(r, RF_MALLORN)) { + cmistake(mage, co->order, 291, MSG_MAGIC); + return 0; + } + + /* half the trees will die */ + rsettrees(r, 2, rtrees(r, 2) / 2); + rsettrees(r, 1, rtrees(r, 1) / 2); + rsettrees(r, 0, rtrees(r, 0) / 2); + fset(r, RF_MALLORN); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("mallorn_effect", "mage", mage); + message *unseen = msg_message("mallorn_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Segen der Erde / Regentanz + * Stufe: 1 + * Kategorie: Region, positiv + * Gebiet: Gwyrrd + * + * Wirkung: + * Alle Bauern verdienen Stufe-Wochen 1 Silber mehr. + * + * Flags: + * (FARCASTING | SPELLLEVEL | ONSHIPCAST | REGIONSPELL) + */ +static int sp_blessedharvest(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + int duration = (int)power + 1; + /* Attribut auf Region. + * Existiert schon ein curse, so wird dieser verstaerkt + * (Max(Dauer), Max(Staerke))*/ + + if (create_curse(mage, &r->attribs, ct_find("blessedharvest"), power, + duration, 1.0, 0)) { + message *seen = msg_message("harvest_effect", "mage", mage); + message *unseen = msg_message("harvest_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Hainzauber + * Stufe: 2 + * Kategorie: Region, positiv + * Gebiet: Gwyrrd + * Syntax: ZAUBER [REGION x y] [STUFE 2] "Hain" + * Wirkung: + * Erschafft Stufe-10*Stufe Jungbaeume + * + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_hain(castorder * co) +{ + int trees; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + + if (!r->land) { + cmistake(mage, co->order, 296, MSG_MAGIC); + return 0; + } + if (fval(r, RF_MALLORN)) { + cmistake(mage, co->order, 92, MSG_MAGIC); + return 0; + } + + trees = lovar((int)(force * 10 * RESOURCE_QUANTITY)) + (int)force; + rsettrees(r, 1, rtrees(r, 1) + trees); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("growtree_effect", "mage amount", mage, trees); + message *unseen = + msg_message("growtree_effect", "mage amount", NULL, trees); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Segne Mallornstecken - Mallorn Hainzauber + * Stufe: 4 + * Kategorie: Region, positiv + * Gebiet: Gwyrrd + * Syntax: ZAUBER [REGION x y] [STUFE 4] "Segne Mallornstecken" + * Wirkung: + * Erschafft Stufe-10*Stufe Jungbaeume + * + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_mallornhain(castorder * co) +{ + int trees; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + + if (!r->land) { + cmistake(mage, co->order, 296, MSG_MAGIC); + return 0; + } + if (!fval(r, RF_MALLORN)) { + cmistake(mage, co->order, 91, MSG_MAGIC); + return 0; + } + + trees = lovar((int)(force * 10 * RESOURCE_QUANTITY)) + (int)force; + rsettrees(r, 1, rtrees(r, 1) + trees); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("growtree_effect", "mage amount", mage, trees); + message *unseen = + msg_message("growtree_effect", "mage amount", NULL, trees); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +void patzer_ents(castorder * co) +{ + int ents; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + /* int cast_level = co->level; */ + double force = co->force; + + if (!r->land) { + cmistake(mage, co->order, 296, MSG_MAGIC); + return; + } + + ents = (int)(force * 10); + u = create_unit(r, get_monsters(), ents, new_race[RC_TREEMAN], 0, NULL, NULL); + + if (u) { + message *unseen; + + /* 'Erfolg' melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_patzer", + "unit region command", mage, mage->region, co->order)); + + /* melden, 1x pro Partei */ + unseen = msg_message("entrise", "region", r); + report_effect(r, mage, unseen, unseen); + msg_release(unseen); + } +} + +/* ------------------------------------------------------------- */ +/* Name: Rosthauch + * Stufe: 3 + * Kategorie: Einheit, negativ + * Gebiet: Gwyrrd + * Wirkung: + * Zerstoert zwischen Stufe und Stufe*10 Eisenwaffen + * + * Flag: + * (FARCASTING | SPELLLEVEL | UNITSPELL | TESTCANSEE | TESTRESISTANCE) + */ +/* Syntax: ZAUBER [REGION x y] [STUFE 2] "Rosthauch" 1111 2222 3333 */ + +typedef struct iron_weapon { + const struct item_type *type; + const struct item_type *rusty; + float chance; + struct iron_weapon *next; +} iron_weapon; + +static iron_weapon *ironweapons = NULL; + +void +add_ironweapon(const struct item_type *type, const struct item_type *rusty, + float chance) +{ + iron_weapon *iweapon = malloc(sizeof(iron_weapon)); + iweapon->type = type; + iweapon->rusty = rusty; + iweapon->chance = chance; + iweapon->next = ironweapons; + ironweapons = iweapon; +} + +static int sp_rosthauch(castorder * co) +{ + int n; + int success = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + int force = (int)co->force; + spellparameter *pa = co->par; + + if (ironweapons == NULL) { + add_ironweapon(it_find("sword"), it_find("rustysword"), 1.0); + add_ironweapon(it_find("axe"), it_find("rustyaxe"), 1.0); + add_ironweapon(it_find("greatsword"), it_find("rustygreatsword"), 1.0); + add_ironweapon(it_find("halberd"), it_find("rustyhalberd"), 0.5f); +#ifndef NO_RUSTY_ARMOR + add_ironweapon(it_find("shield"), it_find("rustyshield"), 0.5f); + add_ironweapon(it_find("chainmail"), it_find("rustychainmail"), 0.2f); +#endif + } + + if (force > 0) { + force = rng_int() % ((int)(force * 10)) + force; + } + /* fuer jede Einheit */ + for (n = 0; n < pa->length; n++) { + unit *u = pa->param[n]->data.u; + int ironweapon = 0; + iron_weapon *iweapon = ironweapons; + + if (force <= 0) + break; + if (pa->param[n]->flag & (TARGET_RESISTS | TARGET_NOTFOUND)) + continue; + + for (; iweapon != NULL; iweapon = iweapon->next) { + item **ip = i_find(&u->items, iweapon->type); + if (*ip) { + int i = MIN((*ip)->number, force); + if (iweapon->chance < 1.0) { + i = (int)(i * iweapon->chance); + } + if (i > 0) { + force -= i; + ironweapon += i; + i_change(ip, iweapon->type, -i); + if (iweapon->rusty) { + i_change(&u->items, iweapon->rusty, i); + } + } + } + if (force <= 0) + break; + } + + if (ironweapon > 0) { + /* {$mage mage} legt einen Rosthauch auf {target}. {amount} Waffen + * wurden vom Rost zerfressen */ + ADDMSG(&mage->faction->msgs, msg_message("rust_effect", + "mage target amount", mage, u, ironweapon)); + ADDMSG(&u->faction->msgs, msg_message("rust_effect", + "mage target amount", + cansee(u->faction, r, mage, 0) ? mage : NULL, u, ironweapon)); + success += ironweapon; + } else { + /* {$mage mage} legt einen Rosthauch auf {target}, doch der + * Rosthauch fand keine Nahrung */ + ADDMSG(&mage->faction->msgs, msg_message("rust_fail", "mage target", mage, + u)); + } + } + /* in success stehen nun die insgesamt zerstoerten Waffen. Im + * unguenstigsten Fall kann pro Stufe nur eine Waffe verzaubert werden, + * darum wird hier nur fuer alle Faelle in denen noch weniger Waffen + * betroffen wurden ein Kostennachlass gegeben */ + return MIN(success, cast_level); +} + +/* ------------------------------------------------------------- */ +/* Name: Kaelteschutz + * Stufe: 3 + * Kategorie: Einheit, positiv + * Gebiet: Gwyrrd + * + * Wirkung: + * schuetzt ein bis mehrere Einheiten mit bis zu Stufe*10 Insekten vor + * den Auswirkungen der Kaelte. Sie koennen Gletscher betreten und dort + * ganz normal alles machen. Die Wirkung haelt Stufe Wochen an + * Insekten haben in Gletschern den selben Malus wie in Bergen. Zu + * lange drin, nicht mehr aendern + * + * Flag: + * (UNITSPELL | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + */ +/* Syntax: ZAUBER [STUFE n] "Kaelteschutz" eh1 [eh2 [eh3 [...]]] */ + +static int sp_kaelteschutz(castorder * co) +{ + unit *u; + int n, i = 0; + int men; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = MAX(cast_level, (int)force) + 1; + spellparameter *pa = co->par; + double effect; + + force *= 10; /* 10 Personen pro Force-Punkt */ + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 0; n < pa->length; n++) { + if (force < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + if (force < u->number) { + men = (int)force; + } else { + men = u->number; + } + + effect = 1; + create_curse(mage, &u->attribs, ct_find("insectfur"), cast_level, + duration, effect, men); + + force -= u->number; + ADDMSG(&mage->faction->msgs, msg_message("heat_effect", "mage target", mage, + u)); + if (u->faction != mage->faction) + ADDMSG(&u->faction->msgs, msg_message("heat_effect", "mage target", + cansee(u->faction, r, mage, 0) ? mage : NULL, u)); + i = cast_level; + } + /* Erstattung? */ + return i; +} + +/* ------------------------------------------------------------- */ +/* Name: Verwuenschung, Funkenregen, Naturfreund, ... + * Stufe: 1 + * Kategorie: Einheit, rein visuell + * Gebiet: Alle + * + * Wirkung: + * Die Einheit wird von einem magischen Effekt heimgesucht, der in ihrer + * Beschreibung auftaucht, aber nur visuellen Effekt hat. + * + * Flag: + * (UNITSPELL | TESTCANSEE | SPELLLEVEL) + */ +/* Syntax: ZAUBER "Funkenregen" eh1 */ + +static int sp_sparkle(castorder * co) +{ + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + int duration = cast_level + 1; + double effect; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + effect = rng_int() % 0xffffff; + create_curse(mage, &u->attribs, ct_find("sparkle"), cast_level, + duration, effect, u->number); + + ADDMSG(&mage->faction->msgs, msg_message("sparkle_effect", "mage target", + mage, u)); + if (u->faction != mage->faction) { + ADDMSG(&u->faction->msgs, msg_message("sparkle_effect", "mage target", mage, + u)); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Eisengolem + * Stufe: 2 + * Kategorie: Beschwoerung, positiv + * Gebiet: Gwyrrd + * Wirkung: + * Erschafft eine Einheit Eisengolems mit Stufe*8 Golems. Jeder Golem + * hat jede Runde eine Chance von 15% zu Staub zu zerfallen. Gibt man + * den Golems den Befehl 'mache Schwert/Bihaender' oder 'mache + * Schild/Kettenhemd/Plattenpanzer', so werden pro Golem 5 Eisenbarren + * verbaut und der Golem loest sich auf. + * + * Golems sind zu langsam um wirklich im Kampf von Nutzen zu sein. + * Jedoch fangen sie eine Menge Schaden auf und sollten sie zufaellig + * treffen, so ist der Schaden fast immer toedlich. (Eisengolem: HP + * 50, AT 4, PA 2, Ruestung 2(KH), 2d10+4 TP, Magieresistenz 0.25) + * + * Golems nehmen nix an und geben nix. Sie bewegen sich immer nur 1 + * Region weit und ziehen aus Strassen keinen Nutzen. Ein Golem wiegt + * soviel wie ein Stein. Kann nicht im Sumpf gezaubert werden + * + * Flag: + * (SPELLLEVEL) + * + * #define GOLEM_IRON 4 + */ + +static int sp_create_irongolem(castorder * co) +{ + unit *u2; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int number = lovar(force * 8 * RESOURCE_QUANTITY); + if (number < 1) + number = 1; + + if (r->terrain == newterrain(T_SWAMP)) { + cmistake(mage, co->order, 188, MSG_MAGIC); + return 0; + } + + u2 = + create_unit(r, mage->faction, number, rc_find("irongolem"), 0, NULL, mage); + + set_level(u2, SK_ARMORER, 1); + set_level(u2, SK_WEAPONSMITH, 1); + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 0; + a->data.ca[1] = IRONGOLEM_CRUMBLE; + a_add(&u2->attribs, a); + + ADDMSG(&mage->faction->msgs, + msg_message("magiccreate_effect", "region command unit amount object", + mage->region, co->order, mage, number, + LOC(mage->faction->locale, rc_name(rc_find("irongolem"), 1)))); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Steingolem + * Stufe: 1 + * Kategorie: Beschwoerung, positiv + * Gebiet: Gwyrrd + * Wirkung: + * Erschafft eine Einheit Steingolems mit Stufe*5 Golems. Jeder Golem + * hat jede Runde eine Chance von 10% zu Staub zu zerfallen. Gibt man + * den Golems den Befehl 'mache Burg' oder 'mache Strasse', so werden + * pro Golem 10 Steine verbaut und der Golem loest sich auf. + * + * Golems sind zu langsam um wirklich im Kampf von Nutzen zu sein. + * Jedoch fangen sie eine Menge Schaden auf und sollten sie zufaellig + * treffen, so ist der Schaden fast immer toedlich. (Steingolem: HP 60, + * AT 4, PA 2, Ruestung 4(PP), 2d12+6 TP) + * + * Golems nehmen nix an und geben nix. Sie bewegen sich immer nur 1 + * Region weit und ziehen aus Strassen keinen Nutzen. Ein Golem wiegt + * soviel wie ein Stein. + * + * Kann nicht im Sumpf gezaubert werden + * + * Flag: + * (SPELLLEVEL) + * + * #define GOLEM_STONE 4 + */ +static int sp_create_stonegolem(castorder * co) +{ + unit *u2; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + int number = lovar(co->force * 5 * RESOURCE_QUANTITY); + if (number < 1) + number = 1; + + if (r->terrain == newterrain(T_SWAMP)) { + cmistake(mage, co->order, 188, MSG_MAGIC); + return 0; + } + + u2 = + create_unit(r, mage->faction, number, rc_find("stonegolem"), 0, NULL, mage); + set_level(u2, SK_ROAD_BUILDING, 1); + set_level(u2, SK_BUILDING, 1); + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 0; + a->data.ca[1] = STONEGOLEM_CRUMBLE; + a_add(&u2->attribs, a); + + ADDMSG(&mage->faction->msgs, + msg_message("magiccreate_effect", "region command unit amount object", + mage->region, co->order, mage, number, + LOC(mage->faction->locale, rc_name(rc_find("stonegolem"), 1)))); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Große Duerre + * Stufe: 17 + * Kategorie: Region, negativ + * Gebiet: Gwyrrd + * + * Wirkung: + * 50% alle Bauern, Pferde, Baeume sterben. + * Zu 25% terraform: Gletscher wird mit 50% zu Sumpf, sonst Ozean, + * Sumpf wird zu Steppe, Ebene zur Steppe, Steppe zur Wueste. + * Besonderheiten: + * neuer Terraintyp Steppe: + * 5000 Felder, 500 Baeume, Strasse: 250 Steine. Anlegen wie in Ebene + * moeglich + * + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ + +static void destroy_all_roads(region * r) +{ + int i; + + for (i = 0; i < MAXDIRECTIONS; i++) { + rsetroad(r, (direction_t) i, 0); + } +} + +static int sp_great_drought(castorder * co) +{ + unit *u; + bool terraform = false; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = 2; + double effect; + + if (fval(r->terrain, SEA_REGION)) { + cmistake(mage, co->order, 189, MSG_MAGIC); + /* TODO: vielleicht einen netten Patzer hier? */ + return 0; + } + + /* sterben */ + rsetpeasants(r, rpeasants(r) / 2); /* evtl wuerfeln */ + rsettrees(r, 2, rtrees(r, 2) / 2); + rsettrees(r, 1, rtrees(r, 1) / 2); + rsettrees(r, 0, rtrees(r, 0) / 2); + rsethorses(r, rhorses(r) / 2); + + /* Arbeitslohn = 1/4 */ + effect = 4.0; /* curses: higher is stronger */ + create_curse(mage, &r->attribs, ct_find("drought"), force, duration, effect, + 0); + + /* terraforming */ + if (rng_int() % 100 < 25) { + terraform = true; + + switch (rterrain(r)) { + case T_PLAIN: + /* rsetterrain(r, T_GRASSLAND); */ + destroy_all_roads(r); + break; + + case T_SWAMP: + /* rsetterrain(r, T_GRASSLAND); */ + destroy_all_roads(r); + break; +/* + case T_GRASSLAND: + rsetterrain(r, T_DESERT); + destroy_all_roads(r); + break; +*/ + case T_GLACIER: + if (rng_int() % 100 < 50) { + rsetterrain(r, T_SWAMP); + destroy_all_roads(r); + } else { /* Ozean */ + destroy_all_roads(r); + rsetterrain(r, T_OCEAN); + /* Einheiten duerfen hier auf keinen Fall geloescht werden! */ + for (u = r->units; u; u = u->next) { + if (u_race(u) != new_race[RC_SPELL] && u->ship == 0) { + set_number(u, 0); + } + } + while (r->buildings) { + remove_building(&r->buildings, r->buildings); + } + } + break; + + default: + terraform = false; + break; + } + } + + if (!fval(r->terrain, SEA_REGION)) { + /* not destroying the region, so it should be safe to make this a local + * message */ + message *msg; + const char *mtype; + if (r->terrain == newterrain(T_SWAMP) && terraform) { + mtype = "drought_effect_1"; + } else if (!terraform) { + mtype = "drought_effect_2"; + } else { + mtype = "drought_effect_3"; + } + msg = msg_message(mtype, "mage region", mage, r); + add_message(&r->msgs, msg); + msg_release(msg); + } else { + /* possible that all units here get killed so better to inform with a global + * message */ + message *msg = msg_message("drought_effect_4", "mage region", mage, 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); + add_message(&u->faction->msgs, msg); + } + } + if (!fval(mage->faction, FFL_SELECT)) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: 'Weg der Baeume' + * Stufe: 9 + * Kategorie: Teleport + * Gebiet: Gwyrrd + * Wirkung: + * Der Druide kann 5*Stufe GE in die astrale Ebene schicken. + * Der Druide wird nicht mitteleportiert, es sei denn, er gibt sich + * selbst mit an. + * Der Zauber funktioniert nur in Waeldern. + * + * Syntax: Zauber "Weg der Baeume" ... + * + * Flags: + * (UNITSPELL | SPELLLEVEL | TESTCANSEE) + */ +static int sp_treewalkenter(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + spellparameter *pa = co->par; + double power = co->force; + int cast_level = co->level; + region *rt; + int remaining_cap; + int n; + int erfolg = 0; + + if (getplane(r) != 0) { + cmistake(mage, co->order, 190, MSG_MAGIC); + return 0; + } + + if (!r_isforest(r)) { + cmistake(mage, co->order, 191, MSG_MAGIC); + return 0; + } + + rt = r_standard_to_astral(r); + if (rt == NULL || is_cursed(rt->attribs, C_ASTRALBLOCK, 0) + || fval(rt->terrain, FORBIDDEN_REGION)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)(power * 500); + + /* fuer jede Einheit */ + for (n = 0; n < pa->length; n++) { + unit *u = pa->param[n]->data.u; + spllprm *param = pa->param[n]; + + if (param->flag & (TARGET_RESISTS | TARGET_NOTFOUND)) { + continue; + } + + if (!ucontact(u, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact", "target", u)); + } else { + int w; + message *m; + unit *u2; + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + continue; + } + + w = weight(u); + if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + continue; + } + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + erfolg = cast_level; + + /* Meldungen in der Ausgangsregion */ + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: 'Sog des Lebens' + * Stufe: 9 + * Kategorie: Teleport + * Gebiet: Gwyrrd + * Wirkung: + * Der Druide kann 5*Stufe GE aus die astrale Ebene schicken. Der + * Druide wird nicht mitteleportiert, es sei denn, er gibt sich selbst + * mit an. + * Der Zauber funktioniert nur, wenn die Zielregion ein Wald ist. + * + * Syntax: Zauber "Sog des Lebens" ... + * + * Flags: + * (UNITSPELL|SPELLLEVEL) + */ +static int sp_treewalkexit(castorder * co) +{ + region *rt; + region_list *rl, *rl2; + int tax, tay; + unit *u, *u2; + int remaining_cap; + int n; + int erfolg = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + double power = co->force; + spellparameter *pa = co->par; + int cast_level = co->level; + + if (!is_astral(r)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralonly", "")); + return 0; + } + if (is_cursed(r->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)(power * 500); + + if (pa->param[0]->typ != SPP_REGION) { + report_failure(mage, co->order); + return 0; + } + + /* Koordinaten setzen und Region loeschen fuer Überpruefung auf + * Gueltigkeit */ + rt = pa->param[0]->data.r; + tax = rt->x; + tay = rt->y; + rt = NULL; + + rl = astralregions(r, inhabitable); + rt = 0; + + rl2 = rl; + while (rl2) { + if (rl2->data->x == tax && rl2->data->y == tay) { + rt = rl2->data; + break; + } + rl2 = rl2->next; + } + free_regionlist(rl); + + if (!rt) { + cmistake(mage, co->order, 195, MSG_MAGIC); + return 0; + } + + if (!r_isforest(rt)) { + cmistake(mage, co->order, 196, MSG_MAGIC); + return 0; + } + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 1; n < pa->length; n++) { + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact", "target", u)); + } else { + int w = weight(u); + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } else { + message *m; + + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + erfolg = cast_level; + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + } + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: Heiliger Boden + * Stufe: 9 + * Kategorie: perm. Regionszauber + * Gebiet: Gwyrrd + * Wirkung: + * Es entstehen keine Untoten mehr, Untote betreten die Region + * nicht mehr. + * + * ZAUBER "Heiliger Boden" + * Flags: (0) + */ +static int sp_holyground(castorder * co) +{ + static const curse_type *ctype = NULL; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + message *msg = msg_message("sp_holyground_effect", "mage region", mage, r); + report_spell(mage, r, msg); + msg_release(msg); + + if (!ctype) { + ctype = ct_find("holyground"); + } + create_curse(mage, &r->attribs, ctype, power * power, 1, zero_effect, 0); + + a_removeall(&r->attribs, &at_deathcount); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Heimstein + * Stufe: 7 + * Kategorie: Artefakt + * Gebiet: Gwyrrd + * Wirkung: + * Die Burg kann nicht mehr durch Donnerbeben oder andere + * Gebaeudezerstoerenden Sprueche kaputt gemacht werden. Auch + * schuetzt der Zauber vor Belagerungskatapulten. + * + * ZAUBER Heimstein + * Flags: (0) + */ +static int sp_homestone(castorder * co) +{ + unit *u; + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + double effect; + message *msg; + if (!mage->building || mage->building->type != bt_find("castle")) { + cmistake(mage, co->order, 197, MSG_MAGIC); + return 0; + } + + c = create_curse(mage, &mage->building->attribs, ct_find("magicwalls"), + force * force, 1, zero_effect, 0); + + if (c == NULL) { + cmistake(mage, co->order, 206, MSG_MAGIC); + return 0; + } + c_setflag(c, CURSE_NOAGE | CURSE_ONLYONE); + + /* Magieresistenz der Burg erhoeht sich um 50% */ + effect = 50; + c = create_curse(mage, &mage->building->attribs, + ct_find("magicresistance"), force * force, 1, effect, 0); + c_setflag(c, CURSE_NOAGE); + + /* melden, 1x pro Partei in der Burg */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + msg = msg_message("homestone_effect", "mage building", mage, mage->building); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (u->building == mage->building) { + r_addmessage(r, u->faction, msg); + } + } + } + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Duerre + * Stufe: 13 + * Kategorie: Region, negativ + * Gebiet: Gwyrrd + * Wirkung: + * temporaer veraendert sich das Baummaximum und die maximalen Felder in + * einer Region auf die Haelfte des normalen. + * Die Haelfte der Baeume verdorren und Pferde verdursten. + * Arbeiten bringt nur noch 1/4 des normalen Verdienstes + * + * Flags: + * (FARCASTING|REGIONSPELL|TESTRESISTANCE), + */ +static int sp_drought(castorder * co) +{ + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + int duration = (int)power + 1; + message *msg; + + if (fval(r->terrain, SEA_REGION)) { + cmistake(mage, co->order, 189, MSG_MAGIC); + /* TODO: vielleicht einen netten Patzer hier? */ + return 0; + } + + /* melden, 1x pro Partei */ + msg = msg_message("sp_drought_effect", "mage region", mage, r); + report_spell(mage, r, msg); + msg_release(msg); + + /* Wenn schon Duerre herrscht, dann setzen wir nur den Power-Level + * hoch (evtl dauert dann die Duerre laenger). Ansonsten volle + * Auswirkungen. + */ + c = get_curse(r->attribs, ct_find("drought")); + if (c) { + c->vigour = MAX(c->vigour, power); + c->duration = MAX(c->duration, (int)power); + } else { + double effect; + /* Baeume und Pferde sterben */ + rsettrees(r, 2, rtrees(r, 2) / 2); + rsettrees(r, 1, rtrees(r, 1) / 2); + rsettrees(r, 0, rtrees(r, 0) / 2); + rsethorses(r, rhorses(r) / 2); + + effect = 4.0; + create_curse(mage, &r->attribs, ct_find("drought"), power, duration, effect, + 0); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Bergwaechter + * Stufe: 9 + * Gebiet: Gwyrrd + * Kategorie: Beschwoerung, negativ + * + * Wirkung: + * Erschafft in Bergen oder Gletschern einen Waechter, der durch bewachen + * den Eisen/Laen-Abbau fuer nicht-Allierte verhindert. Bergwaechter + * verhindern auch Abbau durch getarnte/unsichtbare Einheiten und lassen + * sich auch durch Belagerungen nicht aufhalten. + * + * (Ansonsten in economic.c:manufacture() entsprechend anpassen). + * + * Faehigkeiten (factypes.c): 50% Magieresistenz, 25 HP, 4d4 Schaden, + * 4 Ruestung (=PP) + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_ironkeeper(castorder * co) +{ + unit *keeper; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + + if (r->terrain != newterrain(T_MOUNTAIN) + && r->terrain != newterrain(T_GLACIER)) { + report_failure(mage, co->order); + return 0; + } + + keeper = + create_unit(r, mage->faction, 1, new_race[RC_IRONKEEPER], 0, NULL, mage); + + /*keeper->age = cast_level + 2; */ + setstatus(keeper, ST_AVOID); /* kaempft nicht */ + guard(keeper, GUARD_MINING); + fset(keeper, UFL_ISNEW); + /* Parteitarnen, damit man nicht sofort weiß, wer dahinter steckt */ + if (rule_stealth_faction()) { + fset(keeper, UFL_ANON_FACTION); + } + + { + trigger *tkill = trigger_killunit(keeper); + add_trigger(&keeper->attribs, "timer", trigger_timeout(cast_level + 2, + tkill)); + } + + msg = msg_message("summon_effect", "mage amount race", mage, 1, u_race(keeper)); + r_addmessage(r, NULL, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Sturmwind - Beschwoere einen Sturmelementar + * Stufe: 6 + * Gebiet: Gwyrrd + * + * Wirkung: + * Verdoppelt Geschwindigkeit aller angegebener Schiffe fuer diese + * Runde. Kombinierbar mit "Guenstige Winde", aber nicht mit + * "Luftschiff". + * + * Anstelle des alten ship->enchanted benutzen wir einen kurzfristigen + * Curse. Das ist zwar ein wenig aufwendiger, aber weitaus flexibler + * und erlaubt es zB, die Dauer spaeter problemlos zu veraendern. + * + * Flags: + * (SHIPSPELL|ONSHIPCAST|OCEANCASTABLE|TESTRESISTANCE) + */ + +static int sp_stormwinds(castorder * co) +{ + ship *sh; + unit *u; + int erfolg = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + double power = co->force; + spellparameter *pa = co->par; + int n, force = (int)power; + message *m = NULL; + + /* melden vorbereiten */ + freset(mage->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + + for (n = 0; n < pa->length; n++) { + if (force <= 0) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + sh = pa->param[n]->data.sh; + + /* mit C_SHIP_NODRIFT haben wir kein Problem */ + if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_spell_on_flying_ship", "ship", sh)) + continue; + } + if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_spell_on_ship_already", "ship", sh)) + continue; + } + + /* Duration = 1, nur diese Runde */ + create_curse(mage, &sh->attribs, ct_find("stormwind"), power, 1, + zero_effect, 0); + /* Da der Spruch nur diese Runde wirkt wird er nie im Report + * erscheinen */ + erfolg++; + force--; + + /* melden vorbereiten: */ + for (u = r->units; u; u = u->next) { + if (u->ship == sh) { + /* nur den Schiffsbesatzungen! */ + fset(u->faction, FFL_SELECT); + } + } + } + if (erfolg < pa->length) { + ADDMSG(&mage->faction->msgs, msg_message("stormwinds_reduced", + "unit ships maxships", mage, erfolg, pa->length)); + } + /* melden, 1x pro Partei auf Schiff und fuer den Magier */ + fset(mage->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + if (fval(u->faction, FFL_SELECT)) { + freset(u->faction, FFL_SELECT); + if (erfolg > 0) { + if (!m) { + m = msg_message("stormwinds_effect", "unit", mage); + } + r_addmessage(r, u->faction, m); + } + } + } + if (m) + msg_release(m); + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: Donnerbeben + * Stufe: 6 + * Gebiet: Gwyrrd + * + * Wirkung: + * Zerstoert Stufe*10 "Steineinheiten" aller Gebaeude der Region, aber nie + * mehr als 25% des gesamten Gebaeudes (aber natuerlich mindestens ein + * Stein). + * + * Flags: + * (FARCASTING|REGIONSPELL|TESTRESISTANCE) + */ +static int sp_earthquake(castorder * co) +{ + int kaputt; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + building **blist = &r->buildings; + + while (*blist) { + building *burg = *blist; + + if (burg->size != 0 && !is_cursed(burg->attribs, C_MAGICWALLS, 0)) { + /* Magieresistenz */ + if (!target_resists_magic(mage, burg, TYP_BUILDING, 0)) { + kaputt = MIN(10 * cast_level, burg->size / 4); + kaputt = MAX(kaputt, 1); + burg->size -= kaputt; + if (burg->size == 0) { + /* TODO: sollten die Insassen nicht Schaden nehmen? */ + remove_building(blist, burg); + } + } + } + if (*blist == burg) + blist = &burg->next; + } + + /* melden, 1x pro Partei */ + msg = msg_message("earthquake_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* CHAOS / M_DRAIG / Draig */ +/* ------------------------------------------------------------- */ +void patzer_peasantmob(castorder * co) +{ + int anteil = 6, n; + unit *u; + attrib *a; + region *r; + unit *mage = co->magician.u; + + r = mage->region->land ? mage->region : co_get_region(co); + + if (r->land) { + faction *f = get_monsters(); + const struct locale *lang = f->locale; + message *msg; + + anteil += rng_int() % 4; + n = rpeasants(r) * anteil / 10; + rsetpeasants(r, rpeasants(r) - n); + assert(rpeasants(r) >= 0); + + u = + create_unit(r, f, n, new_race[RC_PEASANT], 0, LOC(f->locale, "angry_mob"), + NULL); + fset(u, UFL_ISNEW); + /* guard(u, GUARD_ALL); hier zu frueh! Befehl BEWACHE setzten */ + addlist(&u->orders, create_order(K_GUARD, lang, NULL)); + set_order(&u->thisorder, default_order(lang)); + a = a_new(&at_unitdissolve); + a->data.ca[0] = 1; /* An rpeasants(r). */ + a->data.ca[1] = 10; /* 10% */ + a_add(&u->attribs, a); + a_add(&u->attribs, make_hate(mage)); + + msg = msg_message("mob_warning", ""); + r_addmessage(r, NULL, msg); + msg_release(msg); + } + return; +} + +/* ------------------------------------------------------------- */ +/* Name: Waldbrand + * Stufe: 10 + * Kategorie: Region, negativ + * Gebiet: Draig + * Wirkung: + * Vernichtet 10-80% aller Baeume in der Region. Kann sich auf benachbarte + * Regionen ausbreiten, wenn diese (stark) bewaldet sind. Fuer jeweils + * 10 verbrannte Baeume in der Startregion gibts es eine 1%-Chance, dass + * sich das Feuer auf stark bewaldete Nachbarregionen ausdehnt, auf + * bewaldeten mit halb so hoher Wahrscheinlichkeit. Dort verbrennen + * dann prozentual halbsoviele bzw ein viertel soviele Baeume wie in der + * Startregion. + * + * Im Extremfall: 1250 Baeume in Region, 80% davon verbrennen (1000). + * Dann breitet es sich mit 100% Chance in stark bewaldete Regionen + * aus, mit 50% in bewaldete. Dort verbrennen dann 40% bzw 20% der Baeume. + * Weiter als eine Nachbarregion breitet sich dass Feuer nicht aus. + * + * Sinn: Ein Feuer in einer "stark bewaldeten" Wueste hat so trotzdem kaum + * eine Chance, sich weiter auszubreiten, waehrend ein Brand in einem Wald + * sich fast mit Sicherheit weiter ausbreitet. + * + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_forest_fire(castorder * co) +{ + unit *u; + region *nr; + direction_t i; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double probability; + double percentage = (rng_int() % 8 + 1) * 0.1; /* 10 - 80% */ + message *msg; + + int vernichtet_schoesslinge = (int)(rtrees(r, 1) * percentage); + int destroyed = (int)(rtrees(r, 2) * percentage); + + if (destroyed < 1) { + cmistake(mage, co->order, 198, MSG_MAGIC); + return 0; + } + + rsettrees(r, 2, rtrees(r, 2) - destroyed); + rsettrees(r, 1, rtrees(r, 1) - vernichtet_schoesslinge); + probability = destroyed * 0.001; /* Chance, dass es sich ausbreitet */ + + /* melden, 1x pro Partei */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + msg = + msg_message("forestfire_effect", "mage region amount", mage, r, + destroyed + vernichtet_schoesslinge); + r_addmessage(r, NULL, msg); + add_message(&mage->faction->msgs, msg); + msg_release(msg); + + for (i = 0; i < MAXDIRECTIONS; i++) { + nr = rconnect(r, i); + assert(nr); + destroyed = 0; + vernichtet_schoesslinge = 0; + + if (rtrees(nr, 2) + rtrees(nr, 1) >= 800) { + if (chance(probability)) { + destroyed = (int)(rtrees(nr, 2) * percentage / 2); + vernichtet_schoesslinge = (int)(rtrees(nr, 1) * percentage / 2); + } + } else if (rtrees(nr, 2) + rtrees(nr, 1) >= 600) { + if (chance(probability / 2)) { + destroyed = (int)(rtrees(nr, 2) * percentage / 4); + vernichtet_schoesslinge = (int)(rtrees(nr, 1) * percentage / 4); + } + } + + if (destroyed > 0 || vernichtet_schoesslinge > 0) { + message *m = msg_message("forestfire_spread", "region next trees", + r, nr, destroyed + vernichtet_schoesslinge); + + add_message(&r->msgs, m); + add_message(&mage->faction->msgs, m); + msg_release(m); + + rsettrees(nr, 2, rtrees(nr, 2) - destroyed); + rsettrees(nr, 1, rtrees(nr, 1) - vernichtet_schoesslinge); + } + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Chaosfluch + * Stufe: 5 + * Gebiet: Draig + * Kategorie: (Antimagie) Kraftreduzierer, Einheit, negativ + * Wirkung: + * Auf einen Magier gezaubert verhindert/erschwert dieser Chaosfluch + * das Zaubern. Patzer werden warscheinlicher. + * Jeder Zauber muss erst gegen den Wiederstand des Fluchs gezaubert + * werden und schwaecht dessen Antimagiewiederstand um 1. + * Wirkt MAX(Stufe(Magier) - Stufe(Ziel), rand(3)) Wochen + * Patzer: + * Magier wird selbst betroffen + * + * Flags: + * (UNITSPELL | SPELLLEVEL | TESTCANSEE | TESTRESISTANCE) + * + */ +static int sp_fumblecurse(castorder * co) +{ + unit *target; + int rx, sx; + int duration; + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + double effect; + curse *c; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + rx = rng_int() % 3; + sx = cast_level - effskill(target, SK_MAGIC); + duration = MAX(sx, rx) + 1; + + effect = force * 0.5; + c = create_curse(mage, &target->attribs, ct_find("fumble"), + force, duration, effect, 0); + if (c == NULL) { + report_failure(mage, co->order); + return 0; + } + + ADDMSG(&target->faction->msgs, msg_message("fumblecurse", "unit region", + target, target->region)); + + return cast_level; +} + +void patzer_fumblecurse(castorder * co) +{ + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = (cast_level / 2) + 1; + double effect; + curse *c; + + effect = force * 0.5; + c = create_curse(mage, &mage->attribs, ct_find("fumble"), force, + duration, effect, 0); + if (c != NULL) { + ADDMSG(&mage->faction->msgs, msg_message("magic_fumble", + "unit region command", mage, mage->region, co->order)); + } + return; +} + +/* ------------------------------------------------------------- */ +/* Name: Drachenruf + * Stufe: 11 + * Gebiet: Draig + * Kategorie: Monster, Beschwoerung, negativ + * + * Wirkung: + * In einer Wueste, Sumpf oder Gletscher gezaubert kann innerhalb der + * naechsten 6 Runden ein bis 6 Dracheneinheiten bis Groeße Wyrm + * entstehen. + * + * Mit Stufe 12-15 erscheinen Jung- oder normaler Drachen, mit Stufe + * 16+ erscheinen normale Drachen oder Wyrme. + * + * Flag: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_summondragon(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + unit *u; + int cast_level = co->level; + double power = co->force; + region_list *rl, *rl2; + faction *f; + int time; + int number; + const race *race; + + f = get_monsters(); + + if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT) + && r->terrain != newterrain(T_GLACIER)) { + report_failure(mage, co->order); + return 0; + } + + for (time = 1; time < 7; time++) { + if (rng_int() % 100 < 25) { + switch (rng_int() % 3) { + case 0: + race = new_race[RC_WYRM]; + number = 1; + break; + + case 1: + race = new_race[RC_DRAGON]; + number = 2; + break; + + case 2: + default: + race = new_race[RC_FIREDRAGON]; + number = 6; + break; + } + { + trigger *tsummon = trigger_createunit(r, f, race, number); + add_trigger(&r->attribs, "timer", trigger_timeout(time, tsummon)); + } + } + } + + rl = all_in_range(r, (short)power, NULL); + + for (rl2 = rl; rl2; rl2 = rl2->next) { + region *r2 = rl2->data; + for (u = r2->units; u; u = u->next) { + if (u_race(u) == new_race[RC_WYRM] || u_race(u) == new_race[RC_DRAGON]) { + attrib *a = a_find(u->attribs, &at_targetregion); + if (!a) { + a = a_add(&u->attribs, make_targetregion(r)); + } else { + a->data.v = r; + } + } + } + } + + ADDMSG(&mage->faction->msgs, msg_message("summondragon", + "unit region command target", mage, mage->region, co->order, r)); + + free_regionlist(rl); + return cast_level; +} + +static int sp_firewall(castorder * co) +{ + connection *b; + wall_data *fd; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + direction_t dir; + region *r2; + + dir = finddirection(pa->param[0]->data.xs, mage->faction->locale); + if (dir < MAXDIRECTIONS && dir != NODIRECTION) { + r2 = rconnect(r, dir); + } else { + report_failure(mage, co->order); + return 0; + } + + if (!r2 || r2 == r) { + report_failure(mage, co->order); + return 0; + } + + b = get_borders(r, r2); + while (b != NULL) { + if (b->type == &bt_firewall) + break; + b = b->next; + } + if (b == NULL) { + b = new_border(&bt_firewall, r, r2); + fd = (wall_data *) b->data.v; + fd->force = (int)(force / 2 + 0.5); + fd->mage = mage; + fd->active = false; + fd->countdown = cast_level + 1; + } else { + fd = (wall_data *) b->data.v; + fd->force = (int)MAX(fd->force, force / 2 + 0.5); + fd->countdown = MAX(fd->countdown, cast_level + 1); + } + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("firewall_effect", "mage region", mage, r); + message *unseen = msg_message("firewall_effect", "mage region", NULL, r); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Unheilige Kraft + * Stufe: 10 + * Gebiet: Draig + * Kategorie: Untote Einheit, positiv + * + * Wirkung: + * transformiert (Stufe)W10 Untote in ihre staerkere Form + * + * + * Flag: + * (SPELLLEVEL | TESTCANSEE) + */ + +static int sp_unholypower(castorder * co) +{ + region * r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + int i; + int n; + int wounds; + + n = dice((int)co->force, 10); + + for (i = 0; i < pa->length && n > 0; i++) { + const race *target_race; + unit *u; + + if (pa->param[i]->flag == TARGET_RESISTS + || pa->param[i]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[i]->data.u; + + switch (old_race(u_race(u))) { + case RC_SKELETON: + target_race = new_race[RC_SKELETON_LORD]; + break; + case RC_ZOMBIE: + target_race = new_race[RC_ZOMBIE_LORD]; + break; + case RC_GHOUL: + target_race = new_race[RC_GHOUL_LORD]; + break; + default: + cmistake(mage, co->order, 284, MSG_MAGIC); + continue; + } + /* Untote heilen nicht, darum den neuen Untoten maximale hp geben + * und vorhandene Wunden abziehen */ + wounds = unit_max_hp(u) * u->number - u->hp; + + if (u->number <= n) { + n -= u->number; + u->irace = NULL; + u_setrace(u, target_race); + u->hp = unit_max_hp(u) * u->number - wounds; + ADDMSG(&r->msgs, msg_message("unholypower_effect", + "mage target race", mage, u, target_race)); + } else { + unit *un; + + /* Wird hoffentlich niemals vorkommen. Es gibt im Source + * vermutlich eine ganze Reihe von Stellen, wo das nicht + * korrekt abgefangen wird. Besser (aber nicht gerade einfach) + * waere es, eine solche Konstruktion irgendwie zu kapseln. */ + if (fval(u, UFL_LOCKED) || fval(u, UFL_HUNGER) + || is_cursed(u->attribs, C_SLAVE, 0)) { + cmistake(mage, co->order, 74, MSG_MAGIC); + continue; + } + /* Verletzungsanteil der transferierten Personen berechnen */ + wounds = wounds * n / u->number; + + un = create_unit(r, u->faction, 0, target_race, 0, NULL, u); + transfermen(u, un, n); + un->hp = unit_max_hp(un) * n - wounds; + ADDMSG(&r->msgs, msg_message("unholypower_limitedeffect", + "mage target race amount", mage, u, target_race, n)); + n = 0; + } + } + + return cast_level; +} + +static int dc_age(struct curse *c) +/* age returns 0 if the attribute needs to be removed, !=0 otherwise */ +{ + region *r = (region *) c->data.v; + unit **up; + unit *mage = c->magician; + + if (r == NULL || mage == NULL || mage->number == 0) { + /* if the mage disappears, so does the spell. */ + return AT_AGE_REMOVE; + } + + up = &r->units; + if (curse_active(c)) + while (*up != NULL) { + unit *u = *up; + double damage = c->effect * u->number; + + freset(u->faction, FFL_SELECT); + if (u->number <= 0 || target_resists_magic(mage, u, TYP_UNIT, 0)) { + up = &u->next; + continue; + } + + /* Reduziert durch Magieresistenz */ + damage *= (1.0 - magic_resistance(u)); + change_hitpoints(u, -(int)damage); + + if (*up == u) + up = &u->next; + } + + return AT_AGE_KEEP; +} + +static struct curse_type ct_deathcloud = { + "deathcloud", CURSETYP_REGION, 0, NO_MERGE, cinfo_simple, NULL, NULL, NULL, + NULL, dc_age +}; + +static curse *mk_deathcloud(unit * mage, region * r, double force, int duration) +{ + double effect; + curse *c; + + effect = force * 0.5; + c = + create_curse(mage, &r->attribs, &ct_deathcloud, force, duration, effect, 0); + c->data.v = r; + return c; +} + +#define COMPAT_DEATHCLOUD +#ifdef COMPAT_DEATHCLOUD +static int dc_read_compat(struct attrib *a, void *target, storage * store) +/* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */ +{ + region *r = NULL; + unit *u; + variant var; + int duration = store->r_int(store); + double strength = store->r_flt(store); + short rx, ry; + + var.i = store->r_id(store); + u = findunit(var.i); + + /* this only affects really old data. no need to change: */ + rx = (short)store->r_int(store); + ry = (short)store->r_int(store); + r = findregion(rx, ry); + + if (r != NULL) { + double effect; + curse *c; + + effect = strength; + c = + create_curse(u, &r->attribs, &ct_deathcloud, strength * 2, duration, + effect, 0); + c->data.v = r; + if (u == NULL) { + ur_add(var, &c->magician, resolve_unit); + } + } + return AT_READ_FAIL; /* we don't care for the attribute. */ +} + +attrib_type at_deathcloud_compat = { + "zauber_todeswolke", NULL, NULL, NULL, NULL, dc_read_compat +}; +#endif + +/* ------------------------------------------------------------- */ +/* Name: Todeswolke +* Stufe: 11 +* Gebiet: Draig +* Kategorie: Region, negativ +* +* Wirkung: +* Personen in der Region verlieren stufe/2 Trefferpunkte pro Runde. +* Dauer force/2 +* Wirkt gegen MR +* Ruestung wirkt nicht +* Patzer: +* Magier geraet in den Staub und verliert zufaellige Zahl von HP bis +* auf MAX(hp,2) +* Besonderheiten: +* Nicht als curse implementiert, was schlecht ist - man kann dadurch +* kein dispell machen. Wegen fix unter Zeitdruck erstmal nicht zu +* aendern... +* Missbrauchsmoeglichkeit: +* Hat der Magier mehr HP als Rasse des Feindes (extrem: Daemon/Goblin) +* so kann er per Farcasting durch mehrmaliges Zaubern eine +* Nachbarregion ausloeschen. Darum sollte dieser Spruch nur einmal auf +* eine Region gelegt werden koennen. +* +* Flag: +* (FARCASTING | REGIONSPELL | TESTRESISTANCE) +*/ + +static int sp_deathcloud(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + attrib *a = r->attribs; + unit *u; + + while (a) { + if ((a->type->flags & ATF_CURSE)) { + curse *c = a->data.v; + if (c->type == &ct_deathcloud) { + report_failure(mage, co->order); + return 0; + } + a = a->next; + } else + a = a->nexttype; + } + + mk_deathcloud(mage, r, co->force, co->level); + + /* melden, 1x pro Partei */ + 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("deathcloud_effect", + "mage region", cansee(u->faction, r, mage, 0) ? mage : NULL, r)); + } + } + + if (!fval(mage->faction, FFL_SELECT)) { + ADDMSG(&mage->faction->msgs, msg_message("deathcloud_effect", + "mage region", mage, r)); + } + + return co->level; +} + +void patzer_deathcloud(castorder * co) +{ + unit *mage = co->magician.u; + int hp = (mage->hp - 2); + + change_hitpoints(mage, -rng_int() % hp); + + ADDMSG(&mage->faction->msgs, msg_message("magic_fumble", + "unit region command", mage, mage->region, co->order)); + + return; +} + +/* ------------------------------------------------------------- */ +/* Name: Pest + * Stufe: 7 + * Gebiet: Draig + * Wirkung: + * ruft eine Pest in der Region hervor. + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + * Syntax: ZAUBER [REGION x y] "Pest" + */ +static int sp_plague(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + plagues(r, true); + + ADDMSG(&mage->faction->msgs, msg_message("plague_spell", + "region mage", r, mage)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Beschwoere Schattendaemon + * Stufe: 8 + * Gebiet: Draig + * Kategorie: Beschwoerung, positiv + * Wirkung: + * Der Magier beschwoert Stufe^2 Schattendaemonen. + * Schattendaemonen haben Tarnung = (Magie_Magier+ Tarnung_Magier)/2 und + * Wahrnehmung 1. Sie haben einen Attacke-Bonus von 8, einen + * Verteidigungsbonus von 11 und machen 2d3 Schaden. Sie entziehen bei + * einem Treffer dem Getroffenen einen Attacke- oder + * Verteidigungspunkt. (50% Chance.) Sie haben 25 Hitpoints und + * Ruestungsschutz 3. + * Flag: + * (SPELLLEVEL) + */ +static int sp_summonshadow(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + unit *u; + int val, number = (int)(force * force); + + u = create_unit(r, mage->faction, number, new_race[RC_SHADOW], 0, NULL, mage); + + /* Bekommen Tarnung = (Magie+Tarnung)/2 und Wahrnehmung 1. */ + val = get_level(mage, SK_MAGIC) + get_level(mage, SK_STEALTH); + + set_level(u, SK_STEALTH, val); + set_level(u, SK_PERCEPTION, 1); + + ADDMSG(&mage->faction->msgs, msg_message("summonshadow_effect", + "mage number", mage, number)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Beschwoere Schattenmeister + * Stufe: 12 + * Gebiet: Draig + * Kategorie: Beschwoerung, positiv + * Wirkung: + * Diese hoeheren Schattendaemonen sind erheblich gefaehrlicher als die + * einfachen Schattendaemonen. Sie haben Tarnung entsprechend dem + * Magietalent des Beschwoerer-1 und Wahrnehmung 5, 75 HP, + * Ruestungsschutz 4, Attacke-Bonus 11 und Verteidigungsbonus 13, machen + * bei einem Treffer 2d4 Schaden, entziehen einen Staerkepunkt und + * entziehen 5 Talenttage in einem zufaelligen Talent. + * Stufe^2 Daemonen. + * + * Flag: + * (SPELLLEVEL) + * */ +static int sp_summonshadowlords(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int amount = (int)(force * force); + + u = + create_unit(r, mage->faction, amount, new_race[RC_SHADOWLORD], 0, NULL, + mage); + + /* Bekommen Tarnung = Magie und Wahrnehmung 5. */ + set_level(u, SK_STEALTH, get_level(mage, SK_MAGIC)); + set_level(u, SK_PERCEPTION, 5); + + ADDMSG(&mage->faction->msgs, msg_message("summon_effect", "mage amount race", + mage, amount, u_race(u))); + return cast_level; +} + +static bool chaosgate_valid(const connection * b) +{ + const attrib *a = a_findc(b->from->attribs, &at_direction); + if (!a) + a = a_findc(b->to->attribs, &at_direction); + if (!a) + return false; + return true; +} + +struct region *chaosgate_move(const connection * b, struct unit *u, + struct region *from, struct region *to, bool routing) +{ + if (!routing) { + int maxhp = u->hp / 4; + if (maxhp < u->number) + maxhp = u->number; + u->hp = maxhp; + } + return to; +} + +border_type bt_chaosgate = { + "chaosgate", VAR_NONE, + b_transparent, /* transparent */ + NULL, /* init */ + NULL, /* destroy */ + NULL, /* read */ + NULL, /* write */ + b_blocknone, /* block */ + NULL, /* name */ + b_rinvisible, /* rvisible */ + b_finvisible, /* fvisible */ + b_uinvisible, /* uvisible */ + chaosgate_valid, + chaosgate_move +}; + +/* ------------------------------------------------------------- */ +/* Name: Chaossog + * Stufe: 14 + * Gebiet: Draig + * Kategorie: Teleport + * Wirkung: + * Durch das Opfern von 200 Bauern kann der Chaosmagier ein Tor zur + * astralen Welt oeffnen. Das Tor kann im Folgemonat verwendet werden, + * es loest sich am Ende des Folgemonats auf. + * + * Flag: (0) + */ +static int sp_chaossuction(castorder * co) +{ + region *rt; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + if (getplane(r) != get_normalplane()) { + /* Der Zauber funktioniert nur in der materiellen Welt. */ + cmistake(mage, co->order, 190, MSG_MAGIC); + return 0; + } + + rt = r_standard_to_astral(r); + + if (rt == NULL || fval(rt->terrain, FORBIDDEN_REGION)) { + /* Hier gibt es keine Verbindung zur astralen Welt. */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } else if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + /* TODO: implement with a building */ + create_special_direction(r, rt, 2, "vortex_desc", "vortex"); + create_special_direction(rt, r, 2, "vortex_desc", "vortex"); + new_border(&bt_chaosgate, r, rt); + + add_message(&r->msgs, msg_message("chaosgate_effect_1", "mage", mage)); + add_message(&rt->msgs, msg_message("chaosgate_effect_2", "")); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Magic Boost - Gabe des Chaos + * Stufe: 3 + * Gebiet: Draig + * Kategorie: Einheit, positiv + * + * Wirkung: + * Erhoeht die maximalen Magiepunkte und die monatliche Regeneration auf + * das doppelte. Dauer: 4 Wochen Danach sinkt beides auf die Haelfte des + * normalen ab. + * Dauer: 6 Wochen + * Patzer: + * permanenter Stufen- (Talenttage), Regenerations- oder maxMP-Verlust + * Besonderheiten: + * Patzer koennen waehrend der Zauberdauer haeufiger auftreten derzeit + * +10% + * + * Flag: + * (ONSHIPCAST) + */ + +static int sp_magicboost(castorder * co) +{ + curse *c; + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + double effect; + trigger *tsummon; + static const curse_type *ct_auraboost; + static const curse_type *ct_magicboost; + + if (!ct_auraboost) { + ct_auraboost = ct_find("auraboost"); + ct_magicboost = ct_find("magicboost"); + assert(ct_auraboost != NULL); + assert(ct_magicboost != NULL); + } + /* fehler, wenn schon ein boost */ + if (is_cursed(mage->attribs, C_MBOOST, 0)) { + report_failure(mage, co->order); + return 0; + } + + effect = 6; + c = create_curse(mage, &mage->attribs, ct_magicboost, power, 10, effect, 1); + + /* one aura boost with 200% aura now: */ + effect = 200; + c = create_curse(mage, &mage->attribs, ct_auraboost, power, 4, effect, 1); + + /* and one aura boost with 50% aura in 5 weeks: */ + tsummon = trigger_createcurse(mage, mage, ct_auraboost, power, 6, 50, 1); + add_trigger(&mage->attribs, "timer", trigger_timeout(5, tsummon)); + + ADDMSG(&mage->faction->msgs, msg_message("magicboost_effect", + "unit region command", c->magician, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: kleines Blutopfer + * Stufe: 4 + * Gebiet: Draig + * Kategorie: Einheit, positiv + * + * Wirkung: + * Hitpoints to Aura: + * skill < 8 = 4:1 + * skill < 12 = 3:1 + * skill < 15 = 2:1 + * skill < 18 = 1:2 + * skill > = 2:1 + * Patzer: + * permanenter HP verlust + * + * Flag: + * (ONSHIPCAST) + */ +static int sp_bloodsacrifice(castorder * co) +{ + unit *mage = co->magician.u; + int cast_level = co->level; + int aura; + int skill = eff_skill(mage, SK_MAGIC, mage->region); + int hp = (int)(co->force * 8); + + if (hp <= 0) { + report_failure(mage, co->order); + return 0; + } + + aura = lovar(hp); + + if (skill < 8) { + aura /= 4; + } else if (skill < 12) { + aura /= 3; + } else if (skill < 15) { + aura /= 2; + /* von 15 bis 17 ist hp = aura */ + } else if (skill > 17) { + aura *= 2; + } + + if (aura <= 0) { + report_failure(mage, co->order); + return 0; + } + + /* sicherheitshalber gibs hier einen HP gratis. sonst schaffen es + * garantiert ne ganze reihe von leuten ihren Magier damit umzubringen */ + mage->hp++; + change_spellpoints(mage, aura); + ADDMSG(&mage->faction->msgs, + msg_message("sp_bloodsacrifice_effect", + "unit region command amount", mage, mage->region, co->order, aura)); + return cast_level; +} + +/** gives a summoned undead unit some base skills. + */ +static void skill_summoned(unit * u, int level) +{ + if (level > 0) { + const race *rc = u_race(u); + skill_t sk; + for (sk = 0; sk != MAXSKILLS; ++sk) { + if (rc->bonus[sk] > 0) { + set_level(u, sk, level); + } + } + } +} + +/* ------------------------------------------------------------- */ +/* Name: Totenruf - Maechte des Todes + * Stufe: 6 + * Gebiet: Draig + * Kategorie: Beschwoerung, positiv + * Flag: FARCASTING + * Wirkung: + * Untote aus deathcounther ziehen, bis Stufe*10 Stueck + * + * Patzer: + * Erzeugt Monsteruntote + */ +static int sp_summonundead(castorder * co) +{ + int undead; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + int force = (int)(co->force * 10); + const race *race = new_race[RC_SKELETON]; + + if (!r->land || deathcount(r) == 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "error_nograves", + "target", r)); + return 0; + } + + undead = MIN(deathcount(r), 2 + lovar(force)); + + if (cast_level <= 8) { + race = new_race[RC_SKELETON]; + } else if (cast_level <= 12) { + race = new_race[RC_ZOMBIE]; + } else { + race = new_race[RC_GHOUL]; + } + + u = create_unit(r, mage->faction, undead, race, 0, NULL, mage); + make_undead_unit(u); + skill_summoned(u, cast_level / 2); + + ADDMSG(&mage->faction->msgs, msg_message("summonundead_effect_1", + "mage region amount", mage, r, undead)); + ADDMSG(&r->msgs, msg_message("summonundead_effect_2", "mage region", mage, + r)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Astraler Sog + * Stufe: 9 + * Gebiet: Draig + * Kategorie: Region, negativ + * Wirkung: + * Allen Magier in der betroffenen Region wird eine Teil ihrer + * Magischen Kraft in die Gefilde des Chaos entzogen Jeder Magier im + * Einflussbereich verliert Stufe(Zaubernden)*5% seiner Magiepunkte. + * Keine Regeneration in der Woche (fehlt noch) + * + * Flag: + * (REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_auraleak(castorder * co) +{ + int lost_aura; + double lost; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + + lost = MIN(0.95, cast_level * 0.05); + + for (u = r->units; u; u = u->next) { + if (is_mage(u)) { + /* Magieresistenz Einheit? Bei gegenerischen Magiern nur sehr + * geringe Chance auf Erfolg wg erhoehter MR, wuerde Spruch sinnlos + * machen */ + lost_aura = (int)(get_spellpoints(u) * lost); + change_spellpoints(u, -lost_aura); + } + } + msg = msg_message("cast_auraleak_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* BARDE - CERDDOR*/ +/* ------------------------------------------------------------- */ +/* ------------------------------------------------------------- */ +/* Name: Magie analysieren - Gebaeude, Schiffe, Region + * Name: Lied des Ortes analysieren + * Stufe: 8 + * Gebiet: Cerddor + * + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + * + * Flag: + * (SPELLLEVEL|ONSHIPCAST) + */ +static int sp_analysesong_obj(castorder * co) +{ + int obj; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + magicanalyse_region(r, mage, force); + break; + + case SPP_BUILDING: + { + building *b = pa->param[0]->data.b; + magicanalyse_building(b, mage, force); + break; + } + case SPP_SHIP: + { + ship *sh = pa->param[0]->data.sh; + magicanalyse_ship(sh, mage, force); + break; + } + default: + /* Syntax fehlerhaft */ + return 0; + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang des Lebens analysieren + * Name: Magie analysieren - Unit + * Stufe: 5 + * Gebiet: Cerddor + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + * + * Flag: + * (UNITSPELL|ONSHIPCAST|TESTCANSEE) + */ +static int sp_analysesong_unit(castorder * co) +{ + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + + magicanalyse_unit(u, mage, force); + + return cast_level; +} + +static bool can_charm(const unit * u, int maxlevel) +{ + const skill_t expskills[] = + { SK_ALCHEMY, SK_HERBALISM, SK_MAGIC, SK_SPY, SK_TACTICS, NOSKILL }; + skill *sv = u->skills; + + if (fval(u, UFL_HERO)) + return false; + + for (; sv != u->skills + u->skill_size; ++sv) { + int l = 0, h = 5; + skill_t sk = sv->id; + assert(expskills[h] == NOSKILL); + while (l < h) { + int m = (l + h) / 2; + if (sk == expskills[m]) { + if (skill_limit(u->faction, sk) != INT_MAX) { + return false; + } else if ((int)sv->level > maxlevel) { + return false; + } + break; + } else if (sk > expskills[m]) + l = m + 1; + else + h = m; + } + } + return true; +} + +/* ------------------------------------------------------------- */ +/* Name: Charming + * Stufe: 13 + * Gebiet: Cerddor + * Flag: UNITSPELL + * Wirkung: + * bezauberte Einheit wechselt 'virtuell' die Partei und fuehrt fremde + * Befehle aus. + * Dauer: 3 - force+2 Wochen + * Wirkt gegen Magieresistenz + * + * wirkt auf eine Einheit mit maximal Talent Personen normal. Fuer jede + * zusaetzliche Person gibt es einen Bonus auf Magieresistenz, also auf + * nichtgelingen, von 10%. + * + * Das hoechste Talent der Einheit darf maximal so hoch sein wie das + * Magietalent des Magiers. Fuer jeden Talentpunkt mehr gibt es einen + * Bonus auf Magieresistenz von 15%, was dazu fuehrt, das bei +2 Stufen + * die Magiersistenz bei 90% liegt. + * + * Migrantenzaehlung muss Einheit ueberspringen + * + * Attackiere verbieten + * Flags: + * (UNITSPELL | TESTCANSEE) + */ +static int sp_charmingsong(castorder * co) +{ + unit *target; + int duration; + skill_t i; + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + int resist_bonus = 0; + int tb = 0; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + /* Auf eigene Einheiten versucht zu zaubern? Garantiert Tippfehler */ + if (target->faction == mage->faction) { + /* Die Einheit ist eine der unsrigen */ + cmistake(mage, co->order, 45, MSG_MAGIC); + } + /* niemand mit teurem Talent */ + if (!can_charm(target, cast_level / 2)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noexpensives", "target", target)); + return 0; + } + + /* Magieresistensbonus fuer mehr als Stufe Personen */ + if (target->number > force) { + resist_bonus += (int)((target->number - force) * 10); + } + /* Magieresistensbonus fuer hoehere Talentwerte */ + for (i = 0; i < MAXSKILLS; i++) { + int sk = effskill(target, i); + if (tb < sk) + tb = sk; + } + tb -= effskill(mage, SK_MAGIC); + if (tb > 0) { + resist_bonus += tb * 15; + } + /* Magieresistenz */ + if (target_resists_magic(mage, target, TYP_UNIT, resist_bonus)) { + report_failure(mage, co->order); +#if 0 + sprintf(buf, "%s fuehlt sich einen Moment lang benommen und desorientiert.", + unitname(target)); + addmessage(target->region, target->faction, buf, MSG_EVENT, ML_WARN); +#endif + return 0; + } + + duration = 3 + rng_int() % (int)force; + { + trigger *trestore = trigger_changefaction(target, target->faction); + /* laeuft die Dauer ab, setze Partei zurueck */ + add_trigger(&target->attribs, "timer", trigger_timeout(duration, trestore)); + /* wird die alte Partei von Target aufgeloest, dann auch diese Einheit */ + add_trigger(&target->faction->attribs, "destroy", trigger_killunit(target)); + /* wird die neue Partei von Target aufgeloest, dann auch diese Einheit */ + add_trigger(&mage->faction->attribs, "destroy", trigger_killunit(target)); + } + /* sperre ATTACKIERE, GIB PERSON und ueberspringe Migranten */ + create_curse(mage, &target->attribs, ct_find("slavery"), force, duration, zero_effect, 0); + + /* setze Partei um und loesche langen Befehl aus Sicherheitsgruenden */ + u_setfaction(target, mage->faction); + set_order(&target->thisorder, NULL); + + /* setze Parteitarnung, damit nicht sofort klar ist, wer dahinter + * steckt */ + if (rule_stealth_faction()) { + fset(target, UFL_ANON_FACTION); + } + + ADDMSG(&mage->faction->msgs, msg_message("charming_effect", + "mage unit duration", mage, target, duration)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang des wachen Geistes + * Stufe: 10 + * Gebiet: Cerddor + * Kosten: SPC_LEVEL + * Wirkung: + * Bringt einmaligen Bonus von +15% auf Magieresistenz. Wirkt auf alle + * Aliierten (HELFE BEWACHE) in der Region. + * Dauert Stufe Wochen an, ist nicht kumulativ. + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_song_resistmagic(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = (int)force + 1; + + create_curse(mage, &r->attribs, ct_find("goodmagicresistancezone"), + force, duration, 15, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", mage, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang des schwachen Geistes + * Stufe: 12 + * Gebiet: Cerddor + * Wirkung: + * Bringt einmaligen Malus von -15% auf Magieresistenz. + * Wirkt auf alle Nicht-Aliierten (HELFE BEWACHE) in der Region. + * Dauert Stufe Wochen an, ist nicht kumulativ. + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_song_susceptmagic(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = (int)force + 1; + + create_curse(mage, &r->attribs, ct_find("badmagicresistancezone"), + force, duration, 15, 0); + + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", mage, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Aufruhr beschwichtigen + * Stufe: 15 + * Gebiet: Cerddor + * Flag: FARCASTING + * Wirkung: + * zerstreut einen Monsterbauernmob, Antimagie zu 'Aufruhr + * verursachen' + */ + +static int sp_rallypeasantmob(castorder * co) +{ + unit *u, *un; + int erfolg = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + curse *c; + + for (u = r->units; u; u = un) { + un = u->next; + if (is_monsters(u->faction) && u_race(u) == new_race[RC_PEASANT]) { + rsetpeasants(r, rpeasants(r) + u->number); + rsetmoney(r, rmoney(r) + get_money(u)); + set_money(u, 0); + setguard(u, GUARD_NONE); + set_number(u, 0); + erfolg = cast_level; + } + } + + c = get_curse(r->attribs, ct_find(oldcursename(C_RIOT))); + if (c != NULL) { + remove_curse(&r->attribs, c); + } + + msg = msg_message("cast_rally_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + msg_release(msg); + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: Aufruhr verursachen + * Stufe: 16 + * Gebiet: Cerddor + * Wirkung: + * Wiegelt 60% bis 90% der Bauern einer Region auf. Bauern werden ein + * großer Mob, der zur Monsterpartei gehoert und die Region bewacht. + * Regionssilber sollte auch nicht durch Unterhaltung gewonnen werden + * koennen. + * + * Fehlt: Triggeraktion: loeste Bauernmob auf und gib alles an Region, + * dann koennen die Bauernmobs ihr Silber mitnehmen und bleiben x + * Wochen bestehen + * + * alternativ: Loesen sich langsam wieder auf + * Flag: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_raisepeasantmob(castorder * co) +{ + unit *u; + attrib *a; + int n; + int anteil; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = (int)force + 1; + faction *monsters = get_monsters(); + message *msg; + + anteil = 6 + (rng_int() % 4); + + n = rpeasants(r) * anteil / 10; + n = MAX(0, n); + n = MIN(n, rpeasants(r)); + + if (n <= 0) { + report_failure(mage, co->order); + return 0; + } + + rsetpeasants(r, rpeasants(r) - n); + assert(rpeasants(r) >= 0); + + u = + create_unit(r, monsters, n, new_race[RC_PEASANT], 0, LOC(monsters->locale, + "furious_mob"), NULL); + fset(u, UFL_ISNEW); + guard(u, GUARD_ALL); + a = a_new(&at_unitdissolve); + a->data.ca[0] = 1; /* An rpeasants(r). */ + a->data.ca[1] = 15; /* 15% */ + a_add(&u->attribs, a); + + create_curse(mage, &r->attribs, ct_find("riotzone"), cast_level, duration, + (double)anteil, 0); + + msg = msg_message("sp_raisepeasantmob_effect", "mage region", mage, r); + report_spell(mage, r, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Ritual der Aufnahme / Migrantenwerben + * Stufe: 9 + * Gebiet: Cerddor + * Wirkung: + * Bis zu Stufe Personen fremder Rasse koennen angeworben werden. Die + * angeworbene Einheit muss kontaktieren. Keine teuren Talente + * + * Flag: + * (UNITSPELL | SPELLLEVEL | TESTCANSEE) + */ +static int sp_migranten(castorder * co) +{ + unit *target; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + /* Personen unserer Rasse koennen problemlos normal uebergeben werden */ + if (u_race(target) == mage->faction->race) { + /* u ist von unserer Art, das Ritual waere verschwendete Aura. */ + ADDMSG(&mage->faction->msgs, msg_message("sp_migranten_fail1", + "unit region command target", mage, mage->region, co->order, target)); + } + /* Auf eigene Einheiten versucht zu zaubern? Garantiert Tippfehler */ + if (target->faction == mage->faction) { + cmistake(mage, co->order, 45, MSG_MAGIC); + } + + /* Keine Monstereinheiten */ + if (!playerrace(u_race(target))) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_nomonsters", "")); + return 0; + } + /* niemand mit teurem Talent */ + if (has_limited_skills(target)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noexpensives", "target", target)); + return 0; + } + /* maximal Stufe Personen */ + if (target->number > cast_level || target->number > max_spellpoints(r, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_toomanytargets", "")); + return 0; + } + + /* Kontakt pruefen (aus alter Teleportroutine uebernommen) */ + if (!ucontact(target, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::contact", "target", target)); + return 0; + } + u_setfaction(target, mage->faction); + set_order(&target->thisorder, NULL); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "sp_migranten", + "target", target)); + + return target->number; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang der Friedfertigkeit + * Stufe: 12 + * Gebiet: Cerddor + * Wirkung: + * verhindert jede Attacke fuer lovar(Stufe/2) Runden + */ + +static int sp_song_of_peace(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = 2 + lovar(force / 2); + message *msg[2] = { NULL, NULL }; + + create_curse(mage, &r->attribs, ct_find("peacezone"), force, duration, + zero_effect, 0); + + 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)) { + message *m = NULL; + fset(u->faction, FFL_SELECT); + if (cansee(u->faction, r, mage, 0)) { + if (msg[0] == NULL) + msg[0] = msg_message("song_of_peace_effect_0", "mage", mage); + m = msg[0]; + } else { + if (msg[1] == NULL) + msg[1] = msg_message("song_of_peace_effect_1", ""); + m = msg[1]; + } + r_addmessage(r, u->faction, m); + } + } + if (msg[0]) + msg_release(msg[0]); + if (msg[1]) + msg_release(msg[1]); + return cast_level; + +} + +/* ------------------------------------------------------------- */ +/* Name: Hohes Lied der Gaukelei + * Stufe: 2 + * Gebiet: Cerddor + * Wirkung: + * Das Unterhaltungsmaximum steigt von 20% auf 40% des + * Regionsvermoegens. Der Spruch haelt Stufe Wochen an + */ + +static int sp_generous(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = (int)force + 1; + double effect; + message *msg[2] = { NULL, NULL }; + + if (is_cursed(r->attribs, C_DEPRESSION, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_generous", "")); + return 0; + } + + effect = 2; + create_curse(mage, &r->attribs, ct_find("generous"), force, duration, effect, + 0); + + 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)) { + message *m = NULL; + fset(u->faction, FFL_SELECT); + if (cansee(u->faction, r, mage, 0)) { + if (msg[0] == NULL) + msg[0] = msg_message("generous_effect_0", "mage", mage); + m = msg[0]; + } else { + if (msg[1] == NULL) + msg[1] = msg_message("generous_effect_1", ""); + m = msg[1]; + } + r_addmessage(r, u->faction, m); + } + } + if (msg[0]) + msg_release(msg[0]); + if (msg[1]) + msg_release(msg[1]); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Anwerbung + * Stufe: 4 + * Gebiet: Cerddor + * Wirkung: + * Bauern schliessen sich der eigenen Partei an + * ist zusaetzlich zur Rekrutierungsmenge in der Region + * */ + +static int sp_recruit(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + int num, maxp = rpeasants(r); + double n; + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + faction *f = mage->faction; + const struct race *rc = f->race; + + if (maxp == 0) { + report_failure(mage, co->order); + return 0; + } + /* Immer noch zuviel auf niedrigen Stufen. Deshalb die Rekrutierungskosten + * mit einfliessen lassen und dafuer den Exponenten etwas groeßer. + * Wenn die Rekrutierungskosten deutlich hoeher sind als der Faktor, + * ist das Verhaeltniss von ausgegebene Aura pro Bauer bei Stufe 2 + * ein mehrfaches von Stufe 1, denn in beiden Faellen gibt es nur 1 + * Bauer, nur die Kosten steigen. */ + n = (pow(force, 1.6) * 100) / f->race->recruitcost; + if (rc->recruit_multi != 0) { + double multp = maxp / rc->recruit_multi; + n = MIN(multp, n); + n = MAX(n, 1); + rsetpeasants(r, maxp - (int)(n * rc->recruit_multi)); + } else { + n = MIN(maxp, n); + n = MAX(n, 1); + rsetpeasants(r, maxp - (int)n); + } + + num = (int)n; + u = + create_unit(r, f, num, f->race, 0, LOC(f->locale, + (num == 1 ? "peasant" : "peasant_p")), mage); + set_order(&u->thisorder, default_order(f->locale)); + + ADDMSG(&mage->faction->msgs, msg_message("recruit_effect", "mage amount", + mage, num)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Wanderprediger - Große Anwerbung + * Stufe: 14 + * Gebiet: Cerddor + * Wirkung: + * Bauern schliessen sich der eigenen Partei an + * ist zusaetzlich zur Rekrutierungsmenge in der Region + * */ + +static int sp_bigrecruit(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + int n, maxp = rpeasants(r); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + faction *f = mage->faction; + message *msg; + + if (maxp <= 0) { + report_failure(mage, co->order); + return 0; + } + /* Fuer vergleichbare Erfolge bei unterschiedlichen Rassen die + * Rekrutierungskosten mit einfliessen lassen. */ + + n = (int)force + lovar((force * force * 1000) / f->race->recruitcost); + if (f->race == new_race[RC_ORC]) { + n = MIN(2 * maxp, n); + n = MAX(n, 1); + rsetpeasants(r, maxp - (n + 1) / 2); + } else { + n = MIN(maxp, n); + n = MAX(n, 1); + rsetpeasants(r, maxp - n); + } + + u = + create_unit(r, f, n, f->race, 0, LOC(f->locale, + (n == 1 ? "peasant" : "peasant_p")), mage); + set_order(&u->thisorder, default_order(f->locale)); + + msg = msg_message("recruit_effect", "mage amount", mage, n); + r_addmessage(r, mage->faction, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Aushorchen + * Stufe: 7 + * Gebiet: Cerddor + * Wirkung: + * Erliegt die Einheit dem Zauber, so wird sie dem Magier alles + * erzaehlen, was sie ueber die gefragte Region weiß. Ist in der Region + * niemand ihrer Partei, so weiß sie nichts zu berichten. Auch kann + * sie nur das erzaehlen, was sie selber sehen koennte. + * Flags: + * (UNITSPELL | TESTCANSEE) + */ + +/* restistenz der einheit pruefen */ +static int sp_pump(castorder * co) +{ + unit *u, *target; + region *rt; + bool see = false; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + if (fval(u_race(target), RCF_UNDEAD)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_not_on_undead", "")); + return 0; + } + if (is_magic_resistant(mage, target, 0) || is_monsters(target->faction)) { + report_failure(mage, co->order); + return 0; + } + + rt = pa->param[1]->data.r; + + for (u = rt->units; u; u = u->next) { + if (u->faction == target->faction) + see = true; + } + + if (see) { + ADDMSG(&mage->faction->msgs, msg_message("pump_effect", "mage unit tregion", + mage, target, rt)); + } else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "spellfail_pump", + "target tregion", target, rt)); + return cast_level / 2; + } + + u = + create_unit(rt, mage->faction, RS_FARVISION, new_race[RC_SPELL], 0, + "spell/pump", NULL); + u->age = 2; + set_level(u, SK_PERCEPTION, eff_skill(target, SK_PERCEPTION, u->region)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Verfuehrung + * Stufe: 6 + * Gebiet: Cerddor + * Wirkung: + * Betoert eine Einheit, so das sie ihm den groeßten Teil ihres Bargelds + * und 50% ihres Besitzes schenkt. Sie behaelt jedoch immer soviel, wie + * sie zum ueberleben braucht. Wirkt gegen Magieresistenz. + * MIN(Stufe*1000$, u->money - maintenace) + * Von jedem Item wird 50% abgerundet ermittelt und uebergeben. Dazu + * kommt Itemzahl%2 mit 50% chance + * + * Flags: + * (UNITSPELL | TESTRESISTANCE | TESTCANSEE) + */ +static int sp_seduce(castorder * co) +{ + item *items = NULL; + unit *target; + item **itmp; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + double force = co->force; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + if (fval(u_race(target), RCF_UNDEAD)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noundead", "")); + return 0; + } + + /* Erfolgsmeldung */ + + itmp = &target->items; + while (*itmp) { + item *itm = *itmp; + int loot; + if (itm->type == i_silver) { + loot = + MIN(cast_level * 1000, get_money(target) - (maintenance_cost(target))); + loot = MAX(loot, 0); + } else { + loot = itm->number / 2; + if (itm->number % 2) { + loot += rng_int() % 2; + } + if (loot > 0) { + loot = (int)MIN(loot, force * 5); + } + } + if (loot > 0) { + i_change(&mage->items, itm->type, loot); + i_change(&items, itm->type, loot); + i_change(itmp, itm->type, -loot); + } + if (*itmp == itm) + itmp = &itm->next; + } + + if (items) { + ADDMSG(&mage->faction->msgs, msg_message("seduce_effect_0", "mage unit items", + mage, target, items)); + i_freeall(&items); + ADDMSG(&target->faction->msgs, msg_message("seduce_effect_1", "unit", + target)); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Monster friedlich stimmen + * Stufe: 6 + * Gebiet: Cerddor + * Wirkung: + * verhindert Angriffe des bezauberten Monsters auf die Partei des + * Barden fuer Stufe Wochen. Nicht uebertragbar, dh Verbuendete werden vom + * Monster natuerlich noch angegriffen. Wirkt nicht gegen Untote + * Jede Einheit kann maximal unter einem Beherrschungszauber dieser Art + * stehen, dh wird auf die selbe Einheit dieser Zauber von einem + * anderen Magier nochmal gezaubert, schlaegt der Zauber fehl. + * + * Flags: + * (UNITSPELL | ONSHIPCAST | TESTRESISTANCE | TESTCANSEE) + */ + +static int sp_calm_monster(castorder * co) +{ + curse *c; + unit *target; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + double force = co->force; + double effect; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + if (fval(u_race(target), RCF_UNDEAD)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noundead", "")); + return 0; + } + + effect = mage->faction->subscription; + c = create_curse(mage, &target->attribs, ct_find("calmmonster"), force, + (int)force, effect, 0); + if (c == NULL) { + report_failure(mage, co->order); + return 0; + } + + msg = msg_message("calm_effect", "mage unit", mage, target); + r_addmessage(mage->region, mage->faction, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: schaler Wein + * Stufe: 7 + * Gebiet: Cerddor + * Wirkung: + * wird gegen Magieresistenz gezaubert Das Opfer vergisst bis zu + * Talenttage seines hoechsten Talentes und tut die Woche nix. + * Nachfolgende Zauber sind erschwert. + * Wirkt auf bis zu 10 Personen in der Einheit + * + * Flags: + * (UNITSPELL | TESTRESISTANCE | TESTCANSEE) + */ + +static int sp_headache(castorder * co) +{ + skill *smax = NULL; + int i; + unit *target; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + message *msg; + + /* Macht alle nachfolgenden Zauber doppelt so teuer */ + countspells(mage, 1); + + target = pa->param[0]->data.u; /* Zieleinheit */ + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (target->number == 0 || pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* finde das groeßte Talent: */ + for (i = 0; i != target->skill_size; ++i) { + skill *sv = target->skills + i; + if (smax == NULL || skill_compare(sv, smax) > 0) { + smax = sv; + } + } + if (smax != NULL) { + /* wirkt auf maximal 10 Personen */ + int change = MIN(10, target->number) * (rng_int() % 2 + 1) / target->number; + reduce_skill(target, smax, change); + } + set_order(&target->thisorder, NULL); + + msg = msg_message("hangover_effect_0", "mage unit", mage, target); + r_addmessage(mage->region, mage->faction, msg); + msg_release(msg); + + msg = msg_message("hangover_effect_1", "unit", target); + r_addmessage(target->region, target->faction, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Mob + * Stufe: 10 + * Gebiet: Cerddor + * Wirkung: + * Wiegelt Stufe*250 Bauern zu einem Mob auf, der sich der Partei des + * Magier anschliesst Pro Woche beruhigen sich etwa 15% wieder und + * kehren auf ihre Felder zurueck + * + * Flags: + * (SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_raisepeasants(castorder * co) +{ + int bauern; + unit *u2; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + message *msg; + + if (rpeasants(r) == 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_nopeasants", "")); + return 0; + } + bauern = (int)MIN(rpeasants(r), power * 250); + rsetpeasants(r, rpeasants(r) - bauern); + + u2 = + create_unit(r, mage->faction, bauern, new_race[RC_PEASANT], 0, + LOC(mage->faction->locale, "furious_mob"), mage); + + fset(u2, UFL_LOCKED); + if (rule_stealth_faction()) { + fset(u2, UFL_ANON_FACTION); + } + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 1; /* An rpeasants(r). */ + a->data.ca[1] = 15; /* 15% */ + a_add(&u2->attribs, a); + + msg = + msg_message("sp_raisepeasants_effect", "mage region amount", mage, r, + u2->number); + r_addmessage(r, NULL, msg); + if (mage->region != r) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Truebsal + * Stufe: 11 + * Kategorie: Region, negativ + * Wirkung: + * in der Region kann fuer einige Wochen durch Unterhaltung kein Geld + * mehr verdient werden + * + * Flag: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_depression(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = (int)force + 1; + message *msg; + + create_curse(mage, &r->attribs, ct_find("depression"), force, duration, + zero_effect, 0); + + msg = msg_message("sp_depression_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + if (mage->region != r) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* TRAUM - Illaun */ +/* ------------------------------------------------------------- */ + +/* Name: Seelenfrieden + * Stufe: 2 + * Kategorie: Region, positiv + * Gebiet: Illaun + * Wirkung: + * Reduziert Untotencounter + * Flag: (0) + */ + +int sp_puttorest(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int dead = deathcount(r); + int laid_to_rest = dice((int)(co->force * 2), 100); + message *seen = msg_message("puttorest", "mage", mage); + message *unseen = msg_message("puttorest", "mage", NULL); + + laid_to_rest = MAX(laid_to_rest, dead); + + deathcounts(r, -laid_to_rest); + + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + return co->level; +} + +/* Name: Traumschloeßchen + * Stufe: 3 + * Kategorie: Region, Gebaeude, positiv + * Gebiet: Illaun + * Wirkung: + * Mit Hilfe dieses Zaubers kann der Traumweber die Illusion eines + * beliebigen Gebaeudes erzeugen. Die Illusion kann betreten werden, ist + * aber ansonsten funktionslos und benoetigt auch keinen Unterhalt + * Flag: (0) + */ + +int sp_icastle(castorder * co) +{ + building *b; + const building_type *type; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + icastle_data *data; + const char *bname; + message *msg; + static const building_type *bt_illusion; + + if (bt_illusion == NULL) + bt_illusion = bt_find("illusioncastle"); + if (bt_illusion == NULL) { + return 0; + } + + if ((type = + findbuildingtype(pa->param[0]->data.xs, mage->faction->locale)) == NULL) { + type = bt_find("castle"); + } + + b = new_building(bt_illusion, r, mage->faction->locale); + + /* Groeße festlegen. */ + if (type == bt_illusion) { + b->size = (rng_int() % (int)((power * power) + 1) * 10); + } else if (type->maxsize == -1) { + b->size = ((rng_int() % (int)(power)) + 1) * 5; + } else { + b->size = type->maxsize; + } + + if (type->name == NULL) { + bname = LOC(mage->faction->locale, type->_name); + } else { + bname = LOC(mage->faction->locale, buildingtype(type, b, 0)); + } + building_setname(b, bname); + + /* TODO: Auf timeout und action_destroy umstellen */ + a = a_add(&b->attribs, a_new(&at_icastle)); + data = (icastle_data *) a->data.v; + data->type = type; + data->building = b; + data->time = + 2 + (rng_int() % (int)(power) + 1) * (rng_int() % (int)(power) + 1); + + if (mage->region == r) { + if (leave(mage, false)) { + u_set_building(mage, b); + } + } + + ADDMSG(&mage->faction->msgs, msg_message("icastle_create", + "unit region command", mage, mage->region, co->order)); + + msg = msg_message("sp_icastle_effect", "region", r); + report_spell(mage, r, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gestaltwandlung + * Stufe: 3 + * Gebiet: Illaun + * Wirkung: + * Zieleinheit erscheint fuer (Stufe) Wochen als eine andere Gestalt + * (wie bei daemonischer Rassetarnung). + * Syntax: ZAUBERE "Gestaltwandlung" + * Flags: + * (UNITSPELL | SPELLLEVEL) + */ + +int sp_illusionary_shapeshift(castorder * co) +{ + unit *u; + const race *rc; + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + const race *irace; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + + rc = findrace(pa->param[1]->data.xs, mage->faction->locale); + if (rc == NULL) { + cmistake(mage, co->order, 202, MSG_MAGIC); + return 0; + } + + /* aehnlich wie in laws.c:setealth() */ + if (!playerrace(rc)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "sp_shapeshift_fail", "target race", u, rc)); + return 0; + } + irace = u_irace(u); + if (irace == u_race(u)) { + trigger *trestore = trigger_changerace(u, NULL, irace); + add_trigger(&u->attribs, "timer", trigger_timeout((int)power + 2, + trestore)); + u->irace = rc; + } + + ADDMSG(&mage->faction->msgs, msg_message("shapeshift_effect", + "mage target race", mage, u, rc)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Regionstraum analysieren + * Stufe: 9 + * Aura: 18 + * Kosten: SPC_FIX + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + */ +int sp_analyseregionsdream(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + magicanalyse_region(r, mage, cast_level); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Traumbilder erkennen + * Stufe: 5 + * Aura: 12 + * Kosten: SPC_FIX + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + */ +int sp_analysedream(castorder * co) +{ + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + magicanalyse_unit(u, mage, cast_level); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Schlechte Traeume + * Stufe: 10 + * Kategorie: Region, negativ + * Wirkung: + * Dieser Zauber ermoeglicht es dem Traeumer, den Schlaf aller + * nichtaliierten Einheiten (HELFE BEWACHE) in der Region so starkzu + * stoeren, das sie 1 Talentstufe in allen Talenten + * voruebergehend verlieren. Der Zauber wirkt erst im Folgemonat. + * + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + * */ +int sp_baddreams(castorder * co) +{ + int duration; + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + region *r = co_get_region(co); + curse *c; + double effect; + + /* wirkt erst in der Folgerunde, soll mindestens eine Runde wirken, + * also duration+2 */ + duration = (int)MAX(1, power / 2); /* Stufe 1 macht sonst mist */ + duration = 2 + rng_int() % duration; + + /* Nichts machen als ein entsprechendes Attribut in die Region legen. */ + effect = -1; + c = create_curse(mage, &r->attribs, ct_find("gbdream"), power, duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", c->magician, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Schoene Traeume + * Stufe: 8 + * Kategorie: + * Wirkung: + * Dieser Zauber ermoeglicht es dem Traeumer, den Schlaf aller aliierten + * Einheiten in der Region so zu beeinflussen, daß sie fuer einige Zeit + * einen Bonus von 1 Talentstufe in allen Talenten + * bekommen. Der Zauber wirkt erst im Folgemonat. + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +int sp_gooddreams(castorder * co) +{ + int duration; + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + double effect; + + /* wirkt erst in der Folgerunde, soll mindestens eine Runde wirken, + * also duration+2 */ + duration = (int)MAX(1, power / 2); /* Stufe 1 macht sonst mist */ + duration = 2 + rng_int() % duration; + effect = 1; + c = create_curse(mage, &r->attribs, ct_find("gbdream"), power, duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", c->magician, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: + * Stufe: 9 + * Kategorie: + * Wirkung: + * Es wird eine Kloneinheit erzeugt, die nichts kann. Stirbt der + * Magier, wird er mit einer Wahrscheinlichkeit von 90% in den Klon + * transferiert. + * Flags: + * (NOTFAMILARCAST) + */ +int sp_clonecopy(castorder * co) +{ + unit *clone; + region *r = co_get_region(co); + region *target_region = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + char name[NAMESIZE]; + + if (get_clone(mage) != NULL) { + cmistake(mage, co->order, 298, MSG_MAGIC); + return 0; + } + + snprintf(name, sizeof(name), (const char *)LOC(mage->faction->locale, + "clone_of"), unitname(mage)); + clone = + create_unit(target_region, mage->faction, 1, new_race[RC_CLONE], 0, name, + mage); + setstatus(clone, ST_FLEE); + fset(clone, UFL_LOCKED); + + create_newclone(mage, clone); + + msg = msg_message("sp_clone_effet", "mage", mage); + r_addmessage(r, mage->faction, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_dreamreading(castorder * co) +{ + unit *u, *u2; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + double power = co->force; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + + /* Illusionen und Untote abfangen. */ + if (fval(u_race(u), RCF_UNDEAD | RCF_ILLUSIONARY)) { + ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, co->order, + pa->param[0])); + return 0; + } + + /* Entfernung */ + if (distance(mage->region, u->region) > power) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_distance", "")); + return 0; + } + + u2 = + create_unit(u->region, mage->faction, RS_FARVISION, new_race[RC_SPELL], 0, + "spell/dreamreading", NULL); + set_number(u2, 1); + u2->age = 2; /* Nur fuer diese Runde. */ + set_level(u2, SK_PERCEPTION, eff_skill(u, SK_PERCEPTION, u2->region)); + + msg = + msg_message("sp_dreamreading_effect", "mage unit region", mage, u, + u->region); + r_addmessage(r, mage->faction, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Wirkt power/2 Runden auf bis zu power^2 Personen + * mit einer Chance von 5% vermehren sie sich */ +int sp_sweetdreams(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + int men, n; + int duration = (int)(power / 2) + 1; + int opfer = (int)(power * power); + + /* Schleife ueber alle angegebenen Einheiten */ + for (n = 0; n < pa->length; n++) { + curse *c; + unit *u; + double effect; + message *msg; + /* sollte nie negativ werden */ + if (opfer < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS || + pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + /* Zieleinheit */ + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + cmistake(mage, co->order, 40, MSG_EVENT); + continue; + } + men = MIN(opfer, u->number); + opfer -= men; + + /* Nichts machen als ein entsprechendes Attribut an die Einheit legen. */ + effect = 0.05; + c = create_curse(mage, &u->attribs, ct_find("orcish"), power, duration, effect, men); + + msg = msg_message("sp_sweetdreams_effect", "mage unit region", c->magician, u, r); + r_addmessage(r, mage->faction, msg); + if (u->faction != mage->faction) { + r_addmessage(r, u->faction, msg); + } + msg_release(msg); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_disturbingdreams(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + int duration = 1 + (int)(power / 6); + double effect; + curse *c; + + effect = 10; + c = create_curse(mage, &r->attribs, ct_find("badlearn"), power, duration, effect, 0); + + ADDMSG(&mage->faction->msgs, msg_message("sp_disturbingdreams_effect", + "mage region", c->magician, r)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* ASTRAL / THEORIE / M_TYBIED */ +/* ------------------------------------------------------------- */ +/* Name: Magie analysieren + * Stufe: 1 + * Aura: 1 + * Kosten: SPC_LINEAR + * Komponenten: + * + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + * + * Flags: + * UNITSPELL, SHIPSPELL, BUILDINGSPELL + */ + +int sp_analysemagic(castorder * co) +{ + int obj; + unit *mage = co_get_caster(co); + int cast_level = co->level; + spellparameter *pa = co->par; + + if (!pa->param) { + return 0; + } + /* Objekt ermitteln */ + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + { + region *tr = pa->param[0]->data.r; + magicanalyse_region(tr, mage, cast_level); + break; + } + case SPP_TEMP: + case SPP_UNIT: + { + unit *u; + u = pa->param[0]->data.u; + magicanalyse_unit(u, mage, cast_level); + break; + } + case SPP_BUILDING: + { + building *b; + b = pa->param[0]->data.b; + magicanalyse_building(b, mage, cast_level); + break; + } + case SPP_SHIP: + { + ship *sh; + sh = pa->param[0]->data.sh; + magicanalyse_ship(sh, mage, cast_level); + break; + } + default: + /* Fehlerhafter Parameter */ + return 0; + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ + +int sp_itemcloak(castorder * co) +{ + unit *target; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + double power = co->force; + int duration = (int)MAX(2.0, power + 1); /* works in the report, and ageing this round would kill it if it's <=1 */ + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* Zieleinheit */ + target = pa->param[0]->data.u; + + create_curse(mage, &target->attribs, ct_find("itemcloak"), power, duration, + zero_effect, 0); + ADDMSG(&mage->faction->msgs, msg_message("itemcloak", "mage target", mage, + target)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Magieresistenz erhoehen + * Stufe: 3 + * Aura: 5 MP + * Kosten: SPC_LEVEL + * Komponenten: + * + * Wirkung: + * erhoeht die Magierestistenz der Personen um 20 Punkte fuer 6 Wochen + * Wirkt auf Stufe*5 Personen kann auf mehrere Einheiten gezaubert + * werden, bis die Zahl der moeglichen Personen erschoepft ist + * + * Flags: + * UNITSPELL + */ +int sp_resist_magic_bonus(castorder * co) +{ + unit *u; + int n, m; + int duration = 6; + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + /* Pro Stufe koennen bis zu 5 Personen verzaubert werden */ + double maxvictims = 5; + int victims = (int)maxvictims; + + /* Schleife ueber alle angegebenen Einheiten */ + for (n = 0; n < pa->length; n++) { + message *msg; + /* sollte nie negativ werden */ + if (victims < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + /* Ist die Einheit schon verzaubert, wirkt sich dies nur auf die + * Menge der Verzauberten Personen aus. + if (is_cursed(u->attribs, C_MAGICRESISTANCE, 0)) + continue; + */ + + m = MIN(u->number, victims); + victims -= m; + + create_curse(mage, &u->attribs, ct_find("magicresistance"), + power, duration, 20, m); + + msg = msg_message("magicresistance_effect", "unit", u); + add_message(&u->faction->msgs, msg); + + /* und noch einmal dem Magier melden */ + if (u->faction != mage->faction) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + } + + cast_level = MIN(cast_level, (int)(cast_level * (victims + 4) / maxvictims)); + return MAX(cast_level, 1); +} + +/** spell 'Astraler Weg'. + * Syntax "ZAUBERE [STUFE n] 'Astraler Weg' [ ...]", + * + * Parameter: + * pa->param[0]->data.xs +*/ +int sp_enterastral(castorder * co) +{ + region *rt, *ro; + unit *u, *u2; + int remaining_cap; + int n, w; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + + switch (getplaneid(r)) { + case 0: + rt = r_standard_to_astral(r); + ro = r; + break; + default: + cmistake(mage, co->order, 190, MSG_MAGIC); + return 0; + } + + if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_astralregion", "")); + return 0; + } + + if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0) + || is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)((power - 3) * 1500); + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 0; n < pa->length; n++) { + if (pa->param[n]->flag == TARGET_NOTFOUND) + continue; + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + if (power > 10 && !is_magic_resistant(mage, u, 0) && can_survive(u, rt)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", + mage, u)); + } else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + w = weight(u); + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } else { + message *m; + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return cast_level; +} + +/** Spell 'Astraler Ruf' / 'Astral Call'. + */ +int sp_pullastral(castorder * co) +{ + region *rt, *ro; + unit *u, *u2; + region_list *rl, *rl2; + int remaining_cap; + int n, w; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + + switch (getplaneid(r)) { + case 1: + rt = r; + ro = pa->param[0]->data.r; + rl = astralregions(r, NULL); + rl2 = rl; + while (rl2 != NULL) { + region *r2 = rl2->data; + if (r2->x == ro->x && r2->y == ro->y) { + ro = r2; + break; + } + rl2 = rl2->next; + } + if (!rl2) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::nocontact", "target", rt)); + free_regionlist(rl); + return 0; + } + free_regionlist(rl); + break; + default: + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralonly", "")); + return 0; + } + + if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0) + || is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)((power - 3) * 1500); + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 1; n < pa->length; n++) { + spllprm *spobj = pa->param[n]; + if (spobj->flag == TARGET_NOTFOUND) + continue; + + u = spobj->data.u; + + if (u->region != ro) { + /* Report this as unit not found */ + if (spobj->typ == SPP_UNIT) { + spobj->data.i = u->no; + } else { + spobj->data.i = ualias(u); + } + spobj->flag = TARGET_NOTFOUND; + ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, co->order, spobj)); + return false; + } + + if (!ucontact(u, mage)) { + if (power > 12 && spobj->flag != TARGET_RESISTS && can_survive(u, rt)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + } else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + w = weight(u); + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } else { + message *m; + + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", mage, + u)); + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return cast_level; +} + +int sp_leaveastral(castorder * co) +{ + region *rt, *ro; + region_list *rl, *rl2; + unit *u, *u2; + int remaining_cap; + int n, w; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + + switch (getplaneid(r)) { + case 1: + ro = r; + rt = pa->param[0]->data.r; + if (!rt) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::noway", "")); + return 0; + } + rl = astralregions(r, inhabitable); + rl2 = rl; + while (rl2 != NULL) { + if (rl2->data == rt) + break; + rl2 = rl2->next; + } + if (rl2 == NULL) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::noway", "")); + free_regionlist(rl); + return 0; + } + free_regionlist(rl); + break; + default: + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spell_astral_only", "")); + return 0; + } + + if (ro == NULL || is_cursed(ro->attribs, C_ASTRALBLOCK, 0) + || is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)((power - 3) * 1500); + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 1; n < pa->length; n++) { + if (pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + if (power > 10 && !pa->param[n]->flag == TARGET_RESISTS + && can_survive(u, rt)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", + mage, u)); + } else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + w = weight(u); + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } else { + message *m; + + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return cast_level; +} + +int sp_fetchastral(castorder * co) +{ + int n; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + double power = co->force; + int remaining_cap = (int)((power - 3) * 1500); + region_list *rtl = NULL; + region *rt = co_get_region(co); /* region to which we are fetching */ + region *ro = NULL; /* region in which the target is */ + + if (rplane(rt) != get_normalplane()) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "error190", "")); + return 0; + } + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 0; n != pa->length; ++n) { + unit *u2, *u = pa->param[n]->data.u; + int w; + message *m; + + if (pa->param[n]->flag & TARGET_NOTFOUND) + continue; + + if (u->region != ro) { + /* this can happen several times if the units are from different astral + * regions. Only possible on the intersections of schemes */ + region_list *rfind; + if (!is_astral(u->region)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralonly", "")); + continue; + } + if (rtl != NULL) + free_regionlist(rtl); + rtl = astralregions(u->region, NULL); + for (rfind = rtl; rfind != NULL; rfind = rfind->next) { + if (rfind->data == mage->region) + break; + } + if (rfind == NULL) { + /* the region r is not in the schemes of rt */ + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_distance", "target", u)); + continue; + } + ro = u->region; + } + + if (is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + continue; + } + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + continue; + } + + w = weight(u); + if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + continue; + } + + if (!ucontact(u, mage)) { + if (power > 12 && !(pa->param[n]->flag & TARGET_RESISTS)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", + mage, u)); + } else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + remaining_cap -= w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + for (u2 = ro->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = ro->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, ro, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(ro, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + if (rtl != NULL) + free_regionlist(rtl); + return cast_level; +} + +#ifdef SHOWASTRAL_NOT_BORKED +int sp_showastral(castorder * co) +{ + unit *u; + region *rt; + int n = 0; + int c = 0; + region_list *rl, *rl2; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + + switch (getplaneid(r)) { + case 0: + rt = r_standard_to_astral(r); + if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { + /* Hier gibt es keine Verbindung zur astralen Welt */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } + break; + case 1: + rt = r; + break; + default: + /* Hier gibt es keine Verbindung zur astralen Welt */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } + + rl = all_in_range(rt, power / 5); + + /* Erst Einheiten zaehlen, fuer die Grammatik. */ + + for (rl2 = rl; rl2; rl2 = rl2->next) { + region *r2 = rl2->data; + if (!is_cursed(r2->attribs, C_ASTRALBLOCK, 0)) { + for (u = r2->units; u; u = u->next) { + if (u_race(u) != new_race[RC_SPECIAL] && u_race(u) != new_race[RC_SPELL]) + n++; + } + } + } + + if (n == 0) { + /* sprintf(buf, "%s kann niemanden im astralen Nebel entdecken.", + unitname(mage)); */ + cmistake(mage, co->order, 220, MSG_MAGIC); + } else { + + /* Ausgeben */ + + sprintf(buf, "%s hat eine Vision der astralen Ebene. Im astralen " + "Nebel zu erkennen sind ", unitname(mage)); + + for (rl2 = rl; rl2; rl2 = rl2->next) { + if (!is_cursed(rl2->data->attribs, C_ASTRALBLOCK, 0)) { + for (u = rl2->data->units; u; u = u->next) { + if (u_race(u) != new_race[RC_SPECIAL] && u_race(u) != new_race[RC_SPELL]) { + c++; + scat(unitname(u)); + scat(" ("); + if (!fval(u, UFL_ANON_FACTION)) { + scat(factionname(u->faction)); + scat(", "); + } + icat(u->number); + scat(" "); + scat(LOC(mage->faction->locale, rc_name(u_race(u), u->number != 1))); + scat(", Entfernung "); + icat(distance(rl2->data, rt)); + scat(")"); + if (c == n - 1) { + scat(" und "); + } else if (c < n - 1) { + scat(", "); + } + } + } + } + } + scat("."); + addmessage(r, mage->faction, buf, MSG_MAGIC, ML_INFO); + } + + free_regionlist(rl); + return cast_level; + unused(co); + return 0; +} +#endif + +/* ------------------------------------------------------------- */ +int sp_viewreality(castorder * co) +{ + region_list *rl, *rl2; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *m; + + if (getplaneid(r) != 1) { + /* sprintf(buf, "Dieser Zauber kann nur im Astralraum gezaubert werden."); */ + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spell_astral_only", "")); + return 0; + } + + rl = astralregions(r, NULL); + + /* Irgendwann mal auf Curses u/o Attribut umstellen. */ + for (rl2 = rl; rl2; rl2 = rl2->next) { + region *rt = rl2->data; + if (!is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { + u = + create_unit(rt, mage->faction, RS_FARVISION, new_race[RC_SPELL], 0, + "spell/viewreality", NULL); + set_level(u, SK_PERCEPTION, co->level / 2); + u->age = 2; + } + } + + free_regionlist(rl); + + m = msg_message("viewreality_effect", "unit", mage); + r_addmessage(r, mage->faction, m); + msg_release(m); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_disruptastral(castorder * co) +{ + region_list *rl, *rl2; + region *rt; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + int duration = (int)(power / 3) + 1; + + switch (getplaneid(r)) { + case 0: + rt = r_standard_to_astral(r); + if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { + /* "Hier gibt es keine Verbindung zur astralen Welt." */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } + break; + case 1: + rt = r; + break; + default: + /* "Von hier aus kann man die astrale Ebene nicht erreichen." */ + cmistake(mage, co->order, 215, MSG_MAGIC); + return 0; + } + + rl = all_in_range(rt, (short)(power / 5), NULL); + + for (rl2 = rl; rl2 != NULL; rl2 = rl2->next) { + attrib *a; + double effect; + region *r2 = rl2->data; + spec_direction *sd; + int inhab_regions = 0; + region_list *trl = NULL; + + if (is_cursed(r2->attribs, C_ASTRALBLOCK, 0)) + continue; + + if (r2->units != NULL) { + region_list *trl2; + + trl = astralregions(rl2->data, inhabitable); + for (trl2 = trl; trl2; trl2 = trl2->next) + ++inhab_regions; + } + + /* Nicht-Permanente Tore zerstoeren */ + a = a_find(r->attribs, &at_direction); + + while (a != NULL && a->type == &at_direction) { + attrib *a2 = a->next; + sd = (spec_direction *) (a->data.v); + if (sd->duration != -1) + a_remove(&r->attribs, a); + a = a2; + } + + /* Einheiten auswerfen */ + + if (trl != NULL) { + for (u = r2->units; u; u = u->next) { + if (u_race(u) != new_race[RC_SPELL]) { + region_list *trl2 = trl; + region *tr; + int c = rng_int() % inhab_regions; + + /* Zufaellige Zielregion suchen */ + while (c-- != 0) + trl2 = trl2->next; + tr = trl2->data; + + if (!is_magic_resistant(mage, u, 0) && can_survive(u, tr)) { + message *msg = msg_message("disrupt_astral", "unit region", u, tr); + add_message(&u->faction->msgs, msg); + add_message(&tr->msgs, msg); + msg_release(msg); + + move_unit(u, tr, NULL); + } + } + } + free_regionlist(trl); + } + + /* Kontakt unterbinden */ + effect = 100; + create_curse(mage, &rl2->data->attribs, ct_find("astralblock"), + power, duration, effect, 0); + } + + free_regionlist(rl); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Mauern der Ewigkeit + * Stufe: 7 + * Kategorie: Artefakt + * Gebiet: Tybied + * Wirkung: + * Das Gebaeude kostet keinen Unterhalt mehr + * + * ZAUBER "Mauern der Ewigkeit" + * Flags: (0) + */ +static int sp_eternizewall(castorder * co) +{ + unit *u; + curse *c; + building *b; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + b = pa->param[0]->data.b; + c = create_curse(mage, &b->attribs, ct_find("nocostbuilding"), + power * power, 1, zero_effect, 0); + + if (c == NULL) { /* ist bereits verzaubert */ + cmistake(mage, co->order, 206, MSG_MAGIC); + return 0; + } + + /* melden, 1x pro Partei in der Burg */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + msg = + msg_message("sp_eternizewall_effect", "mage building region", mage, b, r); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (u->building == b) { + r_addmessage(r, u->faction, msg); + } + } + } + if (r != mage->region) { + add_message(&mage->faction->msgs, msg); + } else if (mage->building != b) { + r_addmessage(r, mage->faction, msg); + } + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Opfere Kraft + * Stufe: 15 + * Gebiet: Tybied + * Kategorie: Einheit, positiv + * Wirkung: + * Mit Hilfe dieses Zaubers kann der Magier einen Teil seiner + * magischen Kraft permanent auf einen anderen Magier uebertragen. Auf + * einen Tybied-Magier kann er die Haelfte der eingesetzten Kraft + * uebertragen, auf einen Magier eines anderen Gebietes ein Drittel. + * + * Flags: + * (UNITSPELL) + * + * Syntax: + * ZAUBERE \"Opfere Kraft\" + * "ui" + */ +int sp_permtransfer(castorder * co) +{ + int aura; + unit *tu; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + const spell *sp = co->sp; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + tu = pa->param[0]->data.u; + aura = pa->param[1]->data.i; + + if (!is_mage(tu)) { +/* sprintf(buf, "%s in %s: 'ZAUBER %s': Einheit ist kein Magier." + , unitname(mage), regionname(mage->region, mage->faction),sa->strings[0]); */ + cmistake(mage, co->order, 214, MSG_MAGIC); + return 0; + } + + aura = MIN(get_spellpoints(mage) - spellcost(mage, sp), aura); + + change_maxspellpoints(mage, -aura); + change_spellpoints(mage, -aura); + + if (get_mage(tu)->magietyp == get_mage(mage)->magietyp) { + change_maxspellpoints(tu, aura / 2); + } else { + change_maxspellpoints(tu, aura / 3); + } + + msg = + msg_message("sp_permtransfer_effect", "mage target amount", mage, tu, aura); + add_message(&mage->faction->msgs, msg); + if (tu->faction != mage->faction) { + add_message(&tu->faction->msgs, msg); + } + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* TODO: specialdirections? */ + +int sp_movecastle(castorder * co) +{ + building *b; + direction_t dir; + region *target_region; + unit *u, *unext; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + b = pa->param[0]->data.b; + dir = finddirection(pa->param[1]->data.xs, mage->faction->locale); + + if (dir == NODIRECTION) { + /* Die Richtung wurde nicht erkannt */ + cmistake(mage, co->order, 71, MSG_PRODUCE); + return 0; + } + + if (b->size > (cast_level - 12) * 250) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "sp_movecastle_fail_0", "")); + return cast_level; + } + + target_region = rconnect(r, dir); + + if (!(target_region->terrain->flags & LAND_REGION)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "sp_movecastle_fail_1", "direction", dir)); + return cast_level; + } + + bunhash(b); + translist(&r->buildings, &target_region->buildings, b); + b->region = target_region; + b->size -= b->size / (10 - rng_int() % 6); + bhash(b); + + for (u = r->units; u;) { + unext = u->next; + if (u->building == b) { + uunhash(u); + translist(&r->units, &target_region->units, u); + uhash(u); + } + u = unext; + } + + if ((b->type == bt_find("caravan") || b->type == bt_find("dam") + || b->type == bt_find("tunnel"))) { + direction_t d; + for (d = 0; d != MAXDIRECTIONS; ++d) { + if (rroad(r, d)) { + rsetroad(r, d, rroad(r, d) / 2); + } + } + } + + msg = msg_message("sp_movecastle_effect", "building direction", b, dir); + r_addmessage(r, NULL, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Luftschiff + * Stufe: 6 + * + * Wirkung: + * Laeßt ein Schiff eine Runde lang fliegen. Wirkt nur auf Boote und + * Langboote. + * Kombinierbar mit "Guenstige Winde", aber nicht mit "Sturmwind". + * + * Flag: + * (ONSHIPCAST | SHIPSPELL | TESTRESISTANCE) + */ +int sp_flying_ship(castorder * co) +{ + ship *sh; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + message *m = NULL; + int cno; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + sh = pa->param[0]->data.sh; + if (sh->type->construction->maxsize > 50) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_flying_ship_too_big", "ship", sh)); + return 0; + } + + /* Duration = 1, nur diese Runde */ + + cno = levitate_ship(sh, mage, power, 1); + if (cno == 0) { + if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) { + /* Auf dem Schiff befindet liegt bereits so ein Zauber. */ + cmistake(mage, co->order, 211, MSG_MAGIC); + } else if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { + /* Es ist zu gefaehrlich, ein sturmgepeitschtes Schiff fliegen zu lassen. */ + cmistake(mage, co->order, 210, MSG_MAGIC); + } + return 0; + } + sh->coast = NODIRECTION; + + /* melden, 1x pro Partei */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + /* das sehen natuerlich auch die Leute an Land */ + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (!m) + m = msg_message("flying_ship_result", "mage ship", mage, sh); + add_message(&u->faction->msgs, m); + } + } + if (m) + msg_release(m); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Stehle Aura + * Stufe: 6 + * Kategorie: Einheit, negativ + * Wirkung: + * Mit Hilfe dieses Zaubers kann der Magier einem anderen Magier + * seine Aura gegen dessen Willen entziehen und sich selber + * zufuehren. + * + * Flags: + * (FARCASTING | SPELLLEVEL | UNITSPELL | TESTRESISTANCE | + * TESTCANSEE) + * */ +int sp_stealaura(castorder * co) +{ + int taura; + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + double power = co->force; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* Zieleinheit */ + u = pa->param[0]->data.u; + + if (!get_mage(u)) { + ADDMSG(&mage->faction->msgs, msg_message("stealaura_fail", "unit target", + mage, u)); + ADDMSG(&u->faction->msgs, msg_message("stealaura_fail_detect", "unit", u)); + return 0; + } + + taura = (get_mage(u)->spellpoints * (rng_int() % (int)(3 * power) + 1)) / 100; + + if (taura > 0) { + get_mage(u)->spellpoints -= taura; + get_mage(mage)->spellpoints += taura; +/* sprintf(buf, "%s entzieht %s %d Aura.", unitname(mage), unitname(u), + taura); */ + ADDMSG(&mage->faction->msgs, msg_message("stealaura_success", + "mage target aura", mage, u, taura)); +/* sprintf(buf, "%s fuehlt seine magischen Kraefte schwinden und verliert %d " + "Aura.", unitname(u), taura); */ + ADDMSG(&u->faction->msgs, msg_message("stealaura_detect", "unit aura", u, + taura)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("stealaura_fail", "unit target", + mage, u)); + ADDMSG(&u->faction->msgs, msg_message("stealaura_fail_detect", "unit", u)); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Astrale Schwaechezone + * Stufe: 5 + * Kategorie: + * Wirkung: + * Reduziert die Staerke jedes Spruch in der Region um Level Haelt + * Sprueche bis zu einem Gesammtlevel von Staerke*10 aus, danach ist + * sie verbraucht. + * leibt bis zu Staerke Wochen aktiv. + * Ein Ring der Macht erhoeht die Staerke um 1, in einem Magierturm + * gezaubert gibt nochmal +1 auf Staerke. (force) + * + * Beispiel: + * Eine Antimagiezone Stufe 7 haelt bis zu 7 Wochen an oder Sprueche mit + * einem Gesammtlevel bis zu 70 auf. Also zB 7 Stufe 10 Sprueche, 10 + * Stufe 7 Sprueche oder 35 Stufe 2 Sprueche. Sie reduziert die Staerke + * (level+boni) jedes Spruchs, der in der Region gezaubert wird, um + * 7. Alle Sprueche mit einer Staerke kleiner als 7 schlagen fehl + * (power = 0). + * + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + * + */ +int sp_antimagiczone(castorder * co) +{ + double power; + double effect; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + int duration = (int)force + 1; + + /* Haelt Sprueche bis zu einem summierten Gesamtlevel von power aus. + * Jeder Zauber reduziert die 'Lebenskraft' (vigour) der Antimagiezone + * um seine Stufe */ + power = force * 10; + + /* Reduziert die Staerke jedes Spruchs um effect */ + effect = cast_level; + + create_curse(mage, &r->attribs, ct_find("antimagiczone"), power, duration, + effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", mage, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Schutzrunen + * Stufe: 8 + * Kosten: SPC_FIX + * + * Wirkung: + * Gibt Gebaeuden einen Bonus auf Magieresistenz von +20%. Der Zauber + * dauert 3+rng_int()%Level Wochen an, also im Extremfall bis zu 2 Jahre + * bei Stufe 20 + * + * Es koennen mehrere Zauber uebereinander gelegt werden, der Effekt + * summiert sich, jedoch wird die Dauer dadurch nicht verlaengert. + * + * oder: + * + * Gibt Schiffen einen Bonus auf Magieresistenz von +20%. Der Zauber + * dauert 3+rng_int()%Level Wochen an, also im Extremfall bis zu 2 Jahre + * bei Stufe 20 + * + * Es koennen mehrere Zauber uebereinander gelegt werden, der Effekt + * summiert sich, jedoch wird die Dauer dadurch nicht verlaengert. + * + * Flags: + * (ONSHIPCAST | TESTRESISTANCE) + * + * Syntax: + * ZAUBERE \"Runen des Schutzes\" GEBAEUDE + * ZAUBERE \"Runen des Schutzes\" SCHIFF + * "kc" + */ + +static int sp_magicrunes(castorder * co) +{ + int duration; + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + double effect; + + duration = 3 + rng_int() % cast_level; + effect = 20; + + switch (pa->param[0]->typ) { + case SPP_BUILDING: + { + building *b; + b = pa->param[0]->data.b; + + /* Magieresistenz der Burg erhoeht sich um 20% */ + create_curse(mage, &b->attribs, ct_find("magicrunes"), force, + duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("objmagic_effect", + "unit region command target", mage, mage->region, co->order, + buildingname(b))); + break; + } + case SPP_SHIP: + { + ship *sh; + sh = pa->param[0]->data.sh; + /* Magieresistenz des Schiffs erhoeht sich um 20% */ + create_curse(mage, &sh->attribs, ct_find("magicrunes"), force, + duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("objmagic_effect", + "unit region command target", mage, mage->region, co->order, + shipname(sh))); + break; + } + default: + /* fehlerhafter Parameter */ + return 0; + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Zeitdehnung + * + * Flags: + * (UNITSPELL | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + * Syntax: + * "u+" + */ + +int sp_speed2(castorder * co) +{ + int n, maxmen, used = 0, dur, men; + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + + maxmen = 2 * cast_level * cast_level; + dur = MAX(1, cast_level / 2); + + for (n = 0; n < pa->length; n++) { + double effect; + /* sollte nie negativ werden */ + if (maxmen < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + men = MIN(maxmen, u->number); + effect = 2; + create_curse(mage, &u->attribs, ct_find("speed"), force, dur, effect, men); + maxmen -= men; + used += men; + } + + ADDMSG(&mage->faction->msgs, msg_message("speed_time_effect", + "unit region amount", mage, mage->region, used)); + /* Effektiv benoetigten cast_level (mindestens 1) zurueckgeben */ + used = (int)sqrt(used / 2); + return MAX(1, used); +} + +/* ------------------------------------------------------------- */ +/* Name: Magiefresser + * Stufe: 7 + * Kosten: SPC_LEVEL + * + * Wirkung: + * Kann eine bestimmte Verzauberung angreifen und aufloesen. Die Staerke + * des Zaubers muss staerker sein als die der Verzauberung. + * Syntax: + * ZAUBERE \"Magiefresser\" REGION + * ZAUBERE \"Magiefresser\" EINHEIT + * ZAUBERE \"Magiefresser\" GEBAEUDE + * ZAUBERE \"Magiefresser\" SCHIFF + * + * "kc?c" + * Flags: + * (FARCASTING | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + */ +/* Jeder gebrochene Zauber verbraucht c->vigour an Zauberkraft + * (force) */ +int sp_q_antimagie(castorder * co) +{ + attrib **ap; + int obj; + curse *c = NULL; + int succ; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + const char *ts = NULL; + + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + ap = &r->attribs; + ts = regionname(r, mage->faction); + break; + + case SPP_TEMP: + case SPP_UNIT: + { + unit *u = pa->param[0]->data.u; + ap = &u->attribs; + ts = unitid(u); + break; + } + case SPP_BUILDING: + { + building *b = pa->param[0]->data.b; + ap = &b->attribs; + ts = buildingid(b); + break; + } + case SPP_SHIP: + { + ship *sh = pa->param[0]->data.sh; + ap = &sh->attribs; + ts = shipid(sh); + break; + } + default: + /* Das Zielobjekt wurde vergessen */ + cmistake(mage, co->order, 203, MSG_MAGIC); + return 0; + } + + succ = break_curse(ap, cast_level, force, c); + + if (succ) { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_effect", + "unit region command succ target", mage, mage->region, co->order, succ, + ts)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_noeffect", + "unit region command", mage, mage->region, co->order)); + } + return MAX(succ, 1); +} + +/* ------------------------------------------------------------- */ +/* Name: Fluch brechen + * Stufe: 7 + * Kosten: SPC_LEVEL + * + * Wirkung: + * Kann eine bestimmte Verzauberung angreifen und aufloesen. Die Staerke + * des Zaubers muss staerker sein als die der Verzauberung. + * Syntax: + * ZAUBERE \"Fluch brechen\" REGION + * ZAUBERE \"Fluch brechen\" EINHEIT + * ZAUBERE \"Fluch brechen\" GEBAEUDE + * ZAUBERE \"Fluch brechen\" SCHIFF + * + * "kcc" + * Flags: + * (FARCASTING | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + */ +int sp_break_curse(castorder * co) +{ + attrib **ap; + int obj; + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double force = co->force; + spellparameter *pa = co->par; + const char *ts = NULL; + + if (pa->length < 2) { + /* Das Zielobjekt wurde vergessen */ + cmistake(mage, co->order, 203, MSG_MAGIC); + return 0; + } + + obj = pa->param[0]->typ; + + c = findcurse(atoi36(pa->param[1]->data.s)); + if (!c) { + /* Es wurde kein Ziel gefunden */ + ADDMSG(&mage->faction->msgs, msg_message("spelltargetnotfound", + "unit region command", mage, mage->region, co->order)); + } else { + switch (obj) { + case SPP_REGION: + ap = &r->attribs; + ts = regionname(r, mage->faction); + break; + + case SPP_TEMP: + case SPP_UNIT: + { + unit *u = pa->param[0]->data.u; + ap = &u->attribs; + ts = unitid(u); + break; + } + case SPP_BUILDING: + { + building *b = pa->param[0]->data.b; + ap = &b->attribs; + ts = buildingid(b); + break; + } + case SPP_SHIP: + { + ship *sh = pa->param[0]->data.sh; + ap = &sh->attribs; + ts = shipid(sh); + break; + } + default: + /* Das Zielobjekt wurde vergessen */ + cmistake(mage, co->order, 203, MSG_MAGIC); + return 0; + } + + /* ueberpruefung, ob curse zu diesem objekt gehoert */ + if (!is_cursed_with(*ap, c)) { + /* Es wurde kein Ziel gefunden */ + ADDMSG(&mage->faction->msgs, + msg_message("spelltargetnotfound", "unit region command", + mage, mage->region, co->order)); + } + + /* curse aufloesen, wenn zauber staerker (force > vigour) */ + c->vigour -= force; + + if (c->vigour <= 0.0) { + remove_curse(ap, c); + + ADDMSG(&mage->faction->msgs, msg_message("destroy_curse_effect", + "unit region command id target", mage, mage->region, co->order, + pa->param[1]->data.xs, ts)); + } else { + ADDMSG(&mage->faction->msgs, msg_message("destroy_curse_noeffect", + "unit region command id target", mage, mage->region, co->order, + pa->param[1]->data.xs, ts)); + } + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_becomewyrm(castorder * co) +{ + return 0; +} + +/* ------------------------------------------------------------- */ +/* Name: WDW-Pyramidenfindezauber + * Stufe: unterschiedlich + * Gebiet: alle + * Wirkung: + * gibt die ungefaehre Entfernung zur naechstgelegenen Pyramiden- + * region an. + * + * Flags: + */ +static int sp_wdwpyramid(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + if (a_find(r->attribs, &at_wdwpyramid) != NULL) { + ADDMSG(&mage->faction->msgs, msg_message("wdw_pyramidspell_found", + "unit region command", mage, r, co->order)); + } else { + region *r2; + int mindist = INT_MAX; + int minshowdist; + int maxshowdist; + + for (r2 = regions; r2; r2 = r2->next) { + if (a_find(r2->attribs, &at_wdwpyramid) != NULL) { + int dist = distance(mage->region, r2); + if (dist < mindist) { + mindist = dist; + } + } + } + + assert(mindist >= 1); + + minshowdist = mindist - rng_int() % 5; + maxshowdist = minshowdist + 4; + + ADDMSG(&mage->faction->msgs, msg_message("wdw_pyramidspell_notfound", + "unit region command mindist maxdist", mage, r, co->order, + MAX(1, minshowdist), maxshowdist)); + } + + return cast_level; +} + +typedef struct spelldata { + const char *sname; + spell_f cast; + fumble_f fumble; +} spelldata; + +static spelldata spell_functions[] = { + /* M_GWYRRD */ + { "stonegolem", sp_create_stonegolem, 0}, + { "irongolem", sp_create_irongolem, 0}, + { "treegrow", sp_hain, patzer_ents}, + { "rustweapon", sp_rosthauch, 0}, + { "cold_protection", sp_kaelteschutz, 0}, + { "ironkeeper", sp_ironkeeper, 0}, + { "magicstreet", sp_magicstreet, 0}, + { "windshield", sp_windshield, 0}, + { "mallorntreegrow", sp_mallornhain, patzer_ents}, + { "goodwinds", sp_goodwinds, 0}, + { "healing", sp_healing, 0}, + { "reelingarrows", sp_reeling_arrows, 0}, + { "gwyrrdfumbleshield", sp_fumbleshield, 0}, + { "transferauradruide", sp_transferaura, 0}, + { "earthquake", sp_earthquake, 0}, + { "stormwinds", sp_stormwinds, 0}, + { "homestone", sp_homestone, 0}, + { "wolfhowl", sp_wolfhowl, 0}, + { "versteinern", sp_petrify, 0}, + { "strongwall", sp_strong_wall, 0}, + { "gwyrrddestroymagic", sp_destroy_magic, 0}, + { "treewalkenter", sp_treewalkenter, 0}, + { "treewalkexit", sp_treewalkexit, 0}, + { "holyground", sp_holyground, 0}, + { "summonent", sp_summonent, 0}, + { "blessstonecircle", sp_blessstonecircle, 0}, + { "barkskin", sp_armorshield, 0}, + { "summonfireelemental", sp_drought, 0}, + { "maelstrom", sp_maelstrom, 0}, + { "magic_roots", sp_mallorn, 0}, + { "great_drought", sp_great_drought, 0}, + /* M_DRAIG */ + { "sparklechaos", sp_sparkle, 0}, + { "magicboost", sp_magicboost, 0}, + { "bloodsacrifice", sp_bloodsacrifice, 0}, + { "berserk", sp_berserk, 0}, + { "fumblecurse", sp_fumblecurse, patzer_fumblecurse}, + { "summonundead", sp_summonundead, patzer_peasantmob}, + { "combatrust", sp_combatrosthauch, 0}, + { "transferaurachaos", sp_transferaura, 0}, + { "firewall", sp_firewall, patzer_peasantmob}, + { "plague", sp_plague, patzer_peasantmob}, + { "chaosrow", sp_chaosrow, 0}, + { "summonshadow", sp_summonshadow, patzer_peasantmob}, + { "undeadhero", sp_undeadhero, 0}, + { "auraleak", sp_auraleak, 0}, + { "draigfumbleshield", sp_fumbleshield, 0}, + { "forestfire", sp_forest_fire, patzer_peasantmob}, + { "draigdestroymagic", sp_destroy_magic, 0}, + { "unholypower", sp_unholypower, 0}, + { "deathcloud", sp_deathcloud, patzer_peasantmob}, + { "summondragon", sp_summondragon, patzer_peasantmob}, + { "summonshadowlords", sp_summonshadowlords, patzer_peasantmob}, + { "chaossuction", sp_chaossuction, patzer_peasantmob}, + /* M_ILLAUN */ + { "sparkledream", sp_sparkle, 0}, + { "shadowknights", sp_shadowknights, 0}, + { "flee", sp_flee, 0}, + { "puttorest", sp_puttorest, 0}, + { "icastle", sp_icastle, 0}, + { "transferauratraum", sp_transferaura, 0}, + { "shapeshift", sp_illusionary_shapeshift, 0}, + { "dreamreading", sp_dreamreading, 0}, + { "tiredsoldiers", sp_tiredsoldiers, 0}, + { "reanimate", sp_reanimate, 0}, + { "analysedream", sp_analysedream, 0}, + { "disturbingdreams", sp_disturbingdreams, 0}, + { "sleep", sp_sleep, 0}, + { "wisps", 0, 0}, /* this spell is gone */ + { "gooddreams", sp_gooddreams, 0}, + { "illaundestroymagic", sp_destroy_magic, 0}, + { "clone", sp_clonecopy, 0}, + { "bad_dreams", sp_baddreams, 0}, + { "mindblast", sp_mindblast_temp, 0}, + { "orkdream", sp_sweetdreams, 0}, + { "summon_alp", sp_summon_alp, 0}, + /* M_CERDDOR */ + { "appeasement", sp_denyattack, 0}, + { "song_of_healing", sp_healing, 0}, + { "generous", sp_generous, 0}, + { "song_of_fear", sp_flee, 0}, + { "courting", sp_recruit, 0}, + { "song_of_confusion", sp_chaosrow, 0}, + { "heroic_song", sp_hero, 0}, + { "transfer_aura_song", sp_transferaura, 0}, + { "analysesong_unit", sp_analysesong_unit, 0}, + { "cerrdorfumbleshield", sp_fumbleshield, 0}, + { "calm_monster", sp_calm_monster, 0}, + { "seduction", sp_seduce, 0}, + { "headache", sp_headache, 0}, + { "sound_out", sp_pump, 0}, + { "bloodthirst", sp_berserk, 0}, + { "frighten", sp_frighten, 0}, + { "analyse_object", sp_analysesong_obj, 0}, + { "cerddor_destroymagic", sp_destroy_magic, 0}, + { "migration", sp_migranten, 0}, + { "summon_familiar", sp_summon_familiar, 0}, + { "raise_mob", sp_raisepeasants, 0}, + { "song_resist_magic", sp_song_resistmagic, 0}, + { "melancholy", sp_depression, 0}, + { "song_suscept_magic", sp_song_susceptmagic, 0}, + { "song_of_peace", sp_song_of_peace, 0}, + { "song_of_slavery", sp_charmingsong, 0}, + { "big_recruit", sp_bigrecruit, 0}, + { "calm_riot", sp_rallypeasantmob, 0}, + { "incite_riot", sp_raisepeasantmob, 0}, + /* M_TYBIED */ + { "analyze_magic", sp_analysemagic, 0}, + { "concealing_aura", sp_itemcloak, 0}, + { "tybiedfumbleshield", sp_fumbleshield, 0}, +#ifdef SHOWASTRAL_NOT_BORKED + { "show_astral", sp_showastral, 0}, +#endif + { "resist_magic", sp_resist_magic_bonus, 0}, + { "keeploot", sp_keeploot, 0}, + { "enterastral", sp_enterastral, 0}, + { "leaveastral", sp_leaveastral, 0}, + { "auratransfer", sp_transferaura, 0}, + { "shockwave", sp_stun, 0}, + { "antimagiczone", sp_antimagiczone, 0}, + { "destroy_magic", sp_destroy_magic, 0}, + { "pull_astral", sp_pullastral, 0}, + { "fetch_astral", sp_fetchastral, 0}, + { "steal_aura", sp_stealaura, 0}, + { "airship", sp_flying_ship, 0}, + { "break_curse", sp_break_curse, 0}, + { "eternal_walls", sp_eternizewall, 0}, + { "protective_runes", sp_magicrunes, 0}, + { "fish_shield", sp_reduceshield, 0}, + { "combat_speed", sp_speed, 0}, + { "view_reality", sp_viewreality, 0}, + { "double_time", sp_speed2, 0}, + { "armor_shield", sp_armorshield, 0}, + { "living_rock", sp_movecastle, 0}, + { "astral_disruption", sp_disruptastral, 0}, + { "sacrifice_strength", sp_permtransfer, 0}, + /* M_GRAY */ + /* Definitionen von Create_Artefaktspruechen */ + { "wyrm_transformation", sp_becomewyrm, 0}, + /* Monstersprueche */ + { "fiery_dragonbreath", sp_dragonodem, 0}, + { "icy_dragonbreath", sp_dragonodem, 0}, + { "powerful_dragonbreath", sp_dragonodem, 0}, + { "drain_skills", sp_dragonodem, 0}, + { "aura_of_fear", sp_flee, 0}, + { "shadowcall", sp_shadowcall, 0}, + { "immolation", sp_immolation, 0}, + { "firestorm", sp_immolation, 0}, + { "coldfront", sp_immolation, 0}, + { "acidrain", sp_immolation, 0}, + /* SPL_NOSPELL MUSS der letzte Spruch der Liste sein */ + { 0, 0, 0 } +}; + +static void register_spelldata(void) +{ + int i; + char zText[32]; + strcpy(zText, "fumble_"); + for (i = 0; spell_functions[i].sname; ++i) { + spelldata *data = spell_functions + i; + if (data->cast) { + register_function((pf_generic)data->cast, data->sname); + } + if (data->fumble) { + strlcpy(zText+7, data->sname, sizeof(zText)-7); + register_function((pf_generic)data->fumble, zText); + } + } +} + +/* ------------------------------------------------------------- */ +/* Name: Plappermaul +* Stufe: 4 +* Gebiet: Cerddor +* Kategorie: Einheit +* +* Wirkung: +* Einheit ausspionieren. Gibt auch Zauber und Kampfstatus aus. Wirkt +* gegen Magieresistenz. Ist diese zu hoch, so wird der Zauber entdeckt +* (Meldung) und der Zauberer erhaelt nur die Talente, nicht die Werte +* der Einheit und auch keine Zauber. +* +* Flag: +* (UNITSPELL | TESTCANSEE) +*/ +static int sp_babbler(castorder * co) +{ + unit *target; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + if (target->faction == mage->faction) { + /* Die Einheit ist eine der unsrigen */ + cmistake(mage, co->order, 45, MSG_MAGIC); + } + + /* Magieresistenz Unit */ + if (target_resists_magic(mage, target, TYP_UNIT, 0)) { + spy_message(5, mage, target); + msg = msg_message("babbler_resist", "unit mage", target, mage); + } else { + spy_message(100, mage, target); + msg = msg_message("babbler_effect", "unit", target); + } + r_addmessage(r, target->faction, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Traumdeuten +* Stufe: 7 +* Kategorie: Einheit +* +* Wirkung: +* Wirkt gegen Magieresistenz. Spioniert die Einheit aus. Gibt alle +* Gegenstaende, Talente mit Stufe, Zauber und Kampfstatus an. +* +* Magieresistenz hier pruefen, wegen Fehlermeldung +* +* Flag: +* (UNITSPELL) +*/ +static int sp_readmind(castorder * co) +{ + unit *target; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + if (target->faction == mage->faction) { + /* Die Einheit ist eine der unsrigen */ + cmistake(mage, co->order, 45, MSG_MAGIC); + } + + /* Magieresistenz Unit */ + if (target_resists_magic(mage, target, TYP_UNIT, 0)) { + cmistake(mage, co->order, 180, MSG_MAGIC); + /* "Fuehlt sich beobachtet" */ + ADDMSG(&target->faction->msgs, msg_message("stealdetect", "unit", target)); + return 0; + } + spy_message(2, mage, target); + + return cast_level; +} + +void register_spells(void) +{ + at_register(&at_wdwpyramid); + at_register(&at_deathcloud_compat); + + /* sp_summon_alp */ + register_alp(); + + /* init_firewall(); */ + ct_register(&ct_firewall); + ct_register(&ct_deathcloud); + + register_function((pf_generic) sp_blessedharvest, "cast_blessedharvest"); + register_function((pf_generic) sp_wdwpyramid, "wdwpyramid"); + register_function((pf_generic) sp_summon_familiar, "cast_familiar"); + register_function((pf_generic) sp_babbler, "cast_babbler"); + register_function((pf_generic) sp_readmind, "cast_readmind"); + register_function((pf_generic) sp_kampfzauber, "combat_spell"); + + register_spelldata(); +} diff --git a/src/spells/spells.h b/src/spells/spells.h new file mode 100644 index 000000000..508e1a18b --- /dev/null +++ b/src/spells/spells.h @@ -0,0 +1,32 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#ifndef H_SPL_SPELLS +#define H_SPL_SPELLS +#ifdef __cplusplus +extern "C" { +#endif + + struct ship; + struct curse; + struct unit; + + extern void register_spells(void); + + void set_spelldata(struct spell *sp); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/spells/unitcurse.c b/src/spells/unitcurse.c new file mode 100644 index 000000000..0587661a0 --- /dev/null +++ b/src/spells/unitcurse.c @@ -0,0 +1,372 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include +#include "unitcurse.h" + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include + +/* ------------------------------------------------------------- */ +/* + * C_AURA + */ +/* erhöht/senkt regeneration und maxaura um effect% */ +static message *cinfo_auraboost(const void *obj, objtype_t typ, const curse * c, + int self) +{ + struct unit *u = (struct unit *)obj; + unused(typ); + assert(typ == TYP_UNIT); + + if (self != 0) { + if (curse_geteffect(c) > 100) { + return msg_message("curseinfo::auraboost_0", "unit id", u, c->no); + } else { + return msg_message("curseinfo::auraboost_1", "unit id", u, c->no); + } + } + return NULL; +} + +static struct curse_type ct_auraboost = { + "auraboost", + CURSETYP_NORM, CURSE_SPREADMODULO, (NO_MERGE), + cinfo_auraboost +}; + +/* Magic Boost - Gabe des Chaos */ +static struct curse_type ct_magicboost = { + "magicboost", + CURSETYP_UNIT, CURSE_SPREADMODULO | CURSE_IMMUNE, M_MEN, cinfo_simple +}; + +/* ------------------------------------------------------------- */ +/* + * C_SLAVE + */ +static message *cinfo_slave(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unit *u; + unused(typ); + + assert(typ == TYP_UNIT); + u = (unit *) obj; + + if (self != 0) { + return msg_message("curseinfo::slave_1", "unit duration id", u, c->duration, + c->no); + } + return NULL; +} + +static struct curse_type ct_slavery = { "slavery", + CURSETYP_NORM, 0, NO_MERGE, + cinfo_slave +}; + +/* ------------------------------------------------------------- */ +/* + * C_CALM + */ +static message *cinfo_calm(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + assert(typ == TYP_UNIT); + + if (c->magician && c->magician->faction) { + faction *f = c->magician->faction; + unit *u = (unit *) obj; + + if (f == NULL || self == 0) { + const struct race *rc = u_irace(c->magician); + return msg_message("curseinfo::calm_0", "unit race id", u, rc, c->no); + } + return msg_message("curseinfo::calm_1", "unit faction id", u, f, c->no); + } + return NULL; +} + +static struct curse_type ct_calmmonster = { + "calmmonster", + CURSETYP_NORM, CURSE_SPREADNEVER | CURSE_ONLYONE, NO_MERGE, + cinfo_calm +}; + +/* ------------------------------------------------------------- */ +/* + * C_SPEED + */ +static message *cinfo_speed(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + assert(typ == TYP_UNIT); + + if (self != 0) { + unit *u = (unit *) obj; + return msg_message("curseinfo::speed_1", "unit number duration id", u, + c->data.i, c->duration, c->no); + } + return NULL; +} + +static struct curse_type ct_speed = { + "speed", + CURSETYP_UNIT, CURSE_SPREADNEVER, M_MEN, + cinfo_speed +}; + +/* ------------------------------------------------------------- */ +/* + * C_ORC + */ +message *cinfo_unit(const void *obj, objtype_t typ, const curse * c, int self) +{ + unused(typ); + assert(typ == TYP_UNIT); + + if (self != 0) { + unit *u = (unit *) obj; + return msg_message(mkname("curseinfo", c->type->cname), "unit id", u, + c->no); + } + return NULL; +} + +static struct curse_type ct_orcish = { + "orcish", + CURSETYP_UNIT, CURSE_SPREADMODULO | CURSE_ISNEW, M_MEN, + cinfo_unit +}; + +/* ------------------------------------------------------------- */ +/* + * C_KAELTESCHUTZ + */ +static message *cinfo_kaelteschutz(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + assert(typ == TYP_UNIT); + + if (self != 0) { + unit *u = (unit *) obj; + return msg_message("curseinfo::warmth_1", "unit number id", u, + get_cursedmen(u, c), c->no); + } + return NULL; +} + +static struct curse_type ct_insectfur = { + "insectfur", + CURSETYP_UNIT, CURSE_SPREADMODULO, (M_MEN | M_DURATION), + cinfo_kaelteschutz +}; + +/* ------------------------------------------------------------- */ +/* + * C_SPARKLE + */ +static message *cinfo_sparkle(const void *obj, objtype_t typ, const curse * c, + int self) +{ + const char *effects[] = { + NULL, /* end grau */ + "sparkle_1", + "sparkle_2", + NULL, /* end traum */ + "sparkle_3", + "sparkle_4", + NULL, /* end tybied */ + "sparkle_5", + "sparkle_6", + "sparkle_7", + "sparkle_8", + NULL, /* end cerrdor */ + "sparkle_9", + "sparkle_10", + "sparkle_11", + "sparkle_12", + NULL, /* end gwyrrd */ + "sparkle_13", + "sparkle_14", + "sparkle_15", + "sparkle_16", + "sparkle_17", + "sparkle_18", + NULL, /* end draig */ + }; + int m, begin = 0, end = 0; + unit *u; + unused(typ); + + assert(typ == TYP_UNIT); + u = (unit *) obj; + + if (!c->magician || !c->magician->faction) + return NULL; + + for (m = 0; m != c->magician->faction->magiegebiet; ++m) { + while (effects[end] != NULL) + ++end; + begin = end + 1; + end = begin; + } + + while (effects[end] != NULL) + ++end; + if (end == begin) + return NULL; + else { + int index = begin + curse_geteffect_int(c) % (end - begin); + return msg_message(mkname("curseinfo", effects[index]), "unit id", u, + c->no); + } +} + +static struct curse_type ct_sparkle = { "sparkle", + CURSETYP_UNIT, CURSE_SPREADMODULO, (M_MEN | M_DURATION), cinfo_sparkle +}; + +/* ------------------------------------------------------------- */ +/* + * C_STRENGTH + */ +static struct curse_type ct_strength = { "strength", + CURSETYP_UNIT, CURSE_SPREADMODULO, M_MEN, cinfo_simple +}; + +/* ------------------------------------------------------------- */ +/* + * C_ALLSKILLS (Alp) + */ +static struct curse_type ct_worse = { + "worse", CURSETYP_UNIT, CURSE_SPREADMODULO | CURSE_NOAGE, M_MEN, cinfo_unit +}; + +/* ------------------------------------------------------------- */ + +/* + * C_ITEMCLOAK + */ +static struct curse_type ct_itemcloak = { + "itemcloak", CURSETYP_UNIT, CURSE_SPREADNEVER, M_DURATION, cinfo_unit +}; + +/* ------------------------------------------------------------- */ + +static struct curse_type ct_fumble = { + "fumble", CURSETYP_NORM, CURSE_SPREADNEVER | CURSE_ONLYONE, NO_MERGE, + cinfo_unit +}; + +/* ------------------------------------------------------------- */ + +static struct curse_type ct_oldrace = { + "oldrace", CURSETYP_NORM, CURSE_SPREADALWAYS, NO_MERGE, NULL +}; + +static struct curse_type ct_magicresistance = { + "magicresistance", CURSETYP_UNIT, CURSE_SPREADMODULO, M_MEN, cinfo_simple +}; + +/* ------------------------------------------------------------- */ +/* + * C_SKILL + */ + +static int read_skill(struct storage *store, curse * c, void *target) +{ + int skill; + if (store->version < CURSETYPE_VERSION) { + skill = store->r_int(store); + store->r_int(store); /* men, deprecated */ + } else { + skill = store->r_int(store); + } + c->data.i = skill; + return 0; +} + +static int +write_skill(struct storage *store, const curse * c, const void *target) +{ + store->w_int(store, c->data.i); + return 0; +} + +static message *cinfo_skillmod(const void *obj, objtype_t typ, const curse * c, + int self) +{ + unused(typ); + + if (self != 0) { + unit *u = (unit *) obj; + int sk = c->data.i; + if (c->effect > 0) { + return msg_message("curseinfo::skill_1", "unit skill id", u, sk, c->no); + } else if (c->effect < 0) { + return msg_message("curseinfo::skill_2", "unit skill id", u, sk, c->no); + } + } + return NULL; +} + +static struct curse_type ct_skillmod = { + "skillmod", CURSETYP_NORM, CURSE_SPREADMODULO, M_MEN, cinfo_skillmod, + NULL, read_skill, write_skill +}; + +/* ------------------------------------------------------------- */ +void register_unitcurse(void) +{ + ct_register(&ct_auraboost); + ct_register(&ct_magicboost); + ct_register(&ct_slavery); + ct_register(&ct_calmmonster); + ct_register(&ct_speed); + ct_register(&ct_orcish); + ct_register(&ct_insectfur); + ct_register(&ct_sparkle); + ct_register(&ct_strength); + ct_register(&ct_worse); + ct_register(&ct_skillmod); + ct_register(&ct_itemcloak); + ct_register(&ct_fumble); + ct_register(&ct_oldrace); + ct_register(&ct_magicresistance); +} diff --git a/src/spells/unitcurse.h b/src/spells/unitcurse.h new file mode 100644 index 000000000..b74146d9a --- /dev/null +++ b/src/spells/unitcurse.h @@ -0,0 +1,32 @@ +/* vi: set ts=2: + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#ifndef _UCURSE_H +#define _UCURSE_H + +#include +#ifdef __cplusplus +extern "C" { +#endif + + struct curse; + struct message; + extern struct message *cinfo_unit(const void *obj, objtype_t typ, + const struct curse *c, int self); + + extern void register_unitcurse(void); + +#ifdef __cplusplus +} +#endif +#endif /* _UCURSE_H */