diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16ec084e0..d54dcb31b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,30 @@
-# Geplante Aenderungen in zukuenftigen Eressea-Versionen
-Als Anhaltspunkt fuer die Empfaenger der woechentlichen Testauswertungnen
-will ich versuchen, die Liste meiner Aenderungen seit dem letzten Release
-zu dokumentieren.
+# 3.26
-## Version 3.12.0
+ - Akademien, Traenke und Verzauberungen wirken auch bei LERNE AUTO
+ - Das lernen in einer Akademie erhoeht die Lernkosten. Koennen diese
+ nicht bezahlt werden, wird ohne deren Bonus gelernt.
+ - Lehrer muessen nicht mehr in der Akademie stehen, damit ihre Schueler
+ den Bonus bekommen
+ - Rohstoffe koennen jetzt bereits gesehen werden, wenn eine Einheit nur
+ die Haelfte des zum Abbau noetigen Talentes hat (statt bisher
+ Talent-1)
+ - Mauern der Ewigkeit und Störe Astrale Integrität brauchen keine
+ Stufenangabe, ihre Kosten sind nicht variabel [2651]
+
+# 3.25
+
+ - Ab sofort ist es nicht mehr erlaubt, Befehle mit weniger als 3
+ Zeichen abzukürzen.
+ - Leuchttürme entdecken Seeschlangen und Drachen auf dem Ozean [2688]
+ - Magieresistenz von Insekten und Goblins repariert [2685]
+ - Getarnte Einheiten können wieder Eisen abbauen [2679]
+ - Gestaltwandlung kann nur einmal auf die selbe Einheit wirken [2680]
+ - Handel benötigt eine Burg mit Mindestgröße 2 [2678]
+ - Geschützte Leerzeichen in Befehlen werden ignoriert [2670]
+
+# 3.12
-- [other] optimierte Berechnung der Sichtbarkeit von Leuchttuermen
- [bug] Einheitenlimit bei GIB PERSON beachten
- [bug] Einheitenlimit bei ALLIANCE JOIN beachten
- [rule] Einheiten- und Personenzahl im Report beinhaltet *alle* Einheiten der Partei.
diff --git a/conf/e2/catalog.xml b/conf/e2/catalog.xml
deleted file mode 100644
index d0c7333aa..000000000
--- a/conf/e2/catalog.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/conf/e2/config.json b/conf/e2/config.json
index 114fffe52..fdb616816 100644
--- a/conf/e2/config.json
+++ b/conf/e2/config.json
@@ -1,63 +1,64 @@
{
- "include": [
- "config://conf/keywords.json",
- "config://conf/calendar.json",
- "config://conf/prefixes.json",
- "config://conf/e2/locales.json",
- "config://conf/e2/terrains.json",
- "config://conf/e2/items.json",
- "config://res/core/ships.xml",
- "config://res/core/common/buildings.xml",
- "config://res/eressea/buildings.xml",
- "config://res/buildings/castle.xml",
- "config://res/eressea/races.xml",
- "config://res/eressea/artrewards.xml",
- "config://res/eressea/spells.xml",
- "config://res/eressea/spellbooks/gray.xml",
- "config://res/eressea/spellbooks/gwyrrd.xml",
- "config://res/eressea/spellbooks/draig.xml",
- "config://res/eressea/spellbooks/illaun.xml",
- "config://res/eressea/spellbooks/cerddor.xml",
- "config://res/eressea/spellbooks/tybied.xml"
- ],
- "disabled": [
- "jsreport"
- ],
- "settings": {
- "game.name" : "Eressea",
- "game.mailcmd" : "ERESSEA",
- "game.id" : 2,
- "orders.default": "work",
- "NewbieImmunity": 8,
- "modules.market": false,
- "modules.astralspace": true,
- "modules.wormhole": true,
- "modules.iceberg": true,
- "modules.volcano": true,
- "monsters.spawn.chance": 50,
- "entertain.base": 0,
- "entertain.perlevel": 20,
- "taxing.perlevel": 20,
- "nmr.timeout": 5,
- "nmr.removenewbie": false,
- "GiveRestriction": 3,
- "hunger.long": false,
- "hunger.damage": "1d8+6",
- "init_spells": 0,
- "game.era": 2,
- "game.start": 184,
- "rules.reserve.twophase": true,
- "rules.give.max_men": -1,
- "rules.check_overload": false,
- "rules.limit.faction": 2500,
- "rules.maxskills.magic": 5,
- "rules.guard.base_stop_prob": 0.30,
- "rules.guard.skill_stop_prob": 0.05,
- "rules.guard.amulet_stop_prob": 0.10,
- "rules.guard.guard_number_stop_prob": 0.001,
- "rules.guard.castle_stop_prob": 0.05,
- "rules.guard.region_type_stop_prob": 0.05,
- "rules.economy.repopulate_maximum": 500,
- "rules.lighthouse.unit_capacity": true
- }
+ "settings": {
+ "game.name": "Eressea",
+ "game.mailcmd": "ERESSEA",
+ "game.id": 2,
+ "orders.default": "work",
+ "NewbieImmunity": 8,
+ "modules.market": false,
+ "modules.astralspace": true,
+ "modules.wormhole": true,
+ "modules.iceberg": true,
+ "modules.volcano": true,
+ "monsters.spawn.chance": 50,
+ "entertain.base": 0,
+ "entertain.perlevel": 20,
+ "taxing.perlevel": 20,
+ "nmr.timeout": 5,
+ "nmr.removenewbie": false,
+ "GiveRestriction": 3,
+ "hunger.long": false,
+ "hunger.damage": "1d8+6",
+ "init_spells": 0,
+ "game.era": 2,
+ "game.start": 184,
+ "rules.reserve.twophase": true,
+ "rules.give.max_men": -1,
+ "rules.check_overload": false,
+ "rules.limit.faction": 2500,
+ "rules.maxskills.magic": 5,
+ "rules.guard.base_stop_prob": 0.30,
+ "rules.guard.skill_stop_prob": 0.05,
+ "rules.guard.amulet_stop_prob": 0.10,
+ "rules.guard.guard_number_stop_prob": 0.001,
+ "rules.guard.castle_stop_prob": 0.05,
+ "rules.guard.region_type_stop_prob": 0.05,
+ "rules.economy.repopulate_maximum": 500,
+ "rules.lighthouse.unit_capacity": true,
+ "resource.visibility.rule": 0
+ },
+ "disabled": [
+ "jsreport"
+ ],
+ "include": [
+ "config://conf/keywords.json",
+ "config://conf/calendar.json",
+ "config://conf/prefixes.json",
+ "config://conf/e2/locales.json",
+ "config://conf/e2/terrains.json",
+ "config://conf/e2/items.json",
+ "config://res/core/ships.xml",
+ "config://res/core/common/buildings.xml",
+ "config://res/eressea/buildings.xml",
+ "config://res/buildings/castle.xml",
+ "config://res/eressea/races.xml",
+ "config://res/eressea/artrewards.xml",
+ "config://res/eressea/spells.xml",
+ "config://res/eressea/spellbooks/gray.xml",
+ "config://res/eressea/spellbooks/gwyrrd.xml",
+ "config://res/eressea/spellbooks/draig.xml",
+ "config://res/eressea/spellbooks/illaun.xml",
+ "config://res/eressea/spellbooks/cerddor.xml",
+ "config://res/eressea/spellbooks/tybied.xml"
+ ]
}
diff --git a/conf/e3/catalog.xml b/conf/e3/catalog.xml
deleted file mode 100644
index 9987e72c8..000000000
--- a/conf/e3/catalog.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/conf/e3/config.json b/conf/e3/config.json
index dcc3e9f53..c7c390372 100644
--- a/conf/e3/config.json
+++ b/conf/e3/config.json
@@ -67,7 +67,6 @@
"init_spells": 0,
"recruit.allow_merge": true,
"study.expensivemigrants": true,
- "study.speedup": 2,
"game.era": 3,
"game.start": 1,
"rules.reserve.twophase": true,
diff --git a/conf/ex/config.json b/conf/ex/config.json
new file mode 100644
index 000000000..58d460f85
--- /dev/null
+++ b/conf/ex/config.json
@@ -0,0 +1,68 @@
+{
+ "include": [
+ "config://conf/keywords.json",
+ "config://conf/calendar.json",
+ "config://conf/prefixes.json",
+ "config://conf/e2/locales.json",
+ "config://conf/e2/terrains.json",
+ "config://conf/e2/items.json",
+ "config://res/core/ships.xml",
+ "config://res/core/common/buildings.xml",
+ "config://res/eressea/buildings.xml",
+ "config://res/buildings/castle.xml",
+ "config://res/eressea/races.xml",
+ "config://res/eressea/artrewards.xml",
+ "config://res/eressea/spells.xml",
+ "config://res/eressea/spellbooks/gray.xml",
+ "config://res/eressea/spellbooks/gwyrrd.xml",
+ "config://res/eressea/spellbooks/draig.xml",
+ "config://res/eressea/spellbooks/illaun.xml",
+ "config://res/eressea/spellbooks/cerddor.xml",
+ "config://res/eressea/spellbooks/tybied.xml"
+ ],
+ "disabled": [
+ "destroy",
+ "steal",
+ "number",
+ "jsreport"
+ ],
+ "settings": {
+ "game.name" : "Eressea",
+ "game.mailcmd" : "ERESSEA",
+ "game.id" : 2,
+ "orders.default": "work",
+ "NewbieImmunity": 8,
+ "modules.market": false,
+ "modules.astralspace": true,
+ "modules.wormhole": true,
+ "modules.iceberg": true,
+ "modules.volcano": true,
+ "monsters.spawn.chance": 50,
+ "entertain.base": 0,
+ "entertain.perlevel": 20,
+ "taxing.perlevel": 20,
+ "nmr.timeout": 5,
+ "nmr.removenewbie": false,
+ "GiveRestriction": 3,
+ "hunger.long": false,
+ "hunger.damage": "1d8+6",
+ "init_spells": 0,
+ "game.era": 2,
+ "game.start": 184,
+ "rules.reserve.twophase": true,
+ "rules.give.max_men": -1,
+ "rules.check_overload": false,
+ "rules.wage.function": 2,
+ "monsters.spawn.chance" : 0,
+ "rules.limit.faction": 2500,
+ "rules.maxskills.magic": 5,
+ "rules.guard.base_stop_prob": 0.30,
+ "rules.guard.skill_stop_prob": 0.05,
+ "rules.guard.amulet_stop_prob": 0.10,
+ "rules.guard.guard_number_stop_prob": 0.001,
+ "rules.guard.castle_stop_prob": 0.05,
+ "rules.guard.region_type_stop_prob": 0.05,
+ "rules.economy.repopulate_maximum": 500,
+ "rules.lighthouse.unit_capacity": true
+ }
+}
diff --git a/conf/ex/items.json b/conf/ex/items.json
new file mode 100644
index 000000000..643248d32
--- /dev/null
+++ b/conf/ex/items.json
@@ -0,0 +1,50 @@
+{
+ "include": [
+ "config://res/core/spoils.xml",
+ "config://res/core/common/herbs.xml",
+ "config://res/core/common/items.xml",
+ "config://res/core/common/luxuries.xml",
+ "config://res/core/common/potions.xml",
+ "config://res/core/armor/chainmail.xml",
+ "config://res/core/armor/laenmail.xml",
+ "config://res/core/armor/laenshield.xml",
+ "config://res/core/armor/plate.xml",
+ "config://res/core/armor/rustychainmail.xml",
+ "config://res/core/armor/rustyshield.xml",
+ "config://res/core/armor/shield.xml",
+ "config://res/core/resources/cart.xml",
+ "config://res/core/resources/horse.xml",
+ "config://res/core/resources/hp.xml",
+ "config://res/core/resources/iron.xml",
+ "config://res/core/resources/laen.xml",
+ "config://res/core/resources/log.xml",
+ "config://res/core/resources/mallorn.xml",
+ "config://res/core/resources/mallornseed.xml",
+ "config://res/core/resources/seed.xml",
+ "config://res/core/resources/peasant.xml",
+ "config://res/core/resources/stone.xml",
+ "config://res/core/weapons/axe.xml",
+ "config://res/core/weapons/bow.xml",
+ "config://res/core/weapons/catapult.xml",
+ "config://res/core/weapons/crossbow.xml",
+ "config://res/core/weapons/firesword.xml",
+ "config://res/core/weapons/greatbow.xml",
+ "config://res/core/weapons/greatsword.xml",
+ "config://res/core/weapons/halberd.xml",
+ "config://res/core/weapons/laensword.xml",
+ "config://res/core/weapons/lance.xml",
+ "config://res/core/weapons/mallornbow.xml",
+ "config://res/core/weapons/mallorncrossbow.xml",
+ "config://res/core/weapons/mallornlance.xml",
+ "config://res/core/weapons/mallornspear.xml",
+ "config://res/core/weapons/runesword.xml",
+ "config://res/core/weapons/rustyaxe.xml",
+ "config://res/core/weapons/rustygreatsword.xml",
+ "config://res/core/weapons/rustyhalberd.xml",
+ "config://res/core/weapons/rustysword.xml",
+ "config://res/core/weapons/spear.xml",
+ "config://res/core/weapons/sword.xml",
+ "config://res/eressea/items.xml",
+ "config://res/adamantium.xml"
+ ]
+}
diff --git a/conf/ex/locales.json b/conf/ex/locales.json
new file mode 100644
index 000000000..02967d134
--- /dev/null
+++ b/conf/ex/locales.json
@@ -0,0 +1,34 @@
+{
+ "include": [
+ "config://res/translations/strings.de.po",
+ "config://res/translations/strings-e2.de.po",
+ "config://res/translations/strings.en.po",
+ "config://res/translations/strings-e2.en.po",
+ "config://res/translations/messages.de.po",
+ "config://res/translations/messages.en.po",
+ "config://res/core/messages.xml"
+ ],
+ "aliases": {
+ "de": {
+ "spell::earthquake": [
+ "Beschwöre einen Erdelementar",
+ "Beschwörung eines Erdelementares"
+ ],
+ "spell::goodwinds": [
+ "Beschwörung eines Wasserelementares",
+ "Beschwöre einen Wasserelementar"
+ ],
+ "spell::stormwinds": [
+ "Beschwöre einen Sturmelementar",
+ "Beschwörung eines Sturmelementares"
+ ],
+ "spell::summonfireelemental": [
+ "Beschwöre einen Hitzeelementar",
+ "Beschwörung eines Hitzeelementares"
+ ]
+ },
+ "en": {
+ "spell::migration": "Rit of Acceptance"
+ }
+ }
+}
diff --git a/conf/ex/readme.txt b/conf/ex/readme.txt
new file mode 100644
index 000000000..c3e8801e9
--- /dev/null
+++ b/conf/ex/readme.txt
@@ -0,0 +1,8 @@
+# Änderungen gegenüber E2
+
+- ARBEITE produziert nur genug Silber für den Unterhalt der Einheit
+- NUMMER Befehl abgeschafft.
+- Ebene und Hochland haben 20%, alle anderen Regionen 10% der Arbeitsplätze von E2.
+- Keine Zufallsmonster
+- ZERSTÖRE Befehl abgeschafft.
+- BEKLAUE Befehl abgeschafft.
diff --git a/conf/ex/terrains.json b/conf/ex/terrains.json
new file mode 100644
index 000000000..eb1019fe1
--- /dev/null
+++ b/conf/ex/terrains.json
@@ -0,0 +1,286 @@
+{
+ "terrains": {
+ "ocean": {
+ "size": 10,
+ "flags": [ "swim", "sea", "sail", "fly" ]
+ },
+ "plain": {
+ "size": 2000,
+ "herbs": [ "h0", "h1", "h2", "h3", "h4", "h5" ],
+ "seed": 3,
+ "road": 50,
+ "flags": [ "forest", "cavalry", "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 0.1,
+ "base": "5d8",
+ "div": "2d20+10",
+ "level": "2d4-1"
+ },
+ "stone": {
+ "chance": 0.15,
+ "base": "5d8",
+ "div": "2d30+20",
+ "level": "1d4"
+ },
+ "laen": {
+ "chance": 0.01,
+ "base": "1d4",
+ "div": "2d20+50",
+ "level": "1d4"
+ }
+ }
+ },
+ "swamp": {
+ "size": 200,
+ "herbs": [ "h6", "h7", "h8" ],
+ "seed": 2,
+ "road": 75,
+ "flags": [ "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 0.02,
+ "base": "5d8",
+ "div": "2d20+10",
+ "level": "2d4-1"
+ },
+ "stone": {
+ "chance": 0.02,
+ "base": "5d8",
+ "div": "2d30+20",
+ "level": "1d4"
+ },
+ "laen": {
+ "chance": 0.02,
+ "base": "1d4",
+ "div": "2d20+50",
+ "level": "1d4"
+ }
+ }
+ },
+ "desert": {
+ "size": 50,
+ "herbs": [ "h9", "h10", "h11" ],
+ "seed": 2,
+ "road": 100,
+ "flags": [ "land", "walk", "sail", "fly", "cavalry" ],
+ "production": {
+ "iron": {
+ "chance": 0.15,
+ "base": "5d8",
+ "div": "2d20+10",
+ "level": "2d4-1"
+ },
+ "stone": {
+ "chance": 0.25,
+ "base": "5d8",
+ "div": "2d30+20",
+ "level": "1d4"
+ },
+ "laen": {
+ "chance": 0.025,
+ "base": "1d4",
+ "div": "2d20+50",
+ "level": "1d4"
+ }
+ }
+ },
+ "highland": {
+ "size": 800,
+ "herbs": [ "h12", "h13", "h14" ],
+ "seed": 2,
+ "road": 100,
+ "flags": [ "land", "walk", "sail", "fly", "cavalry" ],
+ "production": {
+ "iron": {
+ "chance": 0.15,
+ "base": "5d8",
+ "div": "2d20+10",
+ "level": "2d4-1"
+ },
+ "stone": {
+ "chance": 0.25,
+ "base": "5d8",
+ "div": "2d30+20",
+ "level": "1d4"
+ },
+ "laen": {
+ "chance": 0.025,
+ "base": "1d4",
+ "div": "2d20+50",
+ "level": "1d4"
+ }
+ }
+ },
+ "mountain": {
+ "size": 100,
+ "herbs": [ "h15", "h16", "h17" ],
+ "seed": 2,
+ "road": 250,
+ "flags": [ "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 1.0,
+ "base": "50",
+ "div": "50",
+ "level": "1"
+ },
+ "stone": {
+ "chance": 1.0,
+ "base": "100",
+ "div": "100",
+ "level": "1"
+ },
+ "laen": {
+ "chance": 0.05,
+ "base": "4",
+ "div": "100",
+ "level": "1"
+ }
+ }
+ },
+ "glacier": {
+ "size": 10,
+ "herbs": [ "h18", "h19", "h20" ],
+ "seed": 2,
+ "road": 250,
+ "flags": [ "arctic", "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 1.0,
+ "base": "3",
+ "div": "50",
+ "level": "1"
+ },
+ "stone": {
+ "chance": 1.0,
+ "base": "2",
+ "div": "100",
+ "level": "1"
+ },
+ "laen": {
+ "chance": 0.05,
+ "base": "4",
+ "div": "100",
+ "level": "1"
+ }
+ }
+ },
+ "iceberg": {
+ "size": 10,
+ "herbs": [ "h18", "h19", "h20" ],
+ "flags": [ "arctic", "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 0.9,
+ "base": "3",
+ "div": "50",
+ "level": "1"
+ },
+ "stone": {
+ "chance": 0.9,
+ "base": "2",
+ "div": "100",
+ "level": "1"
+ }
+ }
+ },
+ "iceberg_sleep": {
+ "size": 10,
+ "herbs": [ "h18", "h19", "h20" ],
+ "flags": [ "arctic", "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 0.9,
+ "base": "3",
+ "div": "50",
+ "level": "1"
+ },
+ "stone": {
+ "chance": 0.9,
+ "base": "2",
+ "div": "100",
+ "level": "1"
+ },
+ "laen": {
+ "chance": 0.05,
+ "base": "4",
+ "div": "100",
+ "level": "1"
+ }
+ }
+ },
+ "firewall": {
+ "flags": [ "forbidden" ]
+ },
+ "fog": {
+ "flags": [ "walk", "fly" ]
+ },
+ "thickfog": {
+ "flags": [ "forbidden" ]
+ },
+ "volcano": {
+ "size": 50,
+ "road": 250,
+ "seed": 1,
+ "flags": [ "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 0.5,
+ "level": "1",
+ "base": "50",
+ "div": "50"
+ },
+ "stone": {
+ "chance": 0.5,
+ "level": "1",
+ "base": "100",
+ "div": "100"
+ },
+ "laen": {
+ "chance": 0.075,
+ "level": "1",
+ "base": "4",
+ "div": "100"
+ }
+ }
+ },
+ "activevolcano": {
+ "size": 50,
+ "road": 250,
+ "flags": [ "land", "walk", "sail", "fly" ],
+ "production": {
+ "iron": {
+ "chance": 0.5,
+ "level": "1",
+ "base": "50",
+ "div": "50"
+ },
+ "stone": {
+ "chance": 0.5,
+ "level": "1",
+ "base": "100",
+ "div": "100"
+ },
+ "laen": {
+ "chance": 0.075,
+ "level": "1",
+ "base": "4",
+ "div": "100"
+ }
+ }
+ },
+ "hell": {
+ "flags": [ "walk" ]
+ },
+ "hall1": {
+ "flags": [ "land", "walk", "sail" ]
+ },
+ "corridor1": {
+ "flags": [ "land", "walk", "sail" ]
+ },
+ "wall1": {
+ "flags": [ "forbidden", "land" ]
+ }
+ }
+}
diff --git a/res/e3a/spells.xml b/res/e3a/spells.xml
index d72386803..64172390f 100644
--- a/res/e3a/spells.xml
+++ b/res/e3a/spells.xml
@@ -595,7 +595,7 @@
-
+
diff --git a/res/eressea/spells.xml b/res/eressea/spells.xml
index a56b147ef..0570fa80d 100644
--- a/res/eressea/spells.xml
+++ b/res/eressea/spells.xml
@@ -214,7 +214,7 @@
-
+
@@ -422,7 +422,7 @@
-
+
diff --git a/res/translations/messages.de.po b/res/translations/messages.de.po
index 2cd71ecbc..f4a70e083 100644
--- a/res/translations/messages.de.po
+++ b/res/translations/messages.de.po
@@ -1161,7 +1161,7 @@ msgid "spyreport_faction"
msgstr "\"$unit($target) gehört der Partei $faction($faction) an.\""
msgid "ship_drift"
-msgstr "\"Die $ship($ship) treibt nach $direction($dir).\""
+msgstr "\"Die $ship($ship) hat zu wenig Segler und treibt nach $direction($dir).\""
msgid "error_max_magicians"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Es kann maximal $int($amount) Magier pro Partei geben.\""
diff --git a/res/translations/messages.en.po b/res/translations/messages.en.po
index 1ab2b1608..0e1d32a51 100644
--- a/res/translations/messages.en.po
+++ b/res/translations/messages.en.po
@@ -1161,7 +1161,7 @@ msgid "spyreport_faction"
msgstr "\"$unit($target) belongs to $faction($faction).\""
msgid "ship_drift"
-msgstr "\"The ship $ship($ship) drifts to the $direction($dir).\""
+msgstr "\"The ship $ship($ship) needs more sailors and drifts to the $direction($dir).\""
msgid "error_max_magicians"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - There may not be more than $int($amount) magicians in your faction.\""
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 76764f0bd..f14303e6c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -92,7 +92,6 @@ set (PARSER_SRC
set (ERESSEA_SRC
vortex.c
- academy.c
alchemy.c
automate.c
battle.c
@@ -168,6 +167,7 @@ set(SERVER_SRC
bindings.c
console.c
helpers.c
+ signals.c
main.c
)
@@ -179,8 +179,10 @@ set (SERVER_SRC ${SERVER_SRC}
)
endif(CURSES_FOUND)
-find_program(IWYU_PATH NAMES include-what-you-use iwyu)
-if(NOT IWYU_PATH)
+#find_program(IWYU_PATH NAMES include-what-you-use iwyu)
+if(IWYU_PATH)
+ # set(C_INCLUDE_WHAT_YOU_USE "${IWYU_PATH} -Xiwyu --no_fwd_decls")
+else(IWYU_PATH)
message(STATUS "Could not find the program include-what-you-use")
endif()
@@ -212,7 +214,6 @@ target_link_libraries(eressea
)
set(TESTS_SRC
- academy.test.c
alchemy.test.c
automate.test.c
battle.test.c
diff --git a/src/academy.c b/src/academy.c
deleted file mode 100644
index 630552b10..000000000
--- a/src/academy.c
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "platform.h"
-#include "kernel/config.h"
-#include
-#include
-#include
-#include
-
-#include "academy.h"
-#include "study.h"
-
-void academy_teaching_bonus(struct unit *u, skill_t sk, int students) {
- if (students > 0 && sk != NOSKILL) {
- /* actually students * EXPERIENCEDAYS / MAX_STUDENTS */
- learn_skill(u, sk, students);
- }
-}
-
-bool academy_can_teach(unit *teacher, unit *scholar, skill_t sk) {
- const struct building_type *btype = bt_find("academy");
- return (active_building(scholar, btype));
-}
-
diff --git a/src/academy.h b/src/academy.h
deleted file mode 100644
index 3c496d8fa..000000000
--- a/src/academy.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifndef H_ACADEMY
-#define H_ACADEMY
-
-#include
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
- struct unit;
- void academy_teaching_bonus(struct unit *u, skill_t sk, int academy);
- bool academy_can_teach(struct unit *teacher, struct unit *scholar, skill_t sk);
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/src/academy.test.c b/src/academy.test.c
deleted file mode 100644
index 6f26cee93..000000000
--- a/src/academy.test.c
+++ /dev/null
@@ -1,52 +0,0 @@
-#include
-
-#include "academy.h"
-#include "skill.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include "tests.h"
-
-static void test_academy(CuTest * tc)
-{
- faction *f;
- unit *u, *u2;
- region *r;
- building *b;
- const item_type *it_silver;
-
- test_setup();
- config_set_int("skills.cost.alchemy", 100);
- r = test_create_region(0, 0, NULL);
- f = test_create_faction(NULL);
- u = test_create_unit(f, r);
- b = test_create_building(r, test_create_buildingtype("academy"));
- u2 = test_create_unit(f, r);
- it_silver = test_create_silver();
-
- CuAssert(tc, "teacher must be in academy", !academy_can_teach(u, u2, SK_CROSSBOW));
- u_set_building(u, b);
- CuAssert(tc, "student must be in academy", !academy_can_teach(u, u2, SK_CROSSBOW));
- u_set_building(u2, b);
- CuAssert(tc, "student must have 50 silver", !academy_can_teach(u, u2, SK_CROSSBOW));
- i_change(&u2->items, it_silver, 50);
- CuAssert(tc, "building must be maintained", !academy_can_teach(u, u2, SK_CROSSBOW));
- b->flags |= BLD_MAINTAINED;
- CuAssert(tc, "building must have capacity", !academy_can_teach(u, u2, SK_CROSSBOW));
- b->size = 2;
- CuAssertTrue(tc, academy_can_teach(u, u2, SK_CROSSBOW));
- test_teardown();
-}
-
-CuSuite *get_academy_suite(void)
-{
- CuSuite *suite = CuSuiteNew();
- SUITE_ADD_TEST(suite, test_academy);
- return suite;
-}
diff --git a/src/alchemy.c b/src/alchemy.c
index bc66cb931..bd4220bdb 100644
--- a/src/alchemy.c
+++ b/src/alchemy.c
@@ -73,7 +73,6 @@ void herbsearch(unit * u, int max_take)
int herbsfound;
const item_type *whichherb;
int effsk = effskill(u, SK_HERBALISM, NULL);
- int herbs = rherbs(r);
if (effsk == 0) {
cmistake(u, u->thisorder, 59, MSG_PRODUCE);
@@ -91,13 +90,10 @@ void herbsearch(unit * u, int max_take)
return;
}
- if (max_take < herbs) {
- herbs = max_take;
- }
herbsfound = ntimespprob(effsk * u->number,
(double)rherbs(r) / 100.0F, -0.01F);
- if (herbsfound > herbs) herbsfound = herbs;
+ if (herbsfound > max_take) herbsfound = max_take;
rsetherbs(r, rherbs(r) - herbsfound);
if (herbsfound) {
diff --git a/src/automate.c b/src/automate.c
index ecb130fff..24657f893 100644
--- a/src/automate.c
+++ b/src/automate.c
@@ -6,6 +6,8 @@
#include "kernel/order.h"
#include "kernel/region.h"
#include "kernel/unit.h"
+#include "kernel/pool.h"
+#include "kernel/item.h"
#include "util/keyword.h"
#include "util/log.h"
@@ -210,7 +212,12 @@ void do_autostudy(region *r)
autostudy_run(scholars, nscholars);
for (i = 0; i != nscholars; ++i) {
int days = STUDYDAYS * scholars[i].learn;
- learn_skill(scholars[i].u, skill, days);
+ int money = learn_skill(scholars[i].u, skill, days, 0);
+ if (money > 0) {
+ use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, money);
+ ADDMSG(&u->faction->msgs, msg_message("studycost",
+ "unit region cost skill", u, u->region, money, skill));
+ }
}
}
}
diff --git a/src/battle.c b/src/battle.c
index 7e3373019..2d45c6df8 100644
--- a/src/battle.c
+++ b/src/battle.c
@@ -899,7 +899,7 @@ void drain_exp(struct unit *u, int n)
}
}
if (sk != NOSKILL) {
- reduce_skill_days(u, sk, n);
+ change_skill_days(u, sk, -n);
}
}
diff --git a/src/bindings.c b/src/bindings.c
index 6d2481a29..453c29d08 100755
--- a/src/bindings.c
+++ b/src/bindings.c
@@ -310,18 +310,6 @@ static int tolua_create_curse(lua_State * L)
return 1;
}
-static int tolua_learn_skill(lua_State * L)
-{
- unit *u = (unit *)tolua_tousertype(L, 1, 0);
- const char *skname = tolua_tostring(L, 2, 0);
- int days = (int)tolua_tonumber(L, 3, 0);
- skill_t sk = findskill(skname);
- if (sk != NOSKILL) {
- learn_skill(u, sk, days);
- }
- return 0;
-}
-
static int tolua_update_scores(lua_State * L)
{
UNUSED_ARG(L);
@@ -973,7 +961,6 @@ int tolua_bindings_open(lua_State * L, const dictionary *inifile)
tolua_function(L, TOLUA_CAST "remove_empty_units", tolua_remove_empty_units);
tolua_function(L, TOLUA_CAST "update_scores", tolua_update_scores);
tolua_function(L, TOLUA_CAST "update_owners", tolua_update_owners);
- tolua_function(L, TOLUA_CAST "learn_skill", tolua_learn_skill);
tolua_function(L, TOLUA_CAST "create_curse", tolua_create_curse);
tolua_function(L, TOLUA_CAST "translate", &tolua_translate);
tolua_function(L, TOLUA_CAST "spells", tolua_get_spells);
diff --git a/src/creport.c b/src/creport.c
index 4fb9a50b2..551ee7fa9 100644
--- a/src/creport.c
+++ b/src/creport.c
@@ -1363,9 +1363,11 @@ static void cr_output_region(FILE * F, report_context * ctx, region * r)
fprintf(F, "%d;Rekruten\n", rpeasants(r) / RECRUITFRACTION);
}
if (max_production(r)) {
- int p_wage = wage(r, NULL, NULL, turn + 1);
+ /* Im CR steht der Bauernlohn, der bei Trauer nur 10 ist */
+ bool mourn = is_mourning(r, turn);
+ int p_wage = peasant_wage(r, mourn);
fprintf(F, "%d;Lohn\n", p_wage);
- if (is_mourning(r, turn + 1)) {
+ if (mourn) {
fputs("1;mourning\n", F);
}
}
diff --git a/src/economy.c b/src/economy.c
index d7a1b9373..fd87ee20c 100644
--- a/src/economy.c
+++ b/src/economy.c
@@ -1993,7 +1993,8 @@ expandwork(region * r, econ_request * work_begin, econ_request * work_end, int m
/* n: verbleibende Einnahmen */
/* fishes: maximale Arbeiter */
int jobs = maxwork;
- int p_wage = wage(r, NULL, NULL, turn);
+ bool mourn = is_mourning(r, turn);
+ int p_wage = peasant_wage(r, mourn);
int money = rmoney(r);
if (total > 0 && !rule_autowork()) {
econ_request *o;
@@ -2017,7 +2018,7 @@ expandwork(region * r, econ_request * work_begin, econ_request * work_end, int m
assert(workers >= 0);
- u->n = workers * wage(u->region, u->faction, u_race(u), turn);
+ u->n = workers * wage(u->region, u_race(u));
jobs -= workers;
assert(jobs >= 0);
@@ -2061,7 +2062,7 @@ static int work_cmd(unit * u, order * ord, econ_request ** io_req)
}
return 0;
}
- w = wage(r, u->faction, u_race(u), turn);
+ w = wage(r, u_race(u));
add_request(req++, ECON_WORK, u, ord, w * u->number);
*io_req = req;
return u->number;
diff --git a/src/items/xerewards.c b/src/items/xerewards.c
index e0ce80802..25b1acc0b 100644
--- a/src/items/xerewards.c
+++ b/src/items/xerewards.c
@@ -37,7 +37,7 @@ struct order *ord)
skill *sv = u->skills;
while (sv != u->skills + u->skill_size) {
/* only one person learns for 3 weeks */
- learn_skill(u, (skill_t)sv->id, STUDYDAYS * 3);
+ change_skill_days(u, (skill_t)sv->id, STUDYDAYS * 3);
++sv;
}
}
diff --git a/src/items/xerewards.test.c b/src/items/xerewards.test.c
index d135b27fb..c8bda3f04 100644
--- a/src/items/xerewards.test.c
+++ b/src/items/xerewards.test.c
@@ -52,17 +52,17 @@ static void test_skillpotion(CuTest *tc) {
itype = test_create_itemtype("skillpotion");
change_resource(u, itype->rtype, 2);
- learn_skill(u, SK_ENTERTAINMENT, STUDYDAYS * u->number);
+ change_skill_days(u, SK_ENTERTAINMENT, STUDYDAYS * u->number);
pSkill = unit_skill(u, SK_ENTERTAINMENT);
sk_set(pSkill, 5);
initialWeeks_Entertainment = pSkill->weeks = 4;
- learn_skill(u, SK_STAMINA, STUDYDAYS * u->number);
+ change_skill_days(u, SK_STAMINA, STUDYDAYS * u->number);
pSkill = unit_skill(u, SK_STAMINA);
sk_set(pSkill, 5);
initialWeeks_Stamina = pSkill->weeks = 4;
- learn_skill(u, SK_MAGIC, STUDYDAYS * u->number);
+ change_skill_days(u, SK_MAGIC, STUDYDAYS * u->number);
pSkill = unit_skill(u, SK_MAGIC);
sk_set(pSkill, 5);
initialWeeks_Magic = pSkill->weeks = 4;
diff --git a/src/kernel/building.c b/src/kernel/building.c
index a2283c1cc..f93f28948 100644
--- a/src/kernel/building.c
+++ b/src/kernel/building.c
@@ -495,20 +495,22 @@ extern struct attrib_type at_icastle;
/** returns the building's build stage (NOT size in people).
* only makes sense for castles or similar buildings with multiple
* stages */
-int buildingeffsize(const building * b, int img)
+int buildingeffsize(const building * b, bool imaginary)
{
const struct building_type *btype = NULL;
if (b == NULL)
return 0;
- btype = b->type;
- if (img) {
+ if (imaginary) {
const attrib *a = a_find(b->attribs, &at_icastle);
if (a) {
btype = (const struct building_type *)a->data.v;
}
}
+ else {
+ btype = b->type;
+ }
return bt_effsize(btype, b, b->size);
}
@@ -518,7 +520,7 @@ int bt_effsize(const building_type * btype, const building * b, int bsize)
bsize = adjust_size(b, bsize);
}
- if (btype->stages) {
+ if (btype && btype->stages) {
int n = 0;
const building_stage *stage = btype->stages;
do {
@@ -728,7 +730,7 @@ static const int wagetable[7][3] = {
};
static int
-default_wage(const region * r, const faction * f, const race * rc, int in_turn)
+default_wage(const region * r, const race * rc)
{
building *b = largestbuilding(r, cmp_wage, false);
int esize = 0;
@@ -739,25 +741,23 @@ default_wage(const region * r, const faction * f, const race * rc, int in_turn)
esize = buildingeffsize(b, false);
}
- if (f != NULL) {
+ if (rc != NULL) {
+ static const struct race *rc_orc, *rc_snotling;
+ static int rc_cache;
int index = 0;
- if (rc == get_race(RC_ORC) || rc == get_race(RC_SNOTLING)) {
+ if (rc_changed(&rc_cache)) {
+ rc_orc = get_race(RC_ORC);
+ rc_snotling = get_race(RC_SNOTLING);
+ }
+ if (rc == rc_orc || rc == rc_snotling) {
index = 1;
}
wage = wagetable[esize][index];
}
else {
- if (is_mourning(r, in_turn)) {
- wage = 10;
- }
- else if (fval(r->terrain, SEA_REGION)) {
- wage = 11;
- }
- else {
- wage = wagetable[esize][2];
- }
- if (r->attribs && rule_blessed_harvest() == HARVEST_WORK) {
- /* E1 rules */
+ wage = wagetable[esize][2];
+ if (rule_blessed_harvest() & HARVEST_WORK) {
+ /* Geändert in E3 */
wage += harvest_effect(r);
}
}
@@ -766,7 +766,7 @@ default_wage(const region * r, const faction * f, const race * rc, int in_turn)
attrib *a;
curse *c;
variant vm;
-
+
/* Godcurse: Income -10 */
vm = frac_make(wage, 1);
@@ -786,31 +786,37 @@ default_wage(const region * r, const faction * f, const race * rc, int in_turn)
}
static int
-minimum_wage(const region * r, const faction * f, const race * rc, int in_turn)
+minimum_wage(const region * r, const race * rc)
{
- if (f && rc) {
+ if (rc) {
return rc->maintenance;
}
- return default_wage(r, f, rc, in_turn);
+ return default_wage(r, rc);
}
/**
* Gibt Arbeitslohn fuer entsprechende Rasse zurueck, oder fuer
- * die Bauern wenn f == NULL. */
-int wage(const region * r, const faction * f, const race * rc, int in_turn)
+ * die Bauern wenn rc == NULL. */
+int wage(const region * r, const race * rc)
{
static int config;
static int rule_wage;
if (config_changed(&config)) {
rule_wage = config_get_int("rules.wage.function", 1);
}
- if (rule_wage==0) {
+ if (rule_wage == 0) {
return 0;
}
- if (rule_wage==1) {
- return default_wage(r, f, rc, in_turn);
+
+ if (rule_wage == 1) {
+ return default_wage(r, rc);
}
- return minimum_wage(r, f, rc, in_turn);
+ return minimum_wage(r, rc);
+}
+
+int peasant_wage(const struct region *r, bool mourn)
+{
+ return mourn ? 10 : wage(r, NULL);
}
int cmp_wage(const struct building *b, const building * a)
diff --git a/src/kernel/building.h b/src/kernel/building.h
index 74128a036..dfe1b544a 100644
--- a/src/kernel/building.h
+++ b/src/kernel/building.h
@@ -116,8 +116,8 @@ extern "C" {
int id, int size, struct order *ord);
bool building_finished(const struct building *b);
- int wage(const struct region *r, const struct faction *f,
- const struct race *rc, int in_turn);
+ int wage(const struct region *r, const struct race *rc);
+ int peasant_wage(const struct region *r, bool mourn);
typedef int(*cmp_building_cb) (const struct building * b,
const struct building * a);
@@ -130,7 +130,7 @@ extern "C" {
int building_taxes(const building *b);
/* old functions, still in build.c: */
- int buildingeffsize(const building * b, int imaginary);
+ int buildingeffsize(const building * b, bool imaginary);
void bhash(struct building *b);
void bunhash(struct building *b);
int buildingcapacity(const struct building *b);
diff --git a/src/kernel/building.test.c b/src/kernel/building.test.c
index daa75b496..91806ba3d 100644
--- a/src/kernel/building.test.c
+++ b/src/kernel/building.test.c
@@ -434,6 +434,80 @@ static void test_cmp_castle_size(CuTest *tc) {
test_teardown();
}
+static void test_wage(CuTest *tc) {
+ region *r;
+ building *b;
+ building_type *btype;
+ struct building_stage *stage;
+ race *rc_orc, *rc_elf;
+ test_setup();
+ rc_orc = test_create_race("orc");
+ rc_elf = test_create_race("elf");
+ rc_elf->maintenance = 13;
+ btype = test_create_buildingtype("castle");
+ stage = btype->stages;
+ stage->construction->maxsize = 2; /* site */
+ stage = stage->next = calloc(1, sizeof(struct building_stage));
+ stage->construction = calloc(1, sizeof(struct construction));
+ stage->construction->maxsize = 8; /* tradepost */
+ stage = stage->next = calloc(1, sizeof(struct building_stage));
+ stage->construction = calloc(1, sizeof(struct construction));
+ stage->construction->maxsize = 40; /* fortification */
+ stage = stage->next = calloc(1, sizeof(struct building_stage));
+ stage->construction = calloc(1, sizeof(struct construction));
+ stage->construction->maxsize = 200; /* fortification */
+ r = test_create_plain(0, 0);
+ CuAssertIntEquals(tc, 10, wage(r, rc_elf));
+ CuAssertIntEquals(tc, 10, wage(r, rc_orc));
+ CuAssertIntEquals(tc, 11, peasant_wage(r, false));
+ CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+
+ b = test_create_building(r, btype);
+ b->size = 1;
+ CuAssertIntEquals(tc, 0, buildingeffsize(b, false));
+ CuAssertIntEquals(tc, 10, wage(r, rc_elf));
+ CuAssertIntEquals(tc, 10, wage(r, rc_orc));
+ CuAssertIntEquals(tc, 11, peasant_wage(r, false));
+ CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+ b->size = 2;
+ CuAssertIntEquals(tc, 1, buildingeffsize(b, false));
+ b->size = 9;
+ CuAssertIntEquals(tc, 1, buildingeffsize(b, false));
+ CuAssertIntEquals(tc, 10, wage(r, rc_elf));
+ CuAssertIntEquals(tc, 10, wage(r, rc_orc));
+ CuAssertIntEquals(tc, 11, peasant_wage(r, false));
+ CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+ b->size = 10;
+ CuAssertIntEquals(tc, 2, buildingeffsize(b, false));
+ b->size = 49;
+ CuAssertIntEquals(tc, 2, buildingeffsize(b, false));
+ CuAssertIntEquals(tc, 11, wage(r, rc_elf));
+ CuAssertIntEquals(tc, 11, wage(r, rc_orc));
+ CuAssertIntEquals(tc, 12, peasant_wage(r, false));
+ CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+ b->size = 50;
+ CuAssertIntEquals(tc, 3, buildingeffsize(b, false));
+ b->size = 249;
+ CuAssertIntEquals(tc, 3, buildingeffsize(b, false));
+ CuAssertIntEquals(tc, 12, wage(r, rc_elf));
+ CuAssertIntEquals(tc, 11, wage(r, rc_orc));
+ CuAssertIntEquals(tc, 13, peasant_wage(r, false));
+ CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+ b->size = 250;
+ CuAssertIntEquals(tc, 4, buildingeffsize(b, false));
+ CuAssertIntEquals(tc, 13, wage(r, rc_elf));
+ CuAssertIntEquals(tc, 12, wage(r, rc_orc));
+ CuAssertIntEquals(tc, 14, peasant_wage(r, false));
+ CuAssertIntEquals(tc, 10, peasant_wage(r, true));
+ config_set_int("rules.wage.function", 1);
+ CuAssertIntEquals(tc, 13, wage(r, rc_elf));
+ config_set_int("rules.wage.function", 0);
+ CuAssertIntEquals(tc, 0, wage(r, rc_elf));
+ config_set_int("rules.wage.function", 2);
+ CuAssertIntEquals(tc, rc_elf->maintenance, wage(r, rc_elf));
+ test_teardown();
+}
+
static void test_cmp_wage(CuTest *tc) {
region *r;
building *b1, *b2;
@@ -619,6 +693,7 @@ CuSuite *get_building_suite(void)
SUITE_ADD_TEST(suite, test_cmp_castle_size);
SUITE_ADD_TEST(suite, test_cmp_taxes);
SUITE_ADD_TEST(suite, test_cmp_wage);
+ SUITE_ADD_TEST(suite, test_wage);
SUITE_ADD_TEST(suite, test_cmp_current_owner);
SUITE_ADD_TEST(suite, test_register_building);
SUITE_ADD_TEST(suite, test_btype_defaults);
diff --git a/src/kernel/config.h b/src/kernel/config.h
index 9b10f1520..4ee59406b 100644
--- a/src/kernel/config.h
+++ b/src/kernel/config.h
@@ -42,7 +42,7 @@ extern "C" {
bool rule_stealth_anon(void); /* units can anonymize their faction, TARNE PARTEI [NICHT] */
int rule_alliance_limit(void);
int rule_faction_limit(void);
-#define HARVEST_WORK 0x00
+#define HARVEST_WORK 0x02
#define HARVEST_TAXES 0x01
int rule_blessed_harvest(void);
#define GIVE_SELF 1
diff --git a/src/kernel/region.c b/src/kernel/region.c
index 6f700ae03..bd5b66287 100644
--- a/src/kernel/region.c
+++ b/src/kernel/region.c
@@ -602,7 +602,7 @@ int rpeasants(const region * r)
return value;
}
-void rsetpeasants(region * r, int value)
+int rsetpeasants(region * r, int value)
{
assert(r->land || value==0);
assert(value >= 0);
@@ -612,7 +612,9 @@ void rsetpeasants(region * r, int value)
value = USHRT_MAX;
}
r->land->peasants = (unsigned short)value;
+ return r->land->peasants;
}
+ return 0;
}
int rmoney(const region * r)
@@ -746,17 +748,16 @@ int rsettrees(const region * r, int ageclass, int value)
{
if (!r->land) {
assert(value == 0);
+ return 0;
+ }
+ assert(value >= 0);
+ if (value < MAXTREES) {
+ r->land->trees[ageclass] = value;
}
else {
- assert(value >= 0);
- if (value <= MAXTREES) {
- return r->land->trees[ageclass] = value;
- }
- else {
- r->land->trees[ageclass] = MAXTREES;
- }
+ r->land->trees[ageclass] = MAXTREES;
}
- return 0;
+ return r->land->trees[ageclass];
}
region *region_create(int uid)
@@ -1095,11 +1096,10 @@ void init_region(region *r)
if (!fval(r, RF_CHAOTIC)) {
int peasants;
+ int p_wage = 1 + peasant_wage(r, false) + rng_int() % 5;
peasants = (region_maxworkers(r) * (20 + dice(6, 10))) / 100;
if (peasants < 100) peasants = 100;
- rsetpeasants(r, peasants);
- rsetmoney(r, rpeasants(r) * ((wage(r, NULL, NULL,
- INT_MAX) + 1) + rng_int() % 5));
+ rsetmoney(r, rsetpeasants(r, peasants) * p_wage);
}
}
diff --git a/src/kernel/region.h b/src/kernel/region.h
index 89b240f1b..9a7563712 100644
--- a/src/kernel/region.h
+++ b/src/kernel/region.h
@@ -176,7 +176,7 @@ extern "C" {
int rsettrees(const struct region *r, int ageclass, int value);
int rpeasants(const struct region *r);
- void rsetpeasants(struct region *r, int value);
+ int rsetpeasants(struct region *r, int value);
int rmoney(const struct region *r);
void rsetmoney(struct region *r, int value);
int rhorses(const struct region *r);
diff --git a/src/kernel/resources.c b/src/kernel/resources.c
index 17cec5ea2..6c93a0a13 100644
--- a/src/kernel/resources.c
+++ b/src/kernel/resources.c
@@ -118,22 +118,34 @@ static void terraform_default(struct rawmaterial *res, const region * r)
}
static int visible_default(const rawmaterial * res, int skilllevel)
-/* resources are visible, if skill equals minimum skill to mine them
+/* resources are visible if skill equals minimum skill to mine them
* plus current level of difficulty */
{
const struct item_type *itype = res->rtype->itype;
+ int level = res->level + itype->construction->minskill - 1;
if (res->level <= 1
- && res->level + itype->construction->minskill <= skilllevel + 1) {
+ && level <= skilllevel) {
assert(res->amount > 0);
return res->amount;
}
- else if (res->level + itype->construction->minskill <= skilllevel + 2) {
+ else if (level < skilllevel) {
assert(res->amount > 0);
return res->amount;
}
return -1;
}
+static int visible_half_skill(const rawmaterial * res, int skilllevel)
+/* resources are visible if skill equals half as much as normal */
+{
+ const struct item_type *itype = res->rtype->itype;
+ int level = res->level + itype->construction->minskill - 1;
+ if (2 * skilllevel >= level) {
+ return res->amount;
+ }
+ return -1;
+}
+
static void use_default(rawmaterial * res, const region * r, int amount)
{
assert(res->amount > 0 && amount >= 0 && amount <= res->amount);
@@ -171,13 +183,19 @@ struct rawmaterial_type *rmt_get(const struct resource_type *rtype)
struct rawmaterial_type *rmt_create(struct resource_type *rtype)
{
if (!rtype->raw) {
+ int rule = config_get_int("resource.visibility.rule", 0);
rawmaterial_type *rmtype = rtype->raw = malloc(sizeof(rawmaterial_type));
if (!rmtype) abort();
rmtype->rtype = rtype;
rmtype->terraform = terraform_default;
rmtype->update = NULL;
rmtype->use = use_default;
- rmtype->visible = visible_default;
+ if (rule == 0) {
+ rmtype->visible = visible_default;
+ }
+ else {
+ rmtype->visible = visible_half_skill;
+ }
}
return rtype->raw;
}
diff --git a/src/kernel/version.c b/src/kernel/version.c
index 8c694f7f0..0739b11b1 100644
--- a/src/kernel/version.c
+++ b/src/kernel/version.c
@@ -8,7 +8,7 @@
#ifndef ERESSEA_VERSION
/* the version number, if it was not passed to make with -D */
-#define ERESSEA_VERSION "3.25.0"
+#define ERESSEA_VERSION "3.26.0"
#endif
const char *eressea_version(void) {
diff --git a/src/laws.c b/src/laws.c
index 3d3d174b5..8ee1e9341 100644
--- a/src/laws.c
+++ b/src/laws.c
@@ -743,7 +743,8 @@ void immigration(void)
/* if less than 50 are in the region and there is space and no monster or demon units in the region */
if (repopulate) {
int peasants = rpeasants(r);
- int income = wage(r, NULL, NULL, turn) - maintenance_cost(NULL) + 1;
+ bool mourn = is_mourning(r, turn);
+ int income = peasant_wage(r, mourn) - maintenance_cost(NULL) + 1;
if (income >= 0 && r->land && (peasants < repopulate) && region_maxworkers(r) >(peasants + 30) * 2) {
int badunit = 0;
unit *u;
@@ -754,7 +755,7 @@ void immigration(void)
}
}
if (badunit == 0) {
- peasants += (int)(rng_double()*income);
+ peasants += (int)(rng_double() * income);
rsetpeasants(r, peasants);
}
}
@@ -834,8 +835,10 @@ void demographics(void)
if (r->age > 20) {
double mwp = fmax(region_maxworkers(r), 1);
+ bool mourn = is_mourning(r, turn);
+ int p_wage = peasant_wage(r, mourn);
double prob =
- pow(rpeasants(r) / (mwp * wage(r, NULL, NULL, turn) * 0.13), 4.0)
+ pow(rpeasants(r) / (mwp * p_wage * 0.13), 4.0)
* PLAGUE_CHANCE;
if (rng_double() < prob) {
diff --git a/src/lighthouse.c b/src/lighthouse.c
index 7915bdf44..257836d20 100644
--- a/src/lighthouse.c
+++ b/src/lighthouse.c
@@ -21,7 +21,12 @@ attrib_type at_lighthouse = {
bool is_lighthouse(const building_type *btype)
{
- return is_building_type(btype, "lighthouse");
+ static int config;
+ static const building_type *bt_lighthouse;
+ if (bt_changed(&config)) {
+ bt_lighthouse = bt_find("lighthouse");
+ }
+ return btype == bt_lighthouse;
}
/* update_lighthouse: call this function whenever the size of a lighthouse changes
@@ -81,7 +86,7 @@ void remove_lighthouse(const building *lh) {
int lighthouse_range(const building * b)
{
- if (b->size >= 10 && (b->flags & BLD_MAINTAINED)) {
+ if (b->size >= 10) {
return (int)log10(b->size) + 1;
}
return 0;
@@ -112,13 +117,19 @@ bool lighthouse_guarded(const region * r)
for (a = a_find(r->attribs, &at_lighthouse); a && a->type == &at_lighthouse;
a = a->next) {
building *b = (building *)a->data.v;
-
- assert(is_building_type(b->type, "lighthouse"));
- if ((b->flags & BLD_MAINTAINED) && b->size >= 10) {
- int maxd = (int)log10(b->size) + 1;
- int d = distance(r, b->region);
- assert(maxd >= d);
- return true;
+ if (b->flags & BLD_MAINTAINED) {
+ if (r == b->region) {
+ return true;
+ }
+ else {
+ int maxd = lighthouse_range(b);
+ if (maxd > 0) {
+ int d = distance(r, b->region);
+ if (maxd >= d) {
+ return true;
+ }
+ }
+ }
}
}
diff --git a/src/lighthouse.test.c b/src/lighthouse.test.c
index 9dc6d2999..8230b97f2 100644
--- a/src/lighthouse.test.c
+++ b/src/lighthouse.test.c
@@ -30,8 +30,6 @@ static void test_lighthouse_range(CuTest * tc)
b->size = 9;
CuAssertIntEquals(tc, 0, lighthouse_range(b));
b->size = 10;
- CuAssertIntEquals(tc, 0, lighthouse_range(b));
- b->flags |= BLD_MAINTAINED;
CuAssertIntEquals(tc, 2, lighthouse_range(b));
u1->building = b;
u2->building = b;
@@ -39,6 +37,8 @@ static void test_lighthouse_range(CuTest * tc)
set_level(u1, SK_PERCEPTION, 3);
set_level(u2, SK_PERCEPTION, 3);
+ CuAssertIntEquals(tc, 0, lighthouse_view_distance(b, u1));
+ b->flags |= BLD_MAINTAINED;
CuAssertIntEquals(tc, 1, lighthouse_view_distance(b, u1));
set_level(u1, SK_PERCEPTION, 6);
CuAssertIntEquals(tc, 1, lighthouse_view_distance(b, u2));
@@ -126,6 +126,7 @@ static void test_lighthouse_guard(CuTest * tc) {
CuAssertIntEquals(tc, true, lighthouse_guarded(r3));
CuAssertIntEquals(tc, false, lighthouse_guarded(r4));
b->size = 1; /* size can go down in destroy_cmd */
+ update_lighthouse(b);
CuAssertIntEquals(tc, false, lighthouse_guarded(r2));
CuAssertIntEquals(tc, false, lighthouse_guarded(r3));
test_teardown();
diff --git a/src/main.c b/src/main.c
index 130a2f6f7..9cf76f262 100644
--- a/src/main.c
+++ b/src/main.c
@@ -17,6 +17,7 @@
#include "gmtool.h"
#endif
+#include "signals.h"
#include "bindings.h"
#include
@@ -247,39 +248,6 @@ static int parse_args(int argc, char **argv)
return 0;
}
-#ifdef HAVE_BACKTRACE
-#include
-#include
-static void *btrace[50];
-
-static void report_segfault(int signo, siginfo_t * sinf, void *arg)
-{
- 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_RESETHAND | 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
-
void locale_init(void)
{
setlocale(LC_CTYPE, "");
diff --git a/src/modules/museum.c b/src/modules/museum.c
index 8f8350655..cbaf8d3c0 100644
--- a/src/modules/museum.c
+++ b/src/modules/museum.c
@@ -124,7 +124,7 @@ order * ord)
UNUSED_ARG(amount);
/* Pruefen ob in Eingangshalle */
- if (u->region->x != 9525 || u->region->y != 9525) {
+ if (warden == NULL || u->region->x != 9525 || u->region->y != 9525) {
cmistake(u, ord, 266, MSG_MAGIC);
return 0;
}
diff --git a/src/monsters.c b/src/monsters.c
index bd156238b..148e6f46d 100644
--- a/src/monsters.c
+++ b/src/monsters.c
@@ -974,7 +974,11 @@ void spawn_undead(void)
{
region *r;
faction *monsters = get_monsters();
+ int spawn_chance = config_get_int("monsters.spawn.chance", 100) * 100;
+ if (spawn_chance <= 0) {
+ return;
+ }
for (r = regions; r; r = r->next) {
int unburied = deathcount(r);
@@ -985,7 +989,7 @@ void spawn_undead(void)
}
if (r->land && unburied > rpeasants(r) / 20
- && rng_int() % 10000 < 100) {
+ && rng_int() % spawn_chance < 100) {
message *msg;
unit *u;
/* es ist sinnfrei, wenn irgendwo im Wald 3er-Einheiten Untote entstehen.
diff --git a/src/move.c b/src/move.c
index 38cc0d8bb..e9bbea9a0 100644
--- a/src/move.c
+++ b/src/move.c
@@ -1612,9 +1612,9 @@ static const region_list *travel_route(unit * u,
/* Berichte ueber Durchreiseregionen */
if (mode != TRAVEL_TRANSPORTED) {
+ arg_regions ar;
arg_regions *arp = NULL;
if (steps > 1) {
- arg_regions ar;
arp = &ar;
var_create_regions(arp, route_begin, steps - 1);
}
@@ -2192,8 +2192,8 @@ void move_cmd_ex(unit * u, order * ord, const char *directions)
init_order(ord, u->faction->locale);
}
if (u->ship && u == ship_owner(u->ship)) {
- bool drifting = (getkeyword(ord) == K_MOVE);
- sail(u, ord, drifting);
+ keyword_t kwd = getkeyword(ord);
+ sail(u, ord, (kwd == K_MOVE || kwd == K_ROUTE));
}
else {
travel(u, ord);
diff --git a/src/report.c b/src/report.c
index f283b552f..09b9ab82e 100644
--- a/src/report.c
+++ b/src/report.c
@@ -1182,11 +1182,13 @@ static void report_statistics(struct stream *out, const region * r, const factio
if (max_production(r) && (!fval(r->terrain, SEA_REGION)
|| f->race == get_race(RC_AQUARIAN))) {
if (markets_module()) { /* hack */
+ bool mourn = is_mourning(r, turn);
+ int p_wage = peasant_wage(r, mourn);
m =
- msg_message("nr_stat_salary_new", "max", wage(r, NULL, NULL, turn + 1));
+ msg_message("nr_stat_salary_new", "max", p_wage);
}
else {
- m = msg_message("nr_stat_salary", "max", wage(r, f, f->race, turn + 1));
+ m = msg_message("nr_stat_salary", "max", wage(r, f->race));
}
nr_render(m, f->locale, buf, sizeof(buf), f);
paragraph(out, buf, 2, 2, 0);
@@ -1337,7 +1339,7 @@ report_template(const char *filename, report_context * ctx, const char *bom)
}
rps_nowrap(out, buf);
newline(out);
- sprintf(buf, "; ECheck Lohn %d", wage(r, f, f->race, turn + 1));
+ sprintf(buf, "; ECheck Lohn %d", wage(r, f->race));
rps_nowrap(out, buf);
newline(out);
newline(out);
diff --git a/src/signals.c b/src/signals.c
new file mode 100644
index 000000000..7cbe680df
--- /dev/null
+++ b/src/signals.c
@@ -0,0 +1,35 @@
+#ifdef HAVE_BACKTRACE
+#include
+#include
+#include
+#include
+static void *btrace[50];
+
+static void report_segfault(int signo, siginfo_t * sinf, void *arg)
+{
+ 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();
+}
+
+int setup_signal_handler(void)
+{
+ struct sigaction act;
+
+ act.sa_flags = SA_RESETHAND | SA_SIGINFO;
+ act.sa_sigaction = report_segfault;
+ sigfillset(&act.sa_mask);
+ return sigaction(SIGSEGV, &act, 0);
+}
+#else
+int setup_signal_handler(void)
+{
+ return 0;
+}
+#endif
+
diff --git a/src/signals.h b/src/signals.h
new file mode 100644
index 000000000..640a766f7
--- /dev/null
+++ b/src/signals.h
@@ -0,0 +1,4 @@
+#pragma once
+
+int setup_signal_handler(void);
+
diff --git a/src/study.c b/src/study.c
index 74f3d108c..e41d9185d 100644
--- a/src/study.c
+++ b/src/study.c
@@ -8,7 +8,6 @@
#include "move.h"
#include "monsters.h"
#include "alchemy.h"
-#include "academy.h"
#include "kernel/calendar.h"
#include
@@ -154,37 +153,37 @@ const attrib_type at_learning = {
#define EXPERIENCEDAYS 10
-static int study_days(unit * scholar, skill_t sk)
+static int study_days(unit * u, skill_t sk)
{
int speed = STUDYDAYS;
- if (u_race(scholar)->study_speed) {
- speed += u_race(scholar)->study_speed[sk];
+ if (u_race(u)->study_speed) {
+ speed += u_race(u)->study_speed[sk];
if (speed < STUDYDAYS) {
- skill *sv = unit_skill(scholar, sk);
+ skill *sv = unit_skill(u, sk);
if (sv == 0) {
speed = STUDYDAYS;
}
}
}
- return scholar->number * speed;
+ return u->number * speed;
}
static int
-teach_unit(unit * teacher, unit * scholar, int nteaching, skill_t sk,
+teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk,
bool report, int *academy_students)
{
teaching_info *teach = NULL;
attrib *a;
int students;
- if (magic_lowskill(scholar)) {
+ if (magic_lowskill(student)) {
cmistake(teacher, teacher->thisorder, 292, MSG_EVENT);
return 0;
}
- students = scholar->number;
+ students = student->number;
/* subtract already taught students */
- a = a_find(scholar->attribs, &at_learning);
+ a = a_find(student->attribs, &at_learning);
if (a != NULL) {
teach = (teaching_info *)a->data.v;
students -= teach->students;
@@ -194,18 +193,16 @@ teach_unit(unit * teacher, unit * scholar, int nteaching, skill_t sk,
if (students > 0) {
if (teach == NULL) {
- a = a_add(&scholar->attribs, a_new(&at_learning));
+ a = a_add(&student->attribs, a_new(&at_learning));
teach = (teaching_info *)a->data.v;
}
selist_push(&teach->teachers, teacher);
teach->days += students * STUDYDAYS;
teach->students += students;
- if (scholar->building) {
- /* Solange Akademien groessenbeschraenkt sind, sollte Lehrer und
- * Student auch in unterschiedlichen Gebaeuden stehen duerfen */
- /* FIXME comment contradicts implementation */
- if (academy_can_teach(teacher, scholar, sk)) {
+ if (student->building) {
+ const struct building_type *btype = bt_find("academy");
+ if (active_building(student, btype)) {
/* Jeder Schueler zusaetzlich +10 Tage wenn in Uni. */
teach->days += students * EXPERIENCEDAYS; /* learning erhoehen */
/* Lehrer zusaetzlich +1 Tag pro Schueler. */
@@ -258,12 +255,12 @@ int teach_cmd(unit * teacher, struct order *ord)
count = 0;
+#if TEACH_ALL
init_order(ord, NULL);
-#if TEACH_ALL
if (getparam(teacher->faction->locale) == P_ANY) {
skill_t sk;
- unit *scholar;
+ unit *student;
skill_t teachskill[MAXSKILLS];
int t = 0;
@@ -272,15 +269,15 @@ int teach_cmd(unit * teacher, struct order *ord)
teachskill[t] = getskill(teacher->faction->locale);
} while (sk != NOSKILL);
- for (scholar = r->units; teaching > 0 && scholar; scholar = scholar->next) {
- if (LongHunger(scholar)) {
+ for (student = r->units; teaching > 0 && student; student = student->next) {
+ if (LongHunger(student)) {
continue;
}
- else if (scholar->faction == teacher->faction) {
- if (getkeyword(scholar->thisorder) == K_STUDY) {
+ else if (student->faction == teacher->faction) {
+ if (getkeyword(student->thisorder) == K_STUDY) {
/* Input ist nun von student->thisorder !! */
- init_order(scholar->thisorder, scholar->faction->locale);
- sk = getskill(scholar->faction->locale);
+ init_order(student->thisorder, student->faction->locale);
+ sk = getskill(student->faction->locale);
if (sk != NOSKILL && teachskill[0] != NOSKILL) {
for (t = 0; teachskill[t] != NOSKILL; ++t) {
if (sk == teachskill[t]) {
@@ -290,20 +287,20 @@ int teach_cmd(unit * teacher, struct order *ord)
sk = teachskill[t];
}
if (sk != NOSKILL
- && effskill_study(teacher, sk) - TEACHDIFFERENCE > effskill_study(scholar, sk)) {
- teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students);
+ && effskill_study(teacher, sk) - TEACHDIFFERENCE > effskill_study(student, sk)) {
+ teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students);
}
}
}
#ifdef TEACH_FRIENDS
- else if (alliedunit(teacher, scholar->faction, HELP_GUARD)) {
- if (getkeyword(scholar->thisorder) == K_STUDY) {
+ else if (alliedunit(teacher, student->faction, HELP_GUARD)) {
+ if (getkeyword(student->thisorder) == K_STUDY) {
/* Input ist nun von student->thisorder !! */
- init_order(scholar->thisorder, scholar->faction->locale);
- sk = getskill(scholar->faction->locale);
+ init_order(student->thisorder, student->faction->locale);
+ sk = getskill(student->faction->locale);
if (sk != NOSKILL
- && effskill_study(teacher, sk) - TEACHDIFFERENCE >= effskill(scholar, sk, NULL)) {
- teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students);
+ && effskill_study(teacher, sk) - TEACHDIFFERENCE >= effskill(student, sk, NULL)) {
+ teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students);
}
}
}
@@ -322,15 +319,15 @@ int teach_cmd(unit * teacher, struct order *ord)
while (!parser_end()) {
skill_t sk;
- unit *scholar;
+ unit *student;
bool feedback;
- getunit(r, teacher->faction, &scholar);
+ getunit(r, teacher->faction, &student);
++count;
/* Falls die Unit nicht gefunden wird, Fehler melden */
- if (!scholar) {
+ if (!student) {
char tbuf[20];
const char *uid;
const char *token;
@@ -362,8 +359,8 @@ int teach_cmd(unit * teacher, struct order *ord)
continue;
}
- feedback = teacher->faction == scholar->faction
- || alliedunit(scholar, teacher->faction, HELP_GUARD);
+ feedback = teacher->faction == student->faction
+ || alliedunit(student, teacher->faction, HELP_GUARD);
/* Neuen Befehl zusammenbauen. TEMP-Einheiten werden automatisch in
* ihre neuen Nummern uebersetzt. */
@@ -371,102 +368,63 @@ int teach_cmd(unit * teacher, struct order *ord)
strncat(zOrder, " ", sz - 1);
--sz;
}
- sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(scholar->no), sz);
+ sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(student->no), sz);
- if (getkeyword(scholar->thisorder) != K_STUDY) {
+ if (getkeyword(student->thisorder) != K_STUDY) {
ADDMSG(&teacher->faction->msgs,
- msg_feedback(teacher, ord, "teach_nolearn", "student", scholar));
+ msg_feedback(teacher, ord, "teach_nolearn", "student", student));
continue;
}
/* Input ist nun von student->thisorder !! */
parser_pushstate();
- init_order(scholar->thisorder, scholar->faction->locale);
- sk = getskill(scholar->faction->locale);
+ init_order(student->thisorder, student->faction->locale);
+ sk = getskill(student->faction->locale);
parser_popstate();
if (sk == NOSKILL) {
ADDMSG(&teacher->faction->msgs,
- msg_feedback(teacher, ord, "teach_nolearn", "student", scholar));
+ msg_feedback(teacher, ord, "teach_nolearn", "student", student));
continue;
}
- if (effskill_study(scholar, sk) > effskill_study(teacher, sk)
+ if (effskill_study(student, sk) > effskill_study(teacher, sk)
- TEACHDIFFERENCE) {
if (feedback) {
ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord, "teach_asgood",
- "student", scholar));
+ "student", student));
}
continue;
}
if (sk == SK_MAGIC) {
/* ist der Magier schon spezialisiert, so versteht er nur noch
* Lehrer seines Gebietes */
- magic_t mage2 = unit_get_magic(scholar);
+ magic_t mage2 = unit_get_magic(student);
if (mage2 != M_GRAY) {
magic_t mage1 = unit_get_magic(teacher);
if (mage1 != mage2) {
if (feedback) {
ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord,
- "error_different_magic", "target", scholar));
+ "error_different_magic", "target", student));
}
continue;
}
}
}
sk_academy = sk;
- teaching -= teach_unit(teacher, scholar, teaching, sk, false, &academy_students);
+ teaching -= teach_unit(teacher, student, teaching, sk, false, &academy_students);
}
new_order = create_order(K_TEACH, teacher->faction->locale, "%s", zOrder);
replace_order(&teacher->orders, ord, new_order);
free_order(new_order); /* parse_order & set_order have each increased the refcount */
}
- if (academy_students > 0 && sk_academy!=NOSKILL) {
- academy_teaching_bonus(teacher, sk_academy, academy_students);
+ if (academy_students > 0 && sk_academy != NOSKILL) {
+ change_skill_days(teacher, sk_academy, academy_students);
}
reset_order();
return 0;
}
-typedef enum study_rule_t {
- STUDY_DEFAULT = 0,
- STUDY_FASTER = 1,
- STUDY_AUTOTEACH = 2
-} study_rule_t;
-
-static double study_speedup(unit * u, skill_t s, study_rule_t rule)
-{
-#define MINTURN 16
- if (turn > MINTURN) {
- if (rule == STUDY_FASTER) {
- double learnweeks = 0;
- int i;
- for (i = 0; i != u->skill_size; ++i) {
- skill *sv = u->skills + i;
- if (sv->id == s) {
- learnweeks = sv->level * (sv->level + 1) / 2.0;
- if (learnweeks < turn / 3.0) {
- return 2.0;
- }
- }
- }
- return 2.0; /* If the skill was not found it is the first study. */
- }
- if (rule == STUDY_AUTOTEACH) {
- double learnweeks = 0;
- int i;
- for (i = 0; i != u->skill_size; ++i) {
- skill *sv = u->skills + i;
- learnweeks += (sv->level * (sv->level + 1) / 2.0);
- }
- if (learnweeks < turn / 2.0) {
- return 2.0;
- }
- }
- }
- return 1.0;
-}
-
static bool ExpensiveMigrants(void)
{
static bool rule;
@@ -552,17 +510,12 @@ bool check_student(const struct unit *u, struct order *ord, skill_t sk) {
int study_cmd(unit * u, order * ord)
{
- region *r = u->region;
- int p;
- int l;
int studycost, days;
- double multi = 1.0;
attrib *a = NULL;
teaching_info *teach = NULL;
int money = 0;
skill_t sk;
int maxalchemy = 0;
- int speed_rule = (study_rule_t)config_get_int("study.speedup", 0);
static const race *rc_snotling;
static int rc_cache;
@@ -585,7 +538,6 @@ int study_cmd(unit * u, order * ord)
}
}
- p = studycost = study_cost(u, sk);
a = a_find(u->attribs, &at_learning);
if (a != NULL) {
teach = (teaching_info *)a->data.v;
@@ -594,16 +546,13 @@ int study_cmd(unit * u, order * ord)
/* keine kostenpflichtigen Talente fuer Migranten. Vertraute sind
* keine Migranten, wird in is_migrant abgefangen. Vorsicht,
* studycost darf hier noch nicht durch Akademie erhoeht sein */
+ studycost = study_cost(u, sk);
+
if (studycost > 0 && !ExpensiveMigrants() && is_migrant(u)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_migrants_nolearn",
""));
return -1;
}
- /* Akademie: */
- if (active_building(u, bt_find("academy"))) {
- studycost = studycost * 2;
- if (studycost < 50) studycost = 50;
- }
if (sk == SK_MAGIC) {
magic_t mtype;
@@ -684,71 +633,31 @@ int study_cmd(unit * u, order * ord)
}
}
}
+
+ days = teach ? teach->days : 0;
+ days += study_days(u, sk);
+
if (studycost) {
int cost = studycost * u->number;
money = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, cost);
if (money > cost) money = cost;
- }
- if (money < studycost * u->number) {
- studycost = p; /* Ohne Univertreurung */
- if (money > studycost) money = studycost;
- if (p > 0 && money < studycost * u->number) {
- cmistake(u, ord, 65, MSG_EVENT);
- multi = money / (double)(studycost * u->number);
+ if (money < studycost * u->number) {
+ int cost = studycost * u->number;
+ if (money > studycost) money = studycost;
+ if (money < cost) {
+ /* we cannot afford everyone, but use as much as we have */
+ cmistake(u, ord, 65, MSG_EVENT);
+ days = days * money / cost;
+ }
}
}
+ money += learn_skill(u, sk, days, studycost);
- if (teach == NULL) {
- a = a_add(&u->attribs, a_new(&at_learning));
- teach = (teaching_info *)a->data.v;
- assert(teach);
- teach->teachers = NULL;
- }
if (money > 0) {
use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, money);
ADDMSG(&u->faction->msgs, msg_message("studycost",
"unit region cost skill", u, u->region, money, sk));
}
-
- if (get_effect(u, oldpotiontype[P_WISE])) {
- l = get_effect(u, oldpotiontype[P_WISE]);
- if (l > u->number) l = u->number;
- teach->days += l * EXPERIENCEDAYS;
- change_effect(u, oldpotiontype[P_WISE], -l);
- }
- if (get_effect(u, oldpotiontype[P_FOOL])) {
- l = get_effect(u, oldpotiontype[P_FOOL]);
- if (l > u->number) l = u->number;
- teach->days -= l * STUDYDAYS;
- change_effect(u, oldpotiontype[P_FOOL], -l);
- }
-
- if (p != studycost) {
- /* ist_in_gebaeude(r, u, BT_UNIVERSITAET) == 1) { */
- /* p ist Kosten ohne Uni, studycost mit; wenn
- * p!=studycost, ist die Einheit zwangsweise
- * in einer Uni */
- teach->days += u->number * EXPERIENCEDAYS;
- }
-
- if (is_cursed(r->attribs, &ct_badlearn)) {
- teach->days -= u->number * EXPERIENCEDAYS;
- }
-
- multi *= study_speedup(u, sk, speed_rule);
- days = study_days(u, sk);
- days = (int)((days + teach->days) * multi);
-
- /* the artacademy currently improves the learning of entertainment
- of all units in the region, to be able to make it cumulative with
- with an academy */
-
- if (sk == SK_ENTERTAINMENT
- && buildingtype_exists(r, bt_find("artacademy"), false)) {
- days *= 2;
- }
-
- learn_skill(u, sk, days);
if (a != NULL) {
if (teach->teachers) {
msg_teachers(teach->teachers, u, sk);
@@ -789,56 +698,133 @@ void produceexp_ex(struct unit *u, skill_t sk, int n, learn_fun learn)
}
}
-void produceexp(struct unit *u, skill_t sk, int n)
-{
- produceexp_ex(u, sk, n, learn_skill);
-}
-
static learn_fun inject_learn_fun = 0;
void inject_learn(learn_fun fun) {
inject_learn_fun = fun;
}
-/** days should be scaled by u->number; STUDYDAYS * u->number is one week worth of learning */
-void learn_skill(unit *u, skill_t sk, int days) {
- int leveldays = STUDYDAYS * u->number;
- int weeks = 0;
+static void increase_skill_days(unit *u, skill_t sk, int days) {
+ assert(sk >= 0 && sk < MAXSKILLS && days >= 0);
+ if (days > 0) {
+ int leveldays = STUDYDAYS * u->number;
+ int weeks = 0;
+ if (inject_learn_fun) {
+ inject_learn_fun(u, sk, days);
+ return;
+ }
+ while (days >= leveldays) {
+ ++weeks;
+ days -= leveldays;
+ }
+ if (days > 0 && rng_int() % leveldays < days) {
+ ++weeks;
+ }
+ if (weeks > 0) {
+ increase_skill(u, sk, weeks);
+ }
+ }
+}
- if (fval(u, UFL_HUNGER)) {
- days /= 2;
+static void reduce_skill_days(unit *u, skill_t sk, int days) {
+ if (days > 0) {
+ skill *sv = unit_skill(u, sk);
+ if (sv) {
+ while (days > 0) {
+ if (days >= STUDYDAYS * u->number) {
+ reduce_skill(u, sv, 1);
+ days -= STUDYDAYS;
+ }
+ else {
+ if (chance(days / ((double)STUDYDAYS * u->number))) /* (rng_int() % (30 * u->number) < days)*/
+ reduce_skill(u, sv, 1);
+ days = 0;
+ }
+ }
+ }
}
- assert(sk >= 0 && sk < MAXSKILLS);
- if (inject_learn_fun) {
- inject_learn_fun(u, sk, days);
- return;
+}
+
+void produceexp(struct unit *u, skill_t sk, int n)
+{
+ produceexp_ex(u, sk, n, increase_skill_days);
+}
+
+/**
+ * days should be scaled by u->number; STUDYDAYS * u->number is one week worth of learning
+ * @return int
+ * The additional spend, i.e. from an academy.
+ */
+int learn_skill(unit *u, skill_t sk, int days, int studycost) {
+ region *r = u->region;
+ int cost = 0;
+
+ if (r->buildings) {
+ static const building_type *bt_artacademy;
+ static const building_type *bt_academy;
+ static int config;
+
+ if (bt_changed(&config)) {
+ bt_artacademy = bt_find("artacademy");
+ bt_academy = bt_find("academy");
+ }
+
+ /* Akademie: */
+ if (bt_academy && active_building(u, bt_academy)) {
+ int avail, n = u->number;
+ if (studycost < 50) studycost = 50;
+ cost = studycost * u->number;
+ avail = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, studycost * u->number);
+ if (avail < cost) {
+ /* not all students can afford the academy */
+ n = n * avail / cost;
+ cost = n * studycost;
+ }
+ days += EXPERIENCEDAYS * n;
+ }
+
+ /* the artacademy currently improves the learning of entertainment
+ of all units in the region, to be able to make it cumulative with
+ with an academy */
+
+ if (bt_artacademy && sk == SK_ENTERTAINMENT
+ && buildingtype_exists(r, bt_artacademy, false)) {
+ days *= 2;
+ }
}
- while (days >= leveldays) {
- ++weeks;
- days -= leveldays;
+
+ if (u->attribs) {
+ if (get_effect(u, oldpotiontype[P_WISE])) {
+ int effect = get_effect(u, oldpotiontype[P_WISE]);
+ if (effect > u->number) effect = u->number;
+ days += effect * EXPERIENCEDAYS;
+ change_effect(u, oldpotiontype[P_WISE], -effect);
+ }
+ if (get_effect(u, oldpotiontype[P_FOOL])) {
+ int effect = get_effect(u, oldpotiontype[P_FOOL]);
+ if (effect > u->number) effect = u->number;
+ days -= effect * STUDYDAYS;
+ change_effect(u, oldpotiontype[P_FOOL], -effect);
+ }
}
- if (days > 0 && rng_int() % leveldays < days) {
- ++weeks;
+
+ if (is_cursed(r->attribs, &ct_badlearn)) {
+ days -= EXPERIENCEDAYS * u->number;
}
- if (weeks > 0) {
- increase_skill(u, sk, weeks);
+
+ if (fval(u, UFL_HUNGER)) {
+ days /= 2;
}
+ increase_skill_days(u, sk, days);
+ return cost;
}
-void reduce_skill_days(unit *u, skill_t sk, int days) {
- skill *sv = unit_skill(u, sk);
- if (sv) {
- while (days > 0) {
- if (days >= STUDYDAYS * u->number) {
- reduce_skill(u, sv, 1);
- days -= STUDYDAYS;
- }
- else {
- if (chance (days / ((double) STUDYDAYS * u->number))) /* (rng_int() % (30 * u->number) < days)*/
- reduce_skill(u, sv, 1);
- days = 0;
- }
- }
+void change_skill_days(unit *u, skill_t sk, int days) {
+ if (days < 0) {
+ reduce_skill_days(u, sk, -days);
+ }
+ else {
+ increase_skill_days(u, sk, days);
}
}
@@ -885,7 +871,7 @@ void demon_skillchange(unit *u)
}
}
else {
- learn_skill(u, sv->id, STUDYDAYS * u->number * weeks);
+ change_skill_days(u, sv->id, STUDYDAYS * u->number * weeks);
}
}
++sv;
diff --git a/src/study.h b/src/study.h
index 6fed0c50d..dcdc354ca 100644
--- a/src/study.h
+++ b/src/study.h
@@ -34,8 +34,8 @@ extern "C" {
typedef void(*learn_fun)(struct unit *u, skill_t sk, int days);
- void learn_skill(struct unit *u, skill_t sk, int days);
- void reduce_skill_days(struct unit *u, skill_t sk, int days);
+ int learn_skill(struct unit *u, skill_t sk, int days, int studycost);
+ void change_skill_days(struct unit *u, skill_t sk, int days);
void produceexp(struct unit *u, skill_t sk, int n);
void produceexp_ex(struct unit *u, skill_t sk, int n, learn_fun learn);
diff --git a/src/study.test.c b/src/study.test.c
index b57c921f4..6b529b252 100644
--- a/src/study.test.c
+++ b/src/study.test.c
@@ -364,14 +364,14 @@ void test_learn_skill_single(CuTest *tc) {
setup_study();
config_set("study.random_progress", "0");
u = test_create_unit(test_create_faction(NULL), test_create_region(0, 0, NULL));
- learn_skill(u, SK_ALCHEMY, STUDYDAYS);
+ CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS, 0));
CuAssertPtrNotNull(tc, sv = u->skills);
CuAssertIntEquals(tc, SK_ALCHEMY, sv->id);
CuAssertIntEquals(tc, 1, sv->level);
CuAssertIntEquals(tc, 2, sv->weeks);
- learn_skill(u, SK_ALCHEMY, STUDYDAYS);
+ CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS, 0));
CuAssertIntEquals(tc, 1, sv->weeks);
- learn_skill(u, SK_ALCHEMY, STUDYDAYS * 2);
+ CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS * 2, 0));
CuAssertIntEquals(tc, 2, sv->level);
CuAssertIntEquals(tc, 2, sv->weeks);
test_teardown();
@@ -385,14 +385,14 @@ void test_learn_skill_multi(CuTest *tc) {
config_set("study.random_progress", "0");
u = test_create_unit(test_create_faction(NULL), test_create_region(0, 0, NULL));
scale_number(u, 10);
- learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number);
+ CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number, 0));
CuAssertPtrNotNull(tc, sv = u->skills);
CuAssertIntEquals(tc, SK_ALCHEMY, sv->id);
CuAssertIntEquals(tc, 1, sv->level);
CuAssertIntEquals(tc, 2, sv->weeks);
- learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number);
+ CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number, 0));
CuAssertIntEquals(tc, 1, sv->weeks);
- learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number * 2);
+ CuAssertIntEquals(tc, 0, learn_skill(u, SK_ALCHEMY, STUDYDAYS * u->number * 2, 0));
CuAssertIntEquals(tc, 2, sv->level);
CuAssertIntEquals(tc, 2, sv->weeks);
test_teardown();
diff --git a/src/test_eressea.c b/src/test_eressea.c
index fc5c49d1c..452b847c5 100644
--- a/src/test_eressea.c
+++ b/src/test_eressea.c
@@ -93,7 +93,6 @@ int RunAllTests(int argc, char *argv[])
/* items */
ADD_SUITE(xerewards);
/* kernel */
- ADD_SUITE(academy);
ADD_SUITE(alliance);
ADD_SUITE(ally);
ADD_SUITE(building);
diff --git a/src/util/rand.c b/src/util/rand.c
index a14f1def4..6e4e88c89 100644
--- a/src/util/rand.c
+++ b/src/util/rand.c
@@ -64,7 +64,7 @@ bool chance(double x)
{
if (x >= 1.0)
return true;
- return (1-rng_double()) < x;
+ return rng_double() < x;
}
typedef struct random_source {