Wilder maps: rivers of water and lava across free maps (neutral bridges), natural caverns until about a quarter of every map is open

- caverns in free play and the campaign (mission pieces, fortresses, islands, sealed caves and dwarf caves stay untouched)
- caverns sometimes hold a pool of water or lava, veins in their walls, rarely a small treasure or a creature in chains to free
- rivers keep away from the start; test: open share, liquids and a hero path to the core
- standalone file in play/ rebuilt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-04 08:15:34 +01:00
1 parent 51814b8ac8
commit 2de0a1de8a
6 files changed
+198 -2

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@
<script>var register_plugin; /* expected by the quad_net plugin in the bundle */</script> <script>var register_plugin; /* expected by the quad_net plugin in the bundle */</script>
<script> <script>
// the game module is embedded: fetching it is answered from these bytes // the game module is embedded: fetching it is answered from these bytes
var DC_WASM = "AGFzbQEAAAABywZlYAF/AGAAAGADf39/AX9gA39/fwBgBn9/f39/fwBgBX9/f39/AGAEf39/fwBgAn9/AGAHf39/f39/fwF/YAF/AX9gAn9/AX9gBH9/f38Bf2AFf39/f38Bf2ADf319AGAFf319fX0AYAd/fX19fX19AGAGf39/fX9/AGAGf39/f39/AX9gAAF8YAABf2AAAX1gCH9/f39/f39/AGAJf39/f39/f39/AGAHf39/f39/fwBgBH19fX0AYAF9AGAKf39/f39/f39/fwBgBH5+f38BfmADfn5/AX5gAX4Bf2AEf39/fQF/YAJ+fwF/YAJ/fQBgAn1/AX9gBn99fXx/fwBgB39/f39/f3wAYAV/f39/fABgC39/f39/f399fX98AGAHf39/f399fQBgBH9/fX0AYAV/f319fwBgB39/f319f3wAYAN/f30AYA1/f319fX9/f39/f39/AGAFfX19fX8AYAZ9fX19fX8AYAV/fX1/fwBgB39/f319fX8AYAZ/f39/fH8AYAh/f39/f39/fABgBn9/f399fQBgCX99fX9/fX9/fwBgDH99fX9/fX9/f39/fwBgAX8BfWAJf39/f319fX99AGAGf39/fX1/AX9gCH9/fX19fX1/AGACfX0Bf2ACf34AYAd/fX1/fX99AGADf399AX1gB39/fX19f38AYAJ9fQF9YAh/f39/fX19fQBgCX9/fX19f39/fwBgAn98AGAFf31/f38AYAR9fX99AGAEf399fwBgA399fwBgBH99f38AYAp/f39/f39/f319AGAGf399fX19AX9gA39/fQF/YAJ/fwF9YAh/f31/f39/fwBgBX9/fX1/AX9gBH9/f30AYAR/f39+AGAFf39/fn8AYAd/fX19fX9/AGAEf399fQF9YBN/f39/f39/f39/f39/f39/f39/AX9gAX8BfmAGf35/f39/AGAJf39/f39/fn5+AGAEf31/fwF/YAN/fX8Bf2ADf35+AGALf39/f39/f39/f38Bf2AAAX5gAn9+AX9gA35+fgF+YAh/f39/f39/fwF/YAV/f359fQBgA39+fwBgC39/f39/f39/f39/AGACfH8Bf2ABfQF9YAV/fn5+fgBgAn1/AX0C1xR6A2Vudgtjb25zb2xlX2xvZwAAA2VudgdkY19odHRwABEDZW52DGRjX3NldF90b2tlbgAHA2VudgNub3cAEgNlbnYRZGNfY29hcnNlX3BvaW50ZXIAEwNlbnYMZGNfZmlsZV9wb2xsABMDZW52DGRjX2ZpbGVfdGFrZQAAA2VudgxkY19odHRwX3BvbGwACQNlbnYLZGNfaHR0cF9sZW4ACQNlbnYMZGNfaHR0cF90YWtlAAcDZW52DmRjX3N0b3JhZ2Vfc2V0AAYDZW52F2RjX2ZldGNoX2Rvd25sb2FkX3N0YXRlABMDZW52C2RjX2Rvd25sb2FkAAYDZW52DGRjX29wZW5fZmlsZQABA2VudgtkY19vcGVuX3VybAAHA2VudgxkY19jYWNoZV9zZXQABgNlbnYRZGNfZmV0Y2hfZG93bmxvYWQABgNlbnYKaW5pdF93ZWJnbAAAA2VudhFzZXR1cF9jYW52YXNfc2l6ZQAAA2VudgxjYW52YXNfd2lkdGgAEwNlbnYNY2FudmFzX2hlaWdodAATA2VudglkcGlfc2NhbGUAFANlbnYScnVuX2FuaW1hdGlvbl9sb29wAAADZW52DmRjX3N0b3JhZ2VfZ2V0AAoDZW52D2RjX3N0b3JhZ2VfdGFrZQAAA2VudglkY19wcm9tcHQACwNlbnYNZ2xCaW5kVGV4dHVyZQAHA2VudhBnbENvcHlUZXhJbWFnZTJEABUDZW52DWdsR2V0SW50ZWdlcnYABwNlbnYRZ2xHZW5WZXJ0ZXhBcnJheXMABwNlbnYRZ2xCaW5kVmVydGV4QXJyYXkAAANlbnYLZ2xHZXRTdHJpbmcACQNlbnYUc2FwcF9zY2hlZHVsZV91cGRhdGUAAQNlbnYMZ2xHZW5RdWVyaWVzAAcDZW52DGdsQmVnaW5RdWVyeQAHA2VudgpnbEVuZFF1ZXJ5AAADZW52B2dsRmx1c2gAAQNlbnYIZ2xGaW5pc2gAAQNlbnYMY29uc29sZV93YXJuAAADZW52D2dsQ3JlYXRlUHJvZ3JhbQATA2Vudg5nbEF0dGFjaFNoYWRlcgAHA2Vudg1nbExpbmtQcm9ncmFtAAADZW52DmdsRGV0YWNoU2hhZGVyAAcDZW52DmdsRGVsZXRlU2hhZGVyAAADZW52DmdsR2V0UHJvZ3JhbWl2AAMDZW52E2dsR2V0UHJvZ3JhbUluZm9Mb2cABgNlbnYMZ2xVc2VQcm9ncmFtAAADZW52FGdsR2V0VW5pZm9ybUxvY2F0aW9uAAoDZW52DWdsR2VuVGV4dHVyZXMABwNlbnYPZ2xBY3RpdmVUZXh0dXJlAAADZW52DWdsUGl4ZWxTdG9yZWkABwNlbnYSZ2xHZW5SZW5kZXJidWZmZXJzAAcDZW52EmdsQmluZFJlbmRlcmJ1ZmZlcgAHA2VudiBnbFJlbmRlcmJ1ZmZlclN0b3JhZ2VNdWx0aXNhbXBsZQAFA2VudgxnbFRleEltYWdlMkQAFgNlbnYPZ2xUZXhQYXJhbWV0ZXJpAAMDZW52EGdsR2VuZXJhdGVNaXBtYXAAAANlbnYRZ2xHZW5GcmFtZWJ1ZmZlcnMABwNlbnYRZ2xCaW5kRnJhbWVidWZmZXIABwNlbnYWZ2xGcmFtZWJ1ZmZlclRleHR1cmUyRAAFA2VudgxnbFJlYWRQaXhlbHMAFwNlbnYUZ2xEZWxldGVGcmFtZWJ1ZmZlcnMABwNlbnYPZ2xUZXhTdWJJbWFnZTJEABYDZW52GWdsRnJhbWVidWZmZXJSZW5kZXJidWZmZXIABgNlbnYNZ2xEcmF3QnVmZmVycwAHA2VudhhnbENoZWNrRnJhbWVidWZmZXJTdGF0dXMACQNlbnYVZ2xEZWxldGVSZW5kZXJidWZmZXJzAAcDZW52EGdsRGVsZXRlVGV4dHVyZXMABwNlbnYTZ2xHZXRBdHRyaWJMb2NhdGlvbgAKA2VudghnbEVuYWJsZQAAA2VudglnbERpc2FibGUAAANlbnYLZ2xEZXB0aEZ1bmMAAANlbnYLZ2xGcm9udEZhY2UAAANlbnYKZ2xDdWxsRmFjZQAAA2VudhNnbEJsZW5kRnVuY1NlcGFyYXRlAAYDZW52F2dsQmxlbmRFcXVhdGlvblNlcGFyYXRlAAcDZW52C2dsQmxlbmRGdW5jAAcDZW52E2dsU3RlbmNpbE9wU2VwYXJhdGUABgNlbnYVZ2xTdGVuY2lsRnVuY1NlcGFyYXRlAAYDZW52FWdsU3RlbmNpbE1hc2tTZXBhcmF0ZQAHA2VudgtnbENvbG9yTWFzawAGA2VudgxnbEdlbkJ1ZmZlcnMABwNlbnYMZ2xCaW5kQnVmZmVyAAcDZW52DGdsQnVmZmVyRGF0YQAGA2Vudg9nbEJ1ZmZlclN1YkRhdGEABgNlbnYPZ2xEZWxldGVCdWZmZXJzAAcDZW52GmdsRGlzYWJsZVZlcnRleEF0dHJpYkFycmF5AAADZW52D2dsRGVsZXRlUHJvZ3JhbQAAA2VudgpnbFZpZXdwb3J0AAYDZW52CWdsU2Npc3NvcgAGA2VudgtnbFVuaWZvcm0xaQAHA2VudhZnbFZlcnRleEF0dHJpYklQb2ludGVyAAUDZW52FWdsVmVydGV4QXR0cmliUG9pbnRlcgAEA2VudhVnbFZlcnRleEF0dHJpYkRpdmlzb3IABwNlbnYZZ2xFbmFibGVWZXJ0ZXhBdHRyaWJBcnJheQAAA2VudgxnbFVuaWZvcm0xZnYAAwNlbnYMZ2xVbmlmb3JtMmZ2AAMDZW52DGdsVW5pZm9ybTNmdgADA2VudgxnbFVuaWZvcm00ZnYAAwNlbnYMZ2xVbmlmb3JtMWl2AAMDZW52DGdsVW5pZm9ybTJpdgADA2VudgxnbFVuaWZvcm0zaXYAAwNlbnYMZ2xVbmlmb3JtNGl2AAMDZW52EmdsVW5pZm9ybU1hdHJpeDRmdgAGA2VudgxnbENsZWFyQ29sb3IAGANlbnYNZ2xDbGVhckRlcHRoZgAZA2Vudg5nbENsZWFyU3RlbmNpbAAAA2VudgdnbENsZWFyAAADZW52DGdsUmVhZEJ1ZmZlcgAAA2VudhFnbEJsaXRGcmFtZWJ1ZmZlcgAaA2VudhdnbERyYXdFbGVtZW50c0luc3RhbmNlZAAFA2Vudg5nbENyZWF0ZVNoYWRlcgAJA2Vudg5nbFNoYWRlclNvdXJjZQAGA2Vudg9nbENvbXBpbGVTaGFkZXIAAANlbnYNZ2xHZXRTaGFkZXJpdgADA2VudhJnbEdldFNoYWRlckluZm9Mb2cABgNlbnYSc2FwcF9zZXRfY2xpcGJvYXJkAAcDZW52EmZzX2dldF9idWZmZXJfc2l6ZQAJA2Vudg5mc190YWtlX2J1ZmZlcgADA2VudhRzYXBwX3NldF9jdXJzb3JfZ3JhYgAAA2Vudg9zYXBwX3NldF9jdXJzb3IABwNlbnYTc2FwcF9zZXRfZnVsbHNjcmVlbgAAA4wKigoDAwoGAgADCgMBCgMHCgMAAgMXCQAHAAcAFhcWFRUCFgUVBhYFCgcDGwocChwKAQABAAEBAQYFAAAAAAAAAAAAAAAAAAMAAAAAAAcAAAAAAAAAAAAAAAAABQMHBwIDAgcHAgcHBwoKAAoDAgADCh0HBwAHBQcFBwUDBAsKCwsMAwMBAwYHBwMEBAQXBgQDBwcHBwoKCwIMDAIHCwseCwsfCwsMByAKDAICCgICAgIMAgIKAgcCAwMJCQoMCgMDCwMDAwMDAwMDBwMCCgcHCSEHBwcHBwcHBwcJCQcHBwcHBwcHLine truncated var DC_WASM = "AGFzbQEAAAAB0wZmYAF/AGAAAGADf39/AX9gA39/fwBgBn9/f39/fwBgBX9/f39/AGAEf39/fwBgAn9/AGAHf39/f39/fwF/YAJ/fwF/YAF/AX9gBH9/f38Bf2AFf39/f38Bf2ADf319AGAFf319fX0AYAd/fX19fX19AGAGf39/fX9/AGAGf39/f39/AX9gAAF8YAABf2AAAX1gCH9/f39/f39/AGAJf39/f39/f39/AGAHf39/f39/fwBgBH19fX0AYAF9AGAKf39/f39/f39/fwBgBH5+f38BfmADfn5/AX5gAX4Bf2AEf39/fQF/YAJ+fwF/YAJ/fQBgAn1/AX9gBn99fXx/fwBgB39/f39/f3wAYAV/f39/fABgC39/f39/f399fX98AGAHf39/f399fQBgBH9/fX0AYAV/f319fwBgB39/f319f3wAYAN/f30AYA1/f319fX9/f39/f39/AGAFfX19fX8AYAZ9fX19fX8AYAV/fX1/fwBgB39/f319fX8AYAZ/f39/fH8AYAh/f39/f39/fABgBn9/f399fQBgCX99fX9/fX9/fwBgDH99fX9/fX9/f39/fwBgAX8BfWAJf39/f319fX99AGAGf39/fX1/AX9gBX9/f319AGACfX0Bf2ACf34AYAd/fX1/fX99AGADf399AX1gB39/fX19f38AYAJ9fQF9YAh/f39/fX19fQBgCX9/fX19f39/fwBgAn98AGAFf31/f38AYAh/f319fX19fwBgBH19f30AYAR/f31/AGADf31/AGAEf31/fwBgCn9/f39/f39/fX0AYAZ/f319fX0Bf2ADf399AX9gAn9/AX1gCH9/fX9/f39/AGAFf399fX8Bf2AEf39/fQBgBH9/f34AYAV/f39+fwBgB399fX19f38AYAR/f319AX1gE39/f39/f39/f39/f39/f39/f38Bf2ABfwF+YAZ/fn9/f38AYAl/f39/f39+fn4AYAR/fX9/AX9gA399fwF/YAN/fn4AYAt/f39/f39/f39/fwF/YAABfmACf34Bf2ADfn5+AX5gCH9/f39/f39/AX9gBX9/fn19AGADf35/AGALf39/f39/f39/f38AYAJ8fwF/YAF9AX1gBX9+fn5+AGACfX8BfQLXFHoDZW52C2NvbnNvbGVfbG9nAAADZW52B2RjX2h0dHAAEQNlbnYMZGNfc2V0X3Rva2VuAAcDZW52A25vdwASA2VudhFkY19jb2Fyc2VfcG9pbnRlcgATA2VudgxkY19maWxlX3BvbGwAEwNlbnYMZGNfZmlsZV90YWtlAAADZW52DGRjX2h0dHBfcG9sbAAKA2VudgtkY19odHRwX2xlbgAKA2VudgxkY19odHRwX3Rha2UABwNlbnYOZGNfc3RvcmFnZV9zZXQABgNlbnYXZGNfZmV0Y2hfZG93bmxvYWRfc3RhdGUAEwNlbnYLZGNfZG93bmxvYWQABgNlbnYMZGNfb3Blbl9maWxlAAEDZW52C2RjX29wZW5fdXJsAAcDZW52DGRjX2NhY2hlX3NldAAGA2VudhFkY19mZXRjaF9kb3dubG9hZAAGA2Vudgppbml0X3dlYmdsAAADZW52EXNldHVwX2NhbnZhc19zaXplAAADZW52DGNhbnZhc193aWR0aAATA2Vudg1jYW52YXNfaGVpZ2h0ABMDZW52CWRwaV9zY2FsZQAUA2VudhJydW5fYW5pbWF0aW9uX2xvb3AAAANlbnYOZGNfc3RvcmFnZV9nZXQACQNlbnYPZGNfc3RvcmFnZV90YWtlAAADZW52CWRjX3Byb21wdAALA2Vudg1nbEJpbmRUZXh0dXJlAAcDZW52EGdsQ29weVRleEltYWdlMkQAFQNlbnYNZ2xHZXRJbnRlZ2VydgAHA2VudhFnbEdlblZlcnRleEFycmF5cwAHA2VudhFnbEJpbmRWZXJ0ZXhBcnJheQAAA2VudgtnbEdldFN0cmluZwAKA2VudhRzYXBwX3NjaGVkdWxlX3VwZGF0ZQABA2VudgxnbEdlblF1ZXJpZXMABwNlbnYMZ2xCZWdpblF1ZXJ5AAcDZW52CmdsRW5kUXVlcnkAAANlbnYHZ2xGbHVzaAABA2VudghnbEZpbmlzaAABA2Vudgxjb25zb2xlX3dhcm4AAANlbnYPZ2xDcmVhdGVQcm9ncmFtABMDZW52DmdsQXR0YWNoU2hhZGVyAAcDZW52DWdsTGlua1Byb2dyYW0AAANlbnYOZ2xEZXRhY2hTaGFkZXIABwNlbnYOZ2xEZWxldGVTaGFkZXIAAANlbnYOZ2xHZXRQcm9ncmFtaXYAAwNlbnYTZ2xHZXRQcm9ncmFtSW5mb0xvZwAGA2VudgxnbFVzZVByb2dyYW0AAANlbnYUZ2xHZXRVbmlmb3JtTG9jYXRpb24ACQNlbnYNZ2xHZW5UZXh0dXJlcwAHA2Vudg9nbEFjdGl2ZVRleHR1cmUAAANlbnYNZ2xQaXhlbFN0b3JlaQAHA2VudhJnbEdlblJlbmRlcmJ1ZmZlcnMABwNlbnYSZ2xCaW5kUmVuZGVyYnVmZmVyAAcDZW52IGdsUmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlAAUDZW52DGdsVGV4SW1hZ2UyRAAWA2Vudg9nbFRleFBhcmFtZXRlcmkAAwNlbnYQZ2xHZW5lcmF0ZU1pcG1hcAAAA2VudhFnbEdlbkZyYW1lYnVmZmVycwAHA2VudhFnbEJpbmRGcmFtZWJ1ZmZlcgAHA2VudhZnbEZyYW1lYnVmZmVyVGV4dHVyZTJEAAUDZW52DGdsUmVhZFBpeGVscwAXA2VudhRnbERlbGV0ZUZyYW1lYnVmZmVycwAHA2Vudg9nbFRleFN1YkltYWdlMkQAFgNlbnYZZ2xGcmFtZWJ1ZmZlclJlbmRlcmJ1ZmZlcgAGA2Vudg1nbERyYXdCdWZmZXJzAAcDZW52GGdsQ2hlY2tGcmFtZWJ1ZmZlclN0YXR1cwAKA2VudhVnbERlbGV0ZVJlbmRlcmJ1ZmZlcnMABwNlbnYQZ2xEZWxldGVUZXh0dXJlcwAHA2VudhNnbEdldEF0dHJpYkxvY2F0aW9uAAkDZW52CGdsRW5hYmxlAAADZW52CWdsRGlzYWJsZQAAA2VudgtnbERlcHRoRnVuYwAAA2VudgtnbEZyb250RmFjZQAAA2VudgpnbEN1bGxGYWNlAAADZW52E2dsQmxlbmRGdW5jU2VwYXJhdGUABgNlbnYXZ2xCbGVuZEVxdWF0aW9uU2VwYXJhdGUABwNlbnYLZ2xCbGVuZEZ1bmMABwNlbnYTZ2xTdGVuY2lsT3BTZXBhcmF0ZQAGA2VudhVnbFN0ZW5jaWxGdW5jU2VwYXJhdGUABgNlbnYVZ2xTdGVuY2lsTWFza1NlcGFyYXRlAAcDZW52C2dsQ29sb3JNYXNrAAYDZW52DGdsR2VuQnVmZmVycwAHA2VudgxnbEJpbmRCdWZmZXIABwNlbnYMZ2xCdWZmZXJEYXRhAAYDZW52D2dsQnVmZmVyU3ViRGF0YQAGA2Vudg9nbERlbGV0ZUJ1ZmZlcnMABwNlbnYaZ2xEaXNhYmxlVmVydGV4QXR0cmliQXJyYXkAAANlbnYPZ2xEZWxldGVQcm9ncmFtAAADZW52CmdsVmlld3BvcnQABgNlbnYJZ2xTY2lzc29yAAYDZW52C2dsVW5pZm9ybTFpAAcDZW52FmdsVmVydGV4QXR0cmliSVBvaW50ZXIABQNlbnYVZ2xWZXJ0ZXhBdHRyaWJQb2ludGVyAAQDZW52FWdsVmVydGV4QXR0cmliRGl2aXNvcgAHA2VudhlnbEVuYWJsZVZlcnRleEF0dHJpYkFycmF5AAADZW52DGdsVW5pZm9ybTFmdgADA2VudgxnbFVuaWZvcm0yZnYAAwNlbnYMZ2xVbmlmb3JtM2Z2AAMDZW52DGdsVW5pZm9ybTRmdgADA2VudgxnbFVuaWZvcm0xaXYAAwNlbnYMZ2xVbmlmb3JtMml2AAMDZW52DGdsVW5pZm9ybTNpdgADA2VudgxnbFVuaWZvcm00aXYAAwNlbnYSZ2xVbmlmb3JtTWF0cml4NGZ2AAYDZW52DGdsQ2xlYXJDb2xvcgAYA2Vudg1nbENsZWFyRGVwdGhmABkDZW52DmdsQ2xlYXJTdGVuY2lsAAADZW52B2dsQ2xlYXIAAANlbnYMZ2xSZWFkQnVmZmVyAAADZW52EWdsQmxpdEZyYW1lYnVmZmVyABoDZW52F2dsRHJhd0VsZW1lbnRzSW5zdGFuY2VkAAUDZW52DmdsQ3JlYXRlU2hhZGVyAAoDZW52DmdsU2hhZGVyU291cmNlAAYDZW52D2dsQ29tcGlsZVNoYWRlcgAAA2Vudg1nbEdldFNoYWRlcml2AAMDZW52EmdsR2V0U2hhZGVySW5mb0xvZwAGA2VudhJzYXBwX3NldF9jbGlwYm9hcmQABwNlbnYSZnNfZ2V0X2J1ZmZlcl9zaXplAAoDZW52DmZzX3Rha2VfYnVmZmVyAAMDZW52FHNhcHBfc2V0X2N1cnNvcl9ncmFiAAADZW52D3NhcHBfc2V0X2N1cnNvcgAHA2VudhNzYXBwX3NldF9mdWxsc2NyZWVuAAADqQqnCgMDCQYCAAMJAwEJAwcJAwACAxcKAAcABwAWFxYVFQIWBRUGFgUJBwMbCRwJHAkBAAEAAQEBBgUAAAAAAAAAAAAAAAAAAwAAAAAABwAAAAAAAAAAAAAAAAAFAwcHAgMCBwcCBwcHCQkACQMCAAMJHQcHAAMEBwUHBQcFBwUDBAMEAwQMCwkLCwsMDAMDAwMBAwMGBgYDBwcHAwMXBAQEBBcXFwYEAwcHBwcJCQsCDAwCBwsLHgsLHwsLDAcgCQwCAgkCAgICDAICCQIHAgMDCgoJDAkDAwsDAwMDLine truncated
(function () { (function () {
var realFetch = window.fetch ? window.fetch.bind(window) : null; var realFetch = window.fetch ? window.fetch.bind(window) : null;
window.fetch = function (url, opts) { window.fetch = function (url, opts) {
+1
View File
@@ -504,6 +504,7 @@ pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
g.add_rival(side, x, y, diff); g.add_rival(side, x, y, diff);
} }
g.place_dwarf_caves(d.seed); g.place_dwarf_caves(d.seed);
g.add_wilds(d.seed, false);
g.init_research(Some(i)); g.init_research(Some(i));
g.init_fog(); g.init_fog();
let heart = d.gate || d.forts.iter().any(|f| f.heart); let heart = d.gate || d.forts.iter().any(|f| f.heart);
+2 -1
View File
@@ -629,6 +629,7 @@ impl Game {
g.wave_timer = FIRST_WAVE + ((dist - 45.0) * 1.5).clamp(0.0, 300.0); g.wave_timer = FIRST_WAVE + ((dist - 45.0) * 1.5).clamp(0.0, 300.0);
g.guard_treasures(); g.guard_treasures();
g.place_dwarf_caves(seed); g.place_dwarf_caves(seed);
g.add_wilds(seed, true);
g.init_research(None); g.init_research(None);
g.init_fog(); g.init_fog();
g.msg("msg.welcome1", vec![]); g.msg("msg.welcome1", vec![]);
@@ -5133,7 +5134,7 @@ mod tests {
// rubies fill their own tiles // rubies fill their own tiles
g.add_res(1200.0, 2); g.add_res(1200.0, 2);
g.update(0.01); g.update(0.01);
let rubies: i32 = g.map.t.iter().filter(|t| t.kind == Kind::Treasury && t.res == 2).map(|t| t.gold).sum(); let rubies: i32 = g.map.t.iter().filter(|t| t.kind == Kind::Treasury && t.owner == Owner::Player && t.res == 2).map(|t| t.gold).sum();
assert_eq!(rubies, 1200); assert_eq!(rubies, 1200);
assert!(g.map.t.iter().all(|t| t.gold <= TREASURY_TILE as i32 || t.vein().is_some())); assert!(g.map.t.iter().all(|t| t.gold <= TREASURY_TILE as i32 || t.vein().is_some()));
// spending empties tiles (randomly), the total always matches the gold counter // spending empties tiles (randomly), the total always matches the gold counter
+1
View File
@@ -30,6 +30,7 @@ mod sound;
mod spells; mod spells;
mod touch; mod touch;
mod ui; mod ui;
mod wilds;
mod xm; mod xm;
use cloud::{Cloud, CloudEvent}; use cloud::{Cloud, CloudEvent};
+1
View File
@@ -85,6 +85,7 @@ impl Game {
g.msg("msg.welcome1", vec![]); g.msg("msg.welcome1", vec![]);
g.msg("msg.welcome2", vec![]); g.msg("msg.welcome2", vec![]);
g.msg(if rivals == 0 { "msg.welcome_humans" } else if humans { "msg.welcome_both" } else { "msg.welcome_rivals" }, vec![rivals.to_string()]); g.msg(if rivals == 0 { "msg.welcome_humans" } else if humans { "msg.welcome_both" } else { "msg.welcome_rivals" }, vec![rivals.to_string()]);
g.add_wilds(seed, true);
g.init_research(None); g.init_research(None);
g.init_fog(); g.init_fog();
g.place_dwarf_caves(seed); g.place_dwarf_caves(seed);
+192
View File
@@ -0,0 +1,192 @@
// The wild underground: rivers of water and lava across free maps (with neutral bridges) and
// natural caverns until about a quarter of the map is open - some with a pool, veins along the
// walls, now and then a small treasure or a creature in chains waiting to be freed.
// Everything the map or the mission placed on purpose (core, portals, gate, fortresses,
// islands, sealed caves, dwarf caves, rival dungeons) is left alone.
use crate::game::*;
use crate::map::*;
const OPEN_SHARE: f32 = 0.25;
impl Game {
pub fn add_wilds(&mut self, seed: u32, rivers: bool) {
let mut rng = Rng(seed.wrapping_mul(0x2c1b_3c6d) ^ 0x297a_2d39 | 1);
for _ in 0..6 {
rng.next();
}
let (w, h) = (mw() as i32, mh() as i32);
let area = (w * h) as f32;
if rivers {
// one river per ~100x100 tiles, at least one; some of lava
let n = ((area / 10000.0).round() as i32).clamp(1, 4);
for k in 0..n {
// away from the start: vertical rivers in the right part, horizontal ones above or below the core
let vertical = rng.f() < 0.5;
let pos = if vertical { 0.42 + rng.f() * 0.4 } else if rng.f() < 0.5 { 0.12 + rng.f() * 0.18 } else { 0.7 + rng.f() * 0.18 };
let r = River { lava: rng.f() < 0.4, vertical, pos, width: 3 + (rng.f() < 0.5) as i32, bridges: 2 + (area > 15000.0) as u32 };
let bridges = self.map.carve_river(&r, seed.wrapping_add(k as u32 * 7919));
// free maps: the bridges belong to nobody (anybody may claim them)
for b in bridges {
self.map.t[b].owner = Owner::None;
}
}
}
// what must not be touched: owned ground, buildings, mission pieces and their surroundings
let n = self.map.t.len();
let mut keep = vec![false; n];
let mark = |keep: &mut Vec<bool>, x: i32, y: i32, r: i32| {
for yy in y - r..=y + r {
for xx in x - r..=x + r {
if inb(xx, yy) {
keep[idx(xx, yy)] = true;
}
}
}
};
for i in 0..n {
let t = &self.map.t[i];
let special = t.owner != Owner::None
|| matches!(t.kind, Kind::Core | Kind::Portal | Kind::Gate | Kind::Tower | Kind::Shard | Kind::Treasury | Kind::ManaSpring | Kind::RuneChest | Kind::Bridge | Kind::LavaBridge);
if special {
let (x, y) = xy(i);
mark(&mut keep, x, y, 3);
}
}
for c in self.dwarf_caves.clone() {
mark(&mut keep, c.at.0, c.at.1, 7);
}
for e in self.ents.iter().filter(|e| e.alive && (e.chained || e.hero || e.dwarf() || e.rival())) {
mark(&mut keep, e.x as i32, e.y as i32, 4);
}
let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1);
mark(&mut keep, cx, cy, 9);
let open = |m: &Map| m.t.iter().filter(|t| !t.solid() && t.kind != Kind::Rock).count() as f32;
let mut tries = 0;
while open(&self.map) < area * OPEN_SHARE && tries < 400 {
tries += 1;
let (x, y) = (rng.range(4, w - 5), rng.range(4, h - 5));
if keep[idx(x, y)] {
continue;
}
self.carve_cavern(x, y, &keep, &mut rng);
}
}
// a cavern: a few overlapping blobs, ragged edges
fn carve_cavern(&mut self, x: i32, y: i32, keep: &[bool], rng: &mut Rng) {
let (w, h) = (mw() as i32, mh() as i32);
let blobs = 2 + rng.range(0, 3);
let mut tiles: Vec<usize> = Vec::new();
let (mut bx, mut by) = (x, y);
for _ in 0..blobs {
let r = rng.range(2, 5);
for yy in by - r..=by + r {
for xx in bx - r..=bx + r {
if xx < 3 || yy < 3 || xx >= w - 3 || yy >= h - 3 {
continue;
}
let d = (((xx - bx).pow(2) + (yy - by).pow(2)) as f32).sqrt();
if d > r as f32 + 0.3 - rng.f() * 0.9 {
continue;
}
let i = idx(xx, yy);
let t = &self.map.t[i];
if keep[i] || !t.solid() || t.kind == Kind::Rock || t.kind == Kind::Steel {
continue;
}
tiles.push(i);
}
}
bx += rng.range(-r, r);
by += rng.range(-r, r);
}
if tiles.len() < 6 {
return;
}
for &i in &tiles {
let t = &mut self.map.t[i];
t.kind = Kind::Floor;
t.owner = Owner::None;
t.gold = 0;
t.marked = false;
t.reinforced = false;
}
// veins in the walls around
for &i in &tiles {
let (tx, ty) = xy(i);
for &(dx, dy) in &DIR4 {
let (nx, ny) = (tx + dx, ty + dy);
if !inb(nx, ny) || keep[idx(nx, ny)] {
continue;
}
let t = &mut self.map.t[idx(nx, ny)];
if t.kind == Kind::Earth && rng.f() < 0.07 {
let r = rng.f();
let (k, g) = if r < 0.06 { (Kind::Ruby, rng.range(500, 900)) } else if r < 0.3 { (Kind::Silver, rng.range(300, 600)) } else { (Kind::Gold, rng.range(400, 900)) };
t.kind = k;
t.gold = g;
t.hp = 6.0;
}
}
}
// the middle: sometimes a pool, rarely a treasure or a creature in chains
let (sx, sy) = tiles.iter().fold((0i64, 0i64), |a, &i| (a.0 + xy(i).0 as i64, a.1 + xy(i).1 as i64));
let (mx, my) = ((sx / tiles.len() as i64) as i32, (sy / tiles.len() as i64) as i32);
let inside = |m: &Map, x: i32, y: i32| inb(x, y) && m.t[idx(x, y)].kind == Kind::Floor && m.t[idx(x, y)].owner == Owner::None;
let roll = rng.f();
if tiles.len() > 30 && roll < 0.35 {
let kind = if rng.f() < 0.4 { Kind::Lava } else { Kind::Water };
for yy in my - 1..=my + 1 {
for xx in mx - 1..=mx + 1 {
// only where floor surrounds it, so the cavern stays passable around the pool
if inside(&self.map, xx, yy) && DIR8.iter().all(|&(dx, dy)| !self.map.get(xx + dx, yy + dy).map_or(true, |t| t.solid())) {
self.map.t[idx(xx, yy)].kind = kind;
}
}
}
} else if roll < 0.47 && inside(&self.map, mx, my) {
for &(dx, dy) in &[(0, 0), (1, 0)] {
if inside(&self.map, mx + dx, my + dy) {
let t = &mut self.map.t[idx(mx + dx, my + dy)];
t.kind = Kind::Treasury;
t.gold = rng.range(200, 450);
t.res = (rng.f() < 0.3) as u8 * 2;
}
}
} else if roll < 0.53 && inside(&self.map, mx, my) {
let kinds = [EK::Spider, EK::Kobold, EK::Ogre, EK::Bat, EK::Mage];
let kind = kinds[rng.range(0, kinds.len() as i32 - 1) as usize];
let level = rng.range(2, 4) as u32;
let id = self.spawn(kind, false, mx as f32 + 0.5, my as f32 + 0.5, level);
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
e.chained = true;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn open_share(g: &Game) -> f32 {
g.map.t.iter().filter(|t| !t.solid() && t.kind != Kind::Rock).count() as f32 / g.map.t.len() as f32
}
#[test]
fn free_maps_have_rivers_and_caverns_and_the_heroes_find_a_way() {
for seed in 1..=8u32 {
for &(w, h) in &[(64usize, 64usize), (128, 128)] {
let g = Game::new(seed, w, h);
let share = open_share(&g);
assert!(share > 0.2, "seed {} {}x{}: only {:.0}% open", seed, w, h, share * 100.0);
assert!(g.map.t.iter().any(|t| t.liquid()), "rivers or pools");
let (gx, gy) = g.map.gate;
assert!(g.map.hero_path_to_core(idx(gx + 1, gy + 1)).is_some(), "seed {}: heroes reach the core", seed);
let (cx, cy) = g.map.core;
assert_eq!(g.map.t[idx(cx + 1, cy + 1)].kind, Kind::Core);
}
}
}
}