diff --git a/.gitignore b/.gitignore index 370ac8c780..e554394c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ __pycache__ *.kdev4 .kdev4/ *.kate-swp +*~ +*-autosave.kra diff --git a/.gitmodules b/.gitmodules index 7d0228ddba..bc88e8db73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,6 @@ path = external/gamecontrollerdb url = https://github.com/taisei-project/SDL_GameControllerDB.git branch = master +[submodule "external/koishi"] + path = external/koishi + url = https://github.com/taisei-project/koishi diff --git a/atlas/common/part/stardust.png b/atlas/common/part/stardust.png deleted file mode 100644 index 8d7ffeab87..0000000000 Binary files a/atlas/common/part/stardust.png and /dev/null differ diff --git a/atlas/common/proj/hghost.png b/atlas/common/proj/hghost.png deleted file mode 100644 index 4a5f93d2a8..0000000000 Binary files a/atlas/common/proj/hghost.png and /dev/null differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0000.webp b/atlas/coroutines_temp/part/bullet_clear.frame0000.webp new file mode 100644 index 0000000000..4a8dc5ab34 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0000.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0001.webp b/atlas/coroutines_temp/part/bullet_clear.frame0001.webp new file mode 100644 index 0000000000..59f5982f63 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0001.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0002.webp b/atlas/coroutines_temp/part/bullet_clear.frame0002.webp new file mode 100644 index 0000000000..dc9a6139cf Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0002.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0003.webp b/atlas/coroutines_temp/part/bullet_clear.frame0003.webp new file mode 100644 index 0000000000..4d550efdf8 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0003.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0004.webp b/atlas/coroutines_temp/part/bullet_clear.frame0004.webp new file mode 100644 index 0000000000..b36b9cc3c8 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0004.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0005.webp b/atlas/coroutines_temp/part/bullet_clear.frame0005.webp new file mode 100644 index 0000000000..3798ecaca7 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0005.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0006.webp b/atlas/coroutines_temp/part/bullet_clear.frame0006.webp new file mode 100644 index 0000000000..501935245b Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0006.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0007.webp b/atlas/coroutines_temp/part/bullet_clear.frame0007.webp new file mode 100644 index 0000000000..ccb7bd6383 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0007.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0008.webp b/atlas/coroutines_temp/part/bullet_clear.frame0008.webp new file mode 100644 index 0000000000..f9f95aa5de Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0008.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0009.webp b/atlas/coroutines_temp/part/bullet_clear.frame0009.webp new file mode 100644 index 0000000000..039d445a5a Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0009.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0010.webp b/atlas/coroutines_temp/part/bullet_clear.frame0010.webp new file mode 100644 index 0000000000..a6c0f91ca9 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0010.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0011.webp b/atlas/coroutines_temp/part/bullet_clear.frame0011.webp new file mode 100644 index 0000000000..46463fc82f Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0011.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0012.webp b/atlas/coroutines_temp/part/bullet_clear.frame0012.webp new file mode 100644 index 0000000000..91ff4cc273 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0012.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0013.webp b/atlas/coroutines_temp/part/bullet_clear.frame0013.webp new file mode 100644 index 0000000000..c10d1034d3 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0013.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0014.webp b/atlas/coroutines_temp/part/bullet_clear.frame0014.webp new file mode 100644 index 0000000000..196f4999aa Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0014.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0015.webp b/atlas/coroutines_temp/part/bullet_clear.frame0015.webp new file mode 100644 index 0000000000..a3cecf2de5 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0015.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0016.webp b/atlas/coroutines_temp/part/bullet_clear.frame0016.webp new file mode 100644 index 0000000000..e5378fc3ef Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0016.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0017.webp b/atlas/coroutines_temp/part/bullet_clear.frame0017.webp new file mode 100644 index 0000000000..ea0a425bc3 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0017.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0018.webp b/atlas/coroutines_temp/part/bullet_clear.frame0018.webp new file mode 100644 index 0000000000..e5e8692f98 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0018.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0019.webp b/atlas/coroutines_temp/part/bullet_clear.frame0019.webp new file mode 100644 index 0000000000..5a589b819b Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0019.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0020.webp b/atlas/coroutines_temp/part/bullet_clear.frame0020.webp new file mode 100644 index 0000000000..6ef43d5035 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0020.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0021.webp b/atlas/coroutines_temp/part/bullet_clear.frame0021.webp new file mode 100644 index 0000000000..228412be60 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0021.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0022.webp b/atlas/coroutines_temp/part/bullet_clear.frame0022.webp new file mode 100644 index 0000000000..87c3769507 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0022.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0023.webp b/atlas/coroutines_temp/part/bullet_clear.frame0023.webp new file mode 100644 index 0000000000..fd33ac5ea2 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0023.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0024.webp b/atlas/coroutines_temp/part/bullet_clear.frame0024.webp new file mode 100644 index 0000000000..26ec1f75f1 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0024.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0025.webp b/atlas/coroutines_temp/part/bullet_clear.frame0025.webp new file mode 100644 index 0000000000..b82075e2ee Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0025.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0026.webp b/atlas/coroutines_temp/part/bullet_clear.frame0026.webp new file mode 100644 index 0000000000..3f75b2c308 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0026.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0027.webp b/atlas/coroutines_temp/part/bullet_clear.frame0027.webp new file mode 100644 index 0000000000..5e02d56eba Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0027.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0028.webp b/atlas/coroutines_temp/part/bullet_clear.frame0028.webp new file mode 100644 index 0000000000..20b52be7c1 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0028.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0029.webp b/atlas/coroutines_temp/part/bullet_clear.frame0029.webp new file mode 100644 index 0000000000..64c84e1f68 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0029.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0030.webp b/atlas/coroutines_temp/part/bullet_clear.frame0030.webp new file mode 100644 index 0000000000..981196dcb6 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0030.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0031.webp b/atlas/coroutines_temp/part/bullet_clear.frame0031.webp new file mode 100644 index 0000000000..73e091aa14 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0031.webp differ diff --git a/atlas/coroutines_temp/part/bullet_clear.frame0032.webp b/atlas/coroutines_temp/part/bullet_clear.frame0032.webp new file mode 100644 index 0000000000..73e091aa14 Binary files /dev/null and b/atlas/coroutines_temp/part/bullet_clear.frame0032.webp differ diff --git a/atlas/coroutines_temp/part/stardust.webp b/atlas/coroutines_temp/part/stardust.webp new file mode 100644 index 0000000000..7e2749ba45 Binary files /dev/null and b/atlas/coroutines_temp/part/stardust.webp differ diff --git a/atlas/coroutines_temp/proj/hghost.webp b/atlas/coroutines_temp/proj/hghost.webp new file mode 100644 index 0000000000..3809e2aa48 Binary files /dev/null and b/atlas/coroutines_temp/proj/hghost.webp differ diff --git a/atlas/meson.build b/atlas/meson.build index 216e1f0684..284e26816b 100644 --- a/atlas/meson.build +++ b/atlas/meson.build @@ -44,6 +44,7 @@ atlases = [ ['gray16', [preset_png]], ['huge', []], ['portraits', ['--width=4096', '--height=4096']], + ['coroutines_temp', []], # TODO merge this into `common` when coroutines are merged ] atlas_profiles = [ diff --git a/atlas/overrides/proj/hghost.spr b/atlas/overrides/proj/hghost.spr index aa80c2871e..d869dd38a7 100644 --- a/atlas/overrides/proj/hghost.spr +++ b/atlas/overrides/proj/hghost.spr @@ -1,3 +1,2 @@ - -w = 22 -h = 23 +w = 21 +h = 34 diff --git a/emscripten/preamble.js b/emscripten/preamble.js index 203240bb9d..00a9b98e81 100644 --- a/emscripten/preamble.js +++ b/emscripten/preamble.js @@ -1,22 +1,106 @@ -Module['preRun'].push(function() { - ENV["TAISEI_NOASYNC"] = "1"; - ENV["TAISEI_NOUNLOAD"] = "1"; - ENV["TAISEI_PREFER_SDL_VIDEODRIVERS"] = "emscripten"; - ENV["TAISEI_RENDERER"] = "gles30"; - - FS.mkdir('/persistent'); - FS.mount(IDBFS, {}, '/persistent'); - - // This function has been removed from Emscripten, but SDL still uses it... - Module['Pointer_stringify'] = function(ptr) { - return UTF8ToString(ptr); - } +function E(id) { return document.getElementById(id); } + +var statusElement = E('status'); +var progressElement = E('progress'); +var spinnerElement = E('spinner'); +var canvasElement = E('canvas'); +var canvasContainerElement = E('canvasContainer'); +var logToggleElement = E('logToggle'); +var logToggleContainerElement = E('logToggleContainer'); +var logContainerElement = E('logContainer'); +var logOutputElement = E('output'); +var dlMessage = statusElement.innerText; +logToggleElement.checked = false; - Pointer_stringify = Module['Pointer_stringify'] - window.Pointer_stringify = Module['Pointer_stringify'] +function toggleLog() { + logContainerElement.hidden = !logToggleElement.checked; + logOutputElement.scrollTop = logOutputElement.scrollHeight; +} + +var glContext = canvasElement.getContext('webgl2', { + 'alpha' : false, + 'antialias' : false, + 'depth' : false, + 'powerPreference' : 'high-performance', + 'premultipliedAlpha' : true, + 'preserveDrawingBuffer' : false, + 'stencil' : false, }); +// glContext = WebGLDebugUtils.makeDebugContext(glContext); + +canvasElement.addEventListener("webglcontextlost", function(e) { + alert('WebGL context lost. You will need to reload the page.'); + e.preventDefault(); +}, false); + +logOutputElement.value = ''; // clear browser cache + +Module = { + 'preRun': [function() { + ENV["TAISEI_NOASYNC"] = "1"; + ENV["TAISEI_NOUNLOAD"] = "1"; + ENV["TAISEI_PREFER_SDL_VIDEODRIVERS"] = "emscripten"; + ENV["TAISEI_RENDERER"] = "gles30"; + + FS.mkdir('/persistent'); + FS.mount(IDBFS, {}, '/persistent'); + }], + 'postRun': [], + 'onFirstFrame': function() { + canvasContainerElement.hidden = false; + logToggleContainerElement.style.display = "inline-block"; + Module['setStatus']('', true); + }, + 'print': function(text) { + if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); + console.log(text); + logOutputElement.value += text + "\n"; + logOutputElement.scrollTop = logOutputElement.scrollHeight; // focus on bottom + }, + 'printErr': function(text) { + if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); + console.error(text); + }, + 'canvas': canvasElement, + 'preinitializedWebGLContext': glContext, + 'setStatus': function(text, force) { + var ss = Module['setStatus']; + if (!text && !force) return; + if (!ss.last) ss.last = { time: Date.now(), text: '' }; + if (text === ss.last.text) return; + var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); + var now = Date.now(); + if (m && now - ss.last.time < 30) return; // if this is a progress update, skip it if too soon + ss.last.time = now; + ss.last.text = text; + if (m) { + text = m[1]; + progressElement.value = parseInt(m[2])*100; + progressElement.max = parseInt(m[4])*100; + progressElement.hidden = false; + spinnerElement.hidden = false; + } else { + progressElement.value = null; + progressElement.max = null; + progressElement.hidden = true; + if (!text) spinnerElement.hidden = true; + } + statusElement.innerText = text.replace(/^Downloading(?: data)?\.\.\./, dlMessage).replace('...', '…'); + console.log("[STATUS] " + statusElement.innerText); + }, + 'totalDependencies': 0, + 'monitorRunDependencies': function(left) { + Module['totalDependencies'] = Math.max(Module['totalDependencies'], left); + Module['setStatus'](left ? 'Preparing… (' + (Module['totalDependencies']-left) + '/' + Module['totalDependencies'] + ')' : 'All downloads complete.'); + } +}; + +window.onerror = function(error) { + Module['setStatus']('Error: ' + error); +}; + function SyncFS(is_load, ccptr) { FS.syncfs(is_load, function(err) { Module['ccall']( @@ -25,16 +109,6 @@ function SyncFS(is_load, ccptr) { [is_load, err, ccptr] ); }); -}; - -Module['preinitializedWebGLContext'] = document.getElementById('canvas').getContext('webgl2', { - alpha : false, - antialias : false, - depth : false, - powerPreference : 'high-performance', - premultipliedAlpha : true, - preserveDrawingBuffer : false, - stencil : false, -}); +} -// Module['preinitializedWebGLContext'] = WebGLDebugUtils.makeDebugContext(Module['preinitializedWebGLContext']); +var debug_tables; // closure may fail on debug builds without this diff --git a/emscripten/shell.html b/emscripten/shell.html index e25e26e19b..db9210aa69 100644 --- a/emscripten/shell.html +++ b/emscripten/shell.html @@ -210,91 +210,6 @@

Note: this web port is experimental and may not perform as well as the origi
Powered by Emscripten - {{{ SCRIPT }}} diff --git a/external/koishi b/external/koishi new file mode 160000 index 0000000000..29a2780f8a --- /dev/null +++ b/external/koishi @@ -0,0 +1 @@ +Subproject commit 29a2780f8a5c1594f9de66c61106d9b0307bac78 diff --git a/meson.build b/meson.build index 9d8f725d67..d7b95876c8 100644 --- a/meson.build +++ b/meson.build @@ -165,10 +165,12 @@ dep_m = cc.find_library('m', required : false) dep_cglm = subproject('cglm').get_variable('cglm_dep') dep_glad = subproject('glad').get_variable('glad_dep') +dep_koishi = subproject('koishi').get_variable('koishi_dep') taisei_deps = [ dep_cglm, dep_freetype, + dep_koishi, dep_m, dep_png, dep_sdl2, @@ -248,6 +250,11 @@ config.set('TAISEI_BUILDCONF_HAVE_TIMESPEC', have_timespec) config.set('TAISEI_BUILDCONF_HAVE_INT128', cc.sizeof('__int128') == 16) config.set('TAISEI_BUILDCONF_HAVE_LONG_DOUBLE', cc.sizeof('long double') > 8) config.set('TAISEI_BUILDCONF_HAVE_POSIX', have_posix) +config.set('TAISEI_BUILDCONF_HAVE_SINCOS', cc.has_function('sincos', dependencies : dep_m)) + +if config.get('TAISEI_BUILDCONF_HAVE_SINCOS') + config.set('_GNU_SOURCE', true) +endif if host_machine.system() == 'emscripten' # Emscripten bug: https://github.com/emscripten-core/emscripten/issues/10072 diff --git a/resources/00-taisei.pkgdir/gfx/atlas_coroutines_temp_0.tex b/resources/00-taisei.pkgdir/gfx/atlas_coroutines_temp_0.tex new file mode 100644 index 0000000000..4d6cfc9045 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/atlas_coroutines_temp_0.tex @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +source = res/gfx/atlas_coroutines_temp_0.webp + +# -- Pasted from the global override file -- + +anisotropy = 1 diff --git a/resources/00-taisei.pkgdir/gfx/atlas_coroutines_temp_0.webp b/resources/00-taisei.pkgdir/gfx/atlas_coroutines_temp_0.webp new file mode 100644 index 0000000000..bf46cf3bab Binary files /dev/null and b/resources/00-taisei.pkgdir/gfx/atlas_coroutines_temp_0.webp differ diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.ani b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.ani new file mode 100644 index 0000000000..285c0af5f2 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.ani @@ -0,0 +1,3 @@ +@sprite_count = 33 + +main = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0000.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0000.spr new file mode 100644 index 0000000000..480efd829c --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0000.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1609 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0001.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0001.spr new file mode 100644 index 0000000000..a99704f116 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0001.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1442 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0002.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0002.spr new file mode 100644 index 0000000000..cb91f93162 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0002.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1776 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0003.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0003.spr new file mode 100644 index 0000000000..a07c0aa407 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0003.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 774 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0004.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0004.spr new file mode 100644 index 0000000000..27fd1a5c4b --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0004.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1108 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0005.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0005.spr new file mode 100644 index 0000000000..e7d7f2f67a --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0005.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 607 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0006.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0006.spr new file mode 100644 index 0000000000..99d55085a9 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0006.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 941 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0007.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0007.spr new file mode 100644 index 0000000000..57fe0aa3e7 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0007.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1275 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0008.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0008.spr new file mode 100644 index 0000000000..dd7eb0f977 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0008.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1943 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0009.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0009.spr new file mode 100644 index 0000000000..0806335476 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0009.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 2110 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0010.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0010.spr new file mode 100644 index 0000000000..9b0e7c110c --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0010.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 2277 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0011.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0011.spr new file mode 100644 index 0000000000..668d4b0fd9 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0011.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 2778 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0012.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0012.spr new file mode 100644 index 0000000000..413f4c8c84 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0012.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 2611 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0013.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0013.spr new file mode 100644 index 0000000000..9b8b37b84d --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0013.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 2444 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0014.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0014.spr new file mode 100644 index 0000000000..cb42d82b98 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0014.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 2945 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0015.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0015.spr new file mode 100644 index 0000000000..80b92c7325 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0015.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 3112 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0016.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0016.spr new file mode 100644 index 0000000000..0f8789caa1 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0016.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 3279 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0017.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0017.spr new file mode 100644 index 0000000000..60a320289b --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0017.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 3613 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0018.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0018.spr new file mode 100644 index 0000000000..e9559ace60 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0018.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 3446 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0019.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0019.spr new file mode 100644 index 0000000000..6ee1a07a2e --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0019.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 2 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0020.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0020.spr new file mode 100644 index 0000000000..49c2a80434 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0020.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 3780 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0021.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0021.spr new file mode 100644 index 0000000000..2d93f7e424 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0021.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 169 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0022.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0022.spr new file mode 100644 index 0000000000..8695aef242 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0022.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 336 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0023.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0023.spr new file mode 100644 index 0000000000..69f8b23cd1 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0023.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 503 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0024.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0024.spr new file mode 100644 index 0000000000..70b3c51f66 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0024.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 837 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0025.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0025.spr new file mode 100644 index 0000000000..0488ae1b58 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0025.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1338 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0026.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0026.spr new file mode 100644 index 0000000000..2f211c8373 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0026.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1004 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0027.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0027.spr new file mode 100644 index 0000000000..ee2f29858e --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0027.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 670 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0028.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0028.spr new file mode 100644 index 0000000000..3e12a5c49d --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0028.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1171 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0029.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0029.spr new file mode 100644 index 0000000000..f154eb117a --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0029.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 1505 +region_y = 169 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0030.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0030.spr new file mode 100644 index 0000000000..9a4bdcebdd --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0030.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 273 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0031.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0031.spr new file mode 100644 index 0000000000..ba53c3a8a1 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0031.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 106 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0032.spr b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0032.spr new file mode 100644 index 0000000000..b9713cbf4a --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/part/bullet_clear.frame0032.spr @@ -0,0 +1,7 @@ +# Autogenerated by the atlas packer, do not modify + +texture = atlas_coroutines_temp_0 +region_x = 440 +region_y = 2 +region_w = 163 +region_h = 163 diff --git a/resources/00-taisei.pkgdir/gfx/part/stardust.spr b/resources/00-taisei.pkgdir/gfx/part/stardust.spr index fcb72863f2..3101a8906a 100644 --- a/resources/00-taisei.pkgdir/gfx/part/stardust.spr +++ b/resources/00-taisei.pkgdir/gfx/part/stardust.spr @@ -1,7 +1,7 @@ # Autogenerated by the atlas packer, do not modify -texture = atlas_common_0 -region_x = 2362 -region_y = 1724 +texture = atlas_coroutines_temp_0 +region_x = 2 +region_y = 2 region_w = 100 region_h = 100 diff --git a/resources/00-taisei.pkgdir/gfx/proj/hghost.spr b/resources/00-taisei.pkgdir/gfx/proj/hghost.spr index 25d7ce24e8..9491925d0d 100644 --- a/resources/00-taisei.pkgdir/gfx/proj/hghost.spr +++ b/resources/00-taisei.pkgdir/gfx/proj/hghost.spr @@ -1,12 +1,12 @@ # Autogenerated by the atlas packer, do not modify -texture = atlas_common_0 -region_x = 1632 -region_y = 372 -region_w = 22 -region_h = 23 +texture = atlas_coroutines_temp_0 +region_x = 1672 +region_y = 169 +region_w = 42 +region_h = 68 # -- Pasted from the override file -- -w = 22 -h = 23 +w = 21 +h = 34 diff --git a/resources/00-taisei.pkgdir/gfx/stage1/fog.webp b/resources/00-taisei.pkgdir/gfx/stage1/fog.webp index 2d14edf83d..cd12c0d6a5 100644 Binary files a/resources/00-taisei.pkgdir/gfx/stage1/fog.webp and b/resources/00-taisei.pkgdir/gfx/stage1/fog.webp differ diff --git a/resources/00-taisei.pkgdir/gfx/stage1/horizon.tex b/resources/00-taisei.pkgdir/gfx/stage1/horizon.tex new file mode 100644 index 0000000000..0de4adbfd7 --- /dev/null +++ b/resources/00-taisei.pkgdir/gfx/stage1/horizon.tex @@ -0,0 +1,3 @@ + +wrap_s = clamp +wrap_t = clamp diff --git a/resources/00-taisei.pkgdir/gfx/stage1/horizon.webp b/resources/00-taisei.pkgdir/gfx/stage1/horizon.webp new file mode 100644 index 0000000000..205f054a12 Binary files /dev/null and b/resources/00-taisei.pkgdir/gfx/stage1/horizon.webp differ diff --git a/resources/00-taisei.pkgdir/shader/glow_apply.frag.glsl b/resources/00-taisei.pkgdir/shader/glow_apply.frag.glsl new file mode 100644 index 0000000000..99ae5ca6d2 --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/glow_apply.frag.glsl @@ -0,0 +1,14 @@ +#version 330 core + +#include "lib/render_context.glslh" +#include "interface/standard.glslh" + +void main(void) { + vec3 c = texture(tex, texCoord).rgb; + c = max(c, 0); + c = c / (1.0 + c); + // c *= smoothstep(0.0, 1.0, sqrt(c)); + + // c = c * c; + fragColor = vec4(c, 0); +} diff --git a/resources/00-taisei.pkgdir/shader/glow_apply.prog b/resources/00-taisei.pkgdir/shader/glow_apply.prog new file mode 100644 index 0000000000..3389e402f3 --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/glow_apply.prog @@ -0,0 +1,2 @@ + +objects = standard.vert glow_apply.frag diff --git a/resources/00-taisei.pkgdir/shader/glow_feedback.frag.glsl b/resources/00-taisei.pkgdir/shader/glow_feedback.frag.glsl new file mode 100644 index 0000000000..05b99d8986 --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/glow_feedback.frag.glsl @@ -0,0 +1,37 @@ +#version 330 core + +#define BLUR9_SAMPLER_FUNC(s, uv) max(vec4(0), texture(s, uv)) + +#include "interface/standard.glslh" +#include "lib/blur/blur9.glslh" +#include "lib/util.glslh" + +UNIFORM(1) vec2 blur_resolution; +UNIFORM(2) vec2 blur_direction; +UNIFORM(3) float fade; + +#define ONE_OVER_LN2 1.4426950408889634 + +float dim(float x) { + return max(0, smoothmin(x, 3, 1)); +} + +vec3 dim(vec3 v) { + return vec3(dim(v.x), dim(v.y), dim(v.z)); +} + +void main(void) { + vec3 c = sample_blur9(tex, texCoord, blur_direction / blur_resolution).rgb; + // c = mix(c, smoothstep(0.0, 1.0, c), sqrt(fade)); + // c = alphaCompose(c, vec4(0, 0, 0, 0.1*fade)); + + #if 0 + if(fade != 1.0) { + c = dim(c) * fade; + } + #else + c *= fade; + #endif + + fragColor = vec4(c, 0); +} diff --git a/resources/00-taisei.pkgdir/shader/glow_feedback.prog b/resources/00-taisei.pkgdir/shader/glow_feedback.prog new file mode 100644 index 0000000000..188251cf53 --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/glow_feedback.prog @@ -0,0 +1,2 @@ + +objects = standardnotex.vert glow_feedback.frag diff --git a/resources/00-taisei.pkgdir/shader/healthbar_radial.frag.glsl b/resources/00-taisei.pkgdir/shader/healthbar_radial.frag.glsl index 4467ce5bb1..114788b652 100644 --- a/resources/00-taisei.pkgdir/shader/healthbar_radial.frag.glsl +++ b/resources/00-taisei.pkgdir/shader/healthbar_radial.frag.glsl @@ -76,4 +76,6 @@ void main(void) { fragColor = alphaCompose(fragColor, borderColor * inner); fragColor = alphaCompose(fragColor, borderColor * outer); fragColor *= opacity; + + fragGlow = vec4(vec3(0), opacity * (glow + 2 * (fillFactor + inner + outer))); } diff --git a/resources/00-taisei.pkgdir/shader/interface/healthbar.glslh b/resources/00-taisei.pkgdir/shader/interface/healthbar.glslh index 3f31a8e182..1e6585d6ce 100644 --- a/resources/00-taisei.pkgdir/shader/interface/healthbar.glslh +++ b/resources/00-taisei.pkgdir/shader/interface/healthbar.glslh @@ -11,6 +11,7 @@ ATTRIBUTE(2) vec2 texCoordRawIn; #ifdef FRAG_STAGE OUT(0) vec4 fragColor; +OUT(1) vec4 fragGlow; #endif VARYING(0) vec2 texCoord; diff --git a/resources/00-taisei.pkgdir/shader/interface/sprite.glslh b/resources/00-taisei.pkgdir/shader/interface/sprite.glslh index 634b18eb55..a89fcf9abd 100644 --- a/resources/00-taisei.pkgdir/shader/interface/sprite.glslh +++ b/resources/00-taisei.pkgdir/shader/interface/sprite.glslh @@ -31,6 +31,7 @@ ATTRIBUTE(14) vec4 spriteCustomParams; #ifdef FRAG_STAGE OUT(0) vec4 fragColor; +OUT(1) vec4 fragGlow; #endif UNIFORM(0) sampler2D tex; diff --git a/resources/00-taisei.pkgdir/shader/lib/blur/blur13.glslh b/resources/00-taisei.pkgdir/shader/lib/blur/blur13.glslh index 53ff1ca26b..ceea352ed6 100644 --- a/resources/00-taisei.pkgdir/shader/lib/blur/blur13.glslh +++ b/resources/00-taisei.pkgdir/shader/lib/blur/blur13.glslh @@ -4,21 +4,25 @@ #ifndef BLUR_blur13_H #define BLUR_blur13_H +#ifndef BLUR13_SAMPLER_FUNC +#define BLUR13_SAMPLER_FUNC(sampler, uv) texture(sampler, uv) +#endif + vec4 sample_blur13(sampler2D tex, vec2 uv, vec2 dir) { return - texture(tex, uv - dir * 6.0) * 0.008178892620166084 + - texture(tex, uv - dir * 5.0) * 0.02044711192270963 + - texture(tex, uv - dir * 4.0) * 0.04327301698301055 + - texture(tex, uv - dir * 3.0) * 0.07752648775146245 + - texture(tex, uv - dir * 2.0) * 0.11757927456823031 + - texture(tex, uv - dir) * 0.15095905785041683 + - texture(tex, uv) * 0.16407231660800825 + - texture(tex, uv + dir) * 0.15095905785041683 + - texture(tex, uv + dir * 2.0) * 0.11757927456823031 + - texture(tex, uv + dir * 3.0) * 0.07752648775146245 + - texture(tex, uv + dir * 4.0) * 0.04327301698301055 + - texture(tex, uv + dir * 5.0) * 0.02044711192270963 + - texture(tex, uv + dir * 6.0) * 0.008178892620166084; + BLUR13_SAMPLER_FUNC(tex, uv - dir * 6.0) * 0.008178892620166084 + + BLUR13_SAMPLER_FUNC(tex, uv - dir * 5.0) * 0.02044711192270963 + + BLUR13_SAMPLER_FUNC(tex, uv - dir * 4.0) * 0.04327301698301055 + + BLUR13_SAMPLER_FUNC(tex, uv - dir * 3.0) * 0.07752648775146245 + + BLUR13_SAMPLER_FUNC(tex, uv - dir * 2.0) * 0.11757927456823031 + + BLUR13_SAMPLER_FUNC(tex, uv - dir) * 0.15095905785041683 + + BLUR13_SAMPLER_FUNC(tex, uv) * 0.16407231660800825 + + BLUR13_SAMPLER_FUNC(tex, uv + dir) * 0.15095905785041683 + + BLUR13_SAMPLER_FUNC(tex, uv + dir * 2.0) * 0.11757927456823031 + + BLUR13_SAMPLER_FUNC(tex, uv + dir * 3.0) * 0.07752648775146245 + + BLUR13_SAMPLER_FUNC(tex, uv + dir * 4.0) * 0.04327301698301055 + + BLUR13_SAMPLER_FUNC(tex, uv + dir * 5.0) * 0.02044711192270963 + + BLUR13_SAMPLER_FUNC(tex, uv + dir * 6.0) * 0.008178892620166084; } #endif diff --git a/resources/00-taisei.pkgdir/shader/lib/blur/blur25.glslh b/resources/00-taisei.pkgdir/shader/lib/blur/blur25.glslh index 2c3c01f941..95965f383f 100644 --- a/resources/00-taisei.pkgdir/shader/lib/blur/blur25.glslh +++ b/resources/00-taisei.pkgdir/shader/lib/blur/blur25.glslh @@ -4,33 +4,37 @@ #ifndef BLUR_blur25_H #define BLUR_blur25_H +#ifndef BLUR25_SAMPLER_FUNC +#define BLUR25_SAMPLER_FUNC(sampler, uv) texture(sampler, uv) +#endif + vec4 sample_blur25(sampler2D tex, vec2 uv, vec2 dir) { return - texture(tex, uv - dir * 12.0) * 0.0017488220286339797 + - texture(tex, uv - dir * 11.0) * 0.003305608712046086 + - texture(tex, uv - dir * 10.0) * 0.005911712274266129 + - texture(tex, uv - dir * 9.0) * 0.01000302024073443 + - texture(tex, uv - dir * 8.0) * 0.016014191642463458 + - texture(tex, uv - dir * 7.0) * 0.024256878344979343 + - texture(tex, uv - dir * 6.0) * 0.03476328537688979 + - texture(tex, uv - dir * 5.0) * 0.04713708609985263 + - texture(tex, uv - dir * 4.0) * 0.06047288180641397 + - texture(tex, uv - dir * 3.0) * 0.07340313576063635 + - texture(tex, uv - dir * 2.0) * 0.08429941797229662 + - texture(tex, uv - dir) * 0.09159896233905987 + - texture(tex, uv) * 0.09416999480345471 + - texture(tex, uv + dir) * 0.09159896233905987 + - texture(tex, uv + dir * 2.0) * 0.08429941797229662 + - texture(tex, uv + dir * 3.0) * 0.07340313576063635 + - texture(tex, uv + dir * 4.0) * 0.06047288180641397 + - texture(tex, uv + dir * 5.0) * 0.04713708609985263 + - texture(tex, uv + dir * 6.0) * 0.03476328537688979 + - texture(tex, uv + dir * 7.0) * 0.024256878344979343 + - texture(tex, uv + dir * 8.0) * 0.016014191642463458 + - texture(tex, uv + dir * 9.0) * 0.01000302024073443 + - texture(tex, uv + dir * 10.0) * 0.005911712274266129 + - texture(tex, uv + dir * 11.0) * 0.003305608712046086 + - texture(tex, uv + dir * 12.0) * 0.0017488220286339797; + BLUR25_SAMPLER_FUNC(tex, uv - dir * 12.0) * 0.0017488220286339797 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 11.0) * 0.003305608712046086 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 10.0) * 0.005911712274266129 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 9.0) * 0.01000302024073443 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 8.0) * 0.016014191642463458 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 7.0) * 0.024256878344979343 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 6.0) * 0.03476328537688979 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 5.0) * 0.04713708609985263 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 4.0) * 0.06047288180641397 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 3.0) * 0.07340313576063635 + + BLUR25_SAMPLER_FUNC(tex, uv - dir * 2.0) * 0.08429941797229662 + + BLUR25_SAMPLER_FUNC(tex, uv - dir) * 0.09159896233905987 + + BLUR25_SAMPLER_FUNC(tex, uv) * 0.09416999480345471 + + BLUR25_SAMPLER_FUNC(tex, uv + dir) * 0.09159896233905987 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 2.0) * 0.08429941797229662 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 3.0) * 0.07340313576063635 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 4.0) * 0.06047288180641397 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 5.0) * 0.04713708609985263 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 6.0) * 0.03476328537688979 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 7.0) * 0.024256878344979343 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 8.0) * 0.016014191642463458 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 9.0) * 0.01000302024073443 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 10.0) * 0.005911712274266129 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 11.0) * 0.003305608712046086 + + BLUR25_SAMPLER_FUNC(tex, uv + dir * 12.0) * 0.0017488220286339797; } #endif diff --git a/resources/00-taisei.pkgdir/shader/lib/blur/blur5.glslh b/resources/00-taisei.pkgdir/shader/lib/blur/blur5.glslh index 31279b045a..8ff34ba416 100644 --- a/resources/00-taisei.pkgdir/shader/lib/blur/blur5.glslh +++ b/resources/00-taisei.pkgdir/shader/lib/blur/blur5.glslh @@ -4,13 +4,17 @@ #ifndef BLUR_blur5_H #define BLUR_blur5_H +#ifndef BLUR5_SAMPLER_FUNC +#define BLUR5_SAMPLER_FUNC(sampler, uv) texture(sampler, uv) +#endif + vec4 sample_blur5(sampler2D tex, vec2 uv, vec2 dir) { return - texture(tex, uv - dir * 2.0) * 0.09242116269661459 + - texture(tex, uv - dir) * 0.24137602468441252 + - texture(tex, uv) * 0.33240562523794576 + - texture(tex, uv + dir) * 0.24137602468441252 + - texture(tex, uv + dir * 2.0) * 0.09242116269661459; + BLUR5_SAMPLER_FUNC(tex, uv - dir * 2.0) * 0.09242116269661459 + + BLUR5_SAMPLER_FUNC(tex, uv - dir) * 0.24137602468441252 + + BLUR5_SAMPLER_FUNC(tex, uv) * 0.33240562523794576 + + BLUR5_SAMPLER_FUNC(tex, uv + dir) * 0.24137602468441252 + + BLUR5_SAMPLER_FUNC(tex, uv + dir * 2.0) * 0.09242116269661459; } #endif diff --git a/resources/00-taisei.pkgdir/shader/lib/blur/blur9.glslh b/resources/00-taisei.pkgdir/shader/lib/blur/blur9.glslh index 4dd5ba4c75..0cfde8c54e 100644 --- a/resources/00-taisei.pkgdir/shader/lib/blur/blur9.glslh +++ b/resources/00-taisei.pkgdir/shader/lib/blur/blur9.glslh @@ -4,17 +4,21 @@ #ifndef BLUR_blur9_H #define BLUR_blur9_H +#ifndef BLUR9_SAMPLER_FUNC +#define BLUR9_SAMPLER_FUNC(sampler, uv) texture(sampler, uv) +#endif + vec4 sample_blur9(sampler2D tex, vec2 uv, vec2 dir) { return - texture(tex, uv - dir * 4.0) * 0.02111655355354975 + - texture(tex, uv - dir * 3.0) * 0.05871536970456706 + - texture(tex, uv - dir * 2.0) * 0.12189520708673413 + - texture(tex, uv - dir) * 0.18894157329529304 + - texture(tex, uv) * 0.21866259271971203 + - texture(tex, uv + dir) * 0.18894157329529304 + - texture(tex, uv + dir * 2.0) * 0.12189520708673413 + - texture(tex, uv + dir * 3.0) * 0.05871536970456706 + - texture(tex, uv + dir * 4.0) * 0.02111655355354975; + BLUR9_SAMPLER_FUNC(tex, uv - dir * 4.0) * 0.02111655355354975 + + BLUR9_SAMPLER_FUNC(tex, uv - dir * 3.0) * 0.05871536970456706 + + BLUR9_SAMPLER_FUNC(tex, uv - dir * 2.0) * 0.12189520708673413 + + BLUR9_SAMPLER_FUNC(tex, uv - dir) * 0.18894157329529304 + + BLUR9_SAMPLER_FUNC(tex, uv) * 0.21866259271971203 + + BLUR9_SAMPLER_FUNC(tex, uv + dir) * 0.18894157329529304 + + BLUR9_SAMPLER_FUNC(tex, uv + dir * 2.0) * 0.12189520708673413 + + BLUR9_SAMPLER_FUNC(tex, uv + dir * 3.0) * 0.05871536970456706 + + BLUR9_SAMPLER_FUNC(tex, uv + dir * 4.0) * 0.02111655355354975; } #endif diff --git a/resources/00-taisei.pkgdir/shader/lib/sprite_main.frag.glslh b/resources/00-taisei.pkgdir/shader/lib/sprite_main.frag.glslh index 19c7c21674..3e04847683 100644 --- a/resources/00-taisei.pkgdir/shader/lib/sprite_main.frag.glslh +++ b/resources/00-taisei.pkgdir/shader/lib/sprite_main.frag.glslh @@ -1,5 +1,6 @@ #include "../interface/sprite.glslh" +#include "util.glslh" #ifndef SPRITE_USE_DISCARD #define SPRITE_USE_DISCARD 1 @@ -14,15 +15,29 @@ #define SPRITE_DISCARD_DEBUG 0 #endif +#ifdef SPRITE_GLOW_CONTROL +void spriteMain(out vec4 fragColor, out vec4 fragGlow); +#else void spriteMain(out vec4 fragColor); +#endif void main(void) { vec4 color = vec4(0); + vec4 glow = vec4(0); + +#ifdef SPRITE_GLOW_CONTROL + spriteMain(color, glow); +#else spriteMain(color); + glow = glowmap(color); +#endif #if SPRITE_USE_DISCARD // if(color == vec4(0)) { - if(all(lessThan(color, vec4(SPRITE_DISCARD_THRESHOLD)))) { + if( + all(lessThan(color, vec4(SPRITE_DISCARD_THRESHOLD))) && + all(lessThan(glow, vec4(SPRITE_DISCARD_THRESHOLD))) + ) { #if SPRITE_DISCARD_DEBUG if(color == vec4(0)) { color = vec4(0, 1, 0, 1) * 0.05; @@ -36,4 +51,5 @@ void main(void) { #endif fragColor = color; + fragGlow = glow; } diff --git a/resources/00-taisei.pkgdir/shader/lib/util.glslh b/resources/00-taisei.pkgdir/shader/lib/util.glslh index 9aad2c2bdb..e49a67b7c4 100644 --- a/resources/00-taisei.pkgdir/shader/lib/util.glslh +++ b/resources/00-taisei.pkgdir/shader/lib/util.glslh @@ -58,6 +58,28 @@ vec4 alphaCompose(vec4 bg, vec4 fg) { return fg + bg * (1.0 - fg.a); } +vec4 glowmap(vec4 color, float additiveFactor, float opaqueFactor, float transFactor, float opaqueOcclusion) { + vec4 glowAdditive = vec4(additiveFactor * (max(vec3(0), color.rgb) - color.a), 0); + vec4 glowOpaque = vec4(color.rgb * opaqueFactor * color.a, color.a * opaqueOcclusion); + vec4 glowTrans = vec4(color.rgb * transFactor * (1 - color.a), 0); + return alphaCompose(glowAdditive + glowTrans, glowOpaque); +} + +vec4 glowmap(vec4 color) { + float additiveFactor = 1.0; + float opaqueFactor = 0.0; + float transFactor = 0.0; + float opaqueOcclusion = 1.0; + return glowmap(color, additiveFactor, opaqueFactor, transFactor, opaqueOcclusion); +} + +vec4 glowmap(vec4 color, float additiveFactor) { + float opaqueFactor = 0.0; + float transFactor = 0.0; + float opaqueOcclusion = 1.0; + return glowmap(color, additiveFactor, opaqueFactor, transFactor, opaqueOcclusion); +} + // Taken from http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl vec3 rgb2hsv(vec3 c) { @@ -83,6 +105,38 @@ vec3 hueShift(vec3 c, float s) { return hsv2rgb(hsv); } +float smoothmin(float a, float b, float k) { + float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); + return mix(b, a, h) - k * h * (1.0 - h); +} + +vec2 smoothmin(vec2 a, vec2 b, vec2 k) { + vec2 h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); + return mix(b, a, h) - k * h * (1.0 - h); +} + +vec2 smoothmin(vec2 a, vec2 b, float k) { + return smoothmin(a, b, vec2(k)); +} + +vec3 smoothmin(vec3 a, vec3 b, vec3 k) { + vec3 h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); + return mix(b, a, h) - k * h * (1.0 - h); +} + +vec3 smoothmin(vec3 a, vec3 b, float k) { + return smoothmin(a, b, vec3(k)); +} + +vec4 smoothmin(vec4 a, vec4 b, vec4 k) { + vec4 h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); + return mix(b, a, h) - k * h * (1.0 - h); +} + +vec4 smoothmin(vec4 a, vec4 b, float k) { + return smoothmin(a, b, vec4(k)); +} + float flip_topleft_to_native(float x) { #ifdef NATIVE_ORIGIN_BOTTOMLEFT return 1 - x; diff --git a/resources/00-taisei.pkgdir/shader/meson.build b/resources/00-taisei.pkgdir/shader/meson.build index 5fb9e3b194..be5c5c0448 100644 --- a/resources/00-taisei.pkgdir/shader/meson.build +++ b/resources/00-taisei.pkgdir/shader/meson.build @@ -22,6 +22,8 @@ glsl_files = files( 'fxaa.frag.glsl', 'fxaa.vert.glsl', 'glitch.frag.glsl', + 'glow_apply.frag.glsl', + 'glow_feedback.frag.glsl', 'graph.frag.glsl', 'healthbar.vert.glsl', 'healthbar_linear.frag.glsl', @@ -45,7 +47,6 @@ glsl_files = files( 'spellcard_walloftext.frag.glsl', 'sprite_bullet.frag.glsl', 'sprite_bullet.vert.glsl', - 'sprite_bullet_dead.frag.glsl', 'sprite_circleclipped_indicator.frag.glsl', 'sprite_circleclipped_indicator.vert.glsl', 'sprite_default.frag.glsl', @@ -55,11 +56,10 @@ glsl_files = files( 'sprite_hakkero.frag.glsl', 'sprite_hakkero.vert.glsl', 'sprite_negative.frag.glsl', + 'sprite_particle.frag.glsl', 'sprite_silhouette.frag.glsl', 'sprite_silhouette.vert.glsl', 'sprite_yinyang.frag.glsl', - 'sprite_youmu_charged_shot.frag.glsl', - 'sprite_youmu_charged_shot.vert.glsl', 'sprite_youmu_myon_shot.frag.glsl', 'stage1_water.frag.glsl', 'stage6_sky.frag.glsl', diff --git a/resources/00-taisei.pkgdir/shader/sprite_bullet.frag.glsl b/resources/00-taisei.pkgdir/shader/sprite_bullet.frag.glsl index ad8b4a5ae7..4a70cbcb47 100644 --- a/resources/00-taisei.pkgdir/shader/sprite_bullet.frag.glsl +++ b/resources/00-taisei.pkgdir/shader/sprite_bullet.frag.glsl @@ -1,13 +1,22 @@ #version 330 core +// #define SPRITE_GLOW_CONTROL #include "lib/sprite_main.frag.glslh" +#include "lib/util.glslh" -void spriteMain(out vec4 fragColor) { +void spriteMain(out vec4 fragColor/*, out vec4 glowColor*/) { vec4 texel = texture(tex, texCoord); if(texel.a == 0.0) { discard; } - fragColor = (color * texel.g + vec4(texel.b)) * (1 - customParams.r); + vec3 glowHSV = rgb2hsv(color.rgb); + // glowHSV.x -= 0.1; + glowHSV.y = 1 - (1 - glowHSV.y) * (1 - glowHSV.y); + glowHSV.z = 1.0; + vec3 glowRGB = hsv2rgb(glowHSV); + + fragColor = (vec4(glowRGB, 0) * texel.r + color * texel.g + vec4(texel.b)) * customParams.r; + // glowColor = vec4(color.rgb * texel.r * customParams.r * 0.4, 0); } diff --git a/resources/00-taisei.pkgdir/shader/sprite_bullet_dead.frag.glsl b/resources/00-taisei.pkgdir/shader/sprite_bullet_dead.frag.glsl deleted file mode 100644 index b77d871a3a..0000000000 --- a/resources/00-taisei.pkgdir/shader/sprite_bullet_dead.frag.glsl +++ /dev/null @@ -1,14 +0,0 @@ -#version 330 core - -#include "lib/sprite_main.frag.glslh" - -void spriteMain(out vec4 fragColor) { - vec4 texel = texture(tex, texCoord); - float oWhite = texel.b * (1 - clamp(2 * customParams.r, 0, 1)); - float oColor = texel.g * (1 - clamp(2 * customParams.r - 1, 0, 1)); - float o = clamp(oWhite + oColor, 0, 1); - vec4 col = (texel.g * color + vec4(texel.b)) * o; - col.a *= o; - - fragColor = col; -} diff --git a/resources/00-taisei.pkgdir/shader/sprite_bullet_dead.prog b/resources/00-taisei.pkgdir/shader/sprite_bullet_dead.prog deleted file mode 100644 index 2546ced82e..0000000000 --- a/resources/00-taisei.pkgdir/shader/sprite_bullet_dead.prog +++ /dev/null @@ -1 +0,0 @@ -objects = sprite_bullet.vert sprite_bullet_dead.frag diff --git a/resources/00-taisei.pkgdir/shader/sprite_particle.frag.glsl b/resources/00-taisei.pkgdir/shader/sprite_particle.frag.glsl new file mode 100644 index 0000000000..0728dc7286 --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/sprite_particle.frag.glsl @@ -0,0 +1,7 @@ +#version 330 core + +#include "lib/sprite_main.frag.glslh" + +void spriteMain(out vec4 fragColor) { + fragColor = color * texture(tex, texCoord) * customParams.r; +} diff --git a/resources/00-taisei.pkgdir/shader/sprite_particle.prog b/resources/00-taisei.pkgdir/shader/sprite_particle.prog new file mode 100644 index 0000000000..dc6b30e6c8 --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/sprite_particle.prog @@ -0,0 +1 @@ +objects = sprite_bullet.vert sprite_particle.frag diff --git a/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.frag.glsl b/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.frag.glsl deleted file mode 100644 index e165a66ce1..0000000000 --- a/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.frag.glsl +++ /dev/null @@ -1,24 +0,0 @@ -#version 330 core - -#include "lib/sprite_main.frag.glslh" - -/* -ported from: - -.R[1] = mix_colors(c_b, c_r, sqrt(charge)), -.G[1] = c_g, -.B[1] = mix_colors(multiply_colors(c_r, rgba(2, 0, 0, 0)), c_b, 0.75 * charge), -.A[1] = c_a, -*/ - -void spriteMain(out vec4 fragColor) { - vec4 texel = texture(tex, texCoord); - float charge = customParams.r; - - fragColor = vec4(0.0); - fragColor.rgb += texel.r * mix(vec3(color.r, 0.0, 0.0), vec3(0.0, 0.0, color.b), sqrt(charge)); - fragColor.g += texel.g * color.g; - fragColor.rgb += texel.b * mix(vec3(0.0, 0.0, color.b), vec3(2.0 * color.r, 0.0, 0.0), 0.75 * charge); - fragColor.a = texel.a * color.a; - fragColor *= customParams.g; -} diff --git a/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.prog b/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.prog deleted file mode 100644 index 2ff3751073..0000000000 --- a/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.prog +++ /dev/null @@ -1 +0,0 @@ -objects = sprite_youmu_charged_shot.vert sprite_youmu_charged_shot.frag diff --git a/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.vert.glsl b/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.vert.glsl deleted file mode 100644 index 5ae2438f77..0000000000 --- a/resources/00-taisei.pkgdir/shader/sprite_youmu_charged_shot.vert.glsl +++ /dev/null @@ -1,7 +0,0 @@ -#version 330 core - -#define SPRITE_OUT_COLOR -#define SPRITE_OUT_TEXCOORD -#define SPRITE_OUT_CUSTOM - -#include "lib/sprite_default.vert.glslh" diff --git a/resources/00-taisei.pkgdir/shader/sprite_youmu_myon_shot.frag.glsl b/resources/00-taisei.pkgdir/shader/sprite_youmu_myon_shot.frag.glsl index bba1f6d850..0d5b3a0ecd 100644 --- a/resources/00-taisei.pkgdir/shader/sprite_youmu_myon_shot.frag.glsl +++ b/resources/00-taisei.pkgdir/shader/sprite_youmu_myon_shot.frag.glsl @@ -9,5 +9,5 @@ void spriteMain(out vec4 fragColor) { fragColor.rgb += vec3(texel.r); fragColor.rgb += texel.b * vec3(color.r*color.r, color.g*color.g, color.b*color.b); fragColor.a = texel.a * color.a; - fragColor *= (1 - customParams.r); + fragColor *= customParams.r; } diff --git a/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl b/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl index e97d3dc298..cf25a7fff5 100644 --- a/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl +++ b/resources/00-taisei.pkgdir/shader/stage1_water.frag.glsl @@ -4,6 +4,11 @@ #include "interface/standard.glslh" UNIFORM(1) float time; +UNIFORM(2) vec4 water_color; +UNIFORM(3) float wave_offset; + +const vec4 reflection_color = vec4(0.5, 0.8, 0.8, 1.0); +const vec4 wave_color = vec4(0.8, 0.8, 1.0, 1.0); // Based on https://www.shadertoy.com/view/Xl2XWz @@ -36,7 +41,7 @@ float warpedNoise(vec2 p) { } void main(void) { - vec2 uv = flip_native_to_bottomleft(texCoord - vec2(0, 2+time * 0.2)) * rot(-pi/4); + vec2 uv = flip_native_to_bottomleft(texCoord - vec2(0, wave_offset)) * rot(-pi/4); float n = warpedNoise(uv * 4); @@ -47,11 +52,14 @@ void main(void) { bump = bump * bump * 0.5; bump2 = bump2 * bump2 * 0.5; - vec4 cmod = vec4(0.5, 0.8, 0.8, 1.0); - uv = flip_native_to_bottomleft(texCoord); uv += 0.25 * vec2(bump, bump2); uv = flip_bottomleft_to_native(uv); - fragColor = mix(cmod * texture(tex, uv), vec4(0.8, 0.8, 1.0, 1.0), (bump2 - bump * 0.25) * 0.05); + vec4 reflection = texture(tex, uv); + reflection.rgb = mix(reflection.rgb, water_color.rgb, reflection.a * 0.5); + reflection = reflection * reflection_color * 0.5; + vec4 surface = alphaCompose(water_color, reflection); + + fragColor = mix(surface, wave_color, (bump2 - bump * 0.25) * 0.05); } diff --git a/scripts/gen-blur-shader.py b/scripts/gen-blur-shader.py index dee91c03b1..dc65d499f3 100755 --- a/scripts/gen-blur-shader.py +++ b/scripts/gen-blur-shader.py @@ -13,25 +13,31 @@ import argparse +def sampler_func_name(args): + return f'{args.name.upper()}_SAMPLER_FUNC' + + def gen_comment(args): return f'Generated by gen-blur-shader.py. kernel size = {args.kernel_size}; sigma = {args.sigma}' def gen_shader_func(args): - s = "" + s = '' kernel = gen_kernel(args.kernel_size, args.sigma) kernel_half_size = args.kernel_size // 2 s += f'vec4 sample_{args.name}(sampler2D tex, vec2 uv, vec2 dir) {"{"}\n' s += f' return\n' + sampler_func = sampler_func_name(args) + for i in range(-kernel_half_size, kernel_half_size+1): prefix = ' ' * 8 term = ';' if i == kernel_half_size else ' +' uv_ofs_mul = f' * {float(abs(i))}' if abs(i) > 1 else '' - uv_ofs = ((' + dir' if i >= 0 else ' - dir') + uv_ofs_mul) if i != 0 else "" - s += f'{prefix}texture(tex, uv{uv_ofs}) * {kernel[kernel_half_size + i]}{term}\n' + uv_ofs = ((' + dir' if i >= 0 else ' - dir') + uv_ofs_mul) if i != 0 else '' + s += f'{prefix}{sampler_func}(tex, uv{uv_ofs}) * {kernel[kernel_half_size + i]}{term}\n' s += '}\n' @@ -40,6 +46,7 @@ def gen_shader_func(args): def gen_lib_shader(args): macro = f'BLUR_{args.name}_H' + sampler_func = sampler_func_name(args) return ( '\n' @@ -48,6 +55,10 @@ def gen_lib_shader(args): f'#ifndef {macro}\n' f'#define {macro}\n' '\n' + f'#ifndef {sampler_func}\n' + f'#define {sampler_func}(sampler, uv) texture(sampler, uv)\n' + f'#endif\n' + '\n' f'{gen_shader_func(args)}' '\n' '#endif\n' @@ -138,7 +149,7 @@ def kernsize(v): if args.name is None: args.name = f'blur{args.kernel_size}' - shaders = args.rootdir / 'resources' / 'shader' + shaders = args.rootdir / 'resources' / '00-taisei.pkgdir' / 'shader' blurs = shaders / 'lib' / 'blur' blurs.mkdir(parents=True, exist_ok=True) diff --git a/scripts/optimize-all-img.sh b/scripts/optimize-all-img.sh index 235a4d139a..8c5b9110d0 100755 --- a/scripts/optimize-all-img.sh +++ b/scripts/optimize-all-img.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash -cd "$(dirname "${BASH_SOURCE[0]}")"/.. || exit $? -find resources -type f -name "*.png" -or -name '*.webp' | parallel -j$(nproc) scripts/optimize-img.sh +opwd="$PWD" +cd "$(dirname "${BASH_SOURCE[0]}")" || exit $? +script_dir="$PWD" +cd "$opwd" || exit $? + +find "$@" -type f -name "*.png" -or -name '*.webp' | parallel -j$(nproc) "$script_dir"/optimize-img.sh diff --git a/scripts/taiseilib/common.py b/scripts/taiseilib/common.py index 3a2c4ae37e..8fd20a3d93 100644 --- a/scripts/taiseilib/common.py +++ b/scripts/taiseilib/common.py @@ -14,7 +14,8 @@ class TaiseiError(RuntimeError): class DefaultArgs(object): def __init__(self): self.fallback_version = None - self.rootdir = Path(__file__).parent.parent.parent + self.rootdir = Path(os.environ.get('MESON_SOURCE_ROOT', Path(__file__).parent.parent.parent)).resolve() + self.builddir = Path(os.environ.get('MESON_BUILD_ROOT', '.')).resolve() self.depfile = None @@ -33,6 +34,12 @@ def add_common_args(parser, *, depfile=False): help='Taisei source directory' ) + parser.add_argument('--builddir', + type=Path, + default=default_args.builddir, + help='Taisei build directory' + ) + if depfile: parser.add_argument('--depfile', type=Path, @@ -62,15 +69,15 @@ def run_main(func, args=None): def write_depfile(depfile, target, deps): with Path(depfile).open('w') as df: - l = [str(target) + ":"] + list(str(d) for d in deps) + [str(Path(__file__).resolve())] - df.write(" \\\n ".join(l)) + l = [str(target) + ':'] + list(str(d) for d in deps) + [str(Path(__file__).resolve())] + df.write(' \\\n '.join(l)) def update_text_file(outpath, data): import io try: - with open(str(outpath), "r+t") as outfile: + with open(str(outpath), 'r+t') as outfile: contents = outfile.read() if contents == data: @@ -80,7 +87,7 @@ def update_text_file(outpath, data): outfile.write(data) outfile.truncate() except (FileNotFoundError, io.UnsupportedOperation): - with open(str(outpath), "w") as outfile: + with open(str(outpath), 'w') as outfile: outfile.write(data) diff --git a/scripts/upkeep.py b/scripts/upkeep.py index b9c869f6bd..51109b3c1b 100755 --- a/scripts/upkeep.py +++ b/scripts/upkeep.py @@ -26,12 +26,21 @@ def main(args): scripts = pargs.rootdir / 'scripts' / 'upkeep' tasks = ( - 'fixup-source-files', + ['fixup-source-files', 'check-rng-usage'], 'update-glsl-sources', ) with ThreadPoolExecutor() as ex: - tuple(ex.map(lambda task: subprocess.check_call([scripts / f'{task}.py'] + args[1:]), tasks)) + def do_task(task): + if isinstance(task, str): + print('[upkeep] begin task', task) + subprocess.check_call([scripts / f'{task}.py'] + args[1:]) + print('[upkeep] task', task, 'done') + else: + for t in task: + do_task(t) + + tuple(ex.map(do_task, tasks)) if __name__ == '__main__': diff --git a/scripts/upkeep/check-rng-usage.py b/scripts/upkeep/check-rng-usage.py new file mode 100755 index 0000000000..5e38f64b4f --- /dev/null +++ b/scripts/upkeep/check-rng-usage.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +import re +import ast +import json +import shlex +import subprocess +import sys +import code + +from pathlib import Path + +from taiseilib.common import ( + run_main, + add_common_args, +) + +from concurrent.futures import ( + ThreadPoolExecutor, +) + + +re_suspicious_rng = re.compile(r'rng_next\(\)[^;]+?rng_next\(\)', re.DOTALL) +re_fileinfo = re.compile(r'\n# +(\d+) +(".*?").*?\n') + + +def get_source_info(m): + src = m.string[:m.end()] + fileinfo_index = src.rindex('\n# ') + src_since_anchor = src[fileinfo_index:] + + lineno, fname = re_fileinfo.search(src_since_anchor).groups() + lineno = int(lineno) + lineno += src_since_anchor.count('\n') + fname = ast.literal_eval(fname) + + return (fname, lineno) + + +def expand_line(m): + src = m.string + start, end = m.span() + + while src[start] != '\n': + start -= 1 + + while src[end] != '\n': + end += 1 + + return src[start:m.start()] + '\x1b[1;31m' + src[m.start():m.end()] + '\x1b[0m' + src[m.end():end] + + +def find_suspicious_callsites(src): + for m in re_suspicious_rng.finditer(src): + fname, lineno = get_source_info(m) + segment = expand_line(m) + print(f'{fname}:{lineno}: Suspected RNG API misuse: {segment}\n', file=sys.stderr) + + +def remove_flag(cmd, flag, nvalues=0): + while True: + try: + i = cmd.index(flag) + for n in range(1 + nvalues): + del cmd[i] + except ValueError: + break + + +def preprocess(cmd_json): + cmd = cmd_json['command'] + cmd = shlex.split(cmd) + + remove_flag(cmd, '-o') + remove_flag(cmd, '-c') + remove_flag(cmd, '-include', 1) + remove_flag(cmd, '-fpch-preprocess') + remove_flag(cmd, '-MD') + remove_flag(cmd, '-MQ', 1) + remove_flag(cmd, '-MF', 1) + remove_flag(cmd, cmd_json['output']) + + cmd += ['-DRNG_API_CHECK', '-E', '-o', '-'] + + # print(' '.join(cmd)) + p = subprocess.run(cmd, stdout=subprocess.PIPE, cwd=cmd_json['directory']) + + return p.stdout.decode('utf8') + + +def main(args): + import argparse + parser = argparse.ArgumentParser(description='Update build defintions to include all GLSL files.', prog=args[0]) + add_common_args(parser) + + args = parser.parse_args(args[1:]) + + with (args.builddir / 'compile_commands.json').open() as f: + compile_commands = json.load(f) + + with ThreadPoolExecutor() as ex: + tuple(ex.map(lambda c: find_suspicious_callsites(preprocess(c)), compile_commands)) + + +if __name__ == '__main__': + run_main(main) diff --git a/src/aniplayer.h b/src/aniplayer.h index 19a57c3cb7..84cc9022a3 100644 --- a/src/aniplayer.h +++ b/src/aniplayer.h @@ -66,7 +66,7 @@ // aniplayer_hard_switch(&plr->ani, "right", 0); // // Similar examples occur throughout the code so if you want context, you can just look there. -// +// #include "resource/animation.h" #include "stageobjects.h" #include "list.h" @@ -76,7 +76,7 @@ struct AniQueueEntry{ LIST_INTERFACE(AniQueueEntry); AniSequence *sequence; - int clock; // frame counter. As long as clock < duration this entry will keep running + int clock; // frame counter. As long as clock < duration this entry will keep running int duration; // number of frames this sequence will be drawn }; @@ -92,7 +92,6 @@ void aniplayer_create(AniPlayer *plr, Animation *ani, const char *startsequence) void aniplayer_free(AniPlayer *plr); // AniPlayer version of animation_get_frame. -// CAUTION: the returned Sprite is only valid until the next call to animation/aniplayer_get_frame Sprite *aniplayer_get_frame(AniPlayer *plr) attr_nonnull(1); // See comment above for these three stooges. diff --git a/src/audio/audio.c b/src/audio/audio.c index b874696499..08f51a63bd 100644 --- a/src/audio/audio.c +++ b/src/audio/audio.c @@ -29,7 +29,7 @@ static struct enqueued_sound { } *sound_queue; static void play_sound_internal(const char *name, bool is_ui, int cooldown, bool replace, int delay) { - if(!audio_output_works()) { + if(!audio_output_works() || global.frameskip) { return; } @@ -43,7 +43,7 @@ static void play_sound_internal(const char *name, bool is_ui, int cooldown, bool return; } - if(global.frameskip) { + if(taisei_is_skip_mode_enabled()) { return; } @@ -67,7 +67,10 @@ static void* discard_enqueued_sound(List **queue, List *vsnd, void *arg) { } static void* play_enqueued_sound(struct enqueued_sound **queue, struct enqueued_sound *snd, void *arg) { - play_sound_internal(snd->name, false, snd->cooldown, snd->replace, 0); + if(!taisei_is_skip_mode_enabled()) { + play_sound_internal(snd->name, false, snd->cooldown, snd->replace, 0); + } + free(snd->name); free(list_unlink(queue, snd)); return NULL; diff --git a/src/audio/sdl2mixer/audio_sdl2mixer.c b/src/audio/sdl2mixer/audio_sdl2mixer.c index efa2579da3..d918019b24 100644 --- a/src/audio/sdl2mixer/audio_sdl2mixer.c +++ b/src/audio/sdl2mixer/audio_sdl2mixer.c @@ -271,6 +271,10 @@ static bool audio_sdl2mixer_music_play(MusicImpl *imus) { static bool audio_sdl2mixer_music_set_position(double pos) { Mix_RewindMusic(); + // TODO Handle looping here. + // Unfortunately SDL2_Mixer will not tell us the total length of the file, + // so a hack is required here. + if(Mix_SetMusicPosition(pos)) { log_error("Mix_SetMusicPosition() failed: %s", Mix_GetError()); return false; diff --git a/src/boss.c b/src/boss.c index a7201772f3..fc7fff4ce2 100644 --- a/src/boss.c +++ b/src/boss.c @@ -55,6 +55,8 @@ Boss* create_boss(char *name, char *ani, cmplx pos) { boss->bomb_damage_multiplier = 1.0; boss->shot_damage_multiplier = 1.0; + COEVENT_INIT_ARRAY(boss->events); + return boss; } @@ -576,7 +578,7 @@ static void draw_spell_portrait(Boss *b, int time) { r_state_pop(); } -static void BossGlow(Projectile *p, int t) { +static void boss_glow_draw(Projectile *p, int t, ProjDrawRuleArgs args) { float s = 1.0+t/(double)p->timeout*0.5; float fade = 1 - (1.5 - s); float deform = 5 - 10 * fade * fade; @@ -595,27 +597,17 @@ static void BossGlow(Projectile *p, int t) { }); } -static int boss_glow(Projectile *p, int t) { - if(t == EVENT_DEATH) { - free(p->sprite); - } - - return linear(p, t); -} - -static Projectile* spawn_boss_glow(Boss *boss, const Color *clr, int timeout) { - // XXX: memdup is required because the Sprite returned by animation_get_frame is only temporarily valid +static Projectile *spawn_boss_glow(Boss *boss, const Color *clr, int timeout) { return PARTICLE( - .sprite_ptr = memdup(aniplayer_get_frame(&boss->ani), sizeof(Sprite)), + .sprite_ptr = aniplayer_get_frame(&boss->ani), // this is in sync with the boss position oscillation .pos = boss->pos + 6 * sin(global.frames/25.0) * I, .color = clr, - .rule = boss_glow, - .draw_rule = BossGlow, + .draw_rule = boss_glow_draw, .timeout = timeout, .layer = LAYER_PARTICLE_LOW, .shader = "sprite_silhouette", - .flags = PFLAG_REQUIREDPARTICLE, + .flags = PFLAG_REQUIREDPARTICLE | PFLAG_NOMOVE | PFLAG_MANUALANGLE, ); } @@ -630,13 +622,13 @@ static void spawn_particle_effects(Boss *boss) { if(!(global.frames % 13) && !is_extra) { PARTICLE( .sprite = "smoke", - .pos = cexp(I*global.frames), + .pos = cdir(global.frames), .color = RGBA(shadowcolor->r, shadowcolor->g, shadowcolor->b, 0.0), .rule = enemy_flare, .timeout = 180, - .draw_rule = Shrink, + .draw_rule = pdraw_timeout_scale(2, 0.01), .args = { 0, add_ref(boss), }, - .angle = M_PI * 2 * frand(), + .angle = rng_angle(), ); } @@ -805,7 +797,7 @@ static void boss_rule_extra(Boss *boss, float alpha) { if(alpha == 0) { lt += 2; - alpha = 1 * frand(); + alpha = rng_real(); } for(int i = 0; i < cnt; ++i) { @@ -816,7 +808,7 @@ static void boss_rule_extra(Boss *boss, float alpha) { float psina = psin(a); PARTICLE( - .sprite = (frand() < v*0.3 || lt > 1) ? "stain" : "arc", + .sprite = (rng_chance(v * 0.3) || lt > 1) ? "stain" : "arc", .pos = boss->pos + dir * (100 + 50 * psin(alpha*global.frames/10.0+2*i)) * alpha, .color = color_mul_scalar(RGBA( 1.0 - 0.5 * psina * v, @@ -824,10 +816,9 @@ static void boss_rule_extra(Boss *boss, float alpha) { 0.5 + 0.5 * psina * v, 0.0 ), 0.8), - .rule = linear, .timeout = 30*lt, - .draw_rule = GrowFade, - .args = { vel * (1 - 2 * !(global.frames % 10)), 2.5 }, + .draw_rule = pdraw_timeout_scalefade(0, 3.5, 1, 0), + .move = move_linear(vel * (1 - 2 * !(global.frames % 10))), ); } } @@ -952,12 +943,18 @@ static int attack_end_delay(Boss *boss) { return delay; } +static void boss_call_rule(Boss *boss, int t) { + if(boss->current->rule) { + boss->current->rule(boss, t); + } +} + void boss_finish_current_attack(Boss *boss) { AttackType t = boss->current->type; boss->current->hp = 0; boss->current->finished = true; - boss->current->rule(boss, EVENT_DEATH); + boss_call_rule(boss, EVENT_DEATH); aniplayer_soft_switch(&boss->ani,"main",0); @@ -992,6 +989,8 @@ void boss_finish_current_attack(Boss *boss) { boss->current->endtime = global.frames + attack_end_delay(boss); boss->current->endtime_undelayed = global.frames; + + coevent_signal_once(&boss->current->events.finished); } void process_boss(Boss **pboss) { @@ -1024,6 +1023,10 @@ void process_boss(Boss **pboss) { bool extra = boss->current->type == AT_ExtraSpell; bool over = boss->current->finished && global.frames >= boss->current->endtime; + if(time == 0) { + coevent_signal_once(&boss->current->events.started); + } + if(!boss->current->endtime) { int remaining = boss->current->timeout - time; @@ -1031,9 +1034,11 @@ void process_boss(Boss **pboss) { play_sound(remaining <= 6*FPS ? "timeout2" : "timeout1"); } - boss->current->rule(boss, time); + boss_call_rule(boss, time); } + move_update(&boss->pos, &boss->move); + if(extra) { float base = 0.2; float ampl = 0.2; @@ -1095,9 +1100,16 @@ void process_boss(Boss **pboss) { } } - if(boss_is_dying(boss)) { + bool dying = boss_is_dying(boss); + bool fleeing = boss_is_fleeing(boss); + + if(dying || fleeing) { + coevent_signal_once(&boss->events.defeated); + } + + if(dying) { float t = (global.frames - boss->current->endtime)/(float)BOSS_DEATH_DELAY + 1; - tsrand_fill(6); + RNG_ARRAY(rng, 2); Color *clr = RGBA_MUL_ALPHA(0.1 + sin(10*t), 0.1 + cos(10*t), 0.5, t); clr->a = 0; @@ -1106,13 +1118,11 @@ void process_boss(Boss **pboss) { .sprite = "petal", .pos = boss->pos, .rule = asymptotic, - .draw_rule = Petal, + .draw_rule = pdraw_petal_random(), .color = clr, .args = { - sign(anfrand(5))*(3+t*5*afrand(0))*cexp(I*M_PI*8*t), + vrng_sign(rng[0]) * (3 + t * 5 * vrng_real(rng[1])) * cdir(M_PI*8*t), 5+I, - afrand(2) + afrand(3)*I, - afrand(4) + 360.0*I*afrand(1) }, .layer = LAYER_PARTICLE_PETAL, .flags = PFLAG_REQUIREDPARTICLE, @@ -1132,14 +1142,14 @@ void process_boss(Boss **pboss) { } for(int i = 0; i < 256; i++) { - tsrand_fill(3); + RNG_ARRAY(rng, 3); PARTICLE( .sprite = "flare", .pos = boss->pos, - .timeout = 60 + 10 * afrand(2), + .timeout = vrng_range(rng[2], 60, 70), .rule = linear, - .draw_rule = Fade, - .args = { (3+afrand(0)*10)*cexp(I*tsrand_a(1)) }, + .draw_rule = pdraw_timeout_fade(1, 0), + .args = { vrng_range(rng[0], 3, 13) * vrng_dir(rng[1]) }, ); } @@ -1147,16 +1157,14 @@ void process_boss(Boss **pboss) { .proto = pp_blast, .pos = boss->pos, .timeout = 60, - .args = { 0, 3.0 }, - .draw_rule = GrowFade, + .draw_rule = pdraw_timeout_scalefade(0, 4, 1, 0), ); PARTICLE( .proto = pp_blast, .pos = boss->pos, .timeout = 70, - .args = { 0, 2.5 }, - .draw_rule = GrowFade, + .draw_rule = pdraw_timeout_scalefade(0, 3.5, 1, 0), ); } @@ -1180,6 +1188,10 @@ void process_boss(Boss **pboss) { log_debug("Current attack [%s] is over", boss->current->name); + boss_reset_motion(boss); + coevent_signal_once(&boss->current->events.completed); + COEVENT_CANCEL_ARRAY(boss->current->events); + for(;;) { if(boss->current == boss->attacks + boss->acount - 1) { // no more attacks, die @@ -1193,7 +1205,13 @@ void process_boss(Boss **pboss) { if(boss->current->type == AT_Immediate) { boss->current->starttime = global.frames; - boss->current->rule(boss, EVENT_BIRTH); + boss_call_rule(boss, EVENT_BIRTH); + + coevent_signal_once(&boss->current->events.initiated); + coevent_signal_once(&boss->current->events.started); + coevent_signal_once(&boss->current->events.finished); + coevent_signal_once(&boss->current->events.completed); + COEVENT_CANCEL_ARRAY(boss->current->events); if(dialog_is_active(global.dialog)) { break; @@ -1203,6 +1221,7 @@ void process_boss(Boss **pboss) { } if(boss_should_skip_attack(boss, boss->current)) { + COEVENT_CANCEL_ARRAY(boss->current->events); continue; } @@ -1212,7 +1231,15 @@ void process_boss(Boss **pboss) { } } -static void boss_death_effect_draw_overlay(Projectile *p, int t) { +void boss_reset_motion(Boss *boss) { + boss->move.acceleration = 0; + boss->move.attraction = 0; + boss->move.attraction_max_speed = 0; + boss->move.attraction_point = 0; + boss->move.retention = 0.8; +} + +static void boss_death_effect_draw_overlay(Projectile *p, int t, ProjDrawRuleArgs args) { FBPair *framebuffers = stage_get_fbpair(FBPAIR_FG); r_framebuffer(framebuffers->front); r_uniform_sampler("noise_tex", "static"); @@ -1265,23 +1292,26 @@ void boss_death(Boss **boss) { } static void free_attack(Attack *a) { + COEVENT_CANCEL_ARRAY(a->events); free(a->name); } void free_boss(Boss *boss) { - ent_unregister(&boss->ent); + COEVENT_CANCEL_ARRAY(boss->events); - for(int i = 0; i < boss->acount; i++) + for(int i = 0; i < boss->acount; i++) { free_attack(&boss->attacks[i]); + } + ent_unregister(&boss->ent); boss_set_portrait(boss, NULL, NULL); aniplayer_free(&boss->ani); free(boss->name); - free(boss->attacks); free(boss); } void boss_start_attack(Boss *b, Attack *a) { + b->current = a; log_debug("%s", a->name); StageInfo *i; @@ -1301,35 +1331,37 @@ void boss_start_attack(Boss *b, Attack *a) { b->shot_damage_multiplier = 1.0; a->starttime = global.frames + (a->type == AT_ExtraSpell? ATTACK_START_DELAY_EXTRA : ATTACK_START_DELAY); - a->rule(b, EVENT_BIRTH); + boss_call_rule(b, EVENT_BIRTH); if(ATTACK_IS_SPELL(a->type)) { play_sound(a->type == AT_ExtraSpell ? "charge_extra" : "charge_generic"); for(int i = 0; i < 10+5*(a->type == AT_ExtraSpell); i++) { - tsrand_fill(4); + RNG_ARRAY(rng, 4); PARTICLE( .sprite = "stain", - .pos = VIEWPORT_W/2 + VIEWPORT_W/4*anfrand(0)+I*VIEWPORT_H/2+I*anfrand(1)*30, + .pos = CMPLX(VIEWPORT_W/2 + vrng_sreal(rng[0]) * VIEWPORT_W/4, VIEWPORT_H/2 + vrng_sreal(rng[1]) * 30), .color = RGBA(0.2, 0.3, 0.4, 0.0), .rule = linear, .timeout = 50, - .draw_rule = GrowFade, - .args = { sign(anfrand(2))*10*(1+afrand(3)) }, + .draw_rule = pdraw_timeout_scalefade(0, 1, 1, 0), + .args = { vrng_sign(rng[2]) * 10 * vrng_range(rng[3], 1, 4) }, ); } } stage_clear_hazards(CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_FORCE); + boss_reset_motion(b); + coevent_signal_once(&a->events.initiated); } -Attack* boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule) { - boss->attacks = realloc(boss->attacks, sizeof(Attack)*(++boss->acount)); - Attack *a = &boss->attacks[boss->acount-1]; - memset(a, 0, sizeof(Attack)); +Attack *boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule) { + assert(boss->acount < BOSS_MAX_ATTACKS); - boss->current = &boss->attacks[0]; + Attack *a = &boss->attacks[boss->acount]; + boss->acount += 1; + memset(a, 0, sizeof(Attack)); a->type = type; a->name = strdup(name); @@ -1343,6 +1375,34 @@ Attack* boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, a->starttime = global.frames; + COEVENT_INIT_ARRAY(a->events); + + return a; +} + +TASK(attack_task_helper, { + BossAttackTask task; + BossAttackTaskArgs task_args; +}) { + // NOTE: We could do INVOKE_TASK_INDIRECT here, but that's a bit wasteful. + // A better idea than both that and this contraption would be to come up + // with an INVOKE_TASK_INDIRECT_WHEN. + ARGS.task._cotask_BossAttack_thunk(&ARGS.task_args); +} + +static void setup_attack_task(Boss *boss, Attack *a, BossAttackTask task) { + INVOKE_TASK_WHEN(&a->events.initiated, attack_task_helper, + .task = task, + .task_args = { + .boss = ENT_BOX(boss), + .attack = a + } + ); +} + +Attack *boss_add_attack_task(Boss *boss, AttackType type, char *name, float timeout, int hp, BossAttackTask task, BossRule draw_rule) { + Attack *a = boss_add_attack(boss, type, name, timeout, hp, NULL, draw_rule); + setup_attack_task(boss, a, task); return a; } @@ -1396,9 +1456,20 @@ Attack* boss_add_attack_from_info(Boss *boss, AttackInfo *info, char move) { Attack *a = boss_add_attack(boss, info->type, info->name, info->timeout, info->hp, info->rule, info->draw_rule); a->info = info; boss_set_attack_bonus(a, info->bonus_rank); + + if(info->task._cotask_BossAttack_thunk) { + setup_attack_task(boss, a, info->task); + } + return a; } +Boss *_init_boss_attack(const BossAttackTaskArgs *restrict args) { + Boss *boss = TASK_BIND(args->boss); + CANCEL_TASK_AFTER(&args->attack->events.finished, THIS_TASK); + return boss; +} + void boss_preload(void) { preload_resources(RES_SFX, RESF_OPTIONAL, "charge_generic", diff --git a/src/boss.h b/src/boss.h index b397eb9337..f6719d912c 100644 --- a/src/boss.h +++ b/src/boss.h @@ -17,8 +17,10 @@ #include "color.h" #include "projectile.h" #include "entity.h" +#include "coroutine.h" #define BOSS_HURT_RADIUS 16 +#define BOSS_MAX_ATTACKS 24 enum { ATTACK_START_DELAY = 60, @@ -32,7 +34,9 @@ enum { BOSS_DEATH_DELAY = 120, }; -struct Boss; +typedef struct Boss Boss; +typedef struct Attack Attack; +typedef struct AttackInfo AttackInfo; typedef void (*BossRule)(struct Boss*, int time) attr_nonnull(1); @@ -47,7 +51,15 @@ typedef enum AttackType { #define ATTACK_IS_SPELL(a) ((a) == AT_Spellcard || (a) == AT_SurvivalSpell || (a) == AT_ExtraSpell) -typedef struct AttackInfo { +DEFINE_TASK_INTERFACE(BossAttack, { + BoxedBoss boss; + Attack *attack; +}); + +typedef TASK_INDIRECT_TYPE(BossAttack) BossAttackTask; +typedef TASK_IFACE_ARGS_TYPE(BossAttack) BossAttackTaskArgs; + +struct AttackInfo { /* HOW TO SET UP THE IDMAP FOR DUMMIES @@ -78,9 +90,12 @@ typedef struct AttackInfo { cmplx pos_dest; int bonus_rank; -} AttackInfo; -typedef struct Attack { + // TODO: when we no longer need rule, this shall take its place + TASK_INDIRECT_TYPE(BossAttack) task; +}; + +struct Attack { char *name; AttackType type; @@ -100,14 +115,21 @@ typedef struct Attack { BossRule rule; BossRule draw_rule; + COEVENTS_ARRAY( + initiated, // before start delay + started, // after start delay + finished, // before end delay + completed // after end delay + ) events; + AttackInfo *info; // NULL for attacks created directly through boss_add_attack -} Attack; +}; -typedef struct Boss { - ENTITY_INTERFACE_NAMED(struct Boss, ent); +struct Boss { + ENTITY_INTERFACE_NAMED(Boss, ent); cmplx pos; - Attack *attacks; + Attack attacks[BOSS_MAX_ATTACKS]; Attack *current; char *name; @@ -127,6 +149,8 @@ typedef struct Boss { BossRule global_rule; + MoveParams move; + // These are publicly accessible damage multipliers *you* can use to buff your spells. // Just change the numbers. global.shake_view style. 1.0 is the default. // If a new attack starts, they are reset. Nothing can go wrong! @@ -147,7 +171,9 @@ typedef struct Boss { float plrproximity_opacity; float attack_timer; } hud; -} Boss; + + COEVENTS_ARRAY(defeated) events; +}; Boss* create_boss(char *name, char *ani, cmplx pos) attr_nonnull(1, 2) attr_returns_nonnull; void free_boss(Boss *boss) attr_nonnull(1); @@ -160,6 +186,8 @@ void draw_boss_fake_overlay(Boss *boss) attr_nonnull(1); Attack* boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule) attr_nonnull(1) attr_returns_nonnull; +Attack *boss_add_attack_task(Boss *boss, AttackType type, char *name, float timeout, int hp, BossAttackTask task, BossRule draw_rule) + attr_nonnull(1) attr_returns_nonnull; Attack* boss_add_attack_from_info(Boss *boss, AttackInfo *info, char move) attr_nonnull(1, 2) attr_returns_nonnull; void boss_set_attack_bonus(Attack *a, int rank) attr_nonnull(1); @@ -175,10 +203,16 @@ bool boss_is_vulnerable(Boss *boss) attr_nonnull(1); void boss_death(Boss **boss) attr_nonnull(1); +void boss_reset_motion(Boss *boss) attr_nonnull(1); + void boss_preload(void); #define BOSS_DEFAULT_SPAWN_POS (VIEWPORT_W * 0.5 - I * VIEWPORT_H * 0.5) #define BOSS_DEFAULT_GO_POS (VIEWPORT_W * 0.5 + 200.0*I) #define BOSS_NOMOVE (-3142-39942.0*I) +Boss *_init_boss_attack(const BossAttackTaskArgs *restrict args); +#define INIT_BOSS_ATTACK() _init_boss_attack(&ARGS) +#define BEGIN_BOSS_ATTACK() WAIT_EVENT_OR_DIE(&ARGS.attack->events.started) + #endif // IGUARD_boss_h diff --git a/src/common_tasks.c b/src/common_tasks.c new file mode 100644 index 0000000000..92840b4421 --- /dev/null +++ b/src/common_tasks.c @@ -0,0 +1,89 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "common_tasks.h" +#include "random.h" + +DEFINE_EXTERN_TASK(common_drop_items) { + cmplx p = *ARGS.pos; + + for(int i = 0; i < ITEM_LAST - ITEM_FIRST; ++i) { + for(int j = ARGS.items.as_array[i]; j; --j) { + spawn_item(p, i + ITEM_FIRST); + WAIT(2); + } + } +} + +void common_move_loop(cmplx *restrict pos, MoveParams *restrict mp) { + for(;;) { + move_update(pos, mp); + YIELD; + } +} + +DEFINE_EXTERN_TASK(common_move) { + if(ARGS.ent.ent) { + TASK_BIND(ARGS.ent); + } + + MoveParams p = ARGS.move_params; + common_move_loop(ARGS.pos, &p); +} + +DEFINE_EXTERN_TASK(common_move_ext) { + if(ARGS.ent.ent) { + TASK_BIND(ARGS.ent); + } + + common_move_loop(ARGS.pos, ARGS.move_params); +} + +DEFINE_EXTERN_TASK(common_call_func) { + ARGS.func(); +} + +cmplx common_wander(cmplx origin, double dist, Rect bounds) { + int attempts = 32; + double angle; + cmplx dest; + cmplx dir; + + // assert(point_in_rect(origin, bounds)); + + while(attempts--) { + angle = rng_angle(); + dir = cdir(angle); + dest = origin + dist * dir; + + if(point_in_rect(dest, bounds)) { + return dest; + } + } + + log_warn("Clipping fallback origin = %f%+fi dist = %f bounds.top_left = %f%+fi bounds.bottom_right = %f%+fi", + creal(origin), cimag(origin), + dist, + creal(bounds.top_left), cimag(bounds.top_left), + creal(bounds.bottom_right), cimag(bounds.bottom_right) + ); + + // TODO: implement proper line-clipping here? + + double step = cabs(bounds.bottom_right - bounds.top_left) / 16; + dir *= step; + dest = origin; + + while(point_in_rect(dest + dir, bounds)) { + dest += dir; + } + + return dest; +} diff --git a/src/common_tasks.h b/src/common_tasks.h new file mode 100644 index 0000000000..9b1b83dfc1 --- /dev/null +++ b/src/common_tasks.h @@ -0,0 +1,51 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_common_tasks_h +#define IGUARD_common_tasks_h + +#include "taisei.h" + +#include "coroutine.h" +#include "item.h" +#include "move.h" +#include "entity.h" +#include "global.h" + +DECLARE_EXTERN_TASK( + common_drop_items, + { cmplx *pos; ItemCounts items; } +); + +DECLARE_EXTERN_TASK( + common_move, + { cmplx *pos; MoveParams move_params; BoxedEntity ent; } +); + +DECLARE_EXTERN_TASK( + common_move_ext, + { cmplx *pos; MoveParams *move_params; BoxedEntity ent; } +); + +DECLARE_EXTERN_TASK( + common_call_func, + { void (*func)(void); } +); + +void common_move_loop(cmplx *restrict pos, MoveParams *restrict mp); + +INLINE Rect viewport_bounds(double margin) { + return (Rect) { + .top_left = CMPLX(margin, margin), + .bottom_right = CMPLX(VIEWPORT_W - margin, VIEWPORT_H - margin), + }; +} + +cmplx common_wander(cmplx origin, double dist, Rect bounds); + +#endif // IGUARD_common_tasks_h diff --git a/src/coroutine.c b/src/coroutine.c new file mode 100644 index 0000000000..7901133526 --- /dev/null +++ b/src/coroutine.c @@ -0,0 +1,759 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "coroutine.h" +#include "util.h" + +#define CO_STACK_SIZE (64 * 1024) + +// #define EVT_DEBUG + +#ifdef DEBUG +// #define CO_TASK_STATS +#endif + +#ifdef CO_TASK_DEBUG + #define TASK_DEBUG(...) log_debug(__VA_ARGS__) +#else + #define TASK_DEBUG(...) ((void)0) +#endif + +#ifdef EVT_DEBUG + #undef EVT_DEBUG + #define EVT_DEBUG(...) log_debug(__VA_ARGS__) +#else + #define EVT_DEBUG(...) ((void)0) +#endif + +enum { + COTASK_WAIT_NONE, + COTASK_WAIT_DELAY, + COTASK_WAIT_EVENT, +}; + +struct CoTask { + LIST_INTERFACE(CoTask); + koishi_coroutine_t ko; + BoxedEntity bound_ent; + + struct { + CoTaskFunc func; + void *arg; + } finalizer; + + CoTask *supertask; + ListAnchor subtasks; + List subtask_chain; + + uint32_t unique_id; + + struct { + CoWaitResult result; + char wait_type; + + union { + struct { + int remaining; + } delay; + + struct { + CoEvent *pevent; + CoEventSnapshot snapshot; + } event; + }; + } wait; + +#ifdef CO_TASK_DEBUG + char debug_label[256]; +#endif +}; + +#define SUBTASK_NODE_TO_TASK(n) CASTPTR_ASSUME_ALIGNED((char*)(n) - offsetof(CoTask, subtask_chain), CoTask) + +static LIST_ANCHOR(CoTask) task_pool; + +CoSched *_cosched_global; + +#ifdef CO_TASK_STATS +static struct { + size_t num_tasks_allocated; + size_t num_tasks_in_use; + size_t num_switches_this_frame; + size_t peak_stack_usage; +} cotask_stats; + +#define STAT_VAL(name) (cotask_stats.name) +#define STAT_VAL_SET(name, value) ((cotask_stats.name) = (value)) + +// enable stack usage tracking (loose) +#define CO_TASK_STATS_STACK + +#else // CO_TASK_STATS + +#define STAT_VAL(name) (0) +#define STAT_VAL_SET(name, value) (0) + +#endif // CO_TASK_STATS + +#define STAT_VAL_ADD(name, value) STAT_VAL_SET(name, STAT_VAL(name) + (value)) + +#ifdef CO_TASK_DEBUG +static size_t debug_event_id; +#define TASK_DEBUG_EVENT(ev) uint64_t ev = debug_event_id++ +#else +#define TASK_DEBUG_EVENT(ev) ((void)0) +#endif + +#ifdef CO_TASK_STATS_STACK + +/* + * Crude and simple method to estimate stack usage per task: at init time, fill + * the entire stack with a known 32-bit pattern (the "canary"). The canary is + * pseudo-random and depends on the task's unique ID. After the task is finished, + * try to find the first occurrence of the canary since the base of the stack + * with a fast binary search. If we happen to find a false canary somewhere + * in the middle of actually used stack space, well, tough luck. This was never + * meant to be exact, and it's pretty unlikely anyway. A linear search from the + * end of the region would fix this potential problem, but is slower. + * + * Note that we assume that the stack grows down, since that's how it is on most + * systems. + */ + +/* + * Reserve some space for control structures on the stack; we don't want to + * overwrite that with canaries. fcontext requires this. + */ +#define STACK_BUFFER_UPPER 64 +#define STACK_BUFFER_LOWER 0 + +// for splitmix32 +#include "random.h" + +static inline uint32_t get_canary(CoTask *task) { + uint32_t temp = task->unique_id; + return splitmix32(&temp); +} + +static void *get_stack(CoTask *task, size_t *sz) { + char *lower = koishi_get_stack(&task->ko, sz); + + // Not all koishi backends support stack inspection. Give up in those cases. + if(!lower || !*sz) { + log_debug("koishi_get_stack() returned NULL"); + return NULL; + } + + char *upper = lower + *sz; + assert(upper > lower); + + lower += STACK_BUFFER_LOWER; + upper -= STACK_BUFFER_UPPER; + *sz = upper - lower; + return lower; +} + +static void setup_stack(CoTask *task) { + size_t stack_size; + void *stack = get_stack(task, &stack_size); + + if(!stack) { + return; + } + + uint32_t canary = get_canary(task); + assert(stack_size == sizeof(canary) * (stack_size / sizeof(canary))); + + for(uint32_t *p = stack; p < (uint32_t*)stack + stack_size / sizeof(canary); ++p) { + *p = canary; + } +} + +/* + * Find the first occurrence of a canary since the base of the stack, assuming + * the base is at the top. Note that since this is essentially a binary search + * on an array that may or may *not* be sorted, it can sometimes give the wrong + * answer. This is fine though, since the odds are low and we only care about + * finding an approximate upper bound on stack usage for all tasks, anyway. + */ +static uint32_t *find_first_canary(uint32_t *region, size_t offset, size_t sz, uint32_t canary) { + if(sz == 1) { + return region + offset; + } + + size_t half_size = sz / 2; + size_t mid_index = offset + half_size; + + if(region[mid_index] == canary) { + return find_first_canary(region, offset + half_size, sz - half_size, canary); + } else { + return find_first_canary(region, offset, half_size, canary); + } +} + +static void estimate_stack_usage(CoTask *task) { + size_t stack_size; + void *stack = get_stack(task, &stack_size); + + if(!stack) { + return; + } + + uint32_t canary = get_canary(task); + + size_t num_segments = stack_size / sizeof(canary); + uint32_t *first_segment = stack; + uint32_t *p_canary = find_first_canary(stack, 0, num_segments, canary); + assert(p_canary[1] != canary); + + size_t real_stack_size = stack_size + STACK_BUFFER_LOWER + STACK_BUFFER_UPPER; + size_t usage = (uintptr_t)(first_segment + num_segments - p_canary) * sizeof(canary) + STACK_BUFFER_UPPER; + double percentage = usage / (double)real_stack_size; + + if(usage > STAT_VAL(peak_stack_usage)) { + TASK_DEBUG(">>> %s <<<", task->debug_label); + log_debug("New peak stack usage: %zu out of %zu (%.02f%%); recommended CO_STACK_SIZE >= %zu", + usage, + real_stack_size, + percentage * 100, + (size_t)(topow2_u64(usage) * 2) + ); + STAT_VAL_SET(peak_stack_usage, usage); + } +} + +#else // CO_TASK_STATS_STACK + +static void setup_stack(CoTask *task) { } +static void estimate_stack_usage(CoTask *task) { } + +#endif // CO_TASK_STATS_STACK + +BoxedTask cotask_box(CoTask *task) { + return (BoxedTask) { + .ptr = (uintptr_t)task, + .unique_id = task->unique_id, + }; +} + +CoTask *cotask_unbox(BoxedTask box) { + CoTask *task = (void*)box.ptr; + + if(task->unique_id == box.unique_id) { + return task; + } + + return NULL; +} + +CoTask *cotask_new(CoTaskFunc func) { + CoTask *task; + STAT_VAL_ADD(num_tasks_in_use, 1); + + if((task = alist_pop(&task_pool))) { + koishi_recycle(&task->ko, func); + TASK_DEBUG( + "Recycled task %p for proc %p (%zu tasks allocated / %zu in use)", + (void*)task, *(void**)&func, + STAT_VAL(num_tasks_allocated), STAT_VAL(num_tasks_in_use) + ); + } else { + task = calloc(1, sizeof(*task)); + koishi_init(&task->ko, CO_STACK_SIZE, func); + STAT_VAL_ADD(num_tasks_allocated, 1); + TASK_DEBUG( + "Created new task %p for proc %p (%zu tasks allocated / %zu in use)", + (void*)task, *(void**)&func, + STAT_VAL(num_tasks_allocated), STAT_VAL(num_tasks_in_use) + ); + } + + static uint32_t unique_counter = 0; + task->unique_id = ++unique_counter; + setup_stack(task); + assert(unique_counter != 0); + +#ifdef CO_TASK_DEBUG + snprintf(task->debug_label, sizeof(task->debug_label), "", (void*)task, *(void**)&func); +#endif + + return task; +} + +void cotask_free(CoTask *task) { + estimate_stack_usage(task); + + task->unique_id = 0; + task->supertask = NULL; + memset(&task->bound_ent, 0, sizeof(task->bound_ent)); + memset(&task->finalizer, 0, sizeof(task->finalizer)); + memset(&task->subtasks, 0, sizeof(task->subtasks)); + memset(&task->wait, 0, sizeof(task->wait)); + alist_push(&task_pool, task); + + STAT_VAL_ADD(num_tasks_in_use, -1); + + TASK_DEBUG( + "Released task %s (%zu tasks allocated / %zu in use)", + task->debug_label, + STAT_VAL(num_tasks_allocated), STAT_VAL(num_tasks_in_use) + ); +} + +CoTask *cotask_active(void) { + return CASTPTR_ASSUME_ALIGNED((char*)koishi_active() - offsetof(CoTask, ko), CoTask); +} + +static void cotask_finalize(CoTask *task) { + if(task->finalizer.func) { + task->finalizer.func(task->finalizer.arg); + #ifdef DEBUG + task->finalizer.func = (void*(*)(void*)) 0xDECEA5E; + #endif + } + + List *node; + + if(task->supertask) { + TASK_DEBUG( + "Slave task %s detaching from master %s", + task->debug_label, task->supertask->debug_label + ); + + alist_unlink(&task->supertask->subtasks, &task->subtask_chain); + task->supertask = NULL; + } + + while((node = alist_pop(&task->subtasks))) { + CoTask *sub = SUBTASK_NODE_TO_TASK(node); + assume(sub->supertask == task); + sub->supertask = NULL; + cotask_cancel(sub); + + TASK_DEBUG( + "Canceled slave task %s (of master task %s)", + sub->debug_label, task->debug_label + ); + } +} + +static void cotask_force_cancel(CoTask *task) { + // WARNING: It's important to finalize first, because if task == active task, + // then koishi_kill will not return! + cotask_finalize(task); + TASK_DEBUG_EVENT(ev); + TASK_DEBUG("[%zu] Killing task %s", ev, task->debug_label); + koishi_kill(&task->ko, NULL); + TASK_DEBUG("[%zu] koishi_kill returned (%s)", ev, task->debug_label); + assert(cotask_status(task) == CO_STATUS_DEAD); +} + +bool cotask_cancel(CoTask *task) { + if(cotask_status(task) != CO_STATUS_DEAD) { + cotask_force_cancel(task); + return true; + } + + return false; +} + +static void *cotask_force_resume(CoTask *task, void *arg) { + assert(task->wait.wait_type == COTASK_WAIT_NONE); + assert(!task->bound_ent.ent || ENT_UNBOX(task->bound_ent)); + + TASK_DEBUG_EVENT(ev); + TASK_DEBUG("[%zu] Resuming task %s", ev, task->debug_label); + STAT_VAL_ADD(num_switches_this_frame, 1); + arg = koishi_resume(&task->ko, arg); + TASK_DEBUG("[%zu] koishi_resume returned (%s)", ev, task->debug_label); + + if(cotask_status(task) == CO_STATUS_DEAD) { + cotask_finalize(task); + } + + return arg; +} + +static void *cotask_wake_and_resume(CoTask *task, void *arg) { + task->wait.wait_type = COTASK_WAIT_NONE; + return cotask_force_resume(task, arg); +} + +CoEventSnapshot coevent_snapshot(const CoEvent *evt) { + return (CoEventSnapshot) { + .unique_id = evt->unique_id, + .num_signaled = evt->num_signaled, + }; +} + +CoEventStatus coevent_poll(const CoEvent *evt, const CoEventSnapshot *snap) { + EVT_DEBUG("[%p]", (void*)evt); + EVT_DEBUG("evt->unique_id == %u", evt->unique_id); + EVT_DEBUG("snap->unique_id == %u", snap->unique_id); + EVT_DEBUG("evt->num_signaled == %u", evt->num_signaled); + EVT_DEBUG("snap->num_signaled == %u", snap->num_signaled); + + if( + evt->unique_id != snap->unique_id || + evt->num_signaled < snap->num_signaled + ) { + EVT_DEBUG("Event was canceled"); + return CO_EVENT_CANCELED; + } + + if(evt->num_signaled > snap->num_signaled) { + EVT_DEBUG("Event was signaled"); + return CO_EVENT_SIGNALED; + } + + EVT_DEBUG("Event hasn't changed; waiting..."); + return CO_EVENT_PENDING; +} + +static bool cotask_do_wait(CoTask *task) { + switch(task->wait.wait_type) { + case COTASK_WAIT_NONE: { + return false; + } + + case COTASK_WAIT_DELAY: { + if(--task->wait.delay.remaining < 0) { + return false; + } + + break; + } + + case COTASK_WAIT_EVENT: { + TASK_DEBUG("COTASK_WAIT_EVENT in task %s", task->debug_label); + + CoEventStatus stat = coevent_poll(task->wait.event.pevent, &task->wait.event.snapshot); + if(stat != CO_EVENT_PENDING) { + task->wait.result.event_status = stat; + TASK_DEBUG("COTASK_WAIT_EVENT in task %s RESULT = %i", task->debug_label, stat); + return false; + } + + break; + } + } + + task->wait.result.frames++; + return true; +} + +void *cotask_resume(CoTask *task, void *arg) { + if(task->bound_ent.ent && !ENT_UNBOX(task->bound_ent)) { + cotask_force_cancel(task); + return NULL; + } + + if(!cotask_do_wait(task)) { + return cotask_wake_and_resume(task, arg); + } + + assert(task->wait.wait_type != COTASK_WAIT_NONE); + return NULL; +} + +void *cotask_yield(void *arg) { + attr_unused CoTask *task = cotask_active(); + TASK_DEBUG_EVENT(ev); + TASK_DEBUG("[%zu] Yielding from task %s", ev, task->debug_label); + STAT_VAL_ADD(num_switches_this_frame, 1); + arg = koishi_yield(arg); + TASK_DEBUG("[%zu] koishi_yield returned (%s)", ev, task->debug_label); + return arg; +} + +static inline CoWaitResult cotask_wait_init(CoTask *task, char wait_type) { + CoWaitResult wr = task->wait.result; + memset(&task->wait, 0, sizeof(task->wait)); + task->wait.wait_type = wait_type; + return wr; +} + +int cotask_wait(int delay) { + CoTask *task = cotask_active(); + assert(task->wait.wait_type == COTASK_WAIT_NONE); + + if(delay == 1) { + cotask_yield(NULL); + return 1; + } + + cotask_wait_init(task, COTASK_WAIT_DELAY); + task->wait.delay.remaining = delay; + + if(cotask_do_wait(task)) { + cotask_yield(NULL); + } + + return cotask_wait_init(task, COTASK_WAIT_NONE).frames; +} + +static void coevent_add_subscriber(CoEvent *evt, CoTask *task) { + evt->num_subscribers++; + assert(evt->num_subscribers != 0); + + if(evt->num_subscribers >= evt->num_subscribers_allocated) { + if(!evt->num_subscribers_allocated) { + evt->num_subscribers_allocated = 4; + } else { + evt->num_subscribers_allocated *= 2; + assert(evt->num_subscribers_allocated != 0); + } + + evt->subscribers = realloc(evt->subscribers, sizeof(*evt->subscribers) * evt->num_subscribers_allocated); + } + + evt->subscribers[evt->num_subscribers - 1] = cotask_box(task); +} + +CoWaitResult cotask_wait_event(CoEvent *evt, void *arg) { + assert(evt->unique_id > 0); + + CoTask *task = cotask_active(); + coevent_add_subscriber(evt, task); + + cotask_wait_init(task, COTASK_WAIT_EVENT); + task->wait.event.pevent = evt; + task->wait.event.snapshot = coevent_snapshot(evt); + + if(cotask_do_wait(task)) { + cotask_yield(NULL); + } + + return cotask_wait_init(task, COTASK_WAIT_NONE); +} + +CoWaitResult cotask_wait_event_or_die(CoEvent *evt, void *arg) { + CoWaitResult wr = cotask_wait_event(evt, arg); + + if(wr.event_status == CO_EVENT_CANCELED) { + cotask_cancel(cotask_active()); + UNREACHABLE; + } + + return wr; +} + +CoStatus cotask_status(CoTask *task) { + return koishi_state(&task->ko); +} + +EntityInterface *(cotask_bind_to_entity)(CoTask *task, EntityInterface *ent) { + assert(task->bound_ent.ent == 0); + + if(ent == NULL) { + cotask_force_cancel(task); + UNREACHABLE; + } + + task->bound_ent = ENT_BOX(ent); + return ent; +} + +void cotask_set_finalizer(CoTask *task, CoTaskFunc finalizer, void *arg) { + assert(task->finalizer.func == NULL); + task->finalizer.func = finalizer; + task->finalizer.arg = arg; +} + +void cotask_enslave(CoTask *slave) { + assert(slave->supertask == NULL); + + CoTask *master = cotask_active(); + assert(master != NULL); + + slave->supertask = master; + alist_append(&master->subtasks, &slave->subtask_chain); + + TASK_DEBUG( + "Made task %s a slave of task %s", + slave->debug_label, slave->supertask->debug_label + ); +} + +void coevent_init(CoEvent *evt) { + static uint32_t g_uid; + *evt = (CoEvent) { .unique_id = ++g_uid }; + assert(g_uid != 0); +} + +static void coevent_wake_subscribers(CoEvent *evt) { + if(!evt->num_subscribers) { + return; + } + + assert(evt->num_subscribers); + + BoxedTask subs_snapshot[evt->num_subscribers]; + memcpy(subs_snapshot, evt->subscribers, sizeof(subs_snapshot)); + evt->num_subscribers = 0; + + for(int i = 0; i < ARRAY_SIZE(subs_snapshot); ++i) { + CoTask *task = cotask_unbox(subs_snapshot[i]); + + if(task && cotask_status(task) != CO_STATUS_DEAD) { + EVT_DEBUG("Resume CoEvent{%p} subscriber %s", (void*)evt, task->debug_label); + cotask_resume(task, NULL); + } + } +} + +void coevent_signal(CoEvent *evt) { + ++evt->num_signaled; + assert(evt->num_signaled != 0); + coevent_wake_subscribers(evt); +} + +void coevent_signal_once(CoEvent *evt) { + if(!evt->num_signaled) { + coevent_signal(evt); + } +} + +void coevent_cancel(CoEvent* evt) { + evt->num_signaled = evt->unique_id = 0; + coevent_wake_subscribers(evt); + evt->num_subscribers_allocated = 0; + free(evt->subscribers); + evt->subscribers = NULL; +} + +void _coevent_array_action(uint num, CoEvent *events, void (*func)(CoEvent*)) { + for(uint i = 0; i < num; ++i) { + func(events + i); + } +} + +void cosched_init(CoSched *sched) { + memset(sched, 0, sizeof(*sched)); +} + +CoTask *_cosched_new_task(CoSched *sched, CoTaskFunc func, void *arg, CoTaskDebugInfo debug) { + CoTask *task = cotask_new(func); +#ifdef CO_TASK_DEBUG + snprintf(task->debug_label, sizeof(task->debug_label), "#%i <%p> %s (%s:%i:%s)", task->unique_id, (void*)task, debug.label, debug.debug_info.file, debug.debug_info.line, debug.debug_info.func); +#endif + cotask_force_resume(task, arg); + assert(cotask_status(task) == CO_STATUS_SUSPENDED || cotask_status(task) == CO_STATUS_DEAD); + alist_append(&sched->pending_tasks, task); + return task; +} + +uint cosched_run_tasks(CoSched *sched) { + for(CoTask *t; (t = alist_pop(&sched->pending_tasks));) { + alist_append(&sched->tasks, t); + } + + uint ran = 0; + + for(CoTask *t = sched->tasks.first, *next; t; t = next) { + next = t->next; + + if(cotask_status(t) == CO_STATUS_DEAD) { + alist_unlink(&sched->tasks, t); + cotask_free(t); + } else { + assert(cotask_status(t) == CO_STATUS_SUSPENDED); + cotask_resume(t, NULL); + ++ran; + } + } + + return ran; +} + +void cosched_finish(CoSched *sched) { + // FIXME: should we ensure that finalizers get called even here? + + for(CoTask *t = sched->pending_tasks.first, *next; t; t = next) { + next = t->next; + cotask_free(t); + } + + for(CoTask *t = sched->tasks.first, *next; t; t = next) { + next = t->next; + cotask_free(t); + } + + memset(sched, 0, sizeof(*sched)); +} + +void coroutines_init(void) { + +} + +void coroutines_shutdown(void) { + for(CoTask *task; (task = alist_pop(&task_pool));) { + koishi_deinit(&task->ko); + free(task); + } +} + +#ifdef DEBUG +#include "video.h" +#include "resource/font.h" +#endif + +void coroutines_draw_stats(void) { +#ifdef CO_TASK_STATS + static char buf[128]; + + TextParams tp = { + .pos = { SCREEN_W }, + .color = RGB(1, 1, 1), + .shader_ptr = r_shader_get("text_default"), + .font_ptr = get_font("monotiny"), + .align = ALIGN_RIGHT, + }; + + float ls = font_get_lineskip(tp.font_ptr); + + tp.pos.y += ls; + snprintf(buf, sizeof(buf), "Peak stack: %zukb Tasks: %4zu / %4zu ", + STAT_VAL(peak_stack_usage) / 1024, + STAT_VAL(num_tasks_in_use), + STAT_VAL(num_tasks_allocated) + ); + text_draw(buf, &tp); + + tp.pos.y += ls; + snprintf(buf, sizeof(buf), "Switches/frame: %4zu ", STAT_VAL(num_switches_this_frame)); + text_draw(buf, &tp); + + STAT_VAL_SET(num_switches_this_frame, 0); +#endif +} + +DEFINE_EXTERN_TASK(_cancel_task_helper) { + CoTask *task = cotask_unbox(ARGS.task); + + if(task) { + cotask_cancel(task); + } +} + +#include +#include +#include +#include +#include +#include + +#define ENT_TYPE(typename, id) \ + typename *_cotask_bind_to_entity_##typename(CoTask *task, typename *ent) { \ + return ENT_CAST((cotask_bind_to_entity)(task, ent ? &ent->entity_interface : NULL), typename); \ + } + +ENT_TYPES +#undef ENT_TYPE diff --git a/src/coroutine.h b/src/coroutine.h new file mode 100644 index 0000000000..1ea0044c54 --- /dev/null +++ b/src/coroutine.h @@ -0,0 +1,465 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_coroutine_h +#define IGUARD_coroutine_h + +#include "taisei.h" + +#include "entity.h" +#include "util/debug.h" + +#include + +// #define CO_TASK_DEBUG + +typedef struct CoTask CoTask; +typedef struct CoSched CoSched; +typedef void *(*CoTaskFunc)(void *arg); + +// target for the INVOKE_TASK macro +extern CoSched *_cosched_global; + +typedef enum CoStatus { + CO_STATUS_SUSPENDED = KOISHI_SUSPENDED, + CO_STATUS_RUNNING = KOISHI_RUNNING, + CO_STATUS_DEAD = KOISHI_DEAD, +} CoStatus; + +typedef enum CoEventStatus { + CO_EVENT_PENDING, + CO_EVENT_SIGNALED, + CO_EVENT_CANCELED, +} CoEventStatus; + +typedef struct BoxedTask { + uintptr_t ptr; + uint32_t unique_id; +} BoxedTask; + +typedef struct CoEvent { + // ListAnchor subscribers; + // FIXME: Is there a better way than a dynamic array? + // An intrusive linked list just isn't robust enough. + BoxedTask *subscribers; + uint32_t unique_id; + uint16_t num_signaled; + uint8_t num_subscribers; + uint8_t num_subscribers_allocated; +} CoEvent; + +typedef struct CoEventSnapshot { + uint32_t unique_id; + uint16_t num_signaled; +} CoEventSnapshot; + +struct CoSched { + LIST_ANCHOR(CoTask) tasks, pending_tasks; +}; + +typedef struct CoWaitResult { + int frames; + CoEventStatus event_status; +} CoWaitResult; + +#ifdef CO_TASK_DEBUG +typedef struct CoTaskDebugInfo { + const char *label; + DebugInfo debug_info; +} CoTaskDebugInfo; + +#define COTASK_DEBUG_INFO(label) ((CoTaskDebugInfo) { (label), _DEBUG_INFO_INITIALIZER_ }) +#else +typedef char CoTaskDebugInfo; +#define COTASK_DEBUG_INFO(label) (0) +#endif + +void coroutines_init(void); +void coroutines_shutdown(void); +void coroutines_draw_stats(void); + +CoTask *cotask_new(CoTaskFunc func); +void cotask_free(CoTask *task); +bool cotask_cancel(CoTask *task); +void *cotask_resume(CoTask *task, void *arg); +void *cotask_yield(void *arg); +int cotask_wait(int delay); +CoWaitResult cotask_wait_event(CoEvent *evt, void *arg); +CoWaitResult cotask_wait_event_or_die(CoEvent *evt, void *arg); +CoStatus cotask_status(CoTask *task); +CoTask *cotask_active(void); +EntityInterface *cotask_bind_to_entity(CoTask *task, EntityInterface *ent) attr_returns_nonnull; +void cotask_set_finalizer(CoTask *task, CoTaskFunc finalizer, void *arg); +void cotask_enslave(CoTask *slave); + +BoxedTask cotask_box(CoTask *task); +CoTask *cotask_unbox(BoxedTask box); + +void coevent_init(CoEvent *evt); +void coevent_signal(CoEvent *evt); +void coevent_signal_once(CoEvent *evt); +void coevent_cancel(CoEvent *evt); +CoEventSnapshot coevent_snapshot(const CoEvent *evt); +CoEventStatus coevent_poll(const CoEvent *evt, const CoEventSnapshot *snap); + +void _coevent_array_action(uint num, CoEvent *events, void (*func)(CoEvent*)); + +#define COEVENTS_ARRAY(...) union { CoEvent _first_event_; struct { CoEvent __VA_ARGS__; }; } + +#define COEVENT_ARRAY_ACTION(func, array) (_coevent_array_action(sizeof(array)/sizeof(CoEvent), &((array)._first_event_), func)) +#define COEVENT_INIT_ARRAY(array) COEVENT_ARRAY_ACTION(coevent_init, array) +#define COEVENT_CANCEL_ARRAY(array) COEVENT_ARRAY_ACTION(coevent_cancel, array) + +void cosched_init(CoSched *sched); +CoTask *_cosched_new_task(CoSched *sched, CoTaskFunc func, void *arg, CoTaskDebugInfo debug); // creates and runs the task, schedules it for resume on cosched_run_tasks if it's still alive +#define cosched_new_task(sched, func, arg, debug_label) _cosched_new_task(sched, func, arg, COTASK_DEBUG_INFO(debug_label)) +uint cosched_run_tasks(CoSched *sched); // returns number of tasks ran +void cosched_finish(CoSched *sched); + +INLINE void cosched_set_invoke_target(CoSched *sched) { _cosched_global = sched; } + +#define TASK_ARGS_TYPE(name) COARGS_##name + +#define TASK_ARGSDELAY_NAME(name) COARGSDELAY_##name +#define TASK_ARGSDELAY(name) struct TASK_ARGSDELAY_NAME(name) + +#define TASK_ARGSCOND_NAME(name) COARGSCOND_##name +#define TASK_ARGSCOND(name) struct TASK_ARGSCOND_NAME(name) + +#define TASK_IFACE_NAME(iface, suffix) COTASKIFACE_##iface##_##suffix +#define TASK_IFACE_ARGS_TYPE(iface) TASK_IFACE_NAME(iface, ARGS) +#define TASK_INDIRECT_TYPE(iface) TASK_IFACE_NAME(iface, HANDLE) + +#define DEFINE_TASK_INTERFACE(iface, argstruct) \ + typedef TASK_ARGS_STRUCT(argstruct) TASK_IFACE_ARGS_TYPE(iface); \ + typedef struct { void *(*_cotask_##iface##_thunk)(void*); } TASK_INDIRECT_TYPE(iface) /* require semicolon */ + +#define TASK_INDIRECT_TYPE_ALIAS(task) TASK_IFACE_NAME(task, HANDLEALIAS) + +#define ARGS (*_cotask_args) + +#define NO_ARGS { char _dummy_0; } +#define TASK_ARGS_STRUCT(argstruct) struct { struct argstruct; char _dummy_1; } + +#define TASK_COMMON_PRIVATE_DECLARATIONS(name) \ + /* user-defined task body */ \ + static void COTASK_##name(TASK_ARGS_TYPE(name) *_cotask_args); \ + /* called from the entry points before task body (inlined, hopefully) */ \ + INLINE void COTASKPROLOGUE_##name(TASK_ARGS_TYPE(name) *_cotask_args) /* require semicolon */ \ + +#define TASK_COMMON_DECLARATIONS(name, argstype, handletype, linkage) \ + /* produce warning if the task is never used */ \ + linkage char COTASK_UNUSED_CHECK_##name; \ + /* type of indirect handle to a compatible task */ \ + typedef handletype TASK_INDIRECT_TYPE_ALIAS(name); \ + /* user-defined type of args struct */ \ + typedef argstype TASK_ARGS_TYPE(name); \ + /* type of internal args struct for INVOKE_TASK_DELAYED */ \ + struct TASK_ARGSDELAY_NAME(name) { TASK_ARGS_TYPE(name) real_args; int delay; }; \ + /* type of internal args struct for INVOKE_TASK_WHEN */ \ + struct TASK_ARGSCOND_NAME(name) { TASK_ARGS_TYPE(name) real_args; CoEvent *event; bool unconditional; }; \ + /* task entry point for INVOKE_TASK */ \ + attr_unused linkage void *COTASKTHUNK_##name(void *arg); \ + /* task entry point for INVOKE_TASK_DELAYED */ \ + attr_unused linkage void *COTASKTHUNKDELAY_##name(void *arg); \ + /* task entry point for INVOKE_TASK_WHEN and INVOKE_TASK_AFTER */ \ + attr_unused linkage void *COTASKTHUNKCOND_##name(void *arg) /* require semicolon */ \ + +#define TASK_COMMON_THUNK_DEFINITIONS(name, linkage) \ + /* task entry point for INVOKE_TASK */ \ + attr_unused linkage void *COTASKTHUNK_##name(void *arg) { \ + /* copy args to our coroutine stack so that they're valid after caller returns */ \ + TASK_ARGS_TYPE(name) args_copy = *(TASK_ARGS_TYPE(name)*)arg; \ + /* call prologue */ \ + COTASKPROLOGUE_##name(&args_copy); \ + /* call body */ \ + COTASK_##name(&args_copy); \ + /* exit coroutine */ \ + return NULL; \ + } \ + /* task entry point for INVOKE_TASK_DELAYED */ \ + attr_unused linkage void *COTASKTHUNKDELAY_##name(void *arg) { \ + /* copy args to our coroutine stack so that they're valid after caller returns */ \ + TASK_ARGSDELAY(name) args_copy = *(TASK_ARGSDELAY(name)*)arg; \ + /* if delay is negative, bail out early */ \ + if(args_copy.delay < 0) return NULL; \ + /* wait out the delay */ \ + WAIT(args_copy.delay); \ + /* call prologue */ \ + COTASKPROLOGUE_##name(&args_copy.real_args); \ + /* call body */ \ + COTASK_##name(&args_copy.real_args); \ + /* exit coroutine */ \ + return NULL; \ + } \ + /* task entry point for INVOKE_TASK_WHEN and INVOKE_TASK_AFTER */ \ + attr_unused linkage void *COTASKTHUNKCOND_##name(void *arg) { \ + /* copy args to our coroutine stack so that they're valid after caller returns */ \ + TASK_ARGSCOND(name) args_copy = *(TASK_ARGSCOND(name)*)arg; \ + /* wait for event, and if it wasn't canceled (or if we want to run unconditionally)... */ \ + if(WAIT_EVENT(args_copy.event).event_status == CO_EVENT_SIGNALED || args_copy.unconditional) { \ + /* call prologue */ \ + COTASKPROLOGUE_##name(&args_copy.real_args); \ + /* call body */ \ + COTASK_##name(&args_copy.real_args); \ + } \ + /* exit coroutine */ \ + return NULL; \ + } + +#define TASK_COMMON_BEGIN_BODY_DEFINITION(name, linkage) \ + linkage void COTASK_##name(TASK_ARGS_TYPE(name) *_cotask_args) + + +#define DECLARE_TASK_EXPLICIT(name, argstype, handletype, linkage) \ + TASK_COMMON_DECLARATIONS(name, argstype, handletype, linkage) /* require semicolon */ + +#define DEFINE_TASK_EXPLICIT(name, linkage) \ + TASK_COMMON_PRIVATE_DECLARATIONS(name); \ + TASK_COMMON_THUNK_DEFINITIONS(name, linkage) \ + /* empty prologue */ \ + INLINE void COTASKPROLOGUE_##name(TASK_ARGS_TYPE(name) *_cotask_args) { } \ + /* begin task body definition */ \ + TASK_COMMON_BEGIN_BODY_DEFINITION(name, linkage) + + +/* declare a task with static linkage (needs to be defined later) */ +#define DECLARE_TASK(name, argstruct) \ + DECLARE_TASK_EXPLICIT(name, TASK_ARGS_STRUCT(argstruct), void, static) /* require semicolon */ + +/* declare a task with static linkage that conforms to a common interface (needs to be defined later) */ +#define DECLARE_TASK_WITH_INTERFACE(name, iface) \ + DECLARE_TASK_EXPLICIT(name, TASK_IFACE_ARGS_TYPE(iface), TASK_INDIRECT_TYPE(iface), static) /* require semicolon */ + +/* define a task with static linkage (needs to be declared first) */ +#define DEFINE_TASK(name) \ + DEFINE_TASK_EXPLICIT(name, static) + +/* declare and define a task with static linkage */ +#define TASK(name, argstruct) \ + DECLARE_TASK(name, argstruct); \ + DEFINE_TASK(name) + +/* declare and define a task with static linkage that conforms to a common interface */ +#define TASK_WITH_INTERFACE(name, iface) \ + DECLARE_TASK_WITH_INTERFACE(name, iface); \ + DEFINE_TASK(name) + + +/* declare a task with extern linkage (needs to be defined later) */ +#define DECLARE_EXTERN_TASK(name, argstruct) \ + DECLARE_TASK_EXPLICIT(name, TASK_ARGS_STRUCT(argstruct), void, extern) /* require semicolon */ + +/* declare a task with extern linkage that conforms to a common interface (needs to be defined later) */ +#define DECLARE_EXTERN_TASK_WITH_INTERFACE(name, iface) \ + DECLARE_TASK_EXPLICIT(name, TASK_IFACE_ARGS_TYPE(iface), TASK_INDIRECT_TYPE(iface), extern) /* require semicolon */ + +/* define a task with extern linkage (needs to be declared first) */ +#define DEFINE_EXTERN_TASK(name) \ + DEFINE_TASK_EXPLICIT(name, extern) + + +#define DEFINE_TASK_WITH_FINALIZER_EXPLICIT(name, linkage) \ + TASK_COMMON_PRIVATE_DECLARATIONS(name); \ + TASK_COMMON_THUNK_DEFINITIONS(name, linkage) \ + /* error out if using TASK_FINALIZER without TASK_WITH_FINALIZER */ \ + struct COTASK__##name##__not_declared_using_TASK_WITH_FINALIZER { char dummy; }; \ + /* user-defined finalizer function */ \ + INLINE void COTASKFINALIZER_##name(TASK_ARGS_TYPE(name) *_cotask_args); \ + /* real finalizer entry point */ \ + static void *COTASKFINALIZERTHUNK_##name(void *arg) { \ + COTASKFINALIZER_##name((TASK_ARGS_TYPE(name)*)arg); \ + return NULL; \ + } \ + /* prologue; sets up finalizer before executing task body */ \ + INLINE void COTASKPROLOGUE_##name(TASK_ARGS_TYPE(name) *_cotask_args) { \ + cotask_set_finalizer(cotask_active(), COTASKFINALIZERTHUNK_##name, _cotask_args); \ + } \ + /* begin task body definition */ \ + TASK_COMMON_BEGIN_BODY_DEFINITION(name, linkage) + +/* define a task that needs a finalizer with static linkage (needs to be declared first) */ +#define DEFINE_TASK_WITH_FINALIZER(name) \ + DEFINE_TASK_WITH_FINALIZER_EXPLICIT(name, static) + +/* define a task that needs a finalizer with static linkage (needs to be declared first) */ +#define DEFINE_EXTERN_TASK_WITH_FINALIZER(name) \ + DEFINE_TASK_WITH_FINALIZER_EXPLICIT(name, extern) + +/* declare and define a task that needs a finalizer with static linkage */ +#define TASK_WITH_FINALIZER(name, argstruct) \ + DECLARE_TASK(name, argstruct); \ + DEFINE_TASK_WITH_FINALIZER(name) + +/* define the finalizer for a TASK_WITH_FINALIZER */ +#define TASK_FINALIZER(name) \ + /* error out if using TASK_FINALIZER without TASK_WITH_FINALIZER */ \ + attr_unused struct COTASK__##name##__not_declared_using_TASK_WITH_FINALIZER COTASK__##name##__not_declared_using_TASK_WITH_FINALIZER; \ + /* begin finalizer body definition */ \ + static void COTASKFINALIZER_##name(TASK_ARGS_TYPE(name) *_cotask_args) + + +INLINE BoxedTask _cotask_invoke_helper(CoTask *t, bool is_subtask) { + if(is_subtask) { + cotask_enslave(t); + } + + return cotask_box(t); +} + +/* + * INVOKE_TASK(task_name, args...) + * INVOKE_SUBTASK(task_name, args...) + * + * This is the most basic way to start an asynchronous task. Control is transferred + * to the new task immediately when this is called, and returns to the call site + * when the task yields or terminates. + * + * Args are optional. They are treated simply as an initializer for the task's + * args struct, so it's possible to use designated initializer syntax to emulate + * "keyword arguments", etc. + * + * INVOKE_SUBTASK is identical INVOKE_TASK, except the spawned task will attach + * to the currently executing task, becoming its "sub-task" or "slave". When a + * task finishes executing, all of its sub-tasks are also terminated recursively. + * + * Other INVOKE_ macros with a _SUBTASK version behave analogously. + */ + +#define INVOKE_TASK(...) _internal_INVOKE_TASK(false, __VA_ARGS__, ._dummy_1 = 0) +#define INVOKE_SUBTASK(...) _internal_INVOKE_TASK(true, __VA_ARGS__, ._dummy_1 = 0) + +#define _internal_INVOKE_TASK(is_subtask, name, ...) ( \ + (void)COTASK_UNUSED_CHECK_##name, \ + _cotask_invoke_helper(cosched_new_task(_cosched_global, COTASKTHUNK_##name, \ + (&(TASK_ARGS_TYPE(name)) { __VA_ARGS__ }), #name \ + ), is_subtask) \ +) + +/* + * INVOKE_TASK_DELAYED(delay, task_name, args...) + * INVOKE_SUBTASK_DELAYED(delay, task_name, args...) + * + * Like INVOKE_TASK, but the task will yield times before executing the + * actual task body. + * + * If is negative, the task will not be invoked. The arguments are still + * evaluated, however. (Caveat: in the current implementation, a task is spawned + * either way; it just aborts early without executing the body if the delay is + * negative, so there's some overhead). + */ + +#define INVOKE_TASK_DELAYED(_delay, ...) _internal_INVOKE_TASK_DELAYED(false, _delay, __VA_ARGS__, ._dummy_1 = 0) +#define INVOKE_SUBTASK_DELAYED(_delay, ...) _internal_INVOKE_TASK_DELAYED(true, _delay, __VA_ARGS__, ._dummy_1 = 0) + +#define _internal_INVOKE_TASK_DELAYED(is_subtask, _delay, name, ...) ( \ + (void)COTASK_UNUSED_CHECK_##name, \ + _cotask_invoke_helper(cosched_new_task(_cosched_global, COTASKTHUNKDELAY_##name, \ + (&(TASK_ARGSDELAY(name)) { .real_args = { __VA_ARGS__ }, .delay = (_delay) }), #name \ + ), is_subtask) \ +) + +/* + * INVOKE_TASK_WHEN(event, task_name, args...) + * INVOKE_SUBTASK_WHEN(event, task_name, args...) + * + * INVOKE_TASK_AFTER(event, task_name, args...) + * INVOKE_SUBTASK_AFTER(event, task_name, args...) + * + * Both INVOKE_TASK_WHEN and INVOKE_TASK_AFTER spawn a task that waits for an + * event to occur. The difference is that _WHEN aborts the task if the event has + * been canceled, but _AFTER proceeds to execute it unconditionally. + * + * is a pointer to a CoEvent struct. + */ + +#define INVOKE_TASK_WHEN(_event, ...) _internal_INVOKE_TASK_ON_EVENT(false, false, _event, __VA_ARGS__, ._dummy_1 = 0) +#define INVOKE_SUBTASK_WHEN(_event, ...) _internal_INVOKE_TASK_ON_EVENT(true, false, _event, __VA_ARGS__, ._dummy_1 = 0) + +#define INVOKE_TASK_AFTER(_event, ...) _internal_INVOKE_TASK_ON_EVENT(false, true, _event, __VA_ARGS__, ._dummy_1 = 0) +#define INVOKE_SUBTASK_AFTER(_event, ...) _internal_INVOKE_TASK_ON_EVENT(true, true, _event, __VA_ARGS__, ._dummy_1 = 0) + +#define _internal_INVOKE_TASK_ON_EVENT(is_subtask, is_unconditional, _event, name, ...) ( \ + (void)COTASK_UNUSED_CHECK_##name, \ + _cotask_invoke_helper(cosched_new_task(_cosched_global, COTASKTHUNKCOND_##name, \ + (&(TASK_ARGSCOND(name)) { .real_args = { __VA_ARGS__ }, .event = (_event), .unconditional = is_unconditional }), #name \ + ), is_subtask) \ +) + +/* + * CANCEL_TASK_WHEN(event, boxed_task) + * CANCEL_TASK_AFTER(event, boxed_task) + * + * Invokes an auxiliary task that will wait for an event, and then cancel another + * running task. The difference between WHEN and AFTER is the same as in + * INVOKE_TASK_WHEN/INVOKE_TASK_AFTER -- this is a simple wrapper around those. + * + * is a pointer to a CoEvent struct. + * is a BoxedTask struct; use cotask_box to obtain one from a pointer. + * You can also use the THIS_TASK macro to refer to the currently running task. + */ + +#define CANCEL_TASK_WHEN(_event, _task) INVOKE_TASK_WHEN(_event, _cancel_task_helper, _task) +#define CANCEL_TASK_AFTER(_event, _task) INVOKE_TASK_AFTER(_event, _cancel_task_helper, _task) + +DECLARE_EXTERN_TASK(_cancel_task_helper, { BoxedTask task; }); + +#define TASK_INDIRECT(iface, task) ( \ + (void)COTASK_UNUSED_CHECK_##task, \ + (TASK_INDIRECT_TYPE_ALIAS(task)) { ._cotask_##iface##_thunk = COTASKTHUNK_##task } \ +) + +#define TASK_INDIRECT_INIT(iface, task) \ + { ._cotask_##iface##_thunk = COTASKTHUNK_##task } \ + +#define INVOKE_TASK_INDIRECT_(is_subtask, iface, taskhandle, ...) ( \ + _cotask_invoke_helper(cosched_new_task(_cosched_global, taskhandle._cotask_##iface##_thunk, \ + (&(TASK_IFACE_ARGS_TYPE(iface)) { __VA_ARGS__ }), "" \ + ), is_subtask) \ +) + +#define INVOKE_TASK_INDIRECT(iface, ...) INVOKE_TASK_INDIRECT_(false, iface, __VA_ARGS__, ._dummy_1 = 0) +#define INVOKE_SUBTASK_INDIRECT(iface, ...) INVOKE_TASK_INDIRECT_(true, iface, __VA_ARGS__, ._dummy_1 = 0) + +#define THIS_TASK cotask_box(cotask_active()) + +#define YIELD cotask_yield(NULL) +// #define WAIT(delay) do { int _delay = (delay); while(_delay-- > 0) YIELD; } while(0) +#define WAIT(delay) cotask_wait(delay) +#define WAIT_EVENT(e) cotask_wait_event((e), NULL) +#define WAIT_EVENT_OR_DIE(e) cotask_wait_event_or_die((e), NULL) +// #define STALL do { YIELD; } while(1) +#define STALL cotask_wait(INT_MAX) + +// to use these inside a coroutine, define a BREAK_CONDITION macro and a BREAK label. +#define CHECK_BREAK do { if(BREAK_CONDITION) goto BREAK; } while(0) +#define BYIELD do { YIELD; CHECK_BREAK; } while(0) +#define BWAIT(frames) do { WAIT(frames); CHECK_BREAK; } while(0) +#define BSTALL do { BYIELD; } while(1) + +#define ENT_TYPE(typename, id) \ + struct typename; \ + struct typename *_cotask_bind_to_entity_##typename(CoTask *task, struct typename *ent) attr_returns_nonnull attr_returns_max_aligned; + +ENT_TYPES +#undef ENT_TYPE + +#define cotask_bind_to_entity(task, ent) (_Generic((ent), \ + struct Projectile*: _cotask_bind_to_entity_Projectile, \ + struct Laser*: _cotask_bind_to_entity_Laser, \ + struct Enemy*: _cotask_bind_to_entity_Enemy, \ + struct Boss*: _cotask_bind_to_entity_Boss, \ + struct Player*: _cotask_bind_to_entity_Player, \ + struct Item*: _cotask_bind_to_entity_Item, \ + EntityInterface*: cotask_bind_to_entity \ +)(task, ent)) + +#define TASK_BIND(box) cotask_bind_to_entity(cotask_active(), ENT_UNBOX(box)) +#define TASK_BIND_UNBOXED(ent) cotask_bind_to_entity(cotask_active(), ent) + +#endif // IGUARD_coroutine_h diff --git a/src/difficulty.c b/src/difficulty.c index 16c3219a8a..b741b86dfd 100644 --- a/src/difficulty.c +++ b/src/difficulty.c @@ -10,6 +10,7 @@ #include "difficulty.h" #include "resource/resource.h" +#include "global.h" typedef struct DiffDef { const char *name; @@ -58,3 +59,10 @@ void difficulty_preload(void) { preload_resource(RES_SPRITE, difficulty_sprite_name(diff), RESF_PERMANENT); } } + +double difficulty_value(double easy, double normal, double hard, double lunatic) { + uint idx = global.diff - D_Easy; + double vals[NUM_SELECTABLE_DIFFICULTIES] = { easy, normal, hard, lunatic }; + assert(idx < ARRAY_SIZE(vals)); + return vals[idx]; +} diff --git a/src/difficulty.h b/src/difficulty.h index 7bf0315e60..2780d5ba0c 100644 --- a/src/difficulty.h +++ b/src/difficulty.h @@ -35,4 +35,7 @@ const Color* difficulty_color(Difficulty diff) void difficulty_preload(void); +double difficulty_value(double easy, double normal, double hard, double lunatic) + attr_pure; + #endif // IGUARD_difficulty_h diff --git a/src/drawlayers.inc.h b/src/drawlayers.inc.h index 99a3ebe750..056a8105c0 100644 --- a/src/drawlayers.inc.h +++ b/src/drawlayers.inc.h @@ -1,4 +1,5 @@ +LAYER(NODRAW) LAYER(BACKGROUND) LAYER(PLAYER_SHOT) LAYER(PARTICLE_LOW) diff --git a/src/enemy.c b/src/enemy.c index 508fdb9885..a8ed8921e3 100644 --- a/src/enemy.c +++ b/src/enemy.c @@ -57,6 +57,23 @@ static void fix_pos0_visual(Enemy *e) { e->pos0_visual = x + y * I; } +static inline int enemy_call_logic_rule(Enemy *e, int t) { + if(t == EVENT_KILLED) { + coevent_signal(&e->events.killed); + } + + if(e->logic_rule) { + return e->logic_rule(e, t); + } else { + // TODO: backport unified left/right move animations from the obsolete `newart` branch + cmplx v = move_update(&e->pos, &e->move); + e->moving = fabs(creal(v)) >= 1; + e->dir = creal(v) < 0; + } + + return ACTION_NONE; +} + Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule visual_rule, EnemyLogicRule logic_rule, cmplx a1, cmplx a2, cmplx a3, cmplx a4) { if(IN_DRAW_CODE) { @@ -90,35 +107,58 @@ Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule v e->ent.draw_func = ent_draw_enemy; e->ent.damage_func = ent_damage_enemy; + coevent_init(&e->events.killed); fix_pos0_visual(e); ent_register(&e->ent, ENT_ENEMY); - e->logic_rule(e, EVENT_BIRTH); + enemy_call_logic_rule(e, EVENT_BIRTH); return e; } +static void enemy_death_effect(cmplx pos) { + for(int i = 0; i < 10; i++) { + RNG_ARRAY(rng, 2); + PARTICLE( + .sprite = "flare", + .pos = pos, + .timeout = 10, + .rule = linear, + .draw_rule = pdraw_timeout_fade(1, 0), + .args = { vrng_range(rng[0], 3, 13) * vrng_dir(rng[1]) }, + ); + } + + PARTICLE( + .proto = pp_blast, + .pos = pos, + .timeout = 20, + .draw_rule = pdraw_blast(), + .flags = PFLAG_REQUIREDPARTICLE + ); + + PARTICLE( + .proto = pp_blast, + .pos = pos, + .timeout = 20, + .draw_rule = pdraw_blast(), + .flags = PFLAG_REQUIREDPARTICLE + ); + + PARTICLE( + .proto = pp_blast, + .pos = pos, + .timeout = 15, + .draw_rule = pdraw_timeout_scalefade(0, rng_f32_range(1, 2), 1, 0), + .flags = PFLAG_REQUIREDPARTICLE + ); +} + static void* _delete_enemy(ListAnchor *enemies, List* enemy, void *arg) { Enemy *e = (Enemy*)enemy; if(e->hp <= 0 && e->hp != ENEMY_IMMUNE && e->hp != ENEMY_BOMB) { play_sound("enemydeath"); - - for(int i = 0; i < 10; i++) { - tsrand_fill(2); - - PARTICLE( - .sprite = "flare", - .pos = e->pos, - .timeout = 10, - .rule = linear, - .draw_rule = Fade, - .args = { (3+afrand(0)*10)*cexp(I*afrand(1)*2*M_PI) }, - ); - } - - PARTICLE(.proto = pp_blast, .pos = e->pos, .timeout = 20, .draw_rule = Blast, .flags = PFLAG_REQUIREDPARTICLE); - PARTICLE(.proto = pp_blast, .pos = e->pos, .timeout = 20, .draw_rule = Blast, .flags = PFLAG_REQUIREDPARTICLE); - PARTICLE(.proto = pp_blast, .pos = e->pos, .timeout = 15, .draw_rule = GrowFade, .flags = PFLAG_REQUIREDPARTICLE); + enemy_death_effect(e->pos); for(Projectile *p = global.projs.first; p; p = p->next) { if(p->type == PROJ_ENEMY && !(p->flags & PFLAG_NOCOLLISION) && cabs(p->pos - e->pos) < 64) { @@ -127,7 +167,8 @@ static void* _delete_enemy(ListAnchor *enemies, List* enemy, void *arg) { } } - e->logic_rule(e, EVENT_DEATH); + enemy_call_logic_rule(e, EVENT_DEATH); + coevent_cancel(&e->events.killed); ent_unregister(&e->ent); objpool_release(stage_object_pools.enemies, alist_unlink(enemies, enemy)); @@ -216,14 +257,16 @@ int enemy_flare(Projectile *p, int t) { // a[0] velocity, a[1] ref to enemy void BigFairy(Enemy *e, int t, bool render) { if(!render) { if(!(t % 5)) { - cmplx offset = (frand()-0.5)*30 + (frand()-0.5)*20.0*I; + cmplx offset = rng_sreal() * 15; + offset += rng_sreal() * 10 * I; PARTICLE( .sprite = "smoothdot", .pos = offset, .color = RGBA(0.0, 0.2, 0.3, 0.0), .rule = enemy_flare, - .draw_rule = Shrink, + .draw_rule = pdraw_timeout_scalefade(2+2*I, 0.5+2*I, 1, 0), + .angle = M_PI/2, .timeout = 50, .args = { (-50.0*I-offset)/50.0, add_ref(e) }, ); @@ -349,12 +392,12 @@ void process_enemies(EnemyList *enemies) { next = enemy->next; if(enemy->hp == ENEMY_KILLED) { - enemy->logic_rule(enemy, EVENT_KILLED); + enemy_call_logic_rule(enemy, EVENT_KILLED); delete_enemy(enemies, enemy); continue; } - int action = enemy->logic_rule(enemy, global.frames - enemy->birthtime); + int action = enemy_call_logic_rule(enemy, global.frames - enemy->birthtime); if(enemy->hp > ENEMY_IMMUNE && enemy->alpha >= 1.0 && cabs(enemy->pos - global.plr.pos) < ENEMY_HURT_RADIUS) { ent_damage(&global.plr.ent, &(DamageInfo) { .type = DMG_ENEMY_COLLISION }); diff --git a/src/enemy.h b/src/enemy.h index 3b65294bb9..63a9655d6f 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -15,6 +15,8 @@ #include "projectile.h" #include "objectpool.h" #include "entity.h" +#include "coroutine.h" +#include "move.h" #ifdef DEBUG #define ENEMY_DEBUG @@ -40,20 +42,28 @@ struct Enemy { cmplx pos0; cmplx pos0_visual; - long birthtime; - - int dir; - bool moving; + union { + cmplx args[RULE_ARGC]; + MoveParams move; + }; EnemyLogicRule logic_rule; EnemyVisualRule visual_rule; + struct { + CoEvent killed; + } events; + + int birthtime; + int dir; + float spawn_hp; float hp; - cmplx args[RULE_ARGC]; float alpha; + bool moving; + #ifdef ENEMY_DEBUG DebugInfo debug; #endif diff --git a/src/entity.c b/src/entity.c index e1083a780e..334bcddc32 100644 --- a/src/entity.c +++ b/src/entity.c @@ -92,7 +92,7 @@ void ent_register(EntityInterface *ent, EntityType type) { // This is not really an error, but it may result in weird draw order // in some corner cases. If this overflow ever actually occurs, though, // then you've probably got much bigger problems. - log_debug("spawn_id just overflowed. You might be spawning stuff waaaay too often"); + log_warn("spawn_id just overflowed. You might be spawning stuff waaaay too often"); } if(entities.capacity < entities.num) { @@ -107,6 +107,7 @@ void ent_register(EntityInterface *ent, EntityType type) { } void ent_unregister(EntityInterface *ent) { + ent->spawn_id = 0; EntityInterface *sub = entities.array[--entities.num]; assert(ent->index <= entities.num); assert(entities.array[ent->index] == ent); @@ -128,6 +129,10 @@ static int ent_cmp(const void *ptr1, const void *ptr2) { return r; } +static inline bool ent_is_drawable(EntityInterface *ent) { + return (ent->draw_layer & ~LAYER_LOW_MASK) > LAYER_NODRAW && ent->draw_func; +} + void ent_draw(EntityPredicate predicate) { call_hooks(&entities.hooks.pre_draw, NULL); qsort(entities.array, entities.num, sizeof(EntityInterface*), ent_cmp); @@ -137,7 +142,7 @@ void ent_draw(EntityPredicate predicate) { ent->index = _ent - entities.array; assert(entities.array[ent->index] == ent); - if(ent->draw_func && predicate(ent)) { + if(ent_is_drawable(ent) && predicate(ent)) { call_hooks(&entities.hooks.pre_draw, ent); r_state_push(); ent->draw_func(ent); @@ -150,7 +155,7 @@ void ent_draw(EntityPredicate predicate) { ent->index = _ent - entities.array; assert(entities.array[ent->index] == ent); - if(ent->draw_func) { + if(ent_is_drawable(ent)) { call_hooks(&entities.hooks.pre_draw, ent); r_state_push(); ent->draw_func(ent); @@ -242,3 +247,32 @@ void ent_hook_post_draw(EntityDrawHookCallback callback, void *arg) { void ent_unhook_post_draw(EntityDrawHookCallback callback) { remove_hook(&entities.hooks.post_draw, callback); } + +BoxedEntity _ent_box_Entity(EntityInterface *ent) { + BoxedEntity h; + h.ent = (uintptr_t)ent; + h.spawn_id = ent->spawn_id; + return h; +} + +EntityInterface *_ent_unbox_Entity(BoxedEntity box) { + EntityInterface *e = (EntityInterface*)box.ent; + + if(e->spawn_id == box.spawn_id) { + return e; + } + + return NULL; +} + +#define ENT_TYPE(typename, id) \ + Boxed##typename _ent_box_##typename(struct typename *ent) { \ + return (Boxed##typename) { .as_generic = _ent_box_Entity(&ent->entity_interface) };\ + } \ + struct typename *_ent_unbox_##typename(Boxed##typename box) { \ + EntityInterface *e = _ent_unbox_Entity(box.as_generic); \ + return e ? ENT_CAST(e, typename) : NULL; \ + } + +ENT_TYPES +#undef ENT_TYPE diff --git a/src/entity.h b/src/entity.h index 2f2853118d..be653d9a43 100644 --- a/src/entity.h +++ b/src/entity.h @@ -13,6 +13,7 @@ #include "objectpool.h" #include "util/geometry.h" +#include "util/macrohax.h" #define LAYER_LOW_BITS 16 #define LAYER_LOW_MASK ((1 << LAYER_LOW_BITS) - 1) @@ -51,6 +52,7 @@ typedef enum EntityType { typedef struct EntityInterface EntityInterface; typedef struct EntityListNode EntityListNode; +typedef struct BoxedEntity BoxedEntity; typedef enum DamageType { DMG_UNDEFINED, @@ -152,4 +154,137 @@ void ent_unhook_pre_draw(EntityDrawHookCallback callback); void ent_hook_post_draw(EntityDrawHookCallback callback, void *arg); void ent_unhook_post_draw(EntityDrawHookCallback callback); +struct BoxedEntity { + uintptr_t ent; // not an actual pointer to avert the temptation to use it directly. + uint_fast32_t spawn_id; +}; + +BoxedEntity _ent_box_Entity(EntityInterface *ent); +EntityInterface *_ent_unbox_Entity(BoxedEntity box); + +#define ENT_TYPE(typename, id) \ + typedef union Boxed##typename { \ + BoxedEntity as_generic; \ + struct { \ + uintptr_t ent; \ + uint_fast32_t spawn_id; \ + }; \ + } Boxed##typename; \ + struct typename; \ + Boxed##typename _ent_box_##typename(struct typename *ent); \ + struct typename *_ent_unbox_##typename(Boxed##typename box); \ + INLINE Boxed##typename _ent_boxed_passthrough_helper_##typename(Boxed##typename box) { return box; } + +ENT_TYPES +#undef ENT_TYPE + +INLINE BoxedEntity _ent_boxed_passthrough_helper_Entity(BoxedEntity box) { return box; } + +#define ENT_UNBOXED_DISPATCH_TABLE(func_prefix) \ + struct Projectile*: func_prefix##Projectile, \ + struct Laser*: func_prefix##Laser, \ + struct Enemy*: func_prefix##Enemy, \ + struct Boss*: func_prefix##Boss, \ + struct Player*: func_prefix##Player, \ + struct Item*: func_prefix##Item, \ + EntityInterface*: func_prefix##Entity \ + +#define ENT_BOXED_DISPATCH_TABLE(func_prefix) \ + BoxedProjectile: func_prefix##Projectile, \ + BoxedLaser: func_prefix##Laser, \ + BoxedEnemy: func_prefix##Enemy, \ + BoxedBoss: func_prefix##Boss, \ + BoxedPlayer: func_prefix##Player, \ + BoxedItem: func_prefix##Item, \ + BoxedEntity: func_prefix##Entity \ + +#define ENT_UNBOXED_DISPATCH_FUNCTION(func_prefix, ...) \ + _Generic((MACROHAX_FIRST(__VA_ARGS__)), \ + ENT_UNBOXED_DISPATCH_TABLE(func_prefix) \ + )(MACROHAX_EXPAND(__VA_ARGS__)) + +#define ENT_BOXED_DISPATCH_FUNCTION(func_prefix, ...) \ + _Generic((MACROHAX_FIRST(__VA_ARGS__)), \ + ENT_BOXED_DISPATCH_TABLE(func_prefix) \ + )(MACROHAX_EXPAND(__VA_ARGS__)) + +#define ENT_MIXED_DISPATCH_FUNCTION(func_prefix_unboxed, func_prefix_boxed, ...) \ + _Generic((MACROHAX_FIRST(__VA_ARGS__)), \ + ENT_UNBOXED_DISPATCH_TABLE(func_prefix_unboxed), \ + ENT_BOXED_DISPATCH_TABLE(func_prefix_boxed) \ + )(MACROHAX_EXPAND(__VA_ARGS__)) + +#define ENT_BOX(ent) ENT_UNBOXED_DISPATCH_FUNCTION(_ent_box_, ent) +#define ENT_UNBOX(box) ENT_BOXED_DISPATCH_FUNCTION(_ent_unbox_, box) +#define ENT_BOX_OR_PASSTHROUGH(ent) ENT_MIXED_DISPATCH_FUNCTION(_ent_box_, _ent_boxed_passthrough_helper_, ent) + +typedef struct BoxedEntityArray { + BoxedEntity *array; + uint capacity; + uint size; +} BoxedEntityArray; + +#define ENT_TYPE(typename, id) \ + typedef union Boxed##typename##Array { \ + BoxedEntityArray as_generic_UNSAFE; \ + struct { \ + Boxed##typename *array; \ + uint capacity; \ + uint size; \ + }; \ + } Boxed##typename##Array; \ + INLINE void _ent_array_add_##typename(Boxed##typename box, Boxed##typename##Array *a) { \ + assert(a->size < a->capacity); \ + a->array[a->size++] = box; \ + } + +ENT_TYPES +#undef ENT_TYPE + +INLINE void _ent_array_add_BoxedEntity(BoxedEntity box, BoxedEntityArray *a) { + assert(a->size < a->capacity); + a->array[a->size++] = box; +} + +INLINE void _ent_array_add_Entity(struct EntityInterface *ent, BoxedEntityArray *a) { + _ent_array_add_BoxedEntity(ENT_BOX(ent), a); +} + +#define ENT_ARRAY_ADD(_array, _ent) ENT_BOXED_DISPATCH_FUNCTION(_ent_array_add_, ENT_BOX_OR_PASSTHROUGH(_ent), _array) +#define ENT_ARRAY_GET_BOXED(_array, _index) ((_array)->array[_index]) +#define ENT_ARRAY_GET(_array, _index) ENT_UNBOX(ENT_ARRAY_GET_BOXED(_array, _index)) + +#define DECLARE_ENT_ARRAY(_ent_type, _name, _capacity) \ + Boxed##_ent_type##Array _name; \ + _name.size = 0; \ + _name.capacity = _capacity; \ + Boxed##_ent_type _ent_array_data##_name[_name.capacity]; \ + _name.array = _ent_array_data##_name + +#define ENT_ARRAY(_typename, _capacity) \ + ((Boxed##_typename##Array) { .array = (Boxed##_typename[_capacity]) { 0 }, .capacity = (_capacity), .size = 0 }) + +#define ENT_ARRAY_FOREACH(_array, _var, _block) do { \ + for(uint MACROHAX_ADDLINENUM(_ent_array_iterator) = 0; MACROHAX_ADDLINENUM(_ent_array_iterator) < (_array)->size; ++MACROHAX_ADDLINENUM(_ent_array_iterator)) { \ + void *MACROHAX_ADDLINENUM(_ent_array_temp) = ENT_ARRAY_GET((_array), MACROHAX_ADDLINENUM(_ent_array_iterator)); \ + if(MACROHAX_ADDLINENUM(_ent_array_temp) != NULL) { \ + _var = MACROHAX_ADDLINENUM(_ent_array_temp); \ + _block \ + } \ + } \ +} while(0) + +#define ENT_ARRAY_FOREACH_COUNTER(_array, _cntr_var, _ent_var, _block) do { \ + for(uint MACROHAX_ADDLINENUM(_ent_array_iterator) = 0; MACROHAX_ADDLINENUM(_ent_array_iterator) < (_array)->size; ++MACROHAX_ADDLINENUM(_ent_array_iterator)) { \ + void *MACROHAX_ADDLINENUM(_ent_array_temp) = ENT_ARRAY_GET((_array), MACROHAX_ADDLINENUM(_ent_array_iterator)); \ + if(MACROHAX_ADDLINENUM(_ent_array_temp) != NULL) { \ + _cntr_var = MACROHAX_ADDLINENUM(_ent_array_iterator); \ + _ent_var = MACROHAX_ADDLINENUM(_ent_array_temp); \ + _block \ + } \ + } \ +} while(0) + +#define ENT_ARRAY_CLEAR(_array) ((_array)->size = 0) + #endif // IGUARD_entity_h diff --git a/src/eventloop/eventloop.c b/src/eventloop/eventloop.c index 1bf794356a..39b7ab6215 100644 --- a/src/eventloop/eventloop.c +++ b/src/eventloop/eventloop.c @@ -77,6 +77,10 @@ LogicFrameAction handle_logic(LoopFrame **pframe, const FrameTimes *ftimes) { do { lframe_action = run_logic_frame(*pframe); + if(lframe_action == LFRAME_SKIP && taisei_is_skip_mode_enabled()) { + cnt = 0; + } + while(evloop.stack_ptr != *pframe) { *pframe = evloop.stack_ptr; lframe_action = run_logic_frame(*pframe); @@ -100,6 +104,10 @@ LogicFrameAction handle_logic(LoopFrame **pframe, const FrameTimes *ftimes) { } RenderFrameAction run_render_frame(LoopFrame *frame) { + if(taisei_is_skip_mode_enabled()) { + return RFRAME_DROP; + } + attr_unused LoopFrame *stack_prev = evloop.stack_ptr; r_framebuffer_clear(NULL, CLEAR_ALL, RGBA(0, 0, 0, 1), 1); RenderFrameAction a = frame->render(frame->context); diff --git a/src/eventloop/eventloop.h b/src/eventloop/eventloop.h index ebb37d4ea0..94714be7b7 100644 --- a/src/eventloop/eventloop.h +++ b/src/eventloop/eventloop.h @@ -12,14 +12,14 @@ #include "taisei.h" typedef enum RenderFrameAction { - RFRAME_SWAP, - RFRAME_DROP, + RFRAME_SWAP, + RFRAME_DROP, } RenderFrameAction; typedef enum LogicFrameAction { - LFRAME_WAIT, - LFRAME_SKIP, - LFRAME_STOP, + LFRAME_WAIT, + LFRAME_SKIP, + LFRAME_STOP, } LogicFrameAction; typedef LogicFrameAction (*LogicFrameFunc)(void *context); diff --git a/src/global.c b/src/global.c index 512c4c008e..147302c0aa 100644 --- a/src/global.c +++ b/src/global.c @@ -15,9 +15,9 @@ Global global; void init_global(CLIAction *cli) { memset(&global, 0, sizeof(global)); - tsrand_init(&global.rand_game, time(0)); - tsrand_init(&global.rand_visual, time(0)); - tsrand_switch(&global.rand_visual); + rng_init(&global.rand_game, time(0)); + rng_init(&global.rand_visual, time(0)); + rng_make_active(&global.rand_visual); memset(&global.replay, 0, sizeof(Replay)); @@ -61,3 +61,19 @@ void taisei_commit_persistent_data(void) { progress_save(); vfs_sync(VFS_SYNC_STORE, NO_CALLCHAIN); } + +#ifdef DEBUG +#include "audio/audio.h" +static bool _skip_mode; +bool taisei_is_skip_mode_enabled(void) { return _skip_mode; } +void taisei_set_skip_mode(bool state) { + if(_skip_mode && !state && current_bgm.started_at >= 0) { + audio_music_set_position((global.frames - current_bgm.started_at) / (double)FPS); + } + + _skip_mode = state; +} +#else +bool taisei_is_skip_mode_enabled(void) { return false; } +void taisei_set_skip_mode(bool state) { } +#endif diff --git a/src/global.h b/src/global.h index 06bbed7d8d..23f9616706 100644 --- a/src/global.h +++ b/src/global.h @@ -148,6 +148,9 @@ void taisei_quit(void); bool taisei_quit_requested(void); void taisei_commit_persistent_data(void); +bool taisei_is_skip_mode_enabled(void); +void taisei_set_skip_mode(bool state); + // XXX: Move this somewhere? bool gamekeypressed(KeyIndex key); diff --git a/src/hashtable.inc.h b/src/hashtable.inc.h index c93f51c5b5..4dcca28583 100644 --- a/src/hashtable.inc.h +++ b/src/hashtable.inc.h @@ -903,10 +903,12 @@ HT_DECLARE_PRIV_FUNC(void, resize, (HT_BASETYPE *ht, size_t new_size)) { free(ht->table); ht->table = new_table; + /* log_debug( "Resized hashtable at %p: %"PRIuMAX" -> %"PRIuMAX"", (void*)ht, (uintmax_t)ht->table_size, (uintmax_t)new_size ); + */ ht->table_size = new_size; ht->hash_mask = new_hash_mask; diff --git a/src/item.c b/src/item.c index 6353e8f229..4d38bd0242 100644 --- a/src/item.c +++ b/src/item.c @@ -142,14 +142,14 @@ Item *create_clear_item(cmplx pos, uint clear_flags) { type = ITEM_VOLTAGE; } - Item *i = create_item(pos, -10*I + 5*nfrand(), type); + Item *i = create_item(pos, -10*I + 5*rng_sreal(), type); if(i) { PARTICLE( .sprite = "flare", .pos = pos, .timeout = 30, - .draw_rule = Fade, + .draw_rule = pdraw_timeout_fade(1, 0), .layer = LAYER_BULLET+1 ); @@ -251,7 +251,7 @@ void process_items(void) { if(collect_item(item, 1)) { item->pos0 = item->pos; item->birthtime = global.frames; - item->v = -20*I + 10*nfrand(); + item->v = -20*I + 10*rng_sreal(); } } @@ -276,7 +276,7 @@ void process_items(void) { item->auto_collect = 0; item->pos0 = item->pos; item->birthtime = global.frames; - item->v = -10*I + 5*nfrand(); + item->v = -10*I + 5*rng_sreal(); } } @@ -347,8 +347,11 @@ int collision_item(Item *i) { } static void spawn_item_internal(cmplx pos, ItemType type, float collect_value) { - tsrand_fill(2); - Item *i = create_item(pos, (12 + 6 * afrand(0)) * (cexp(I*(3*M_PI/2 + anfrand(1)*M_PI/11))) - 3*I, type); + cmplx v = rng_range(12, 18); + v *= cdir(3*M_PI/2 + rng_sreal() * M_PI/11); + v -= 3*I; + + Item *i = create_item(pos, v, type); if(i != NULL && collect_value >= 0) { collect_item(i, collect_value); diff --git a/src/item.h b/src/item.h index 721d4a1272..d6d774c6b1 100644 --- a/src/item.h +++ b/src/item.h @@ -37,6 +37,23 @@ typedef enum { ITEM_LAST = ITEM_LIFE, } ItemType; +typedef union ItemCounts { + struct { + // CAUTION: must match enum order! + int piv; + int points; + int power_mini; + int power; + int surge; + int voltage; + int bomb_fragment; + int life_fragment; + int bomb; + int life; + }; + int as_array[ITEM_LAST - ITEM_FIRST]; +} ItemCounts; + struct Item { ENTITY_INTERFACE_NAMED(Item, ent); diff --git a/src/laser.c b/src/laser.c index cb804fec72..7c1e7ee761 100644 --- a/src/laser.c +++ b/src/laser.c @@ -451,7 +451,7 @@ void process_lasers(void) { .sprite = "flare", .pos = p, .timeout = 20, - .draw_rule = GrowFade + .draw_rule = pdraw_timeout_scalefade(0, 1, 1, 0), ); laser->deathtime = 0; } diff --git a/src/main.c b/src/main.c index 96481f464c..526a93a574 100644 --- a/src/main.c +++ b/src/main.c @@ -25,6 +25,7 @@ #include "version.h" #include "credits.h" #include "taskmanager.h" +#include "coroutine.h" attr_unused static void taisei_shutdown(void) { @@ -49,6 +50,7 @@ static void taisei_shutdown(void) { vfs_shutdown(); events_shutdown(); time_shutdown(); + coroutines_shutdown(); log_info("Good bye"); SDL_Quit(); @@ -250,6 +252,8 @@ int main(int argc, char **argv) { log_info("Girls are now preparing, please wait warmly..."); + coroutines_init(); + free_cli_action(&ctx->cli); vfs_setup(CALLCHAIN(main_post_vfsinit, ctx)); return 0; diff --git a/src/meson.build b/src/meson.build index 6bfc1a23f4..1fee4e19f1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -64,7 +64,9 @@ taisei_src = files( 'cli.c', 'color.c', 'color.c', + 'common_tasks.c', 'config.c', + 'coroutine.c', 'credits.c', 'dialog.c', 'difficulty.c', @@ -82,6 +84,7 @@ taisei_src = files( 'list.c', 'log.c', 'main.c', + 'move.c', 'player.c', 'plrmodes.c', 'progress.c', @@ -190,25 +193,29 @@ if host_machine.system() == 'emscripten' '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'ENVIRONMENT=web', '-s', 'EXIT_RUNTIME=0', + # NOTE: the _SDL_LoadWAV_RW is an LTO workaround; we don't actually care about that function. + '-s', 'EXPORTED_FUNCTIONS=["_main", "_vfs_sync_callback", "_SDL_LoadWAV_RW"]', '-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall"]', - '-s', 'EXPORT_NAME=Taisei', '-s', 'FILESYSTEM=1', '-s', 'FORCE_FILESYSTEM=1', '-s', 'GL_POOL_TEMP_BUFFERS=0', + '-s', 'GL_PREINITIALIZED_CONTEXT=1', '-s', 'LZ4=1', + '-s', 'MAX_WEBGL_VERSION=2', + '-s', 'MIN_WEBGL_VERSION=2', '-s', 'MODULARIZE=0', + '-s', 'STRICT=1', '-s', 'TOTAL_MEMORY=268435456', - '-s', 'USE_WEBGL2=1', '-s', 'WASM=1', - # NOTE: the _SDL_LoadWAV_RW is an LTO workaround; we don't actually care about that function. - '-s', 'EXPORTED_FUNCTIONS=["_main", "_vfs_sync_callback", "_SDL_LoadWAV_RW"]', - '-s', 'GL_PREINITIALIZED_CONTEXT=1', + '-lGL', + '-legl.js', '-lidbfs.js', - - # Try enabling this if unpatched Freetype crashes - # '-s', 'EMULATE_FUNCTION_POINTER_CASTS=1', ] + em_link_args += subproject('koishi').get_variable('koishi_external_link_args') + + taisei_c_args += ['-s', 'STRICT=1'] + if em_debug # em_link_output_suffixes += ['wasm.map'] em_link_args += [ @@ -229,6 +236,14 @@ if host_machine.system() == 'emscripten' ] endif + if get_option('optimization') != '0' + em_link_args += ['--closure', '1'] + + if em_debug + em_link_args += ['-g1'] + endif + endif + foreach suffix : em_link_output_suffixes em_link_outputs += ['@0@.@1@'.format(taisei_basename, suffix)] endforeach @@ -250,8 +265,8 @@ if host_machine.system() == 'emscripten' cc.cmd_array(), taisei.full_path(), emscripten_global_link_args, - em_bundle_link_args, '--pre-js', em_preamble, + em_bundle_link_args, '--shell-file', em_shell, get_option('c_args'), get_option('c_link_args'), @@ -267,6 +282,7 @@ if host_machine.system() == 'emscripten' output : em_link_outputs, install : true, install_dir : bindir, + console : true, ) bindist_deps += taisei_html diff --git a/src/move.c b/src/move.c new file mode 100644 index 0000000000..7daace3c72 --- /dev/null +++ b/src/move.c @@ -0,0 +1,41 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "move.h" +#include "util/miscmath.h" + +cmplx move_update(cmplx *restrict pos, MoveParams *restrict p) { + cmplx v = p->velocity; + + *pos += v; + p->velocity = p->acceleration + p->retention * v; + + if(p->attraction) { + cmplx av = p->attraction_point - *pos; + + if(p->attraction_max_speed) { + av = cclampabs(av, p->attraction_max_speed); + } + + p->velocity += p->attraction * av; + } + + return v; +} + +cmplx move_update_multiple(uint times, cmplx *restrict pos, MoveParams *restrict p) { + cmplx v = p->velocity; + + while(times--) { + move_update(pos, p); + } + + return v; +} diff --git a/src/move.h b/src/move.h new file mode 100644 index 0000000000..c6dad3f840 --- /dev/null +++ b/src/move.h @@ -0,0 +1,58 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_move_h +#define IGUARD_move_h + +#include "taisei.h" + +/* + * Simple generalized projectile movement based on laochailan's idea + */ + +typedef struct MoveParams { + cmplx velocity, acceleration, retention; + cmplx attraction; + cmplx attraction_point; + double attraction_max_speed; +} MoveParams; + +cmplx move_update(cmplx *restrict pos, MoveParams *restrict params); +cmplx move_update_multiple(uint times, cmplx *restrict pos, MoveParams *restrict params); + +INLINE MoveParams move_linear(cmplx vel) { + return (MoveParams) { vel, 0, 1 }; +} + +INLINE MoveParams move_accelerated(cmplx vel, cmplx accel) { + return (MoveParams) { vel, accel, 1 }; +} + +INLINE MoveParams move_asymptotic(cmplx vel0, cmplx vel1, cmplx retention) { + // NOTE: retention could be derived by something like: exp(-1 / halflife) + return (MoveParams) { vel0, vel1 * (1 - retention), retention }; +} + +INLINE MoveParams move_asymptotic_simple(cmplx vel, double boost_factor) { + // NOTE: this matches the old asymptotic rule semantics exactly + double retention = 0.8; + return move_asymptotic(vel * (1 + boost_factor * retention), vel, retention); +} + +INLINE MoveParams move_towards(cmplx target, cmplx attraction) { + return (MoveParams) { + .attraction = attraction, + .attraction_point = target, + }; +} + +INLINE MoveParams move_stop(cmplx retention) { + return (MoveParams) { .retention = retention }; +} + +#endif // IGUARD_move_h diff --git a/src/player.c b/src/player.c index 67bb5376cc..02685333c9 100644 --- a/src/player.c +++ b/src/player.c @@ -17,6 +17,7 @@ #include "stagetext.h" #include "stagedraw.h" #include "entity.h" +#include "util/glm.h" void player_init(Player *plr) { memset(plr, 0, sizeof(Player)); @@ -431,7 +432,7 @@ static void _powersurge_trail_draw(Projectile *p, float t, float cmul) { }); } -static void powersurge_trail_draw(Projectile *p, int t) { +static void powersurge_trail_draw(Projectile *p, int t, ProjDrawRuleArgs args) { if(t > 0) { _powersurge_trail_draw(p, t - 0.5, 0.25); _powersurge_trail_draw(p, t, 0.25); @@ -446,7 +447,6 @@ static int powersurge_trail(Projectile *p, int t) { } if(t == EVENT_DEATH) { - free(p->sprite); return ACTION_ACK; } @@ -505,7 +505,7 @@ static void player_powersurge_logic(Player *plr) { player_powersurge_calc_bonus(plr, &plr->powersurge.bonus); PARTICLE( - .sprite_ptr = memdup(aniplayer_get_frame(&plr->ani), sizeof(Sprite)), + .sprite_ptr = aniplayer_get_frame(&plr->ani), .pos = plr->pos, .color = RGBA(1, 1, 1, 0.5), .rule = powersurge_trail, @@ -515,22 +515,24 @@ static void player_powersurge_logic(Player *plr) { .flags = PFLAG_NOREFLECT, ); - if(!(global.frames % 6)) { + if(!(global.frames % 6) && plr->powersurge.bonus.discharge_range > 0) { Sprite *spr = get_sprite("part/powersurge_field"); double scale = 2 * plr->powersurge.bonus.discharge_range / spr->w; - double angle = frand() * 2 * M_PI; + double angle = rng_angle(); + + assert(scale > 0); PARTICLE( .sprite_ptr = spr, .pos = plr->pos, - .color = color_mul_scalar(frand() < 0.5 ? RGBA(1.5, 0.5, 0.0, 0.1) : RGBA(0.0, 0.5, 1.5, 0.1), 0.25), + .color = color_mul_scalar(rng_bool() ? RGBA(1.5, 0.5, 0.0, 0.1) : RGBA(0.0, 0.5, 1.5, 0.1), 0.25), .rule = powersurge_charge_particle, - .draw_rule = ScaleFade, + .draw_rule = pdraw_timeout_fade(1, 0), .timeout = 14, .angle = angle, - .args = { 0, 0, (1+I)*scale, 0}, .layer = LAYER_PLAYER - 1, .flags = PFLAG_NOREFLECT, + .scale = scale, ); PARTICLE( @@ -538,12 +540,12 @@ static void player_powersurge_logic(Player *plr) { .pos = plr->pos, .color = RGBA(0.5, 0.5, 0.5, 0), .rule = powersurge_charge_particle, - .draw_rule = ScaleFade, + .draw_rule = pdraw_timeout_fade(1, 0), .timeout = 3, .angle = angle, - .args = { 0, 0, (1+I)*scale, 0}, .layer = LAYER_PLAYER - 1, .flags = PFLAG_NOREFLECT, + .scale = scale, ); } @@ -709,12 +711,12 @@ static int powersurge_discharge(Projectile *p, int t) { return ACTION_NONE; } -static void powersurge_distortion_draw(Projectile *p, int t) { +static void powersurge_distortion_draw(Projectile *p, int t, ProjDrawRuleArgs args) { if(config_get_int(CONFIG_POSTPROCESS) < 1) { return; } - double radius = p->args[0] * pow(1 - t / p->timeout, 8) * (2 * t / 10.0); + double radius = args[0].as_float[0] * pow(1 - t / p->timeout, 8) * (2 * t / 10.0); Framebuffer *fb_aux = stage_get_fbpair(FBPAIR_FG_AUX)->front; Framebuffer *fb_main = r_framebuffer_current(); @@ -750,8 +752,10 @@ static void player_powersurge_expired(Player *plr) { .size = 1+I, .pos = plr->pos, .timeout = 60, - .draw_rule = powersurge_distortion_draw, - .args = { bonus.discharge_range }, + .draw_rule = { + powersurge_distortion_draw, + .args[0].as_float = { bonus.discharge_range }, + }, .layer = LAYER_PLAYER, .flags = PFLAG_REQUIREDPARTICLE | PFLAG_NOREFLECT, ); @@ -760,10 +764,10 @@ static void player_powersurge_expired(Player *plr) { .sprite_ptr = blast, .pos = plr->pos, .color = RGBA(0.6, 1.0, 4.4, 0.0), - .draw_rule = ScaleFade, + .draw_rule = pdraw_timeout_scalefade(2, 0, 1, 0), .timeout = 20, - .args = { 0, 0, scale * (2 + 0 * I) }, - .angle = M_PI*2*frand(), + .angle = rng_angle(), + .scale = scale, .flags = PFLAG_REQUIREDPARTICLE | PFLAG_NOREFLECT, ); @@ -774,7 +778,7 @@ static void player_powersurge_expired(Player *plr) { PROJECTILE( .pos = plr->pos, .size = 1+I, - .draw_rule = ProjNoDraw, + .layer = LAYER_NODRAW, .timeout = 10, .type = PROJ_PLAYER, .rule = powersurge_discharge, @@ -849,7 +853,7 @@ void player_realdeath(Player *plr) { plr->lives--; } -static void player_death_effect_draw_overlay(Projectile *p, int t) { +static void player_death_effect_draw_overlay(Projectile *p, int t, ProjDrawRuleArgs args) { FBPair *framebuffers = stage_get_fbpair(FBPAIR_FG); r_framebuffer(framebuffers->front); r_uniform_sampler("noise_tex", "static"); @@ -867,37 +871,40 @@ static void player_death_effect_draw_overlay(Projectile *p, int t) { r_state_push(); } -static void player_death_effect_draw_sprite(Projectile *p, int t) { +static void player_death_effect_draw_sprite(Projectile *p, int t, ProjDrawRuleArgs args) { float s = t / p->timeout; - float stretch_range = 3, sx, sy; - sx = 0.5 + 0.5 * cos(M_PI * (2 * pow(s, 0.5) + 1)); - sx = (1 - s) * (1 + (stretch_range - 1) * sx) + s * stretch_range * sx; - sy = 1 + pow(s, 3); + s = glm_ease_quad_in(s); + + sx = (1 - pow(2 * pow(1 - s, 4) - 1, 4)); + sx = lerp(1 + (stretch_range - 1) * sx, stretch_range * sx, s); + sy = 1 + 2 * (stretch_range - 1) * pow(s, 4); if(sx <= 0 || sy <= 0) { return; } - r_draw_sprite(&(SpriteParams) { - .sprite_ptr = p->sprite, - .pos = { creal(p->pos), cimag(p->pos) }, - .scale = { .x = sx, .y = sy }, - }); + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + sp.scale.x *= sx; + sp.scale.y *= sy; + sp.rotation.angle = 0; + r_draw_sprite(&sp); } static int player_death_effect(Projectile *p, int t) { if(t < 0) { if(t == EVENT_DEATH) { for(int i = 0; i < 12; ++i) { + RNG_ARRAY(R, 4); PARTICLE( .proto = pp_blast, - .pos = p->pos + 2 * frand() * cexp(I*M_PI*2*frand()), + .pos = p->pos + vrng_range(R[0], 2, 3) * vrng_dir(R[1]), .color = RGBA(0.15, 0.2, 0.5, 0), - .timeout = 12 + i + 2 * nfrand(), - .draw_rule = GrowFade, - .angle = M_PI*2*frand(), + .timeout = i + vrng_range(R[2], 10, 14), + .draw_rule = pdraw_timeout_scalefade(0, 1, 1, 0), + .angle = vrng_angle(R[3]), .flags = PFLAG_NOREFLECT, .layer = LAYER_OVERLAY, ); @@ -918,14 +925,13 @@ void player_death(Player *plr) { play_sound("death"); for(int i = 0; i < 60; i++) { - tsrand_fill(2); + RNG_ARRAY(R, 2); PARTICLE( .sprite = "flare", .pos = plr->pos, - .rule = linear, .timeout = 40, - .draw_rule = Shrink, - .args = { (3+afrand(0)*7)*cexp(I*tsrand_a(1)) }, + .draw_rule = pdraw_timeout_scale(2, 0.01), + .move = move_linear(vrng_range(R[0], 3, 10) * vrng_dir(R[1])), .flags = PFLAG_NOREFLECT, ); } @@ -937,9 +943,9 @@ void player_death(Player *plr) { .pos = plr->pos, .color = RGBA(0.5, 0.15, 0.15, 0), .timeout = 35, - .draw_rule = GrowFade, - .args = { 0, 2.4 }, - .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + .draw_rule = pdraw_timeout_scalefade(0, 3.4, 1, 0), + .angle = rng_angle(), + .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE | PFLAG_NOMOVE | PFLAG_MANUALANGLE, ); PARTICLE( @@ -956,7 +962,7 @@ void player_death(Player *plr) { PARTICLE( .sprite_ptr = aniplayer_get_frame(&plr->ani), .pos = plr->pos, - .timeout = 30, + .timeout = 38, .rule = player_death_effect, .draw_rule = player_death_effect_draw_sprite, .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, @@ -1350,16 +1356,14 @@ void player_graze(Player *plr, cmplx pos, int pts, int effect_intensity, const C c->a = 0; for(int i = 0; i < effect_intensity; ++i) { - tsrand_fill(4); - + RNG_ARRAY(R, 4); PARTICLE( .sprite = "graze", .color = c, .pos = pos, - .rule = asymptotic, - .timeout = 24 + 5 * afrand(2), - .draw_rule = ScaleSquaredFade, - .args = { 0.2 * (1+afrand(0)*5)*cexp(I*M_PI*2*afrand(1)), 16 * (1 + 0.5 * anfrand(3)), 1 }, + .draw_rule = pdraw_timeout_scalefade_exp(1, 0, 1, 0, 2), + .move = move_asymptotic_simple(0.2 * vrng_range(R[0], 1, 6) * vrng_dir(R[1]), 16 * (1 + 0.5 * vrng_sreal(R[3]))), + .timeout = vrng_range(R[2], 4, 29), .flags = PFLAG_NOREFLECT, // .layer = LAYER_PARTICLE_LOW, ); @@ -1446,7 +1450,7 @@ void player_add_bombs(Player *plr, int bombs) { } static void scoretext_update(StageText *txt, int t, float a) { - float r = bits_to_float((uintptr_t)txt->custom.data1); + double r = bits_to_double((uintptr_t)txt->custom.data1); txt->pos -= I * cexp(I*r) * a; } @@ -1468,7 +1472,7 @@ static StageText *find_scoretext_combination_candidate(cmplx pos, bool is_piv) { } static void add_score_text(Player *plr, cmplx location, uint points, bool is_piv) { - float rnd = nfrand(); + double rnd = rng_f64s(); StageText *stxt = find_scoretext_combination_candidate(location, is_piv); @@ -1513,7 +1517,7 @@ static void add_score_text(Player *plr, cmplx location, uint points, bool is_piv timings.delay, timings.lifetime, timings.fadeintime, timings.fadeouttime ); - stxt->custom.data1 = (void*)(uintptr_t)float_to_bits(rnd); + stxt->custom.data1 = (void*)(uintptr_t)double_to_bits(rnd); stxt->custom.update = scoretext_update; } else { stxt->color = c; diff --git a/src/plrmodes/marisa_a.c b/src/plrmodes/marisa_a.c index c2f8e791f9..b71456a111 100644 --- a/src/plrmodes/marisa_a.c +++ b/src/plrmodes/marisa_a.c @@ -86,14 +86,14 @@ static void trace_laser(Enemy *e, cmplx vel, float damage) { } if(col.type & col_types) { - tsrand_fill(3); + RNG_ARRAY(R, 3); PARTICLE( .sprite = "flare", .pos = col.location, .rule = linear, - .timeout = 3 + 5 * afrand(2), - .draw_rule = Shrink, - .args = { (2+afrand(0)*6)*cexp(I*M_PI*2*afrand(1)) }, + .timeout = vrng_range(R[0], 3, 8), + .draw_rule = pdraw_timeout_scale(2, 0.01), + .args = { vrng_range(R[1], 2, 8) * vrng_dir(R[2]) }, .flags = PFLAG_NOREFLECT, .layer = LAYER_PARTICLE_HIGH, ); @@ -315,24 +315,13 @@ static int marisa_laser_renderer(Enemy *renderer, int t) { #undef FOR_EACH_SLAVE #undef FOR_EACH_REAL_SLAVE -static void marisa_laser_flash_draw(Projectile *p, int t) { - Animation *fire = get_ani("fire"); - AniSequence *seq = get_ani_sequence(fire, "main"); - Sprite *spr = animation_get_frame(fire, seq, p->birthtime); - - Color *c = color_mul_scalar(COLOR_COPY(&p->color), 1 - t / p->timeout); - c->r *= (1 - t / p->timeout); - - cmplx pos = p->pos; - pos += p->args[0] * 10; - - r_draw_sprite(&(SpriteParams) { - .sprite_ptr = spr, - .color = c, - .pos = { creal(pos), cimag(pos)}, - .rotation.angle = p->angle + M_PI/2, - .scale.both = 0.40, - }); +static void marisa_laser_flash_draw(Projectile *p, int t, ProjDrawRuleArgs args) { + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + float o = 1 - t / p->timeout; + color_mul_scalar(&spbuf.color, o); + spbuf.color.r *= o; + r_draw_sprite(&sp); } static int marisa_laser_slave(Enemy *e, int t) { @@ -372,15 +361,21 @@ static int marisa_laser_slave(Enemy *e, int t) { cmplx dir = -cexp(I*(angle*factor + ld->lean + M_PI/2)); trace_laser(e, 5 * dir, creal(e->args[1])); + Animation *fire = get_ani("fire"); + AniSequence *seq = get_ani_sequence(fire, "main"); + Sprite *spr = animation_get_frame(fire, seq, global.frames); + PARTICLE( + .sprite_ptr = spr, .size = 1+I, - .pos = e->pos, + .pos = e->pos + dir * 10, .color = color_mul_scalar(RGBA(2, 0.2, 0.5, 0), 0.2), .rule = linear, .draw_rule = marisa_laser_flash_draw, .timeout = 8, .args = { dir, 0, 0.6 + 0.2*I, }, .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + .scale = 0.4, // .layer = LAYER_PARTICLE_LOW, ); } @@ -471,7 +466,7 @@ static int masterspark(Enemy *e, int t2) { if(t2 < 0) return 1; - e->args[0] *= cexp(I*(0.005*creal(global.plr.velocity) + nfrand() * 0.005)); + e->args[0] *= cexp(I*(0.005*creal(global.plr.velocity) + rng_sreal() * 0.005)); cmplx diroffset = e->args[0]; float t = player_get_bomb_progress(&global.plr); @@ -480,7 +475,7 @@ static int masterspark(Enemy *e, int t2) { global.shake_view = 8 * (1 - t * 4 + 3); } else if(t2 % 2 == 0) { cmplx dir = -cexp(1.5*I*sin(t2*M_PI*1.12))*I; - Color *c = HSLA(-t*5.321,1,0.5,0.5*frand()); + Color *c = HSLA(-t*5.321, 1, 0.5, rng_range(0, 0.5)); uint flags = PFLAG_NOREFLECT; @@ -494,9 +489,9 @@ static int masterspark(Enemy *e, int t2) { .color = c, .rule = masterspark_star, .timeout = 50, - .args= { (10 * dir - 10*I)*diroffset, 4 }, - .angle = nfrand(), - .draw_rule = GrowFade, + .args= { (10 * dir - 10*I)*diroffset }, + .angle = rng_angle(), + .draw_rule = pdraw_timeout_scalefade(0, 5, 1, 0), .flags = flags, ); dir = -conj(dir); @@ -506,21 +501,20 @@ static int masterspark(Enemy *e, int t2) { .color = c, .rule = masterspark_star, .timeout = 50, - .args = { (10 * dir - 10*I)*diroffset, 4 }, - .angle = nfrand(), - .draw_rule = GrowFade, + .args = { (10 * dir - 10*I)*diroffset }, + .angle = rng_angle(), + .draw_rule = pdraw_timeout_scalefade(0, 5, 1, 0), .flags = flags, ); PARTICLE( .sprite = "smoke", .pos = global.plr.pos-40*I, .color = HSLA(2*t,1,2,0), //RGBA(0.3, 0.6, 1, 0), - .rule = linear, .timeout = 50, - .args = { -7*dir + 7*I, 6 }, - .angle = nfrand(), - .draw_rule = GrowFade, - .flags = flags, + .move = move_linear(-7*dir + 7*I), + .angle = rng_angle(), + .draw_rule = pdraw_timeout_scalefade(0, 7, 1, 0), + .flags = flags | PFLAG_MANUALANGLE, ); } diff --git a/src/plrmodes/marisa_b.c b/src/plrmodes/marisa_b.c index c11c2db475..455b98b0e7 100644 --- a/src/plrmodes/marisa_b.c +++ b/src/plrmodes/marisa_b.c @@ -68,15 +68,14 @@ static int marisa_star_projectile(Projectile *p, int t) { p->pos0 = p->pos - p->pos0; p->angle = carg(p->pos0); - if(t%(2+(int)round(2*frand())) == 0) { + if(t%(2+(int)round(2*rng_real())) == 0) { // please never write stuff like this ever again PARTICLE( .sprite = "stardust", .pos = p->pos, .color = RGBA(0.5*(1-focus),0,0.5*focus,0), .timeout = 5, .angle = t*0.1, - .draw_rule = GrowFade, - .args = { 1, 0.4}, + .draw_rule = pdraw_timeout_scalefade(0, 1.4, 1, 0), .flags = PFLAG_NOREFLECT, ); } @@ -180,8 +179,7 @@ static int marisa_star_orbit(Enemy *e, int t) { .color = color2, .timeout = 10, .angle = t*0.1, - .draw_rule = GrowFade, - .args = { 1, tb*4}, + .draw_rule = pdraw_timeout_scalefade(0, 1 + 4 * tb, 1, 0), .flags = PFLAG_NOREFLECT, ); } @@ -192,10 +190,10 @@ static int marisa_star_orbit(Enemy *e, int t) { .pos = e->pos, .color = &color, .rule = marisa_star_orbit_star, - .draw_rule = GrowFade, + .draw_rule = pdraw_timeout_scalefade(0, 6, 1, 0), .timeout = 150, .flags = PFLAG_NOREFLECT, - .args = { -5*dir/cabs(dir), 5 }, + .args = { -5*dir/cabs(dir) }, ); } diff --git a/src/plrmodes/reimu.c b/src/plrmodes/reimu.c index 1905bc64df..56f4d6859a 100644 --- a/src/plrmodes/reimu.c +++ b/src/plrmodes/reimu.c @@ -52,45 +52,19 @@ double reimu_common_property(Player *plr, PlrProperty prop) { UNREACHABLE; } -static int reimu_ofuda_trail(Projectile *p, int t) { - int r = linear(p, t); - - if(t < 0) { - return r; - } - - p->color.g *= 0.95; - - return r; -} - -int reimu_common_ofuda(Projectile *p, int t) { - if(t == EVENT_DEATH) { - return ACTION_ACK; - } - - p->angle = carg(p->args[0]); - - if(t == EVENT_BIRTH) { - return ACTION_ACK; - } - - p->pos += p->args[0]; - - PARTICLE( +Projectile *reimu_common_ofuda_swawn_trail(Projectile *p, ProjectileList *dest) { + return PARTICLE( // .sprite_ptr = p->sprite, .sprite_ptr = get_sprite("proj/hghost"), .color = &p->color, .timeout = 12, - .pos = p->pos + p->args[0] * 0.3, - .args = { p->args[0] * 0.5, 0, 1+2*I }, - .rule = reimu_ofuda_trail, - .draw_rule = ScaleFade, + .pos = p->pos + p->move.velocity * 0.3, + .move = move_linear(p->move.velocity * 0.5), + .draw_rule = pdraw_timeout_scalefade(1, 2, 1, 0), .layer = LAYER_PARTICLE_LOW, .flags = PFLAG_NOREFLECT, + .dest = dest, ); - - return ACTION_NONE; } void reimu_common_draw_yinyang(Enemy *e, int t, const Color *c) { diff --git a/src/plrmodes/reimu.h b/src/plrmodes/reimu.h index 8512f642fa..8d1351d683 100644 --- a/src/plrmodes/reimu.h +++ b/src/plrmodes/reimu.h @@ -20,6 +20,7 @@ extern PlayerMode plrmode_reimu_b; double reimu_common_property(Player *plr, PlrProperty prop); int reimu_common_ofuda(Projectile *p, int t); +Projectile *reimu_common_ofuda_swawn_trail(Projectile *p, ProjectileList *dest); void reimu_common_draw_yinyang(Enemy *e, int t, const Color *c); void reimu_common_bomb_bg(Player *p, float alpha); void reimu_common_bomb_buffer_init(void); diff --git a/src/plrmodes/reimu_a.c b/src/plrmodes/reimu_a.c index 4f8ca64f33..2370c50889 100644 --- a/src/plrmodes/reimu_a.c +++ b/src/plrmodes/reimu_a.c @@ -65,9 +65,8 @@ static int reimu_spirit_needle(Projectile *p, int t) { .color = &c, .timeout = 12, .pos = p->pos, - .args = { p->args[0] * 0.8, 0, 0+2*I }, - .rule = linear, - .draw_rule = ScaleFade, + .move = move_linear(p->args[0] * 0.8), + .draw_rule = pdraw_timeout_scalefade(0, 2, 1, 0), .layer = LAYER_PARTICLE_LOW, .flags = PFLAG_NOREFLECT, ); @@ -77,16 +76,7 @@ static int reimu_spirit_needle(Projectile *p, int t) { #define REIMU_SPIRIT_HOMING_SCALE 0.75 -static void reimu_spirit_homing_draw(Projectile *p, int t) { - r_mat_mv_push(); - r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0); - r_mat_mv_rotate(p->angle + M_PI/2, 0, 0, 1); - r_mat_mv_scale(REIMU_SPIRIT_HOMING_SCALE, REIMU_SPIRIT_HOMING_SCALE, 1); - ProjDrawCore(p, &p->color); - r_mat_mv_pop(); -} - -static Projectile* reimu_spirit_spawn_ofuda_particle(Projectile *p, int t, double vfactor) { +static Projectile *reimu_spirit_spawn_ofuda_particle(Projectile *p, int t, double vfactor) { Color *c = HSLA_MUL_ALPHA(t * 0.1, 0.6, 0.7, 0.3); c->a = 0; @@ -96,12 +86,12 @@ static Projectile* reimu_spirit_spawn_ofuda_particle(Projectile *p, int t, doubl .color = c, .timeout = 12, .pos = p->pos, - .args = { p->args[0] * (0.6 + 0.4 * frand()) * vfactor, 0, (1+1.5*I) * REIMU_SPIRIT_HOMING_SCALE }, .angle = p->angle, - .rule = linear, - .draw_rule = ScaleFade, + .move = move_linear(p->args[0] * rng_range(0.6, 1.0) * vfactor), + .draw_rule = pdraw_timeout_scalefade(1, 1.5, 1, 0), .layer = LAYER_PARTICLE_LOW, .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + .scale = REIMU_SPIRIT_HOMING_SCALE, ); } @@ -127,12 +117,12 @@ static Projectile* reimu_spirit_spawn_homing_impact(Projectile *p, int t) { .color = &p->color, .timeout = 32, .pos = p->pos, - .args = { 0, 0, (1+1.5*I) * REIMU_SPIRIT_HOMING_SCALE }, .angle = p->angle, .rule = reimu_spirit_homing_impact, - .draw_rule = ScaleFade, + .draw_rule = pdraw_timeout_scalefade(1, 1.5, 1, 0), .layer = LAYER_PARTICLE_HIGH, .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + .scale = REIMU_SPIRIT_HOMING_SCALE, ); } @@ -173,24 +163,6 @@ static Color *reimu_spirit_orb_color(Color *c, int i) { return c; } -static void reimu_spirit_bomb_orb_visual(Projectile *p, int t) { - cmplx pos = p->pos; - - for(int i = 0; i < 3; i++) { - cmplx offset = (10 + pow(t, 0.5)) * cexp(I * (2 * M_PI / 3*i + sqrt(1 + t * t / 300.0))); - - Color c; - r_draw_sprite(&(SpriteParams) { - .sprite_ptr = p->sprite, - .shader_ptr = p->shader, - .pos = { creal(pos+offset), cimag(pos+offset) }, - .color = reimu_spirit_orb_color(&c, i), - -// .shader_params = &(ShaderCustomParams) {.vector = {0.3,0,0,0}}, - }); - } -} - static int reimu_spirit_bomb_orb_trail(Projectile *p, int t) { if(t < 0) { return ACTION_ACK; @@ -204,162 +176,218 @@ static int reimu_spirit_bomb_orb_trail(Projectile *p, int t) { return ACTION_NONE; } -static void reimu_spirit_bomb_orb_draw_impact(Projectile *p, int t) { - float attack = min(1, (7 + 5 * p->args[0]) * t / p->timeout); - float decay = t / p->timeout; +static void reimu_spirit_bomb_impact_balls(cmplx pos, int count) { + real offset = rng_real(); - Color c = p->color; - color_lerp(&c, RGBA(0.2, 0.1, 0, 1.0), decay); - color_mul_scalar(&c, pow(1 - decay, 2) * 0.75); - - r_draw_sprite(&(SpriteParams) { - .sprite_ptr = p->sprite, - .pos = { creal(p->pos), cimag(p->pos) }, - .color = &c, - .shader_ptr = p->shader, - .shader_params = &p->shader_params, - .scale.both = (0.75 + 0.25 / (pow(decay, 3.0) + 1.0)) + sqrt(5 * (1 - attack)), - }); + for(int i = 0; i < count; i++) { + PARTICLE( + .sprite_ptr = get_sprite("proj/glowball"), + .shader = "sprite_bullet", + .color = HSLA(3 * (float)i / count + offset, 1, 0.5, 0), + .timeout = 60, + .pos = pos, + .args = { cdir(2 * M_PI / count * (i + offset)) * 15 }, + .angle = rng_angle(), + .rule = linear, + .draw_rule = pdraw_timeout_fade(1, 0), + .layer = LAYER_BOSS, + .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + ); + } } -static int reimu_spirit_bomb_orb(Projectile *p, int t) { - int index = creal(p->args[1]) + 0.5; +TASK(reimu_spirit_bomb_orb_impact, { BoxedProjectile orb; }) { + cmplx pos = ENT_UNBOX(ARGS.orb)->pos; - if(t == EVENT_BIRTH) { - if(index == 0) - global.shake_view = 4; - p->args[3] = global.plr.pos; - return ACTION_ACK; - } + play_sound("boom"); + play_sound("spellend"); - if(t == EVENT_DEATH) { - if(global.gameover > 0) { - return ACTION_ACK; - } + global.shake_view = 20; + global.shake_view_fade = 0.6; - global.shake_view = 20; - global.shake_view_fade = 0.6; + double damage = 2000; + double range = 300; - double damage = 2000; - double range = 300; + ent_area_damage(pos, range, &(DamageInfo){damage, DMG_PLAYER_BOMB}, NULL, NULL); + stage_clear_hazards_at(pos, range, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW); - ent_area_damage(p->pos, range, &(DamageInfo){damage, DMG_PLAYER_BOMB}, NULL, NULL); - stage_clear_hazards_at(p->pos, range, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW); + reimu_spirit_bomb_impact_balls(pos, 21); - int count = 21; - double offset = frand(); + int num_impacts = 3; + int t = global.frames; + BoxedProjectileArray impact_effects = ENT_ARRAY(Projectile, 3); + RNG_ARRAY(rand, num_impacts); + Color base_colors[3]; - for(int i = 0; i < count; i++) { - PARTICLE( - .sprite_ptr = get_sprite("proj/glowball"), - .shader = "sprite_bullet", - .color = HSLA(3 * (float)i / count + offset, 1, 0.5, 0), // reimu_spirit_orb_color(&(Color){0}, i%3),w - .timeout = 60, - .pos = p->pos, - .args = { cexp(I * 2 * M_PI / count * (i + offset)) * 15 }, - .angle = 2*M_PI*frand(), - .rule = linear, - .draw_rule = Fade, - .layer = LAYER_BOSS, - .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, - ); - } + for(int i = 0; i < 3; ++i) { + base_colors[i] = *reimu_spirit_orb_color(&(Color){0}, i); - for(int i = 0; i < 3; ++i) { - PARTICLE( - .sprite = "blast", - .color = color_mul_scalar(reimu_spirit_orb_color(&(Color){0}, i), 2), - .pos = p->pos + 30 * cexp(I*2*M_PI/3*(i+t*0.1)), - .timeout = 40, - .draw_rule = ScaleFade, - .layer = LAYER_BOSS + 2, - .args = { 0, 0, 7.5*I }, - .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, - ); + PARTICLE( + .sprite = "blast", + .color = color_mul_scalar(COLOR_COPY(&base_colors[i]), 2), + .pos = pos + 30 * cexp(I*2*M_PI/num_impacts*(i+t*0.1)), + .timeout = 40, + .draw_rule = pdraw_timeout_scalefade(0, 7.5, 1, 0), + .layer = LAYER_BOSS + 2, + .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + ); - PARTICLE( - .sprite = "fantasyseal_impact", - .color = reimu_spirit_orb_color(&(Color){0}, i), - .pos = p->pos + 2 * cexp(I*2*M_PI/3*(i+t*0.1)), - .timeout = 120, - .draw_rule = reimu_spirit_bomb_orb_draw_impact, - .layer = LAYER_BOSS + 1, - .args = { frand() }, - .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, - ); - } + ENT_ARRAY_ADD(&impact_effects, PARTICLE( + .sprite = "fantasyseal_impact", + .color = reimu_spirit_orb_color(&(Color){0}, i), + .pos = pos + 2 * cexp(I*2*M_PI/num_impacts*(i+t*0.1)), + .timeout = 120, + .layer = LAYER_BOSS + 1, + .angle = -M_PI/2, + .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + )); + } - play_sound("boom"); - play_sound("spellend"); + for(;;) { + int live = 0; - return ACTION_ACK; + ENT_ARRAY_FOREACH_COUNTER(&impact_effects, int i, Projectile *p, { + float t = (global.frames - p->birthtime) / p->timeout; + float attack = min(1, vrng_range(rand[i], 7, 12) * t); + float decay = t; + + Color c = base_colors[i]; + color_lerp(&c, RGBA(0.2, 0.1, 0, 1.0), decay); + color_mul_scalar(&c, powf(1.0f - decay, 2.0f) * 0.75f); + p->color = c; + p->scale = (0.75f + 0.25f / (powf(decay, 3.0f) + 1.0f)) + sqrtf(5.0f * (1.0f - attack)); + + ++live; + }); + + if(!live) { + break; + } + + YIELD; } +} + +TASK(reimu_spirit_bomb_orb_visual_kill, { BoxedProjectileArray components; }) { + ENT_ARRAY_FOREACH(&ARGS.components, Projectile *p, { + kill_projectile(p); + }); +} + +TASK(reimu_spirit_bomb_orb_visual, { BoxedProjectile orb; }) { + Projectile *orb = TASK_BIND(ARGS.orb); + DECLARE_ENT_ARRAY(Projectile, components, 3); - if(!player_is_bomb_active(&global.plr) > 0) { - return ACTION_DESTROY; + Sprite *glowball = get_sprite("proj/glowball"); + ShaderProgram *shader = r_shader_get("sprite_bullet"); + + for(int i = 0; i < components.capacity; ++i) { + ENT_ARRAY_ADD(&components, PARTICLE( + .sprite_ptr = glowball, + .shader_ptr = shader, + .color = reimu_spirit_orb_color(&(Color){0}, i), + .opacity = 0.7, + .layer = LAYER_PLAYER_FOCUS - 1, + .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + )); } - double circletime = 100+20*index; + INVOKE_TASK_AFTER(&orb->events.killed, reimu_spirit_bomb_orb_visual_kill, components); + CANCEL_TASK_AFTER(&orb->events.killed, THIS_TASK); + + for(;;) { + cmplx pos = orb->pos; - if(t == circletime) { - p->args[3] = global.plr.pos - 256*I; - p->flags &= ~PFLAG_NOCOLLISION; - play_sound("redirect"); + ENT_ARRAY_FOREACH_COUNTER(&components, int i, Projectile *p, { + real t = global.frames - p->birthtime; + cmplx32 offset = (10 + pow(t, 0.5)) * cdir(2.0 * M_PI / 3*i + sqrt(1 + t * t / 300)); + p->pos = pos + offset; + }); + + YIELD; } +} - cmplx target_circle = global.plr.pos + 10 * sqrt(t) * p->args[0]*(1 + 0.1 * sin(0.2*t)); - p->args[0] *= cexp(I*0.12); +TASK(reimu_spirit_bomb_orb, { BoxedPlayer plr; int index; real angle; }) { + int index = ARGS.index; + + Player *plr = ENT_UNBOX(ARGS.plr); + Projectile *orb = TASK_BIND_UNBOXED(PROJECTILE( + .pos = plr->pos, + .timeout = 200 + 20 * index, + .type = PROJ_PLAYER, + .damage = 1000, + .damage_type = DMG_PLAYER_BOMB, + .size = 10 * (1+I), + .layer = LAYER_NODRAW, + .flags = PFLAG_NOREFLECT | PFLAG_NOCOLLISION | PFLAG_NOMOVE | PFLAG_MANUALANGLE, + )); + + BoxedProjectile b_orb = ENT_BOX(orb); + + INVOKE_TASK(reimu_spirit_bomb_orb_visual, b_orb); + INVOKE_TASK_WHEN(&orb->events.killed, reimu_spirit_bomb_orb_impact, b_orb); + CANCEL_TASK_AFTER(&orb->events.killed, THIS_TASK); + + int circletime = 100 + 20 * index; + cmplx target_homing = plr->pos; + cmplx dir = cdir(ARGS.angle); + cmplx vel = 0; + + for(int t = 0;; ++t) { + if(!player_is_bomb_active(plr)) { + kill_projectile(orb); + return; + } - double circlestrength = 1.0 / (1 + exp(t-circletime)); + if(t == circletime) { + target_homing = global.plr.pos - 256*I; + orb->flags &= ~PFLAG_NOCOLLISION; + play_sound("redirect"); + } - p->args[3] = plrutil_homing_target(p->pos, p->args[3]); - cmplx target_homing = p->args[3]; - cmplx homing = target_homing - p->pos; - cmplx v = 0.3 * (circlestrength * (target_circle - p->pos) + 0.2 * (1-circlestrength) * (homing + 2*homing/(cabs(homing)+0.01))); - p->args[2] += (v - p->args[2]) * 0.1; - p->pos += p->args[2]; + cmplx target_circle = plr->pos + 10 * sqrt(t) * dir * (1 + 0.1 * sin(0.2 * t)); + dir *= cdir(0.12); - for(int i = 0; i < 3 /*&& circlestrength < 1*/; i++) { - cmplx trail_pos = p->pos + 10 * cexp(I*2*M_PI/3*(i+t*0.1)); - cmplx trail_vel = global.plr.pos - trail_pos; - trail_vel *= 3 * circlestrength / cabs(trail_vel); + double circlestrength = 1.0 / (1 + exp(t - circletime)); - PARTICLE( - .sprite_ptr = get_sprite("part/stain"), - // .color = reimu_spirit_orb_color(&(Color){0}, i), - .color = HSLA(t/p->timeout, 0.3, 0.3, 0.0), - .pos = trail_pos, - .angle = 2*M_PI*frand(), - .timeout = 30, - .draw_rule = ScaleFade, - .rule = reimu_spirit_bomb_orb_trail, - .args = { trail_vel, 0, 0.4 }, - .flags = PFLAG_NOREFLECT, - ); - } + target_homing = plrutil_homing_target(orb->pos, target_homing); + cmplx homing = target_homing - orb->pos; + cmplx v = 0.3 * (circlestrength * (target_circle - orb->pos) + 0.2 * (1 - circlestrength) * (homing + 2*homing/(cabs(homing)+0.01))); + vel += (v - vel) * 0.1; + orb->pos += vel; - return ACTION_NONE; + for(int i = 0; i < 3; i++) { + cmplx trail_pos = orb->pos + 10 * cdir(2*M_PI/3*(i+t*0.1)); + cmplx trail_vel = global.plr.pos - trail_pos; + trail_vel *= 3 * circlestrength / cabs(trail_vel); + + PARTICLE( + .sprite_ptr = get_sprite("part/stain"), + // .color = reimu_spirit_orb_color(&(Color){0}, i), + .color = HSLA(t/orb->timeout, 0.3, 0.3, 0.0), + .pos = trail_pos, + .angle = rng_angle(), + .timeout = 30, + .draw_rule = pdraw_timeout_scalefade(0.4, 0, 1, 0), + .rule = reimu_spirit_bomb_orb_trail, + .args = { trail_vel }, + .flags = PFLAG_NOREFLECT, + ); + } + + YIELD; + } } static void reimu_spirit_bomb(Player *p) { int count = 6; for(int i = 0; i < count; i++) { - PROJECTILE( - .sprite = "glowball", - .pos = p->pos, - .draw_rule = reimu_spirit_bomb_orb_visual, - .rule = reimu_spirit_bomb_orb, - .args = { cexp(I*2*M_PI/count*i), i, 0, 0}, - .timeout = 200 + 20 * i, - .type = PROJ_PLAYER, - .damage = 1000, - .size = 10 + 10*I, - .layer = LAYER_PLAYER_FOCUS - 1, - .flags = PFLAG_NOREFLECT | PFLAG_NOCOLLISION, - ); + INVOKE_TASK_DELAYED(1, reimu_spirit_bomb_orb, ENT_BOX(p), i, 2*M_PI/count*i); } + global.shake_view = 4; play_sound("bomb_reimu_a"); play_sound("bomb_marisa_b"); } @@ -380,21 +408,42 @@ static void reimu_spirit_bomb_bg(Player *p) { colorfill(0, 0.05 * alpha, 0.1 * alpha, alpha * 0.5); } +TASK(reimu_spirit_ofuda, { cmplx pos; cmplx vel; real damage; }) { + Projectile *ofuda = PROJECTILE( + .proto = pp_ofuda, + .pos = ARGS.pos, + .color = RGBA_MUL_ALPHA(1, 1, 1, 0.5), + .move = move_linear(ARGS.vel), + .type = PROJ_PLAYER, + .damage = ARGS.damage, + .shader = "sprite_particle", + ); + + BoxedProjectile b_ofuda = ENT_BOX(ofuda); + ProjectileList trails = { 0 }; + + int t = 0; + while((ofuda = ENT_UNBOX(b_ofuda)) || trails.first) { + if(ofuda) { + reimu_common_ofuda_swawn_trail(ofuda, &trails); + } + + for(Projectile *p = trails.first; p; p = p->next) { + p->color.g *= 0.95; + } + + process_projectiles(&trails, false); + YIELD; + ++t; + } +} + static void reimu_spirit_shot(Player *p) { play_loop("generic_shot"); if(!(global.frames % 3)) { int i = 1 - 2 * (bool)(global.frames % 6); - PROJECTILE( - .proto = pp_ofuda, - .pos = p->pos + 10 * i - 15.0*I, - .color = RGBA_MUL_ALPHA(1, 1, 1, 0.5), - .rule = reimu_common_ofuda, - .args = { -20.0*I }, - .type = PROJ_PLAYER, - .damage = 100 - 8 * (p->power / 100), - .shader = "sprite_default", - ); + INVOKE_TASK(reimu_spirit_ofuda, p->pos + 10 * i - 15.0*I, -20*I, 100 - 8 * (p->power / 100)); } for(int pwr = 0; pwr <= p->power/100; ++pwr) { @@ -446,13 +495,13 @@ static void reimu_spirit_slave_shot(Enemy *e, int t) { .pos = e->pos, .color = RGBA_MUL_ALPHA(1, 0.9, 0.95, 0.7), .rule = reimu_spirit_homing, - .draw_rule = reimu_spirit_homing_draw, .args = { v , 60, 0, e->pos + v * VIEWPORT_H * VIEWPORT_W /*creal(e->pos)*/ }, .type = PROJ_PLAYER, .damage_type = DMG_PLAYER_SHOT, .damage = creal(e->args[2]), // .timeout = 60, .shader = "sprite_default", + .scale = REIMU_SPIRIT_HOMING_SCALE, .flags = PFLAG_NOCOLLISIONEFFECT, ); } @@ -499,25 +548,18 @@ static int reimu_spirit_slave(Enemy *e, int t) { return ACTION_NONE; } -static int reimu_spirit_yinyang_flare(Projectile *p, int t) { - double a = p->angle; - int r = linear(p, t); - p->angle = a; - return r; -} - static void reimu_spirit_yinyang_focused_visual(Enemy *e, int t, bool render) { if(!render && player_should_shoot(&global.plr, true)) { + RNG_ARRAY(R, 4); PARTICLE( .sprite = "stain", - .color = RGBA(0.5, 0.0 + 0.25 * frand(), 0, 0), - .timeout = 8 + 2 * nfrand(), + .color = RGBA(0.5, vrng_range(R[0], 0, 0.25), 0, 0), + .timeout = vrng_range(R[1], 8, 10), .pos = e->pos, - .args = { -5*I * (1 + frand()), 0, 0.25 + 0*I }, - .angle = 2*M_PI*frand(), - .rule = reimu_spirit_yinyang_flare, - .draw_rule = ScaleFade, - .flags = PFLAG_NOREFLECT, + .angle = vrng_angle(R[3]), + .move = move_linear(-I * vrng_range(R[2], 5, 10)), + .draw_rule = pdraw_timeout_scalefade(0.25, 0, 1, 0), + .flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE, ); } @@ -528,16 +570,17 @@ static void reimu_spirit_yinyang_focused_visual(Enemy *e, int t, bool render) { static void reimu_spirit_yinyang_unfocused_visual(Enemy *e, int t, bool render) { if(!render && player_should_shoot(&global.plr, true)) { + RNG_ARRAY(R, 4); PARTICLE( .sprite = "stain", - .color = RGBA(0.5, 0.125, 0.0 + 0.25 * frand(), 0), - .timeout = 8 + 2 * nfrand(), + .color = RGBA(0.5, 0.125, vrng_range(R[0], 0, 0.25), 0), + .timeout = vrng_range(R[1], 8, 10), .pos = e->pos, - .args = { -5*I * (1 + frand()), 0, 0.25 + 0*I }, - .angle = 2*M_PI*frand(), - .rule = reimu_spirit_yinyang_flare, - .draw_rule = ScaleFade, - .flags = PFLAG_NOREFLECT, + .args = { -I * vrng_range(R[2], 5, 10), 0, 0.25 + 0*I }, + .angle = vrng_angle(R[3]), + .move = move_linear(-I * vrng_range(R[2], 5, 10)), + .draw_rule = pdraw_timeout_scalefade(0.25, 0, 1, 0), + .flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE, ); } diff --git a/src/plrmodes/reimu_b.c b/src/plrmodes/reimu_b.c index 3ff703a581..3ff9418ea8 100644 --- a/src/plrmodes/reimu_b.c +++ b/src/plrmodes/reimu_b.c @@ -82,9 +82,8 @@ static int reimu_dream_gap_bomb_projectile(Projectile *p, int t) { .color = &p->color, .pos = p->pos, .timeout = 20, - .draw_rule = ScaleFade, + .draw_rule = pdraw_timeout_scalefade(0, 3 * range / spr->w, 1, 0), .layer = LAYER_BOSS + 2, - .args = { 0, 0, 3 * range / spr->w * I }, .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, ); @@ -108,12 +107,12 @@ static int reimu_dream_gap_bomb_projectile(Projectile *p, int t) { return ACTION_NONE; } -static void reimu_dream_gap_bomb_projectile_draw(Projectile *p, int t) { +static void reimu_dream_gap_bomb_projectile_draw(Projectile *p, int t, ProjDrawRuleArgs args) { r_draw_sprite(&(SpriteParams) { .sprite_ptr = p->sprite, .shader_ptr = p->shader, .color = &p->color, - .shader_params = &p->shader_params, + .shader_params = &(ShaderCustomParams) {{ p->opacity }}, .pos = { creal(p->pos), cimag(p->pos) }, .scale.both = 0.75 * clamp(t / 5.0, 0.1, 1.0), }); @@ -125,7 +124,7 @@ static void reimu_dream_gap_bomb(Enemy *e, int t) { .sprite = "glowball", .size = 32 * (1 + I), .color = HSLA(t/30.0, 0.5, 0.5, 0.5), - .pos = e->pos + e->args[0] * (frand() - 0.5) * GAP_LENGTH * 0.5, + .pos = e->pos + e->args[0] * GAP_LENGTH * 0.25 * rng_sreal(), .rule = reimu_dream_gap_bomb_projectile, .draw_rule = reimu_dream_gap_bomb_projectile_draw, .type = PROJ_PLAYER, @@ -335,26 +334,25 @@ static void reimu_dream_spawn_warp_effect(cmplx pos, bool exit) { .pos = pos, .color = RGBA(0.5, 0.5, 0.5, 0.5), .timeout = 20, - .angle = frand() * M_PI * 2, - .draw_rule = ScaleFade, - .args = { 0, 0, 0.2 + 1 * I }, + .angle = rng_angle(), + .draw_rule = pdraw_timeout_scalefade(0.2, 1, 1, 0), .layer = LAYER_PLAYER_FOCUS, ); + Color *clr = color_mul_scalar(RGBA(0.75, rng_range(0, 0.4), 0.4, 0), 0.8-0.4*exit); PARTICLE( .sprite = exit ? "stain" : "stardust", .pos = pos, - .color = color_mul_scalar(RGBA(0.75, 0.4 * frand(), 0.4, 0), 0.8-0.4*exit), + .color = clr, .timeout = 20, - .angle = frand() * M_PI * 2, - .draw_rule = ScaleFade, - .args = { 0, 0, 0.1 + 0.6 * I }, + .angle = rng_angle(), + .draw_rule = pdraw_timeout_scalefade(0.1, 0.6, 1, 0), .layer = LAYER_PLAYER_FOCUS, ); } -static void reimu_dream_bullet_warp(Projectile *p, int t) { - if(creal(p->args[3]) > 0 /*global.plr.power / 100*/) { +static void reimu_dream_bullet_warp(Projectile *p, int *warp_count) { + if(*warp_count < 1) { return; } @@ -363,7 +361,7 @@ static void reimu_dream_bullet_warp(Projectile *p, int t) { Rect p_bbox = { p->pos - p_long_side * half, p->pos + p_long_side * half }; FOR_EACH_GAP(gap) { - double a = (carg(-gap->pos0) - carg(p->args[0])); + double a = (carg(-gap->pos0) - carg(p->move.velocity)); if(fabs(a) < 2*M_PI/3) { continue; @@ -392,61 +390,55 @@ static void reimu_dream_bullet_warp(Projectile *p, int t) { reimu_dream_spawn_warp_effect(gap->pos + gap->args[0] * GAP_LENGTH * (fract - 0.5), false); reimu_dream_spawn_warp_effect(o, true); - p->args[0] = -cabs(p->args[0]) * ngap->pos0; - p->pos = o + p->args[0]; - p->args[3] += 1; + // p->args[0] = -cabs(p->args[0]) * ngap->pos0; + // p->pos = o + p->args[0]; + // p->args[3] += 1; + + cmplx new_vel = -cabs(p->move.velocity) * ngap->pos0; + real angle_diff = carg(new_vel) - carg(p->move.velocity); + + p->move.velocity *= cdir(angle_diff); + p->move.acceleration *= cdir(angle_diff); + p->pos = o + p->move.velocity; + --*warp_count; } } } -static int reimu_dream_ofuda(Projectile *p, int t) { - if(t >= 0) { - reimu_dream_bullet_warp(p, t); - } - - cmplx ov = p->args[0]; - double s = cabs(ov); - p->args[0] *= clamp(s * (1.5 - t / 10.0), s*1.0, 1.5*s) / s; - int r = reimu_common_ofuda(p, t); - p->args[0] = ov; +TASK(reimu_dream_ofuda, { cmplx pos; cmplx vel; }) { + Projectile *ofuda = PROJECTILE( + .proto = pp_ofuda, + .pos = ARGS.pos, + .color = RGBA_MUL_ALPHA(1, 1, 1, 0.5), + .move = move_asymptotic(1.5 * ARGS.vel, ARGS.vel, 0.8), + .type = PROJ_PLAYER, + .damage = 60, + .shader = "sprite_particle", + ); - return r; -} + BoxedProjectile b_ofuda = ENT_BOX(ofuda); + ProjectileList trails = { 0 }; -static int reimu_dream_needle(Projectile *p, int t) { - if(t >= 0) { - reimu_dream_bullet_warp(p, t); - } + int warp_cnt = 1; + int t = 0; + while((ofuda = ENT_UNBOX(b_ofuda)) || trails.first) { + if(ofuda) { + reimu_dream_bullet_warp(ofuda, &warp_cnt); + reimu_common_ofuda_swawn_trail(ofuda, &trails); + } - p->angle = carg(p->args[0]); + for(Projectile *p = trails.first; p; p = p->next) { + p->color.g *= 0.95; + } - if(t < 0) { - return ACTION_ACK; + process_projectiles(&trails, false); + YIELD; + ++t; } - - p->pos += p->args[0]; - - Color *c = color_mul(COLOR_COPY(&p->color), RGBA_MUL_ALPHA(0.75, 0.5, 1, 0.35)); - c->a = 0; - - PARTICLE( - .sprite_ptr = p->sprite, - .color = c, - .timeout = 12, - .pos = p->pos, - .args = { p->args[0] * 0.8, 0, 0+3*I }, - .rule = linear, - .draw_rule = ScaleFade, - .layer = LAYER_PARTICLE_LOW, - .flags = PFLAG_NOREFLECT, - ); - - return ACTION_NONE; } static void reimu_dream_shot(Player *p) { play_loop("generic_shot"); - int dmg = 60; if(!(global.frames % 6)) { for(int i = -1; i < 2; i += 2) { @@ -454,16 +446,7 @@ static void reimu_dream_shot(Player *p) { cmplx spread_dir = shot_dir * cexp(I*M_PI*0.5); for(int j = -1; j < 2; j += 2) { - PROJECTILE( - .proto = pp_ofuda, - .pos = p->pos + 10 * j * spread_dir, - .color = RGBA_MUL_ALPHA(1, 1, 1, 0.5), - .rule = reimu_dream_ofuda, - .args = { -20.0 * shot_dir }, - .type = PROJ_PLAYER, - .damage = dmg, - .shader = "sprite_default", - ); + INVOKE_TASK(reimu_dream_ofuda, p->pos + 10 * j * spread_dir, -20.0 * shot_dir); } } } @@ -485,6 +468,39 @@ static void reimu_dream_slave_visual(Enemy *e, int t, bool render) { } } +TASK(reimu_dream_needle, { cmplx pos; cmplx vel; }) { + Projectile *p = TASK_BIND_UNBOXED(PROJECTILE( + .proto = pp_needle2, + .pos = ARGS.pos, + .color = RGBA_MUL_ALPHA(1, 1, 1, 0.35), + .move = move_linear(ARGS.vel), + .type = PROJ_PLAYER, + .damage = 42, + .shader = "sprite_particle", + )); + + Color *trail_color = color_mul(COLOR_COPY(&p->color), RGBA_MUL_ALPHA(0.75, 0.5, 1, 0.35)); + trail_color->a = 0; + + int warp_cnt = 1; + for(int t = 0;; ++t) { + reimu_dream_bullet_warp(p, &warp_cnt); + + PARTICLE( + .sprite_ptr = p->sprite, + .color = trail_color, + .timeout = 12, + .pos = p->pos, + .move = move_linear(p->move.velocity * 0.8), + .draw_rule = pdraw_timeout_scalefade(0, 3, 1, 0), + .layer = LAYER_PARTICLE_LOW, + .flags = PFLAG_NOREFLECT, + ); + + YIELD; + } +} + static int reimu_dream_slave(Enemy *e, int t) { if(t < 0) { return ACTION_ACK; @@ -511,16 +527,7 @@ static int reimu_dream_slave(Enemy *e, int t) { if(player_should_shoot(&global.plr, true)) { if(!((global.frames + 3) % 6)) { - PROJECTILE( - .proto = pp_needle2, - .pos = e->pos, - .color = RGBA_MUL_ALPHA(1, 1, 1, 0.35), - .rule = reimu_dream_needle, - .args = { 20.0 * shotdir }, - .type = PROJ_PLAYER, - .damage = 42, - .shader = "sprite_default", - ); + INVOKE_TASK(reimu_dream_needle, e->pos, 20 * shotdir); } } diff --git a/src/plrmodes/youmu.c b/src/plrmodes/youmu.c index 4520e29573..429f1148f5 100644 --- a/src/plrmodes/youmu.c +++ b/src/plrmodes/youmu.c @@ -121,15 +121,6 @@ void youmu_common_bombbg(Player *plr) { capture_frame(bomb_buffer, r_framebuffer_current()); } -void youmu_common_draw_proj(Projectile *p, const Color *c, float scale) { - r_mat_mv_push(); - r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0); - r_mat_mv_rotate(p->angle + M_PI/2, 0, 0, 1); - r_mat_mv_scale(scale, scale, 1); - ProjDrawCore(p, c); - r_mat_mv_pop(); -} - void youmu_common_bomb_buffer_init(void) { FBAttachmentConfig cfg; memset(&cfg, 0, sizeof(cfg)); @@ -141,4 +132,3 @@ void youmu_common_bomb_buffer_init(void) { cfg.tex_params.wrap.t = TEX_WRAP_MIRROR; bomb_buffer = stage_add_background_framebuffer("Youmu bomb FB", 0.25, 1, 1, &cfg); } - diff --git a/src/plrmodes/youmu.h b/src/plrmodes/youmu.h index 4fee81cfcb..973b7d76c5 100644 --- a/src/plrmodes/youmu.h +++ b/src/plrmodes/youmu.h @@ -20,7 +20,6 @@ extern PlayerCharacter character_youmu; double youmu_common_property(Player *plr, PlrProperty prop); void youmu_common_shot(Player *plr); -void youmu_common_draw_proj(Projectile *p, const Color *c, float scale); void youmu_common_bombbg(Player *plr); void youmu_common_bomb_buffer_init(void); diff --git a/src/plrmodes/youmu_a.c b/src/plrmodes/youmu_a.c index a974a1948c..d448423156 100644 --- a/src/plrmodes/youmu_a.c +++ b/src/plrmodes/youmu_a.c @@ -15,7 +15,7 @@ #define MYON (global.plr.slaves.first) -static Color* myon_color(Color *c, float f, float opacity, float alpha) { +static Color *myon_color(Color *c, float f, float opacity, float alpha) { // *RGBA_MUL_ALPHA(0.8+0.2*f, 0.9-0.4*sqrt(f), 1.0-0.2*f*f, a); *c = *RGBA_MUL_ALPHA(0.8+0.2*f, 0.9-0.4*sqrt(f), 1.0-0.35*f*f, opacity); c->a *= alpha; @@ -56,58 +56,75 @@ static int myon_flare_particle_rule(Projectile *p, int t) { return r; } -static void myon_draw_trail(Projectile *p, int t) { - float fadein = clamp(t/10.0, p->args[2], 1); - float s = min(1, 1 - t / (double)p->timeout); - float a = p->color.r*fadein; - Color c; - myon_color(&c, creal(p->args[3]), a * s * s, 0); - youmu_common_draw_proj(p, &c, fadein * (2-s) * p->args[1]); +static void myon_draw_trail_func(Projectile *p, int t, ProjDrawRuleArgs args) { + float focus_factor = args[0].as_float[0]; + float scale = args[0].as_float[1]; + + float fadein = clamp(t/10.0, 0, 1); + float s = 1 - projectile_timeout_factor(p); + + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + + float a = spbuf.color.r * fadein; + myon_color(&spbuf.color, focus_factor, a * s * s, 0); + sp.scale.as_cmplx *= fadein * (2-s) * scale; + + r_draw_sprite(&sp); +} + +static ProjDrawRule myon_draw_trail(float scale, float focus_factor) { + return (ProjDrawRule) { + .func = myon_draw_trail_func, + .args[0].as_float = { focus_factor, scale }, + }; } static void spawn_stardust(cmplx pos, float myon_color_f, int timeout, cmplx v) { + RNG_ARRAY(R, 4); + PARTICLE( .sprite = "stardust", - .pos = pos+5*frand()*cexp(2.0*I*M_PI*frand()), - .draw_rule = myon_draw_trail, + .pos = pos + vrng_range(R[0], 0, 5) * vrng_dir(R[1]), + .draw_rule = myon_draw_trail(vrng_range(R[2], 0.2, 0.3), myon_color_f), .rule = myon_particle_rule, .timeout = timeout, - .args = { v, 0.2 + 0.1 * frand(), 0, myon_color_f }, - .angle = M_PI*2*frand(), + .args = { v, 0, 0, myon_color_f }, + .angle = vrng_angle(R[3]), .flags = PFLAG_NOREFLECT, .layer = LAYER_PARTICLE_LOW | 1, ); } static void myon_spawn_trail(Enemy *e, int t) { - float a = global.frames * 0.07; - cmplx pos = e->pos + 3 * (cos(a) + I * sin(a)); - - cmplx stardust_v = 3 * myon_tail_dir() * cexp(I*M_PI/16*sin(1.33*t)); - float f = abs(global.plr.focus) / 30.0; + cmplx pos = e->pos + 3 * cdir(global.frames * 0.07); + cmplx stardust_v = 3 * myon_tail_dir() * cdir(M_PI/16*sin(1.33*t)); + real f = abs(global.plr.focus) / 30.0; stardust_v = f * stardust_v + (1 - f) * -I; if(player_should_shoot(&global.plr, true)) { + RNG_ARRAY(R, 7); + PARTICLE( .sprite = "smoke", - .pos = pos+10*frand()*cexp(2.0*I*M_PI*frand()), - .draw_rule = myon_draw_trail, + .pos = pos + vrng_range(R[0], 0, 10) * vrng_dir(R[1]), + .draw_rule = myon_draw_trail(0.2, f), .rule = myon_particle_rule, .timeout = 60, - .args = { -I*0.0*cexp(I*M_PI/16*sin(t)), -0.2, 0, f }, + .args = { 0, -0.2, 0, f }, .flags = PFLAG_NOREFLECT, - .angle = M_PI*2*frand(), + .angle = vrng_angle(R[2]), ); PARTICLE( .sprite = "flare", - .pos = pos+5*frand()*cexp(2.0*I*M_PI*frand()), - .draw_rule = Shrink, + .pos = pos + vrng_range(R[3], 0, 5) * vrng_dir(R[4]), + .draw_rule = pdraw_timeout_scale(2, 0.01), .rule = myon_particle_rule, .timeout = 10, - .args = { cexp(I*M_PI*2*frand())*0.5, 0.2, 0, f }, + .args = { 0.5 * vrng_dir(R[5]), 0.2, 0, f }, .flags = PFLAG_NOREFLECT, - .angle = M_PI*2*frand(), + .angle = vrng_angle(R[6]), ); } @@ -117,22 +134,25 @@ static void myon_spawn_trail(Enemy *e, int t) { .rule = myon_flare_particle_rule, .timeout = 40, .args = { f * stardust_v, 0, 0, f }, - .draw_rule = Shrink, + .draw_rule = pdraw_timeout_scale(2, 0.01), .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, - .angle = M_PI*2*frand(), + .angle = rng_angle(), .layer = LAYER_PARTICLE_LOW, ); spawn_stardust(pos, f, 60, stardust_v); } -static void myon_draw_proj_trail(Projectile *p, int t) { - float time_progress = t / p->timeout; +static void myon_draw_proj_trail(Projectile *p, int t, ProjDrawRuleArgs args) { + float time_progress = projectile_timeout_factor(p); float s = 2 * time_progress; float a = min(1, s) * (1 - time_progress); - Color c = p->color; - color_mul_scalar(&c, a); - youmu_common_draw_proj(p, &c, s * p->args[1]); + + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + color_mul_scalar(&spbuf.color, a); + sp.scale.as_cmplx *= s; + r_draw_sprite(&sp); } static int myon_proj(Projectile *p, int t) { @@ -157,22 +177,18 @@ static int myon_proj(Projectile *p, int t) { // .color = derive_color(p->color, CLRMASK_A, rgba(0, 0, 0, 0.075)), .color = c, .draw_rule = myon_draw_proj_trail, - .rule = linear, .timeout = 10, - .args = { p->args[0]*0.8, 0.6 }, + .move = move_linear(p->args[0]*0.8), .flags = PFLAG_NOREFLECT, .angle = p->angle, + .scale = 0.6, ); - p->shader_params.vector[0] = pow(1 - min(1, t / 10.0), 2); + p->opacity = 1.0f - powf(1.0f - fminf(1.0f, t / 10.0f), 2.0f); return ACTION_NONE; } -static void myon_proj_draw(Projectile *p, int t) { - youmu_common_draw_proj(p, &p->color, 1); -} - static Projectile* youmu_mirror_myon_proj(ProjPrototype *proto, cmplx pos, double speed, double angle, double aoffs, double upfactor, float dmg) { cmplx dir = cexp(I*(M_PI/2 + aoffs)) * upfactor + cexp(I * (angle + aoffs)) * (1 - upfactor); dir = dir / cabs(dir); @@ -204,7 +220,6 @@ static Projectile* youmu_mirror_myon_proj(ProjPrototype *proto, cmplx pos, doubl .pos = pos, .rule = myon_proj, .args = { speed*dir }, - .draw_rule = myon_proj_draw, .type = PROJ_PLAYER, .layer = LAYER_PLAYER_SHOT | 0x10, .damage = dmg, @@ -341,7 +356,6 @@ static Projectile* youmu_mirror_self_shot(Player *plr, cmplx ofs, cmplx vel, flo .damage = dmg, .shader = "sprite_default", .layer = LAYER_PLAYER_SHOT | 0x20, - .draw_rule = myon_proj_draw, .rule = youmu_mirror_self_proj, .args = { vel*0.2*cexp(I*M_PI*0.5*sign(creal(ofs))), vel, turntime, @@ -372,17 +386,19 @@ static void youmu_mirror_shot(Player *plr) { } static void youmu_mirror_bomb_damage_callback(EntityInterface *victim, cmplx victim_origin, void *arg) { - victim_origin += cexp(I*M_PI*2*frand()) * 15 * frand(); + cmplx ofs_dir = rng_dir(); + victim_origin += ofs_dir * rng_range(0, 15); + + RNG_ARRAY(R, 6); PARTICLE( .sprite = "blast_huge_halo", .pos = victim_origin, - .color = RGBA(0.6 + 0.1 * frand(), 0.8, 0.7 + 0.075 * frand(), 0.5 * frand()), + .color = RGBA(vrng_range(R[0], 0.6, 0.7), 0.8, vrng_range(R[1], 0.7, 0.775), vrng_range(R[2], 0, 0.5)), .timeout = 30, - .draw_rule = ScaleFade, - .args = { 0, 0, (0.0 + 0.5*I) }, + .draw_rule = pdraw_timeout_scalefade(0, 0.5, 1, 0), .layer = LAYER_PARTICLE_HIGH | 0x4, - .angle = frand() * 2 * M_PI, + .angle = vrng_angle(R[3]), .flags = PFLAG_REQUIREDPARTICLE, ); @@ -390,19 +406,18 @@ static void youmu_mirror_bomb_damage_callback(EntityInterface *victim, cmplx vic return; } - float t = frand(); + real t = rng_real(); + RNG_NEXT(R); PARTICLE( .sprite = "petal", .pos = victim_origin, .rule = asymptotic, - .draw_rule = Petal, + .draw_rule = pdraw_petal_random(), .color = RGBA(sin(5*t) * t, cos(5*t) * t, 0.5 * t, 0), .args = { - sign(nfrand())*(3+t*5*frand())*cexp(I*M_PI*8*t), + vrng_sign(R[0]) * vrng_range(R[1], 3, 3 + 5 * t) * cdir(M_PI*8*t), 5+I, - frand() + frand()*I, - frand() + 360.0*I*frand() }, .layer = LAYER_PARTICLE_PETAL, ); @@ -437,26 +452,28 @@ static int youmu_mirror_bomb_controller(Enemy *e, int t) { .sprite = "arc", .pos = e->pos, .rule = linear, - .draw_rule = Fade, + .draw_rule = pdraw_timeout_fade(1, 0), .color = RGBA(0.9, 0.8, 1.0, 0.0), .timeout = 30, .args = { - 2*cexp(2*I*M_PI*frand()), + 2 * rng_dir(), }, .flags = _i%2 == 0 ? PFLAG_REQUIREDPARTICLE : 0 ); + + RNG_ARRAY(R, 2); + PARTICLE( .sprite = "stain", .pos = e->pos, .rule = accelerated, - .draw_rule = GrowFade, - .angle = 2*M_PI*frand(), + .draw_rule = pdraw_timeout_scalefade(0, 3, 1, 0), + .angle = vrng_angle(R[0]), .color = RGBA(0.2, 0.1, 1.0, 0.0), .timeout = 50, .args = { - -1*e->args[0]*cexp(I*0.2*nfrand())/30, + -1*e->args[0]*cdir(0.2*rng_real())/30, 0.1*e->args[0]*I*sin(t/4.)/30, - 2 }, .flags = _i%2 == 0 ? PFLAG_REQUIREDPARTICLE : 0 ); diff --git a/src/plrmodes/youmu_b.c b/src/plrmodes/youmu_b.c index d28537a5ed..fdc816b3ea 100644 --- a/src/plrmodes/youmu_b.c +++ b/src/plrmodes/youmu_b.c @@ -11,243 +11,64 @@ #include "global.h" #include "plrmodes.h" #include "youmu.h" +#include "util/glm.h" static cmplx youmu_homing_target(cmplx org, cmplx fallback) { return plrutil_homing_target(org, fallback); } -static void youmu_homing_draw_common(Projectile *p, float clrfactor, float scale, float alpha) { - Color c = p->color; - color_mul(&c, RGBA(0.7f + 0.3f * clrfactor, 0.9f + 0.1f * clrfactor, 1, 1)); +static void youmu_homing_trail(Projectile *p, cmplx v, int to) { + uint32_t tmp = p->ent.spawn_id; + float u = M_PI * 2.0f * (float)(splitmix32(&tmp) / (double)UINT32_MAX); - if(alpha <= 0) { - return; - } - - bool special_snowflake_shader_bullshit = p->shader_params.vector[1] != 0; - - if(special_snowflake_shader_bullshit) { - // FIXME: maybe move this to logic someh-- nah. Don't even bother with this crap. - float old = p->shader_params.vector[1]; - p->shader_params.vector[1] = alpha; - youmu_common_draw_proj(p, &c, scale); - p->shader_params.vector[1] = old; - } else { - color_mul_scalar(&c, alpha); - youmu_common_draw_proj(p, &c, scale); - } -} - -static void youmu_homing_draw_proj(Projectile *p, int t) { - float a = clamp(1.0f - (float)t / p->args[2], 0, 1); - youmu_homing_draw_common(p, a, 1, 0.5f); -} - -static void youmu_homing_draw_trail(Projectile *p, int t) { - float a = clamp(1.0f - (float)t / p->timeout, 0, 1); - youmu_homing_draw_common(p, a, 5 * (1 - a), 0.15f * a); -} + RNG_ARRAY(R, 5); -static void youmu_trap_draw_trail(Projectile *p, int t) { - float a = clamp(1.0f - (float)t / p->timeout, 0, 1); - youmu_homing_draw_common(p, a, 2 - a, 0.15f * a); -} - -static void youmu_trap_draw_child_proj(Projectile *p, int t) { - float to = p->args[2]; - float a = clamp(1.0 - 3 * ((t - (to - to/3)) / to), 0, 1); - a = 1 - pow(1 - a, 2); - youmu_homing_draw_common(p, a, 1 + 2 * pow(1 - a, 2), a); -} - -static float youmu_trap_charge(int t) { - return pow(clamp(t / 60.0, 0, 1), 1.5); -} + PARTICLE( + .sprite = "stardust", + .pos = p->pos + vrng_range(R[0], 3, 12) * vrng_dir(R[1]), + .color = RGBA(0.0, 0.3 * vrng_real(R[2]), 0.3, 0.0), + // .draw_rule = pdraw_timeout_fade(1, 0), + .draw_rule = pdraw_timeout_scalefade_exp(0.001*I, vrng_range(R[3], 0.5, 1.0)*(1+I), 2, 0, 2), + .move = move_linear(-v), + .timeout = to, + .scale = 0.5, + .angle = vrng_angle(R[4]), + .flags = PFLAG_NOREFLECT | PFLAG_PLRSPECIALPARTICLE | PFLAG_MANUALANGLE, + .layer = LAYER_PARTICLE_MID, + ); -static Projectile* youmu_homing_trail(Projectile *p, cmplx v, int to) { - return PARTICLE( - .sprite_ptr = p->sprite, + PARTICLE( + .sprite = "smoothdot", .pos = p->pos, - .color = &p->color, - .angle = p->angle, - .rule = linear, + .color = color_mul(RGBA(0.2, 0.24, 0.3, 0.2), &p->color), + .move = move_asymptotic_simple(-0.5*v*cdir(0.2*sin(u+3*creal(p->pos)/VIEWPORT_W*M_TAU) + 0.2*cos(u+3*cimag(p->pos)/VIEWPORT_H*M_TAU)), 2), + .draw_rule = pdraw_timeout_scalefade_exp(0.5+0.5*I, 3+7*I, 1, 0, 2), .timeout = to, - .draw_rule = youmu_homing_draw_trail, - .args = { v }, .flags = PFLAG_NOREFLECT, - .shader_ptr = p->shader, - .shader_params = &p->shader_params, .layer = LAYER_PARTICLE_LOW, ); } -static int youmu_homing(Projectile *p, int t) { // a[0]: velocity, a[1]: aim (r: base, i: gain), a[2]: (r: timeout, i: charge), a[3]: initial target - if(t == EVENT_BIRTH) { - return ACTION_ACK; - } - - if(t == EVENT_DEATH) { - PARTICLE( - .sprite = "blast", - .color = color_lerp(RGBA(0.5, 0.7, 1.0, 0.5), RGBA(1.0, 0.65, 0.8, 0.5), cimag(p->args[2])), - .pos = p->pos, - .timeout = 20, - .draw_rule = ScaleFade, - .layer = LAYER_PARTICLE_HIGH, - .args = { 0, 0, 0.5 * I }, - .flags = PFLAG_NOREFLECT, - .angle = M_PI*nfrand(), - ); - return ACTION_ACK; - } - - if(t > creal(p->args[2])) { - return ACTION_DESTROY; - } - - p->args[3] = youmu_homing_target(p->pos, p->args[3]); - - double v = cabs(p->args[0]); - cmplx aimdir = cexp(I*carg(p->args[3] - p->pos)); - - p->args[0] += creal(p->args[1]) * aimdir; - // p->args[0] = v * cexp(I*carg(p->args[0])) + cimag(p->args[1]) * aimdir; - p->args[0] *= v / cabs(p->args[0]); - - p->args[1] = creal(p->args[1]) + cimag(p->args[1]) * (1 + I); - - p->angle = carg(p->args[0]); - p->pos += p->args[0]; - - Projectile *trail = youmu_homing_trail(p, 0.5 * p->args[0], 12); - trail->args[2] = p->args[2]; - - p->shader_params.vector[0] = cimag(p->args[2]); - trail->shader_params.vector[0] = cimag(p->args[2]); - - return 1; -} - -static Projectile* youmu_trap_trail(Projectile *p, cmplx v, int t, bool additive) { - Projectile *trail = youmu_homing_trail(p, v, t); - trail->draw_rule = youmu_trap_draw_trail; - // trail->args[3] = global.frames - p->birthtime; - trail->shader_params.vector[0] = p->shader_params.vector[0]; - trail->flags |= PFLAG_REQUIREDPARTICLE; - - if(additive) { - trail->color.a = 0; - } else { - trail->flags |= PFLAG_PLRSPECIALPARTICLE; - } - - return trail; -} - -static int youmu_trap(Projectile *p, int t) { - if(t == EVENT_DEATH) { - PARTICLE( - .proto = pp_blast, - .pos = p->pos, - .timeout = 15, - .draw_rule = Blast, - .flags = PFLAG_REQUIREDPARTICLE, - .layer = LAYER_PARTICLE_LOW, - ); - return ACTION_ACK; - } - - // FIXME: replace this with timeout? - double expiretime = creal(p->args[1]); - - if(t > expiretime) { - return ACTION_DESTROY; - } - - if(t < 0) { - return ACTION_ACK; - } - - float charge = youmu_trap_charge(t); - p->shader_params.vector[0] = charge; - - if(!(global.plr.inputflags & INFLAG_FOCUS)) { - PARTICLE( - .proto = pp_blast, - .pos = p->pos, - .timeout = 20, - .draw_rule = Blast, - .flags = PFLAG_REQUIREDPARTICLE, - .layer = LAYER_PARTICLE_LOW, - ); - - PARTICLE( - .proto = pp_blast, - .pos = p->pos, - .timeout = 23, - .draw_rule = Blast, - .flags = PFLAG_REQUIREDPARTICLE, - .layer = LAYER_PARTICLE_LOW, - ); - - int cnt = round(creal(p->args[2])); - int dmg = cimag(p->args[2]); - cmplx aim = p->args[3]; - - for(int i = 0; i < cnt; ++i) { - int dur = 120; // 55 + 20 * nfrand(); - float a = (i / (float)cnt) * M_PI * 2; - cmplx dir = cexp(I*(a)); - - PROJECTILE( - .proto = pp_youmu, - .pos = p->pos, - .color = RGBA(1, 1, 1, 0.85), - .rule = youmu_homing, - .args = { 5 * (1 + charge) * dir, aim, dur + charge*I, creal(p->pos) - VIEWPORT_H*I }, - .type = PROJ_PLAYER, - .damage = dmg, - .draw_rule = youmu_trap_draw_child_proj, - .shader = "sprite_youmu_charged_shot", - .shader_params = &(ShaderCustomParams){{ 0, 1 }}, - ); - } - - // TODO: dedicated sound for this? - play_sound("enemydeath"); - play_sound("hit"); +static void youmu_particle_slice_draw(Projectile *p, int t, ProjDrawRuleArgs args) { + float lifetime = p->timeout; + float tt = t/lifetime; + float f = 0; - return ACTION_DESTROY; - } - - p->angle = global.frames + t; - p->pos += p->args[0] * (0.01 + 0.99 * max(0, (10 - t) / 10.0)); - - youmu_trap_trail(p, cexp(I*p->angle), 30 * (1 + charge), true); - youmu_trap_trail(p, cexp(I*-p->angle), 30, false); - return 1; -} - -static void youmu_particle_slice_draw(Projectile *p, int t) { - double lifetime = p->timeout; - double tt = t/lifetime; - double f = 0; if(tt > 0.1) { f = min(1,(tt-0.1)/0.2); } + if(tt > 0.5) { f = 1+(tt-0.5)/0.5; } - r_mat_mv_push(); - r_mat_mv_translate(creal(p->pos), cimag(p->pos),0); - r_mat_mv_rotate(p->angle, 0, 0, 1); - r_mat_mv_scale(f, 1, 1); - ProjDrawCore(p, &p->color); - r_mat_mv_pop(); + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + sp.scale.x *= f; + r_draw_sprite(&sp); - double slicelen = 500; - cmplx slicepos = p->pos-(tt>0.1)*slicelen*I*cexp(I*p->angle)*(5*pow(tt-0.1,1.1)-0.5); + float slicelen = 500; + cmplx slicepos = p->pos-(tt>0.1)*slicelen*I*cdir(p->angle)*(5*pow(tt-0.1,1.1)-0.5); r_draw_sprite(&(SpriteParams) { .sprite_ptr = aniplayer_get_frame(&global.plr.ani), @@ -283,20 +104,18 @@ static int youmu_particle_slice_logic(Projectile *p, int t) { p->color = *RGBA(a, a, a, 0); - cmplx phase = cexp(p->angle * I); if(t%5 == 0) { - tsrand_fill(4); + cmplx phase = cdir(p->angle); + PARTICLE( .sprite = "petal", .pos = p->pos-400*phase, .rule = youmu_slice_petal, - .draw_rule = Petal, + .draw_rule = pdraw_petal_random(), .args = { phase, - phase*cexp(0.1*I), - afrand(1) + afrand(2)*I, - afrand(3) + 360.0*I*afrand(0) + phase*cdir(0.1), }, .layer = LAYER_PARTICLE_HIGH | 0x2, .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, @@ -340,7 +159,7 @@ static int youmu_slash(Enemy *e, int t) { .rule = youmu_particle_slice_logic, .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, .timeout = 100, - .angle = carg(pos), + .angle = carg(pos) + M_PI/2, .layer = LAYER_PARTICLE_HIGH | 0x1, ); } @@ -379,8 +198,7 @@ static void youmu_haunting_power_shot(Player *plr, int p) { .proto = pp_hghost, .pos = plr->pos, .rule = youmu_asymptotic, - .color = RGB(0.7 + 0.3 * (1-np), 0.8 + 0.2 * sqrt(1-np), 1.0), - .draw_rule = youmu_homing_draw_proj, + .color = color_mul_scalar(RGB(0.7 + 0.3 * (1-np), 0.8 + 0.2 * sqrt(1-np), 1.0), 0.5), .args = { speed * dir * (1 - 0.25 * (1 - np)), 3 * (1 - pow(1 - np, 2)), 60, }, .type = PROJ_PLAYER, .damage = 20, @@ -389,6 +207,237 @@ static void youmu_haunting_power_shot(Player *plr, int p) { } } +TASK(youmu_homing_shot, { BoxedPlayer plr; }) { + Projectile *p = TASK_BIND_UNBOXED(PROJECTILE( + .proto = pp_hghost, + .pos = ENT_UNBOX(ARGS.plr)->pos, + .color = RGB(0.75, 0.9, 1), + .type = PROJ_PLAYER, + .damage = 120, + .shader = "sprite_default", + )); + + real speed = 10; + real aim_strength = 0; + real aim_strength_accel = 0.02; + p->move = move_linear(-I * speed); + cmplx target = VIEWPORT_W * 0.5; + + for(int i = 0; i < 60; ++i) { + target = youmu_homing_target(p->pos, target); + cmplx aimdir = cnormalize(target - p->pos - p->move.velocity*10); + p->move.velocity += aim_strength * aimdir; + p->move.velocity *= speed / cabs(p->move.velocity); + aim_strength += aim_strength_accel; + + youmu_homing_trail(p, 0.5 * p->move.velocity, 12); + YIELD; + } +} + +TASK(youmu_orb_homing_spirit_expire, { BoxedProjectile p; }) { + Projectile *p = ENT_UNBOX(ARGS.p); + + PARTICLE( + .sprite_ptr = p->sprite, + .shader_ptr = p->shader, + .color = &p->color, + .timeout = 30, + .draw_rule = pdraw_timeout_scalefade(1+I, 0.1+I, 1, 0), + .pos = p->pos, + .move = p->move, + .angle = p->angle, + .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, + .layer = LAYER_PLAYER_SHOT, + ); +} + +static int youmu_orb_homing_spirit_timeout(Projectile *orb) { + return orb->timeout - projectile_time(orb); +} + +TASK(youmu_orb_homing_spirit, { cmplx pos; cmplx velocity; cmplx target; real charge; real damage; real spin; BoxedProjectile orb; }) { + int timeout = youmu_orb_homing_spirit_timeout(ENT_UNBOX(ARGS.orb)); + + if(timeout <= 0) { + return; + } + + Projectile *p = TASK_BIND_UNBOXED(PROJECTILE( + .proto = pp_hghost, + .pos = ARGS.pos, + .color = color_mul_scalar(RGB(0.75, 0.9, 1), 0.5), + .type = PROJ_PLAYER, + .timeout = timeout, + .damage = ARGS.damage, + .shader = "sprite_particle", + )); + + INVOKE_TASK_AFTER(&p->events.killed, youmu_orb_homing_spirit_expire, ENT_BOX(p)); + + real speed = cabs(ARGS.velocity); + real speed_target = speed; + real aim_strength = -0.1; + p->move = move_accelerated(ARGS.velocity, -0.06 * ARGS.velocity); + p->move.retention = cdir(ARGS.spin); + cmplx target = ARGS.target; + + bool aim_peaked = false; + bool orb_died = false; + Projectile *orb = NULL; + + for(;;) { + if(!orb_died) { + orb = ENT_UNBOX(ARGS.orb); + + if(orb == NULL) { + orb_died = true; + p->timeout = 0; + // p->move.velocity = 0; + p->move.retention *= 0.9; + aim_peaked = false; + aim_strength = -0.2; + speed *= 1.2; + } + } + + if(orb) { + target = orb->pos; + } else { + target = youmu_homing_target(p->pos, creal(global.plr.pos) - 128*I); + } + + cmplx aimdir = cnormalize(target - p->pos - p->move.velocity); + capproach_asymptotic_p(&p->move.retention, 0.8, 0.1, 1e-3); + p->move.acceleration *= 0.95; + p->move.acceleration += aim_strength * aimdir; + + if(aim_peaked) { + if(!orb) { + approach_p(&aim_strength, 0.00, 0.001); + } + } else { + approach_p(&aim_strength, 0.1, 0.02); + if(aim_strength == 0.1) { + aim_peaked = true; + } + } + + real s = max(speed, cabs(p->move.velocity)); + p->move.velocity = s * cnormalize(p->move.velocity + aim_strength * s * aimdir); + approach_asymptotic_p(&speed, speed_target, 0.05, 1e-5); + + youmu_homing_trail(p, 0.5 * p->move.velocity, 12); + YIELD; + } +} + +TASK(youmu_orb_update, { BoxedPlayer plr; BoxedProjectile orb; }) { + Player *plr = ENT_UNBOX(ARGS.plr); + Projectile *orb = TASK_BIND(ARGS.orb); + + for(;;) { + float tf = glm_ease_bounce_out(1.0f - projectile_timeout_factor(orb)); + orb->color.g = tf * tf; + orb->color.b = tf; + orb->scale = 0.5 + 0.5 * tf; + orb->angle += 0.2; + + // TODO events for player input? + if(!(plr->inputflags & INFLAG_FOCUS)) { + PARTICLE( + .sprite = "blast_huge_rays", + .pos = orb->pos, + .timeout = 20, + .color = RGBA(0.1, 0.5, 0.1, 0.0), + .draw_rule = pdraw_timeout_scalefade_exp(0.01*(1+I), 1, 1, 0, 2), + .flags = PFLAG_REQUIREDPARTICLE, + .angle = rng_angle(), + .layer = LAYER_PARTICLE_LOW, + ); + + PARTICLE( + .sprite = "blast_huge_halo", + .pos = orb->pos, + .timeout = 30, + .color = RGBA(0.1, 0.1, 0.5, 0.0), + .draw_rule = pdraw_timeout_scalefade_exp(1, 0.01*(1+I), 1, 0, 2), + .flags = PFLAG_REQUIREDPARTICLE, + .angle = rng_angle(), + .layer = LAYER_PARTICLE_LOW, + ); + + PARTICLE( + .sprite = "blast_huge_halo", + .pos = orb->pos, + .timeout = 40, + .color = RGBA(0.5, 0.1, 0.1, 0.0), + .draw_rule = pdraw_timeout_scalefade_exp(0.8, -0.3*(1+I), 1, 0, 2), + .flags = PFLAG_REQUIREDPARTICLE, + .angle = rng_angle(), + .layer = LAYER_PARTICLE_LOW, + ); + + // TODO sound effect; + kill_projectile(orb); + break; + } + + YIELD; + } +} + +TASK(youmu_orb_death, { BoxedProjectile orb; BoxedTask control_task; }) { + cotask_cancel(cotask_unbox(ARGS.control_task)); + Projectile *orb = ENT_UNBOX(ARGS.orb); + + PARTICLE( + .proto = pp_blast, + .pos = orb->pos, + .timeout = 20, + .draw_rule = pdraw_blast(), + .flags = PFLAG_REQUIREDPARTICLE, + .layer = LAYER_PARTICLE_LOW, + ); + + PARTICLE( + .proto = pp_blast, + .pos = orb->pos, + .timeout = 23, + .draw_rule = pdraw_blast(), + .flags = PFLAG_REQUIREDPARTICLE, + .layer = LAYER_PARTICLE_LOW, + ); +} + +TASK(youmu_orb_shot, { BoxedPlayer plr; }) { + Player *plr = ENT_UNBOX(ARGS.plr); + int pwr = plr->power / 100; + + Projectile *orb = TASK_BIND_UNBOXED(PROJECTILE( + .proto = pp_youhoming, + .pos = plr->pos, + .color = RGB(1, 1, 1), + .type = PROJ_PLAYER, + .damage = 1000, + .timeout = 100 + 10 * pwr, + .move = move_asymptotic(-30.0*I, -0.7*I, 0.8), + .flags = PFLAG_MANUALANGLE, + )); + + INVOKE_TASK(youmu_orb_update, ARGS.plr, ENT_BOX(orb)); + INVOKE_TASK_AFTER(&orb->events.killed, youmu_orb_death, ENT_BOX(orb), THIS_TASK); + + real pdmg = 120 - 18 * 4 * (1 - pow(1 - pwr / 4.0, 1.5)); + cmplx v = 5 * I; + + for(;;) { + WAIT(11); + INVOKE_TASK(youmu_orb_homing_spirit, orb->pos, v, 0, 0, pdmg, 0.1, ENT_BOX(orb)); + INVOKE_TASK(youmu_orb_homing_spirit, orb->pos, v, 0, 0, pdmg, -0.1, ENT_BOX(orb)); + } +} + static void youmu_haunting_shot(Player *plr) { youmu_common_shot(plr); @@ -397,34 +446,11 @@ static void youmu_haunting_shot(Player *plr) { int pwr = plr->power / 100; if(!(global.frames % (45 - 4 * pwr))) { - int pcnt = 11 + pwr * 4; - int pdmg = 120 - 18 * 4 * (1 - pow(1 - pwr / 4.0, 1.5)); - cmplx aim = 0.15*I; - - PROJECTILE( - .proto = pp_youhoming, - .pos = plr->pos, - .color = RGB(1, 1, 1), - .rule = youmu_trap, - .args = { -30.0*I, 120, pcnt+pdmg*I, aim }, - .type = PROJ_PLAYER, - .damage = 1000, - .shader = "sprite_youmu_charged_shot", - .shader_params = &(ShaderCustomParams){{ 0, 1 }}, - ); + INVOKE_TASK(youmu_orb_shot, ENT_BOX(plr)); } } else { if(!(global.frames % 6)) { - PROJECTILE( - .proto = pp_hghost, - .pos = plr->pos, - .color = RGB(0.75, 0.9, 1), - .rule = youmu_homing, - .args = { -10.0*I, 0.02*I, 60, VIEWPORT_W*0.5 }, - .type = PROJ_PLAYER, - .damage = 120, - .shader = "sprite_default", - ); + INVOKE_TASK(youmu_homing_shot, ENT_BOX(plr)); } for(int p = 1; p <= 2*PLR_MAX_POWER/100; ++p) { @@ -447,10 +473,6 @@ static void youmu_haunting_preload(void) { "part/youmu_slice", NULL); - preload_resources(RES_SHADER_PROGRAM, flags, - "sprite_youmu_charged_shot", - NULL); - preload_resources(RES_TEXTURE, flags, "youmu_bombbg1", NULL); diff --git a/src/projectile.c b/src/projectile.c index 0b3875dcb0..241f42049b 100644 --- a/src/projectile.c +++ b/src/projectile.c @@ -13,12 +13,12 @@ #include "global.h" #include "list.h" #include "stageobjects.h" +#include "util/glm.h" -ht_ptr2int_t shader_sublayer_map; +static ht_ptr2int_t shader_sublayer_map; static ProjArgs defaults_proj = { .sprite = "proj/", - .draw_rule = ProjDraw, .dest = &global.projs, .type = PROJ_ENEMY, .damage_type = DMG_ENEMY_SHOT, @@ -30,13 +30,12 @@ static ProjArgs defaults_proj = { static ProjArgs defaults_part = { .sprite = "part/", - .draw_rule = ProjDraw, .dest = &global.particles, .type = PROJ_PARTICLE, .damage_type = DMG_UNDEFINED, .color = RGB(1, 1, 1), .blend = BLEND_PREMUL_ALPHA, - .shader = "sprite_default", + .shader = "sprite_particle", .layer = LAYER_PARTICLE_MID, }; @@ -60,8 +59,8 @@ static void process_projectile_args(ProjArgs *args, ProjArgs *defaults) { } } - if(!args->draw_rule) { - args->draw_rule = ProjDraw; + if(!args->draw_rule.func) { + args->draw_rule = pdraw_basic(); } if(!args->blend) { @@ -100,6 +99,16 @@ static void process_projectile_args(ProjArgs *args, ProjArgs *defaults) { } } + if(args->scale == 0) { + args->scale = 1+I; + } else if(cimagf(args->scale) == 0) { + args->scale = CMPLXF(crealf(args->scale), crealf(args->scale)); + } + + if(args->opacity == 0) { + args->opacity = 1; + } + assert(args->type <= PROJ_PLAYER); } @@ -145,6 +154,18 @@ static inline int proj_call_rule(Projectile *p, int t) { ACTION_ACK ); } + } else if(t >= 0) { + if(!(p->flags & PFLAG_NOMOVE)) { + move_update(&p->pos, &p->move); + } + + if(!(p->flags & PFLAG_MANUALANGLE)) { + cmplx delta_pos = p->pos - p->prevpos; + + if(delta_pos) { + p->angle = carg(delta_pos); + } + } } if(/*t == 0 ||*/ t == EVENT_BIRTH) { @@ -184,12 +205,48 @@ cmplx projectile_graze_size(Projectile *p) { return 0; } +float32 projectile_timeout_factor(Projectile *p) { + return p->timeout ? (global.frames - p->birthtime) / p->timeout : 0; +} + static double projectile_rect_area(Projectile *p) { double w, h; projectile_size(p, &w, &h); return w * h; } +void projectile_set_layer(Projectile *p, drawlayer_t layer) { + if(!(layer & LAYER_LOW_MASK)) { + drawlayer_low_t sublayer; + + switch(p->type) { + case PROJ_ENEMY: + // 1. Large projectiles go below smaller ones. + sublayer = LAYER_LOW_MASK - (drawlayer_low_t)projectile_rect_area(p); + sublayer = (sublayer << 4) & LAYER_LOW_MASK; + // 2. Group by shader (hardcoded precedence). + sublayer |= ht_get(&shader_sublayer_map, p->shader, 0) & 0xf; + // If specific blending order is required, then you should set up the sublayer manually. + layer |= sublayer; + break; + + case PROJ_PARTICLE: + // 1. Group by shader (hardcoded precedence). + sublayer = ht_get(&shader_sublayer_map, p->shader, 0) & 0xf; + sublayer <<= 4; + sublayer |= 0x100; + // If specific blending order is required, then you should set up the sublayer manually. + layer |= sublayer; + break; + + default: + break; + } + } + + p->ent.draw_layer = layer; +} + static Projectile* _create_projectile(ProjArgs *args) { if(IN_DRAW_CODE) { log_fatal("Tried to spawn a projectile while in drawing code"); @@ -215,14 +272,12 @@ static Projectile* _create_projectile(ProjArgs *args) { p->damage = args->damage; p->damage_type = args->damage_type; p->clear_flags = 0; - - if(args->shader_params != NULL) { - p->shader_params = *args->shader_params; - } + p->move = args->move; + p->scale = args->scale; + p->opacity = args->opacity; memcpy(p->args, args->args, sizeof(p->args)); - p->ent.draw_layer = args->layer; p->ent.draw_func = ent_draw_projectile; projectile_set_prototype(p, args->proto); @@ -234,34 +289,9 @@ static Projectile* _create_projectile(ProjArgs *args) { log_fatal("Tried to spawn a projectile with invalid size %f x %f", creal(p->size), cimag(p->size)); } - if(!(p->ent.draw_layer & LAYER_LOW_MASK)) { - drawlayer_low_t sublayer; - - switch(p->type) { - case PROJ_ENEMY: - // 1. Large projectiles go below smaller ones. - sublayer = LAYER_LOW_MASK - (drawlayer_low_t)projectile_rect_area(p); - sublayer = (sublayer << 4) & LAYER_LOW_MASK; - // 2. Group by shader (hardcoded precedence). - sublayer |= ht_get(&shader_sublayer_map, p->shader, 0) & 0xf; - // If specific blending order is required, then you should set up the sublayer manually. - p->ent.draw_layer |= sublayer; - break; - - case PROJ_PARTICLE: - // 1. Group by shader (hardcoded precedence). - sublayer = ht_get(&shader_sublayer_map, p->shader, 0) & 0xf; - sublayer <<= 4; - sublayer |= 0x100; - // If specific blending order is required, then you should set up the sublayer manually. - p->ent.draw_layer |= sublayer; - break; - - default: - break; - } - } + projectile_set_layer(p, args->layer); + coevent_init(&p->events.killed); ent_register(&p->ent, ENT_PROJECTILE); // TODO: Maybe allow ACTION_DESTROY here? @@ -291,15 +321,16 @@ Projectile* _proj_attach_dbginfo(Projectile *p, DebugInfo *dbg, const char *call } #endif -static void* _delete_projectile(ListAnchor *projlist, List *proj, void *arg) { +static void *_delete_projectile(ListAnchor *projlist, List *proj, void *arg) { Projectile *p = (Projectile*)proj; proj_call_rule(p, EVENT_DEATH); + coevent_signal_once(&p->events.killed); ent_unregister(&p->ent); objpool_release(stage_object_pools.projectiles, alist_unlink(projlist, proj)); return NULL; } -void delete_projectile(ProjectileList *projlist, Projectile *proj) { +static void delete_projectile(ProjectileList *projlist, Projectile *proj) { _delete_projectile((ListAnchor*)projlist, (List*)proj, NULL); } @@ -429,14 +460,14 @@ static void ent_draw_projectile(EntityInterface *ent) { static Projectile prev_state; memcpy(&prev_state, proj, sizeof(Projectile)); - proj->draw_rule(proj, global.frames - proj->birthtime); + proj->draw_rule.func(proj, global.frames - proj->birthtime, proj->draw_rule.args); if(memcmp(&prev_state, proj, sizeof(Projectile))) { set_debug_info(&proj->debug); log_fatal("Projectile modified its state in draw rule"); } #else - proj->draw_rule(proj, global.frames - proj->birthtime); + proj->draw_rule.func(proj, global.frames - proj->birthtime, proj->draw_rule.args); #endif } @@ -466,24 +497,20 @@ Projectile* spawn_projectile_collision_effect(Projectile *proj) { .flags = proj->flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, .layer = LAYER_PARTICLE_HIGH, .shader_ptr = proj->shader, - .rule = linear, - .draw_rule = DeathShrink, + .draw_rule = pdraw_timeout_scale(2+I, 0+I), .angle = proj->angle, - .args = { 5*cexp(I*proj->angle) }, + // .rule = linear, + // .args = { 5*cexp(I*proj->angle) }, + .move = { .velocity = 5*cexp(I*proj->angle), .retention = 0.95 }, .timeout = 10, ); } static void really_clear_projectile(ProjectileList *projlist, Projectile *proj) { - Projectile *effect = spawn_projectile_clear_effect(proj); - Item *clear_item = NULL; + spawn_projectile_clear_effect(proj); if(!(proj->flags & PFLAG_NOCLEARBONUS)) { - clear_item = create_clear_item(proj->pos, proj->clear_flags); - } - - if(clear_item != NULL && effect != NULL) { - effect->args[0] = add_ref(clear_item); + create_clear_item(proj->pos, proj->clear_flags); } delete_projectile(projlist, proj); @@ -508,10 +535,16 @@ bool clear_projectile(Projectile *proj, uint flags) { return true; } +void kill_projectile(Projectile* proj) { + proj->flags |= PFLAG_INTERNAL_DEAD | PFLAG_NOCOLLISION | PFLAG_NOCLEAR; + proj->ent.draw_layer = LAYER_NODRAW; + // WARNING: must be done last, an event handler may cancel the task this function is running in! + coevent_signal_once(&proj->events.killed); +} + void process_projectiles(ProjectileList *projlist, bool collision) { ProjCollisionResult col = { 0 }; - char killed = 0; int action; bool stage_cleared = stage_is_cleared(); @@ -519,6 +552,11 @@ void process_projectiles(ProjectileList *projlist, bool collision) { next = proj->next; proj->prevpos = proj->pos; + if(proj->flags & PFLAG_INTERNAL_DEAD) { + delete_projectile(projlist, proj); + continue; + } + if(stage_cleared) { clear_projectile(proj, CLEAR_HAZARDS_BULLETS | CLEAR_HAZARDS_FORCE); } @@ -530,9 +568,8 @@ void process_projectiles(ProjectileList *projlist, bool collision) { proj->graze_counter_reset_timer = global.frames; } - if(proj->type == PROJ_DEAD && killed < 10 && !(proj->clear_flags & CLEAR_HAZARDS_NOW)) { + if(proj->type == PROJ_DEAD && !(proj->clear_flags & CLEAR_HAZARDS_NOW)) { proj->clear_flags |= CLEAR_HAZARDS_NOW; - killed++; } if(action == ACTION_DESTROY) { @@ -591,6 +628,10 @@ bool projectile_is_clearable(Projectile *p) { return false; } +int projectile_time(Projectile *p) { + return global.frames - p->birthtime; +} + int linear(Projectile *p, int t) { // sure is physics in here; a[0]: velocity if(t == EVENT_DEATH) { return ACTION_ACK; @@ -676,23 +717,25 @@ static inline void apply_common_transforms(Projectile *proj, int t) { */ } -static void bullet_highlight_draw(Projectile *p, int t) { +static void bullet_highlight_draw(Projectile *p, int t, ProjDrawRuleArgs args) { float timefactor = t / p->timeout; - float sx = creal(p->args[0]); - float sy = cimag(p->args[0]); + float sx = args[0].as_float[0]; + float sy = args[0].as_float[1]; + float tex_angle = args[1].as_float[0]; float opacity = pow(1 - timefactor, 2); opacity = min(1, 1.5 * opacity) * min(1, timefactor * 10); + opacity *= p->opacity; r_mat_tex_push(); r_mat_tex_translate(0.5, 0.5, 0); - r_mat_tex_rotate(p->args[1], 0, 0, 1); + r_mat_tex_rotate(tex_angle, 0, 0, 1); r_mat_tex_translate(-0.5, -0.5, 0); r_draw_sprite(&(SpriteParams) { .sprite_ptr = p->sprite, .shader_ptr = p->shader, - .shader_params = &(ShaderCustomParams) {{ 1 - opacity }}, + .shader_params = &(ShaderCustomParams) {{ opacity }}, .color = &p->color, .scale = { .x = sx, .y = sy }, .rotation.angle = p->angle + M_PI * 0.5, @@ -723,17 +766,18 @@ static Projectile* spawn_projectile_highlight_effect_internal(Projectile *p, boo sx = pow(p->sprite->w, 0.7); sy = pow(p->sprite->h, 0.7); + RNG_ARRAY(R, 5); + PARTICLE( .sprite = "stardust_green", .shader = "sprite_bullet", .size = p->size * 4.5, .layer = LAYER_PARTICLE_HIGH | 0x40, - .draw_rule = ScaleSquaredFade, - .args = { 0, 0, (0 + 2*I) * 0.1 * fmax(sx, sy) * (1 - 0.2 * frand()) }, - .angle = frand() * M_PI * 2, - .pos = p->pos + frand() * 8 * cexp(I*M_PI*2*frand()), + .draw_rule = pdraw_timeout_scalefade_exp(0, 0.2f * fmaxf(sx, sy) * vrng_f32_range(R[0], 0.8f, 1.0f), 1, 0, 2), + .angle = vrng_angle(R[1]), + .pos = p->pos + vrng_range(R[2], 0, 8) * vrng_dir(R[3]), .flags = PFLAG_NOREFLECT, - .timeout = 24 + 2 * nfrand(), + .timeout = vrng_range(R[4], 22, 26), .color = &clr, ); } @@ -742,17 +786,22 @@ static Projectile* spawn_projectile_highlight_effect_internal(Projectile *p, boo sy = pow((1.5 * p->sprite->h + 0.5 * p->sprite->w) * 0.5, 0.65); clr.a = 0.2; + RNG_ARRAY(R, 5); + return PARTICLE( .sprite = "bullet_cloud", .size = p->size * 4.5, .shader = "sprite_bullet", .layer = LAYER_PARTICLE_HIGH | 0x80, - .draw_rule = bullet_highlight_draw, - .args = { 0.125 * (sx + I * sy), frand() * M_PI * 2 }, + .draw_rule = { + bullet_highlight_draw, + .args[0].as_cmplx = 0.125 * (sx + I * sy), + .args[1].as_float = vrng_angle(R[0]), + }, .angle = p->angle, - .pos = p->pos + frand() * 5 * cexp(I*M_PI*2*frand()), + .pos = p->pos + vrng_range(R[1], 0, 5) * vrng_dir(R[2]), .flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, - .timeout = 32 + 2 * nfrand(), + .timeout = vrng_range(R[3], 30, 34), .color = &clr, ); } @@ -769,68 +818,47 @@ static Projectile* spawn_bullet_spawning_effect(Projectile *p) { return NULL; } -static void projectile_clear_effect_draw(Projectile *p, int t) { - r_mat_mv_push(); - apply_common_transforms(p, t); - - float timefactor = t / p->timeout; - float plrfactor = clamp(1 - (cabs(p->pos - global.plr.pos) - 64) / 128, 0, 1); - plrfactor *= clamp(timefactor * 10, 0, 1); - float f = 1 - (1 - timefactor) * (1 - plrfactor); - - Sprite spr = *p->sprite; - Sprite *ispr = get_sprite("item/bullet_point"); - spr.w = f * ispr->w + (1 - f) * spr.w; - spr.h = f * ispr->h + (1 - f) * spr.h; +static void projectile_clear_effect_draw(Projectile *p, int t, ProjDrawRuleArgs args) { + float o_tf = projectile_timeout_factor(p); + float tf = glm_ease_circ_out(o_tf); - r_draw_sprite(&(SpriteParams) { - .sprite_ptr = &spr, - .color = RGBA(p->color.r, p->color.g, p->color.b, p->color.a * (1 - 0)), - .shader_params = &(ShaderCustomParams){{ f }}, - // .scale.both = 1 + 0.5 * sqrt(tf), - }); + Animation *ani = args[0].as_ptr; + AniSequence *seq = args[1].as_ptr; + float angle = args[2].as_float[0]; + float scale = args[2].as_float[1]; - r_mat_mv_pop(); -} + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); -static int projectile_clear_effect_logic(Projectile *p, int t) { - if(t == EVENT_DEATH) { - free_ref(p->args[0]); - return ACTION_ACK; - } + float o = spbuf.shader_params.vector[0]; + spbuf.shader_params.vector[0] = o * fmaxf(0, 1.5 * (1 - tf) - 0.5); - if(t == EVENT_BIRTH) { - return ACTION_ACK; - } - - if((int)p->args[0] < 0) { - return ACTION_NONE; - } - - Item *i = REF(p->args[0]); + r_draw_sprite(&sp); - if(i != NULL) { - p->pos = i->pos; - } + sp.sprite_ptr = animation_get_frame(ani, seq, o_tf * (seq->length - 1)); + sp.scale.as_cmplx *= scale * (0.0 + 1.5*tf); + spbuf.color.a *= (1 - tf); + spbuf.shader_params.vector[0] = o; + sp.rotation.angle += t * 0.5*0 + angle; - return ACTION_NONE; + r_draw_sprite(&sp); } -Projectile* spawn_projectile_clear_effect(Projectile *proj) { +Projectile *spawn_projectile_clear_effect(Projectile *proj) { if((proj->flags & PFLAG_NOCLEAREFFECT) || proj->sprite == NULL) { return NULL; } - // spawn_projectile_highlight_effect_internal(proj, false); + cmplx v = proj->move.velocity; + if(!v) { + v = proj->pos - proj->prevpos; + } - ShaderProgram *shader = proj->shader; - uint32_t layer = LAYER_PARTICLE_BULLET_CLEAR; + Animation *ani = get_ani("part/bullet_clear"); + AniSequence *seq = get_ani_sequence(ani, "main"); - if(proj->shader == defaults_proj.shader_ptr) { - // HACK - shader = r_shader_get("sprite_bullet_dead"); - layer |= 0x1; - } + Sprite *sprite_ref = animation_get_frame(ani, seq, 0); + float scale = fmaxf(proj->sprite->w, proj->sprite->h) / sprite_ref->w; return PARTICLE( .sprite_ptr = proj->sprite, @@ -838,110 +866,149 @@ Projectile* spawn_projectile_clear_effect(Projectile *proj) { .pos = proj->pos, .color = &proj->color, .flags = proj->flags | PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE, - .shader_ptr = shader, - .rule = projectile_clear_effect_logic, - .draw_rule = projectile_clear_effect_draw, + .shader_ptr = proj->shader, + .draw_rule = { + projectile_clear_effect_draw, + .args[0].as_ptr = ani, + .args[1].as_ptr = seq, + .args[2].as_float = { rng_angle(), scale }, + }, .angle = proj->angle, - .timeout = 24, - .args = { -1 }, - .layer = layer, + .opacity = proj->opacity, + .scale = proj->scale, + .timeout = seq->length - 1, + .layer = LAYER_PARTICLE_BULLET_CLEAR, + .move = move_asymptotic(v, 0, 0.85), ); } -void ProjDrawCore(Projectile *proj, const Color *c) { +SpriteParams projectile_sprite_params(Projectile *proj, SpriteParamsBuffer *spbuf) { + spbuf->color = proj->color; + spbuf->shader_params = (ShaderCustomParams) {{ proj->opacity, 0, 0, 0 }}; + + SpriteParams sp = { 0 }; + sp.blend = proj->blend; + sp.color = &spbuf->color; + sp.pos.x = creal(proj->pos); + sp.pos.y = cimag(proj->pos); + sp.rotation = (SpriteRotationParams) { + .angle = proj->angle + (float)(M_PI/2), + .vector = { 0, 0, 1 }, + }; + sp.scale.x = crealf(proj->scale); + sp.scale.y = cimagf(proj->scale); + sp.shader_params = &spbuf->shader_params; + sp.shader_ptr = proj->shader; + sp.sprite_ptr = proj->sprite; + + return sp; +} + +static void projectile_draw_sprite(Sprite *s, const Color *clr, float32 opacity, cmplx32 scale) { + if(opacity <= 0 || crealf(scale) == 0) { + return; + } + + ShaderCustomParams p = { + opacity, + }; + r_draw_sprite(&(SpriteParams) { - .sprite_ptr = proj->sprite, - .color = c, - .shader_params = &proj->shader_params, + .sprite_ptr = s, + .color = clr, + .shader_params = &p, + .scale = { crealf(scale), cimagf(scale) }, }); } -void ProjDraw(Projectile *proj, int t) { - r_mat_mv_push(); - apply_common_transforms(proj, t); +void ProjDrawCore(Projectile *proj, const Color *c) { + projectile_draw_sprite(proj->sprite, c, proj->opacity, proj->scale); +} - float eff = proj_spawn_effect_factor(proj, t); +static void pdraw_basic_func(Projectile *proj, int t, ProjDrawRuleArgs args) { + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(proj, &spbuf); - /* - if(eff < 1 && proj->color.a > 0) { - Color c = proj->color; - c.a *= sqrt(eff); - ProjDrawCore(proj, &c); - } else { - ProjDrawCore(proj, &proj->color); - } - */ + float eff = proj_spawn_effect_factor(proj, t); if(eff < 1) { - float o = proj->shader_params.vector[0]; - float a = min(1, eff * 2); - proj->shader_params.vector[0] = 1 - (1 - o) * a; - Color c = proj->color; - c.a *= eff;//sqrt(eff); - ProjDrawCore(proj, &c); - proj->shader_params.vector[0] = o; - } else { - ProjDrawCore(proj, &proj->color); + spbuf.color.a *= eff; + spbuf.shader_params.vector[0] *= fminf(1.0f, eff * 2.0f); } - r_mat_mv_pop(); + r_draw_sprite(&sp); } -void ProjNoDraw(Projectile *proj, int t) { +ProjDrawRule pdraw_basic(void) { + return (ProjDrawRule) { pdraw_basic_func }; } -void Blast(Projectile *p, int t) { - r_mat_mv_push(); - r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0); - r_mat_mv_rotate(creal(p->args[1]) * DEG2RAD, cimag(p->args[1]), creal(p->args[2]), cimag(p->args[2])); +static void pdraw_blast_func(Projectile *p, int t, ProjDrawRuleArgs args) { + vec3 rot_axis = { + args[0].as_float[0], + args[0].as_float[1], + args[1].as_float[0], + }; + + float32 rot_angle = args[1].as_float[1]; + float32 secondary_scale = args[2].as_float[0]; - if(t != p->timeout && p->timeout != 0) { - r_mat_mv_scale(t/(double)p->timeout, t/(double)p->timeout, 1); + float32 tf = projectile_timeout_factor(p); + float32 opacity = (1.0f - tf) * p->opacity; + + if(tf <= 0 || opacity <= 0) { + return; } - float fade = 1.0 - t / (double)p->timeout; - r_color(RGBA_MUL_ALPHA(0.3, 0.6, 1.0, fade)); + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + sp.rotation.angle = rot_angle; + glm_vec3_copy(rot_axis, sp.rotation.vector); + sp.scale.x = tf; + sp.scale.y = tf; - SpriteParams sp = { - .sprite_ptr = p->sprite, - .color = RGBA_MUL_ALPHA(0.3, 0.6, 1.0, fade), - }; + spbuf.color = *RGBA(0.3, 0.6, 1.0, 1); + spbuf.shader_params.vector[0] = opacity; + r_disable(RCAP_CULL_FACE); r_draw_sprite(&sp); - r_mat_mv_scale(0.5+creal(p->args[2]), 0.5+creal(p->args[2]), 1); - r_color4(0.3 * fade, 0.6 * fade, 1.0 * fade, 0); - sp.color = RGBA(0.3 * fade, 0.6 * fade, 1.0 * fade, 0); + sp.scale.as_cmplx *= secondary_scale; + spbuf.color.a = 0; r_draw_sprite(&sp); - r_mat_mv_pop(); } -void Shrink(Projectile *p, int t) { - r_mat_mv_push(); - apply_common_transforms(p, t); - - float s = 2.0-t/(double)p->timeout*2; - if(s != 1) { - r_mat_mv_scale(s, s, 1); - } - - ProjDrawCore(p, &p->color); - r_mat_mv_pop(); +ProjDrawRule pdraw_blast(void) { + float32 rot_angle = rng_f32_angle(); + float32 x = rng_f32(); + float32 y = rng_f32(); + float32 z = rng_f32(); + float32 secondary_scale = rng_f32_range(0.5, 1.5); + + vec3 rot_axis = { x, y, z }; + glm_vec3_normalize(rot_axis); + + return (ProjDrawRule) { + .func = pdraw_blast_func, + .args[0].as_float = { rot_axis[0], rot_axis[1] }, + .args[1].as_float = { rot_axis[2], rot_angle }, + .args[2].as_float = { secondary_scale, }, + }; } -void DeathShrink(Projectile *p, int t) { +void Shrink(Projectile *p, int t, ProjDrawRuleArgs args) { r_mat_mv_push(); apply_common_transforms(p, t); float s = 2.0-t/(double)p->timeout*2; if(s != 1) { - r_mat_mv_scale(s, 1, 1); + r_mat_mv_scale(s, s, 1); } ProjDrawCore(p, &p->color); r_mat_mv_pop(); } -void GrowFade(Projectile *p, int t) { +void GrowFade(Projectile *p, int t, ProjDrawRuleArgs args) { r_mat_mv_push(); apply_common_transforms(p, t); @@ -957,7 +1024,7 @@ void GrowFade(Projectile *p, int t) { r_mat_mv_pop(); } -void Fade(Projectile *p, int t) { +void Fade(Projectile *p, int t, ProjDrawRuleArgs args) { r_mat_mv_push(); apply_common_transforms(p, t); ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), 1 - t/(double)p->timeout)); @@ -979,49 +1046,120 @@ static void ScaleFadeImpl(Projectile *p, int t, int fade_exponent) { r_mat_mv_pop(); } -void ScaleFade(Projectile *p, int t) { +void ScaleFade(Projectile *p, int t, ProjDrawRuleArgs args) { ScaleFadeImpl(p, t, 1); } -void ScaleSquaredFade(Projectile *p, int t) { - ScaleFadeImpl(p, t, 2); +static void pdraw_scalefade_func(Projectile *p, int t, ProjDrawRuleArgs args) { + cmplx32 scale0 = args[0].as_cmplx; + cmplx32 scale1 = args[1].as_cmplx; + float32 opacity0 = args[2].as_float[0]; + float32 opacity1 = args[2].as_float[1]; + float32 opacity_exp = args[3].as_float[0]; + + float32 timefactor = t / p->timeout; + + cmplx32 scale = clerpf(scale0, scale1, timefactor); + float32 opacity = lerpf(opacity0, opacity1, timefactor); + opacity = powf(opacity, opacity_exp); + + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + spbuf.shader_params.vector[0] *= opacity; + sp.scale.as_cmplx = cwmulf(sp.scale.as_cmplx, scale); + + r_draw_sprite(&sp); +} + +ProjDrawRule pdraw_timeout_scalefade_exp(cmplx32 scale0, cmplx32 scale1, float32 opacity0, float32 opacity1, float32 opacity_exp) { + if(cimagf(scale0) == 0) { + scale0 = CMPLXF(crealf(scale0), crealf(scale0)); + } + + if(cimagf(scale1) == 0) { + scale1 = CMPLXF(crealf(scale1), crealf(scale1)); + } + + return (ProjDrawRule) { + .func = pdraw_scalefade_func, + .args[0].as_cmplx = scale0, + .args[1].as_cmplx = scale1, + .args[2].as_float = { opacity0, opacity1 }, + .args[3].as_float = { opacity_exp }, + }; +} + +ProjDrawRule pdraw_timeout_scalefade(cmplx32 scale0, cmplx32 scale1, float32 opacity0, float32 opacity1) { + return pdraw_timeout_scalefade_exp(scale0, scale1, opacity0, opacity1, 1.0f); +} + +ProjDrawRule pdraw_timeout_scale(cmplx32 scale0, cmplx32 scale1) { + // TODO: specialized code path without fade component + return pdraw_timeout_scalefade(scale0, scale1, 1, 1); } -void Petal(Projectile *p, int t) { - float x = creal(p->args[2]); - float y = cimag(p->args[2]); - float z = creal(p->args[3]); +ProjDrawRule pdraw_timeout_fade(float32 opacity0, float32 opacity1) { + // TODO: specialized code path without scale component + return pdraw_timeout_scalefade(1+I, 1+I, opacity0, opacity1); +} - float r = sqrt(x*x+y*y+z*z); - x /= r; y /= r; z /= r; +static void pdraw_petal_func(Projectile *p, int t, ProjDrawRuleArgs args) { + vec3 rot_axis = { + args[0].as_float[0], + args[0].as_float[1], + args[1].as_float[0], + }; + + float32 rot_angle = args[1].as_float[1]; + + SpriteParamsBuffer spbuf; + SpriteParams sp = projectile_sprite_params(p, &spbuf); + glm_vec3_copy(rot_axis, sp.rotation.vector); + sp.rotation.angle = DEG2RAD*t*4.0f + rot_angle; + + spbuf.shader_params.vector[0] *= (1.0f - projectile_timeout_factor(p)); r_disable(RCAP_CULL_FACE); - r_mat_mv_push(); - r_mat_mv_translate(creal(p->pos), cimag(p->pos),0); - r_mat_mv_rotate(DEG2RAD * (t*4.0 + cimag(p->args[3])), x, y, z); - ProjDrawCore(p, &p->color); - r_mat_mv_pop(); + r_draw_sprite(&sp); +} + +ProjDrawRule pdraw_petal(float32 rot_angle, vec3 rot_axis) { + glm_vec3_normalize(rot_axis); + float32 x = rot_axis[0]; + float32 y = rot_axis[1]; + float32 z = rot_axis[2]; + + return (ProjDrawRule) { + .func = pdraw_petal_func, + .args[0].as_float = { x, y }, + .args[1].as_float = { z, rot_angle }, + }; +} + +ProjDrawRule pdraw_petal_random(void) { + float32 x = rng_f32(); + float32 y = rng_f32(); + float32 z = rng_f32(); + float32 rot_angle = rng_f32_angle(); + + return pdraw_petal(rot_angle, (vec3) { x, y, z }); } void petal_explosion(int n, cmplx pos) { for(int i = 0; i < n; i++) { - tsrand_fill(6); - float t = frand(); + cmplx v = rng_dir(); + v *= rng_range(3, 8); + real t = rng_real(); PARTICLE( .sprite = "petal", .pos = pos, .color = RGBA(sin(5*t) * t, cos(5*t) * t, 0.5 * t, 0), .rule = asymptotic, - .draw_rule = Petal, - .args = { - (3+5*afrand(2))*cexp(I*M_PI*2*afrand(3)), - 5, - afrand(4) + afrand(5)*I, - afrand(1) + 360.0*I*afrand(0), - }, + .move = move_asymptotic_simple(v, 5), + .draw_rule = pdraw_petal_random(), // TODO: maybe remove this noreflect, there shouldn't be a cull mode mess anymore - .flags = PFLAG_NOREFLECT | (n % 2 ? 0 : PFLAG_REQUIREDPARTICLE), + .flags = PFLAG_NOREFLECT | (n % 2 ? 0 : PFLAG_REQUIREDPARTICLE) | PFLAG_MANUALANGLE, .layer = LAYER_PARTICLE_PETAL, ); } @@ -1033,7 +1171,6 @@ void projectiles_preload(void) { "sprite_silhouette", defaults_proj.shader, defaults_part.shader, - "sprite_bullet_dead", }; const uint num_shaders = sizeof(shaders)/sizeof(*shaders); @@ -1064,6 +1201,10 @@ void projectiles_preload(void) { "part/stardust_green", NULL); + preload_resources(RES_ANIM, RESF_PERMANENT, + "part/bullet_clear", + NULL); + preload_resources(RES_SFX, RESF_PERMANENT, "shot1", "shot2", diff --git a/src/projectile.h b/src/projectile.h index 349ef58fe9..4d24af95ef 100644 --- a/src/projectile.h +++ b/src/projectile.h @@ -18,8 +18,10 @@ #include "objectpool.h" #include "renderer/api.h" #include "entity.h" +#include "move.h" +#include "coroutine.h" -#ifdef DEBUG +#if defined(DEBUG) && !defined(RNG_API_CHECK) #define PROJ_DEBUG #endif @@ -32,9 +34,20 @@ typedef LIST_ANCHOR(Projectile) ProjectileList; typedef LIST_INTERFACE(Projectile) ProjectileListInterface; typedef int (*ProjRule)(Projectile *p, int t); -typedef void (*ProjDrawRule)(Projectile *p, int t); +// typedef void (*ProjDrawRule)(Projectile *p, int t); typedef bool (*ProjPredicate)(Projectile *p); +typedef union { + float32 as_float[2]; + cmplx32 as_cmplx; + void *as_ptr; +} ProjDrawRuleArgs[RULE_ARGC]; + +typedef struct ProjDrawRule { + void (*func)(Projectile *p, int t, ProjDrawRuleArgs args); + ProjDrawRuleArgs args; +} ProjDrawRule; + typedef enum { PROJ_INVALID, @@ -55,11 +68,13 @@ typedef enum ProjFlags { PFLAG_NOCLEAREFFECT = (1 << 5), // [PROJ_ENEMY, PROJ_DEAD] Don't spawn the standard particle effect when cleared. PFLAG_NOCOLLISIONEFFECT = (1 << 6), // [PROJ_ENEMY, PROJ_DEAD, PROJ_PLAYER] Don't spawn the standard particle effect on collision. PFLAG_NOCLEARBONUS = (1 << 7), // [PROJ_ENEMY, PROJ_DEAD] Don't spawn any bonus items on clear. - // PFLAG_RESERVED = (1 << 8), + PFLAG_NOMOVE = (1 << 8), // [ALL] Don't call move_update for this projectile. PFLAG_NOREFLECT = (1 << 9), // [ALL] Don't render a "reflection" of this on the Stage 1 water surface. PFLAG_REQUIREDPARTICLE = (1 << 10), // [PROJ_PARTICLE] Visible at "minimal" particles setting. PFLAG_PLRSPECIALPARTICLE = (1 << 11), // [PROJ_PARTICLE] Apply Power Surge effect to this particle, as if it was a PROJ_PLAYER. PFLAG_NOCOLLISION = (1 << 12), // [PROJ_ENEMY, PROJ_PLAYER] Disable collision detection. + PFLAG_INTERNAL_DEAD = (1 << 13), // [ALL] Delete as soon as processed. (internal flag, do not use) + PFLAG_MANUALANGLE = (1 << 14), // [ALL] Don't automatically update the angle. PFLAG_NOSPAWNEFFECTS = PFLAG_NOSPAWNFADE | PFLAG_NOSPAWNFLARE, } ProjFlags; @@ -81,8 +96,12 @@ struct Projectile { ShaderProgram *shader; Sprite *sprite; ProjPrototype *proto; + MoveParams move; + struct { + CoEvent killed; + } events; Color color; - ShaderCustomParams shader_params; + attr_deprecated("this won't work") ShaderCustomParams shader_params; BlendMode blend; int birthtime; float damage; @@ -93,6 +112,9 @@ struct Projectile { ProjFlags flags; uint clear_flags; + cmplx32 scale; + float32 opacity; + // XXX: this is in frames of course, but needs to be float // to avoid subtle truncation and integer division gotchas. float timeout; @@ -113,7 +135,7 @@ typedef struct ProjArgs { Sprite *sprite_ptr; const char *shader; ShaderProgram *shader_ptr; - const ShaderCustomParams *shader_params; + attr_deprecated("this won't work") const ShaderCustomParams *shader_params; ProjectileList *dest; ProjRule rule; cmplx args[RULE_ARGC]; @@ -121,6 +143,7 @@ typedef struct ProjArgs { cmplx pos; cmplx size; // affects default draw order, out-of-viewport culling, and grazing cmplx collision_size; // affects collision with player (TODO: make this work for player projectiles too?) + MoveParams move; ProjType type; ProjFlags flags; BlendMode blend; @@ -130,6 +153,9 @@ typedef struct ProjArgs { int max_viewport_dist; drawlayer_t layer; + cmplx32 scale; + float32 opacity; + // XXX: this is in frames of course, but needs to be float // to avoid subtle truncation and integer division gotchas. float timeout; @@ -181,7 +207,6 @@ Projectile* create_particle(ProjArgs *args); #define PROJECTILE(...) _PROJ_GENERIC_SPAWN(create_projectile, __VA_ARGS__) #define PARTICLE(...) _PROJ_GENERIC_SPAWN(create_particle, __VA_ARGS__) -void delete_projectile(ProjectileList *projlist, Projectile *proj); void delete_projectiles(ProjectileList *projlist); void calc_projectile_collision(Projectile *p, ProjCollisionResult *out_col); @@ -196,23 +221,32 @@ Projectile* spawn_projectile_clear_effect(Projectile *proj); Projectile* spawn_projectile_highlight_effect(Projectile *proj); void projectile_set_prototype(Projectile *p, ProjPrototype *proto); +void projectile_set_layer(Projectile *p, drawlayer_t layer); bool clear_projectile(Projectile *proj, uint flags); +void kill_projectile(Projectile *proj); int linear(Projectile *p, int t); int accelerated(Projectile *p, int t); int asymptotic(Projectile *p, int t); +#define DEPRECATED_DRAW_RULE attr_deprecated("") + void ProjDrawCore(Projectile *proj, const Color *c); -void ProjDraw(Projectile *p, int t); -void ProjNoDraw(Projectile *proj, int t); -void Shrink(Projectile *p, int t); -void DeathShrink(Projectile *p, int t); -void Fade(Projectile *p, int t); -void GrowFade(Projectile *p, int t); -void ScaleFade(Projectile *p, int t); -void ScaleSquaredFade(Projectile *p, int t); +void Shrink(Projectile *p, int t, ProjDrawRuleArgs) DEPRECATED_DRAW_RULE; +void Fade(Projectile *p, int t, ProjDrawRuleArgs) DEPRECATED_DRAW_RULE; +void GrowFade(Projectile *p, int t, ProjDrawRuleArgs) DEPRECATED_DRAW_RULE; +void ScaleFade(Projectile *p, int t, ProjDrawRuleArgs) DEPRECATED_DRAW_RULE; + +ProjDrawRule pdraw_basic(void); +ProjDrawRule pdraw_timeout_scalefade_exp(cmplx32 scale0, cmplx32 scale1, float32 opacity0, float32 opacity1, float32 opacity_exp); +ProjDrawRule pdraw_timeout_scalefade(cmplx32 scale0, cmplx32 scale1, float32 opacity0, float32 opacity1); +ProjDrawRule pdraw_timeout_scale(cmplx32 scale0, cmplx32 scale1); +ProjDrawRule pdraw_timeout_fade(float32 opacity0, float32 opacity1); +ProjDrawRule pdraw_petal(float32 rot_angle, vec3 rot_axis); +ProjDrawRule pdraw_petal_random(void); +ProjDrawRule pdraw_blast(void); void Petal(Projectile *p, int t); void petal_explosion(int n, cmplx pos); @@ -223,5 +257,9 @@ void projectiles_preload(void); void projectiles_free(void); cmplx projectile_graze_size(Projectile *p); +float32 projectile_timeout_factor(Projectile *p); +int projectile_time(Projectile *p); + +SpriteParams projectile_sprite_params(Projectile *proj, SpriteParamsBuffer *spbuf); #endif // IGUARD_projectile_h diff --git a/src/projectile_prototypes.c b/src/projectile_prototypes.c index d9a8618358..f6cbbab3e7 100644 --- a/src/projectile_prototypes.c +++ b/src/projectile_prototypes.c @@ -57,8 +57,15 @@ static void pp_blast_init_projectile(ProjPrototype *proto, Projectile *p) { pp_basic_init_projectile(proto, p); assert(p->rule == NULL); assert(p->timeout > 0); - p->args[1] = frand()*360 + frand()*I; - p->args[2] = frand() + frand()*I; + + real a1_x, a1_y, a2_x, a2_y; + a1_x = rng_range(0, 360); + a1_y = rng_real(); + a2_x = rng_real(); + a2_y = rng_real(); + + p->args[1] = CMPLX(a1_x, a1_y); + p->args[2] = CMPLX(a2_x, a2_y); } ProjPrototype _pp_blast = { diff --git a/src/random.c b/src/random.c index 1975831b78..ae374e5228 100644 --- a/src/random.c +++ b/src/random.c @@ -8,12 +8,12 @@ #include "taisei.h" -#include - -#include "global.h" #include "random.h" +#include "util.h" -static RandomState *tsrand_current; +#include + +static RandomState *rng_active_state; uint64_t splitmix64(uint64_t *state) { // from http://xoshiro.di.unimi.it/splitmix64.c @@ -24,8 +24,15 @@ uint64_t splitmix64(uint64_t *state) { return z ^ (z >> 31); } +uint32_t splitmix32(uint32_t *state) { + uint32_t z = (*state += 0x9e3779b9); + z = (z ^ (z >> 15)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae3d; + return z ^ (z >> 16); +} + uint64_t makeseed(void) { - static uint64_t s; + static uint64_t s = 69; return splitmix64(&s) ^ SDL_GetPerformanceCounter(); } @@ -49,64 +56,168 @@ static uint64_t xoshiro256plus(uint64_t s[4]) { return result_plus; } -uint32_t tsrand_p(RandomState *rnd) { - assert(!rnd->locked); - return xoshiro256plus(rnd->state) >> 32; +void rng_init(RandomState *rng, uint64_t seed) { + memset(rng, 0, sizeof(*rng)); + rng_seed(rng, seed); } -uint64_t tsrand64_p(RandomState *rnd) { - assert(!rnd->locked); - return xoshiro256plus(rnd->state); +void rng_seed(RandomState *rng, uint64_t seed) { + rng->state[0] = splitmix64(&seed); + rng->state[1] = splitmix64(&seed); + rng->state[2] = splitmix64(&seed); + rng->state[3] = splitmix64(&seed); } -void tsrand_seed_p(RandomState *rnd, uint64_t seed) { - rnd->state[0] = splitmix64(&seed); - rnd->state[1] = splitmix64(&seed); - rnd->state[2] = splitmix64(&seed); - rnd->state[3] = splitmix64(&seed); +void rng_make_active(RandomState *rng) { + rng_active_state = rng; } -void tsrand_switch(RandomState *rnd) { - tsrand_current = rnd; +rng_val_t rng_next_p(RandomState *rng) { + assert(!rng->locked); + return (rng_val_t) { xoshiro256plus(rng->state) }; } -void tsrand_init(RandomState *rnd, uint64_t seed) { - memset(rnd, 0, sizeof(RandomState)); - tsrand_seed_p(rnd, seed); +rng_val_t rng_next(void) { + return rng_next_p(rng_active_state); } -void tsrand_seed(uint64_t seed) { - tsrand_seed_p(tsrand_current, seed); +void rng_nextn(size_t n, rng_val_t v[n]) { + for(size_t i = 0; i < n; ++i) { + v[i] = rng_next(); + } } -uint32_t tsrand(void) { - return tsrand_p(tsrand_current); +/* + * Output conversion functions + */ + +uint64_t vrng_u64(rng_val_t v) { + return v._value; } -uint64_t tsrand64(void) { - return tsrand64_p(tsrand_current); +int64_t vrng_i64(rng_val_t v) { + return (int64_t)v._value; +} + +uint32_t vrng_u32(rng_val_t v) { + return v._value >> 32; +} + +int32_t vrng_i32(rng_val_t v) { + return (int32_t)(v._value >> 32); +} + +double vrng_f64(rng_val_t v) { + return (v._value >> 11) * 0x1.0p-53; +} + +double vrng_f64s(rng_val_t v) { + DoubleBits db; + db.val = vrng_f64((rng_val_t) { v._value << 1 }); + db.bits |= v._value & (UINT64_C(1) << 63); + return db.val; +} + +float vrng_f32(rng_val_t v) { + return (v._value >> 40) * 0x1.0p-24f; +} + +float vrng_f32s(rng_val_t v) { + FloatBits fb; + fb.val = vrng_f32((rng_val_t) { v._value << 1 }); + fb.bits |= vrng_u32(v) & (1 << 31); + return fb.val; +} + +bool vrng_bool(rng_val_t v) { + return v._value >> 63; +} + +double vrng_f64_sign(rng_val_t v) { + return bits_to_double((UINT64_C(0x3FF) << 52) | (v._value & (UINT64_C(1) << 63))); +} + +float vrng_f32_sign(rng_val_t v) { + return bits_to_float((0x7f << 23) | (vrng_u32(v) & (1 << 31))); +} + +double vrng_f64_range(rng_val_t v, double rmin, double rmax) { + return vrng_f64(v) * (rmax - rmin) + rmin; +} + +float vrng_f32_range(rng_val_t v, float rmin, float rmax) { + return vrng_f32(v) * (rmax - rmin) + rmin; +} + +int64_t vrng_i64_range(rng_val_t v, int64_t rmin, int64_t rmax) { + // NOTE: strictly speaking non-uniform distribution in the general case, but seems good enough for small numbers. + int64_t period = rmax - rmin; + assume(period > 0); + return (int64_t)(v._value % (uint64_t)period) + rmin; +} + +int32_t vrng_i32_range(rng_val_t v, int32_t rmin, int32_t rmax) { + // NOTE: strictly speaking non-uniform distribution in the general case, but seems good enough for small numbers. + int32_t period = rmax - rmin; + assume(period > 0); + return (int32_t)(vrng_u32(v) % (uint32_t)period) + rmin; +} + +double vrng_f64_angle(rng_val_t v) { + return vrng_f64(v) * (M_PI * 2.0); +} + +float vrng_f32_angle(rng_val_t v) { + return vrng_f32(v) * (float)(M_PI * 2.0f); +} + +cmplx vrng_dir(rng_val_t v) { + return cdir(vrng_f64_angle(v)); } -static inline double makedouble(uint64_t x) { - // Range: [0..1) - return (x >> 11) * (1.0 / (UINT64_C(1) << 53)); +bool vrng_f64_chance(rng_val_t v, double chance) { + return vrng_f64(v) < chance; +} + +bool vrng_f32_chance(rng_val_t v, float chance) { + return vrng_f32(v) < chance; +} + +/* + * Deprecated APIs; to be removed + */ + +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + +uint32_t tsrand_p(RandomState *rng) { + return vrng_u32(rng_next_p(rng)); +} + +uint64_t tsrand64_p(RandomState *rng) { + return vrng_u64(rng_next_p(rng)); +} + +uint32_t tsrand(void) { + return rng_u32(); +} + +uint64_t tsrand64(void) { + return rng_u64(); } double frand(void) { - return makedouble(tsrand64()); + return rng_f64(); } double nfrand(void) { - return frand() * 2.0 - 1.0; + return rng_f64s(); } -// we use this to support multiple rands in a single statement without breaking replays across different builds - -static uint64_t tsrand_array[TSRAND_ARRAY_LIMIT]; +static rng_val_t tsrand_array[TSRAND_ARRAY_LIMIT]; static int tsrand_array_elems; static uint64_t tsrand_fillflags = 0; -static void tsrand_error(const char *file, const char *func, uint line, const char *fmt, ...) { +noreturn static void tsrand_error(const char *file, const char *func, uint line, const char *fmt, ...) { char buf[2048] = { 0 }; va_list args; @@ -115,6 +226,7 @@ static void tsrand_error(const char *file, const char *func, uint line, const ch va_end(args); log_fatal("%s(): %s [%s:%u]", func, buf, file, line); + UNREACHABLE; } #define TSRANDERR(...) tsrand_error(file, __func__, line, __VA_ARGS__) @@ -129,18 +241,17 @@ void _tsrand_fill_p(RandomState *rnd, int amount, const char *file, uint line) { tsrand_fillflags = (UINT64_C(1) << amount) - 1; for(int i = 0; i < amount; ++i) { - tsrand_array[i] = tsrand64_p(rnd); + tsrand_array[i] = rng_next_p(rnd); } } void _tsrand_fill(int amount, const char *file, uint line) { - _tsrand_fill_p(tsrand_current, amount, file, line); + _tsrand_fill_p(rng_active_state, amount, file, line); } -uint64_t _tsrand64_a(int idx, const char *file, uint line) { +static rng_val_t _tsrand_val_a(int idx, const char *file, uint line) { if(idx >= tsrand_array_elems || idx < 0) { TSRANDERR("Index out of range (%i / %i)", idx, tsrand_array_elems); - return 0; } if(tsrand_fillflags & (UINT64_C(1) << idx)) { @@ -149,25 +260,21 @@ uint64_t _tsrand64_a(int idx, const char *file, uint line) { } TSRANDERR("Index %i used multiple times", idx); - return 0; } -uint32_t _tsrand_a(int idx, const char *file, uint line) { - return _tsrand64_a(idx, file, line) >> 32; -} -double _afrand(int idx, const char *file, uint line) { - return makedouble(_tsrand64_a(idx, file, line)); +uint64_t _tsrand64_a(int idx, const char *file, uint line) { + return vrng_u64(_tsrand_val_a(idx, file, line)); } -double _anfrand(int idx, const char *file, uint line) { - return _afrand(idx, file, line) * 2 - 1; +uint32_t _tsrand_a(int idx, const char *file, uint line) { + return vrng_u32(_tsrand_val_a(idx, file, line)); } -void tsrand_lock(RandomState *rnd) { - rnd->locked = true; +double _afrand(int idx, const char *file, uint line) { + return vrng_f64(_tsrand_val_a(idx, file, line)); } -void tsrand_unlock(RandomState *rnd) { - rnd->locked = false; +double _anfrand(int idx, const char *file, uint line) { + return vrng_f64s(_tsrand_val_a(idx, file, line)); } diff --git a/src/random.h b/src/random.h index 52653168b2..b4798d8be3 100644 --- a/src/random.h +++ b/src/random.h @@ -11,36 +11,157 @@ #include "taisei.h" +#include "util/crap.h" +#include "util/miscmath.h" + +// #define RNG_DEPRECATED attr_deprecated("Use the new rng_ API") +#define RNG_DEPRECATED + typedef struct RandomState { uint64_t state[4]; +#ifdef DEBUG bool locked; +#endif } RandomState; -uint64_t splitmix64(uint64_t *state); +typedef struct rng_val { + uint64_t _value; +} rng_val_t; + +uint64_t splitmix64(uint64_t *state) attr_nonnull(1); +uint32_t splitmix32(uint32_t *state) attr_nonnull(1); uint64_t makeseed(void); -void tsrand_init(RandomState *rnd, uint64_t seed); -void tsrand_switch(RandomState *rnd); -void tsrand_seed_p(RandomState *rnd, uint64_t seed); -uint32_t tsrand_p(RandomState *rnd); -uint64_t tsrand64_p(RandomState *rnd); +void rng_init(RandomState *rng, uint64_t seed) attr_nonnull(1); +void rng_seed(RandomState *rng, uint64_t seed) attr_nonnull(1); +void rng_make_active(RandomState *rng) attr_nonnull(1); +rng_val_t rng_next_p(RandomState *rng) attr_nonnull(1); + +#ifdef DEBUG +INLINE void rng_lock(RandomState *rng) { rng->locked = true; } +INLINE void rng_unlock(RandomState *rng) { rng->locked = false; } +#else +#define rng_lock(rng) (rng, (void)0) +#define rng_unlock(rng) (rng, (void)0) +#endif + +// NOTE: if you rename this, also update scripts/upkeep/check-rng-usage.py! +rng_val_t rng_next(void); +void rng_nextn(size_t n, rng_val_t v[n]) attr_nonnull(2); + +#define RNG_ARRAY(name, size) rng_val_t name[size]; RNG_NEXT(name) + +#define RNG_NEXT(_val) \ + rng_nextn(sizeof(_val)/sizeof(rng_val_t), _Generic((_val), \ + rng_val_t: &(_val), \ + rng_val_t*: (_val) \ + )) + +uint64_t vrng_u64(rng_val_t v) attr_pure; +#define rng_u64() vrng_u64(rng_next()) +int64_t vrng_i64(rng_val_t v) attr_pure; +#define rng_i64() vrng_i64(rng_next()) + +uint32_t vrng_u32(rng_val_t v) attr_pure; +#define rng_u32() vrng_u32(rng_next()) +int32_t vrng_i32(rng_val_t v) attr_pure; +#define rng_i32() vrng_i32(rng_next()) + +double vrng_f64(rng_val_t v) attr_pure; +#define rng_f64() vrng_f64(rng_next()) +double vrng_f64s(rng_val_t v) attr_pure; +#define rng_f64s() vrng_f64s(rng_next()) + +float vrng_f32(rng_val_t v) attr_pure; +#define rng_f32() vrng_f32(rng_next()) +float vrng_f32s(rng_val_t v) attr_pure; +#define rng_f32s() vrng_f32s(rng_next()) + +#define vrng_real(v) vrng_f64(v) +#define rng_real() vrng_real(rng_next()) +#define vrng_sreal(v) vrng_f64s(v) +#define rng_sreal() vrng_sreal(rng_next()) + +bool vrng_bool(rng_val_t v); +#define rng_bool() vrng_bool(rng_next()) + +double vrng_f64_sign(rng_val_t v) attr_pure; +#define rng_f64_sign() vrng_f64_sign(rng_next()) +float vrng_f32_sign(rng_val_t v) attr_pure; +#define rng_f32_sign() vrng_f32_sign(rng_next()) + +#define vrng_sign(v) vrng_f64_sign(v) +#define rng_sign() vrng_sign(rng_next()) + +double vrng_f64_range(rng_val_t v, double rmin, double rmax) attr_pure; +#define rng_f64_range(rmin, rmax) vrng_f64_range(rng_next(), rmin, rmax) +float vrng_f32_range(rng_val_t v, float rmin, float rmax) attr_pure; +#define rng_f32_range(rmin, rmax) vrng_f32_range(rng_next(), rmin, rmax) + +#define vrng_range(v, rmin, rmax) _Generic((rmin), \ + float: \ + _Generic((rmax), \ + float: vrng_f32_range, \ + default: vrng_f64_range \ + ), \ + default: vrng_f64_range \ +)(v, rmin, rmax) + +#define rng_range(rmin, rmax) vrng_range(rng_next(), rmin, rmax) + +int64_t vrng_i64_range(rng_val_t v, int64_t rmin, int64_t rmax) attr_pure; +#define rng_i64_range(rmin, rmax) vrng_i64_range(rng_next(), rmin, rmax) +int32_t vrng_i32_range(rng_val_t v, int32_t rmin, int32_t rmax) attr_pure; +#define rng_i32_range(rmin, rmax) vrng_i32_range(rng_next(), rmin, rmax) + +#define vrng_irange(v, rmin, rmax) vrng_i32_range(v, rmin, rmax) +#define rng_irange(rmin, rmax) vrng_range(rng_next(), rmin, rmax) + +double vrng_f64_angle(rng_val_t v) attr_pure; +#define rng_f64_angle() vrng_f64_angle(rng_next()) +float vrng_f32_angle(rng_val_t v) attr_pure; +#define rng_f32_angle() vrng_f32_angle(rng_next()) + +#define vrng_angle(v) vrng_f64_angle(v) +#define rng_angle() vrng_angle(rng_next()) + +cmplx vrng_dir(rng_val_t v) attr_pure; +#define rng_dir() vrng_dir(rng_next()) + +bool vrng_f64_chance(rng_val_t v, double chance) attr_pure; +#define rng_f64_chance(chance) vrng_f64_chance(rng_next(), chance) +bool vrng_f32_chance(rng_val_t v, float chance) attr_pure; +#define rng_f32_chance(chance) vrng_f32_chance(rng_next(), chance) + +#define vrng_chance(v, chance) _Generic((chance), \ + float: vrng_f32_chance, \ + default: vrng_f64_chance \ +)(v, chance) + +#define rng_chance(chance) vrng_chance(rng_next(), chance) + +/* + * Deprecated APIs; to be removed + */ -void tsrand_seed(uint64_t seed); -uint32_t tsrand(void); -uint64_t tsrand64(void); +uint32_t tsrand_p(RandomState *rng) RNG_DEPRECATED; +uint64_t tsrand64_p(RandomState *rng) RNG_DEPRECATED; -void tsrand_lock(RandomState *rnd); -void tsrand_unlock(RandomState *rnd); +uint32_t tsrand(void) RNG_DEPRECATED; +uint64_t tsrand64(void) RNG_DEPRECATED; -double frand(void); -double nfrand(void); +double frand(void) RNG_DEPRECATED; // Range: [0.0; 1.0) +double nfrand(void) RNG_DEPRECATED; // Range: (-1.0; 1.0) +bool rand_bool(void) RNG_DEPRECATED; +double rand_sign(void) RNG_DEPRECATED; // 1.0 or -1.0 +float rand_signf(void) RNG_DEPRECATED; // 1.0f or -1.0f -void _tsrand_fill_p(RandomState *rnd, int amount, const char *file, uint line); -void _tsrand_fill(int amount, const char *file, uint line); -uint32_t _tsrand_a(int idx, const char *file, uint line); -uint64_t _tsrand64_a(int idx, const char *file, uint line); -double _afrand(int idx, const char *file, uint line); -double _anfrand(int idx, const char *file, uint line); +void _tsrand_fill_p(RandomState *rnd, int amount, const char *file, uint line) RNG_DEPRECATED; +void _tsrand_fill(int amount, const char *file, uint line) RNG_DEPRECATED; +uint32_t _tsrand_a(int idx, const char *file, uint line) RNG_DEPRECATED; +uint64_t _tsrand64_a(int idx, const char *file, uint line) RNG_DEPRECATED; +double _afrand(int idx, const char *file, uint line) RNG_DEPRECATED; +double _anfrand(int idx, const char *file, uint line) RNG_DEPRECATED; #define tsrand_fill_p(rnd,amount) _tsrand_fill_p(rnd, amount, __FILE__, __LINE__) #define tsrand_fill(amount) _tsrand_fill(amount, __FILE__, __LINE__) diff --git a/src/refs.h b/src/refs.h index b4b344a0b2..6d32a0e06c 100644 --- a/src/refs.h +++ b/src/refs.h @@ -33,4 +33,6 @@ void del_ref(void *ptr); void free_ref(int i); void free_all_refs(void); +#define UPDATE_REF(ref, ptr) ((ptr) = REF(ref)) + #endif // IGUARD_refs_h diff --git a/src/renderer/api.c b/src/renderer/api.c index 2afdd72f9a..4f3fcf37ac 100644 --- a/src/renderer/api.c +++ b/src/renderer/api.c @@ -18,6 +18,7 @@ #include "util/graphics.h" #include "resource/texture.h" #include "resource/sprite.h" +#include "coroutine.h" #define B _r_backend.funcs @@ -601,6 +602,7 @@ VsyncMode r_vsync_current(void) { } void r_swap(SDL_Window *window) { + coroutines_draw_stats(); _r_sprite_batch_end_frame(); B.swap(window); } diff --git a/src/renderer/api.h b/src/renderer/api.h index 42d3d5323e..6e79ad08ba 100644 --- a/src/renderer/api.h +++ b/src/renderer/api.h @@ -361,13 +361,17 @@ typedef struct SpriteStateParams { ShaderProgram *shader; } SpriteStateParams; -typedef struct SpriteScaleParams { - union { - float x; - float both; +typedef union SpriteScaleParams { + struct { + union { + float x; + float both; + }; + + float y; }; - float y; + cmplx32 as_cmplx; } SpriteScaleParams; typedef struct SpriteRotationParams { @@ -388,6 +392,8 @@ typedef struct SpriteParams { ShaderProgram *shader_ptr; Texture *aux_textures[R_NUM_SPRITE_AUX_TEXTURES]; + + // TODO: maybe embed these by value and get rid of SpriteParamsBuffer? const Color *color; const ShaderCustomParams *shader_params; @@ -399,6 +405,11 @@ typedef struct SpriteParams { SpriteFlipParams flip; } SpriteParams; +typedef struct SpriteParamsBuffer { + Color color; + ShaderCustomParams shader_params; +} SpriteParamsBuffer; + // Matches vertex buffer layout typedef struct SpriteInstanceAttribs { mat4 mv_transform; diff --git a/src/renderer/gl33/texture.c b/src/renderer/gl33/texture.c index 5479ad2437..8385ad8da5 100644 --- a/src/renderer/gl33/texture.c +++ b/src/renderer/gl33/texture.c @@ -412,7 +412,7 @@ void gl33_texture_taint(Texture *tex) { void gl33_texture_prepare(Texture *tex) { if(tex->params.mipmap_mode == TEX_MIPMAP_AUTO && tex->mipmaps_outdated) { - log_debug("Generating mipmaps for %s (%u)", tex->debug_label, tex->gl_handle); + // log_debug("Generating mipmaps for %s (%u)", tex->debug_label, tex->gl_handle); gl33_bind_texture(tex, false); gl33_sync_texunit(tex->binding_unit, false, true); diff --git a/src/resource/animation.c b/src/resource/animation.c index 5dc19f0bf4..e947659187 100644 --- a/src/resource/animation.c +++ b/src/resource/animation.c @@ -42,7 +42,7 @@ typedef struct AnimationLoadData { } AnimationLoadData; // See ANIMATION_FORMAT.rst for a documentation of this syntax. -static bool animation_parse_sequence_spec(AniSequence *seq, const char *specstr) { +static bool animation_parse_sequence_spec(AniSequence **seq, int seq_capacity, const char *specstr) { const char *delaytoken = "d"; const char *mirrortoken = "m"; @@ -60,7 +60,6 @@ static bool animation_parse_sequence_spec(AniSequence *seq, const char *specstr) int delay = 1; // standard frame duration: one frame. bool mirrored = false; - int seqcapacity = 0; int command = 1; // This loop is supposed to parse one command per iteration. @@ -124,22 +123,23 @@ static bool animation_parse_sequence_spec(AniSequence *seq, const char *specstr) log_error("AniSequence syntax error: '%s'[%d] sprite index cannot be negative.",specstr,command); return false; } - int spriteidx = arg; + + // temporarily store flipped sprites with negative indices. + // a later pass will allocate actual flipped sprites for these and fix up the indices. + int spriteidx = mirrored ? -(arg + 1) : arg; + for(int i = 0; i < delay; i++) { - if(seqcapacity <= seq->length) { - seqcapacity++; - seqcapacity *= 2; - seq->frames = realloc(seq->frames,sizeof(AniSequenceFrame)*seqcapacity); + if(seq_capacity <= (*seq)->length) { + seq_capacity *= 2; + *seq = realloc(*seq, sizeof(AniSequence) + seq_capacity * sizeof(*(*seq)->frame_indices)); } - seq->frames[seq->length].spriteidx = spriteidx; - seq->frames[seq->length].mirrored = mirrored; - seq->length++; + (*seq)->frame_indices[(*seq)->length++] = spriteidx; } } command++; } - if(seq->length <= 0) { + if((*seq)->length <= 0) { log_error("AniSequence syntax error: '%s' empty sequence.",specstr); return false; } @@ -152,39 +152,46 @@ static bool animation_parse_callback(const char *key, const char *value, void *d // parse fixed keys - if(!strcmp(key,"@sprite_count")) { + if(!strcmp(key, "@sprite_count")) { char *endptr; ani->sprite_count = strtol(value,&endptr,10); if(*value == '\0' || *endptr != '\0' || ani->sprite_count <= 0) { - log_error("Syntax error: %s must be positive integer",key); + log_error("%s must be a positive integer (got `%s`)", key, value); return false; } return true; } + if(key[0] == '@') { + log_warn("Unknown animation property `%s` ignored", key); + return true; + } + // otherwise it is a sequence - AniSequence *seq = calloc(1,sizeof(AniSequence)); - bool rc = animation_parse_sequence_spec(seq, value); - if(!rc) { - free(seq->frames); + AniSequence *prev_seq; + if((prev_seq = ht_get(&ani->sequences, key, NULL))) { + log_warn("Animation sequence `%s` overwritten", key); + } + + int init_seq_size = 4; + AniSequence *seq = calloc(1, sizeof(AniSequence) + init_seq_size * sizeof(*seq->frame_indices)); + if(!animation_parse_sequence_spec(&seq, init_seq_size, value)) { free(seq); return false; } ht_set(&ani->sequences, key, seq); + free(prev_seq); return true; } static void *free_sequence_callback(const char *key, void *data, void *arg) { - AniSequence *seq = data; - free(seq->frames); - free(seq); - + free(data); return NULL; } -void* load_animation_begin(const char *filename, uint flags) { +void *load_animation_begin(const char *filename, uint flags) { char *basename = resource_util_basename(ANI_PATH_PREFIX, filename); char name[strlen(basename) + 1]; strcpy(name, basename); @@ -215,32 +222,83 @@ void* load_animation_begin(const char *filename, uint flags) { return data; } -union check_sequence_frame_callback_arg { - int sprite_count, errors; +struct anim_remap_state { + Animation *ani; + ht_int2int_t flip_map; + int num_flipped_sprites; + int errors; }; -static void *check_sequence_frame_callback(const char *key, void *value, void *varg) { +static void flip_sprite(Sprite *s) { + FloatRect *tex_area = &s->tex_area; + tex_area->x += tex_area->w; + tex_area->w *= -1; +} + +static int remap_frame_index(struct anim_remap_state *st, int idx) { + int64_t remapped_idx; + + if(idx >= 0) { + return idx; + } + + if(st->num_flipped_sprites > 0 && ht_lookup(&st->flip_map, idx, &remapped_idx)) { + return remapped_idx; + } + + if(st->num_flipped_sprites == 0) { + ht_create(&st->flip_map); + } + + remapped_idx = st->ani->sprite_count; + int local_idx = st->num_flipped_sprites; + int orig_idx = -(idx + 1); + + ++st->num_flipped_sprites; + ++st->ani->sprite_count; + + ht_set(&st->flip_map, idx, remapped_idx); + + st->ani->local_sprites = realloc(st->ani->local_sprites, sizeof(*st->ani->local_sprites) * st->num_flipped_sprites); + st->ani->local_sprites[local_idx] = *st->ani->sprites[orig_idx]; + flip_sprite(st->ani->local_sprites + local_idx); + + log_debug("%i -> %i", orig_idx, (int)remapped_idx); + log_debug("sprite_count: %i", st->ani->sprite_count); + + return remapped_idx; +} + +static void *remap_sequence_callback(const char *key, void *value, void *varg) { AniSequence *seq = value; - union check_sequence_frame_callback_arg *arg = varg; - int sprite_count = arg->sprite_count; + struct anim_remap_state *st = varg; + // needs to be cached, because we may grow the count to allocate flipped sprites + int sprite_count = st->ani->sprite_count; int errors = 0; for(int i = 0; i < seq->length; i++) { - if(seq->frames[i].spriteidx >= sprite_count) { - log_error("Animation sequence %s: Sprite index %d is higher than sprite_count.", key, seq->frames[i].spriteidx); + int abs_idx = seq->frame_indices[i]; + abs_idx = abs_idx >= 0 ? abs_idx : -(abs_idx + 1); + + if(abs_idx >= sprite_count) { + log_error("Animation sequence `%s`: Sprite index %i is higher than sprite_count (%i).", key, abs_idx, sprite_count); errors++; } + + if(seq->frame_indices[i] < 0) { + seq->frame_indices[i] = remap_frame_index(st, seq->frame_indices[i]); + } } if(errors) { - arg->errors = errors; - return arg; + st->errors += errors; + return st; } return NULL; } -void* load_animation_end(void *opaque, const char *filename, uint flags) { +void *load_animation_end(void *opaque, const char *filename, uint flags) { AnimationLoadData *data = opaque; if(opaque == NULL) { @@ -266,14 +324,36 @@ void* load_animation_end(void *opaque, const char *filename, uint flags) { ani->sprites[i] = res->data; } - union check_sequence_frame_callback_arg arg = { ani->sprite_count }; + struct anim_remap_state remap_state = { 0 }; + remap_state.ani = ani; + int prev_sprite_count = ani->sprite_count; - if(ht_foreach(&ani->sequences, check_sequence_frame_callback, &arg) != NULL) { + if(ht_foreach(&ani->sequences, remap_sequence_callback, &remap_state) != NULL) { unload_animation(ani); ani = NULL; } + if(ani->sprite_count != prev_sprite_count) { + // remapping generated new flipped sprites - add them to our sprites array + + assume(ani->sprite_count > prev_sprite_count); + assume(remap_state.num_flipped_sprites == ani->sprite_count - prev_sprite_count); + assume(ani->local_sprites != NULL); + ani->sprites = realloc(ani->sprites, sizeof(*ani->sprites) * ani->sprite_count); + + for(int i = 0; i < remap_state.num_flipped_sprites; ++i) { + ani->sprites[prev_sprite_count + i] = ani->local_sprites + i; + } + } else { + assert(remap_state.num_flipped_sprites == 0); + assert(ani->local_sprites == NULL); + } + done: + if(remap_state.num_flipped_sprites > 0) { + ht_destroy(&remap_state.flip_map); + } + free(data->basename); free(data); @@ -285,6 +365,7 @@ void unload_animation(void *vani) { ht_foreach(&ani->sequences, free_sequence_callback, NULL); ht_destroy(&ani->sequences); free(ani->sprites); + free(ani->local_sprites); free(ani); } @@ -296,24 +377,14 @@ AniSequence *get_ani_sequence(Animation *ani, const char *seqname) { AniSequence *seq = ht_get(&ani->sequences, seqname, NULL); if(seq == NULL) { - log_fatal("Sequence '%s' not found.",seqname); + log_fatal("Sequence '%s' not found.", seqname); } return seq; } -Sprite* animation_get_frame(Animation *ani, AniSequence *seq, int seqframe) { - AniSequenceFrame *f = &seq->frames[seqframe%seq->length]; - if(f->mirrored) { - memcpy(&ani->transformed_sprite,ani->sprites[f->spriteidx],sizeof(Sprite)); - // XXX: maybe add sprite_flip() or something - FloatRect *tex_area = &ani->transformed_sprite.tex_area; - tex_area->x += tex_area->w; - tex_area->w *= -1; - return &ani->transformed_sprite; - } - - assert(f->spriteidx < ani->sprite_count); - return ani->sprites[f->spriteidx]; +Sprite *animation_get_frame(Animation *ani, AniSequence *seq, int seqframe) { + int idx = seq->frame_indices[seqframe % seq->length]; + assert(idx < ani->sprite_count); + return ani->sprites[idx]; } - diff --git a/src/resource/animation.h b/src/resource/animation.h index eca2bbd15e..0595c7840b 100644 --- a/src/resource/animation.h +++ b/src/resource/animation.h @@ -14,20 +14,15 @@ #include "resource.h" #include "sprite.h" -typedef struct AniSequenceFrame { - uint spriteidx; - bool mirrored; -} AniSequenceFrame; - typedef struct AniSequence { - AniSequenceFrame *frames; int length; + int frame_indices[]; } AniSequence; typedef struct Animation { ht_str2ptr_t sequences; Sprite **sprites; - Sprite transformed_sprite; // temporary sprite used to apply animation transformations. + Sprite *local_sprites; int sprite_count; } Animation; @@ -40,18 +35,17 @@ void unload_animation(void *vani); Animation *get_ani(const char *name); AniSequence *get_ani_sequence(Animation *ani, const char *seqname); -// Returns a sprite for the specified frame from an animation sequence named seqname. -// CAUTION: this sprite is only valid until the next call to this function. -// +// Returns a sprite for the specified frame from an animation sequence named seqname. +// // The frames correspond 1:1 to real ingame frames, so // // draw_sprite_p(x, y, animation_get_frame(ani,"fly",global.frames)); // // already gives you the fully functional animation rendering. You can use // an AniPlayer instance for queueing. -// +// // Note that seqframe loops (otherwise the example above wouldn’t work). -// +// Sprite *animation_get_frame(Animation *ani, AniSequence *seq, int seqframe); diff --git a/src/stage.c b/src/stage.c index f904965ecb..502c82c74b 100644 --- a/src/stage.c +++ b/src/stage.c @@ -72,7 +72,7 @@ static void add_spellpractice_stages(int *spellnum, bool (*filter)(AttackInfo*), break; } - for(AttackInfo *a = s->spell; a->rule; ++a) { + for(AttackInfo *a = s->spell; a->name; ++a) { if(!filter(a)) { continue; } @@ -95,6 +95,8 @@ static bool spellfilter_extra(AttackInfo *spell) { return spell->type == AT_ExtraSpell; } +#include "stages/corotest.h" + void stage_init_array(void) { int spellnum = 0; @@ -120,6 +122,9 @@ void stage_init_array(void) { add_spellpractice_stage(stages, &stage1_spell_benchmark, &spellnum, STAGE_SPELL_BIT, D_Extra); #endif + add_stage(0xC0, &corotest_procs, STAGE_SPECIAL, "Coroutines!", "wow such concurrency very async", NULL, D_Any); + add_stage(0xC1, &extra_procs, STAGE_SPECIAL, "Extra Stage", "totally a good idea to create already", NULL, D_Extra); + end_stages(); #ifdef DEBUG @@ -272,7 +277,7 @@ static void stage_enter_ingame_menu(MenuData *m, CallChain next) { } void stage_pause(void) { - if(global.gameover == GAMEOVER_TRANSITIONING) { + if(global.gameover == GAMEOVER_TRANSITIONING || taisei_is_skip_mode_enabled()) { return; } @@ -451,6 +456,33 @@ static void stage_input(void) { player_applymovement(&global.plr); } +#ifdef DEBUG +static const char *_skip_to_bookmark; +bool _skip_to_dialog; + +void _stage_bookmark(const char *name) { + log_debug("Bookmark [%s] reached at %i", name, global.frames); + + if(_skip_to_bookmark && !strcmp(_skip_to_bookmark, name)) { + _skip_to_bookmark = NULL; + global.plr.iddqd = false; + } +} + +DEFINE_EXTERN_TASK(stage_bookmark) { + _stage_bookmark(ARGS.name); +} +#endif + +static bool _stage_should_skip(void) { +#ifdef DEBUG + if(_skip_to_bookmark || _skip_to_dialog) { + return true; + } +#endif + return false; +} + static void stage_logic(void) { player_logic(&global.plr); @@ -462,6 +494,16 @@ static void stage_logic(void) { process_projectiles(&global.particles, false); dialog_update(&global.dialog); + if(_stage_should_skip()) { + if(dialog_is_active(global.dialog)) { + dialog_page(&global.dialog); + } + + if(global.boss) { + ent_damage(&global.boss->ent, &(DamageInfo) { 400, DMG_PLAYER_SHOT } ); + } + } + update_sounds(); global.frames++; @@ -550,6 +592,7 @@ static bool ellipse_predicate(EntityInterface *ent, void *varg) { default: UNREACHABLE; } } + void stage_clear_hazards_at(cmplx origin, double radius, ClearHazardsFlags flags) { Circle area = { origin, radius }; stage_clear_hazards_predicate(proximity_predicate, &area, flags); @@ -672,14 +715,11 @@ bool stage_is_cleared(void) { typedef struct StageFrameState { StageInfo *stage; - int transition_delay; - uint16_t last_replay_fps; CallChain cc; + CoSched sched; + int transition_delay; int logic_calls; - -#ifdef DEBUG - bool skip_to_dialog; -#endif + uint16_t last_replay_fps; } StageFrameState; static void stage_update_fps(StageFrameState *fstate) { @@ -723,6 +763,31 @@ static void stage_give_clear_bonus(const StageInfo *stage, StageClearBonus *bonu player_add_points(&global.plr, bonus->total, global.plr.pos); } +inline bool stage_should_yield(void) { + return (global.boss && !boss_is_fleeing(global.boss)) || dialog_is_active(global.dialog); +} + +int stage_yield(void) { + int num_yields = 0; + + do { + cotask_yield(NULL); + ++num_yields; + } while(stage_should_yield()); + + return num_yields; +} + +int stage_wait(int delay) { + int num_yields = 0; + + while(delay-- > 0) { + num_yields += stage_yield(); + } + + return num_yields; +} + static LogicFrameAction stage_logic_frame(void *arg) { StageFrameState *fstate = arg; StageInfo *stage = fstate->stage; @@ -731,11 +796,9 @@ static LogicFrameAction stage_logic_frame(void *arg) { stage_update_fps(fstate); - #ifdef DEBUG - if(fstate->skip_to_dialog) { + if(_stage_should_skip()) { global.plr.iddqd = true; } - #endif if(global.shake_view > 30) { global.shake_view = 30; @@ -752,7 +815,9 @@ static LogicFrameAction stage_logic_frame(void *arg) { ((global.replaymode == REPLAY_PLAY) ? replay_input : stage_input)(); if(global.gameover != GAMEOVER_TRANSITIONING) { - if((!global.boss || boss_is_fleeing(global.boss)) && !dialog_is_active(global.dialog)) { + cosched_run_tasks(&fstate->sched); + + if(!stage_should_yield()) { stage->procs->event(); } @@ -769,7 +834,7 @@ static LogicFrameAction stage_logic_frame(void *arg) { stage->procs->update(); } - replay_stage_check_desync(global.replay_stage, global.frames, (tsrand() ^ global.plr.points) & 0xFFFF, global.replaymode); + replay_stage_check_desync(global.replay_stage, global.frames, (rng_u64() ^ global.plr.points) & 0xFFFF, global.replaymode); stage_logic(); if(fstate->transition_delay) { @@ -788,20 +853,18 @@ static LogicFrameAction stage_logic_frame(void *arg) { return LFRAME_STOP; } - #ifdef DEBUG - if(fstate->skip_to_dialog) { - if(dialog_is_active(global.dialog)) { - fstate->skip_to_dialog = false; - global.plr.iddqd = false; - } else { - return LFRAME_SKIP; - } +#ifdef DEBUG + if(_skip_to_dialog && dialog_is_active(global.dialog)) { + _skip_to_dialog = false; + global.plr.iddqd = false; } - if(gamekeypressed(KEY_SKIP)) { + taisei_set_skip_mode(_stage_should_skip()); + + if(taisei_is_skip_mode_enabled() || gamekeypressed(KEY_SKIP)) { return LFRAME_SKIP; } - #endif +#endif if(global.frameskip || (global.replaymode == REPLAY_PLAY && gamekeypressed(KEY_SKIP))) { return LFRAME_SKIP; @@ -814,19 +877,17 @@ static RenderFrameAction stage_render_frame(void *arg) { StageFrameState *fstate = arg; StageInfo *stage = fstate->stage; -#if DEBUG - if(fstate->skip_to_dialog) { + if(_stage_should_skip()) { return RFRAME_DROP; } -#endif - tsrand_lock(&global.rand_game); - tsrand_switch(&global.rand_visual); + rng_lock(&global.rand_game); + rng_make_active(&global.rand_visual); BEGIN_DRAW_CODE(); stage_draw_scene(stage); END_DRAW_CODE(); - tsrand_unlock(&global.rand_game); - tsrand_switch(&global.rand_game); + rng_unlock(&global.rand_game); + rng_make_active(&global.rand_game); draw_transition(); return RFRAME_SWAP; @@ -834,16 +895,28 @@ static RenderFrameAction stage_render_frame(void *arg) { static void stage_end_loop(void *ctx); +static void stage_stub_proc(void) { } + void stage_enter(StageInfo *stage, CallChain next) { assert(stage); assert(stage->procs); - assert(stage->procs->preload); - assert(stage->procs->begin); - assert(stage->procs->end); - assert(stage->procs->draw); - assert(stage->procs->event); - assert(stage->procs->update); - assert(stage->procs->shader_rules); + + #define STUB_PROC(proc, stub) do {\ + if(!stage->procs->proc) { \ + stage->procs->proc = stub; \ + log_debug(#proc " proc is missing"); \ + } \ + } while(0) + + static const ShaderRule shader_rules_stub[1] = { NULL }; + + STUB_PROC(preload, stage_stub_proc); + STUB_PROC(begin, stage_stub_proc); + STUB_PROC(end, stage_stub_proc); + STUB_PROC(draw, stage_stub_proc); + STUB_PROC(event, stage_stub_proc); + STUB_PROC(update, stage_stub_proc); + STUB_PROC(shader_rules, (ShaderRule*)shader_rules_stub); if(global.gameover == GAMEOVER_WIN) { global.gameover = 0; @@ -861,13 +934,13 @@ void stage_enter(StageInfo *stage, CallChain next) { stage_preload(); stage_draw_init(); - tsrand_switch(&global.rand_game); + rng_make_active(&global.rand_game); stage_start(stage); if(global.replaymode == REPLAY_RECORD) { uint64_t start_time = (uint64_t)time(0); uint64_t seed = makeseed(); - tsrand_seed_p(&global.rand_game, seed); + rng_seed(&global.rand_game, seed); global.replay_stage = replay_create_stage(&global.replay, stage, start_time, seed, global.diff, &global.plr); @@ -893,7 +966,7 @@ void stage_enter(StageInfo *stage, CallChain next) { assert(stg != NULL); assert(stage_get(stg->stage) == stage); - tsrand_seed_p(&global.rand_game, stg->rng_seed); + rng_seed(&global.rand_game, stg->rng_seed); log_debug("REPLAY_PLAY mode: %d events, stage: \"%s\"", stg->numevents, stage->title); log_debug("Start time: %"PRIu64, stg->start_time); @@ -905,21 +978,25 @@ void stage_enter(StageInfo *stage, CallChain next) { stg->playpos = 0; } - stage->procs->begin(); - player_stage_post_init(&global.plr); - - if(global.stage->type != STAGE_SPELL) { - display_stage_title(stage); - } - StageFrameState *fstate = calloc(1 , sizeof(*fstate)); + cosched_init(&fstate->sched); + cosched_set_invoke_target(&fstate->sched); fstate->stage = stage; fstate->cc = next; #ifdef DEBUG - fstate->skip_to_dialog = env_get_int("TAISEI_SKIP_TO_DIALOG", 0); + _skip_to_dialog = env_get_int("TAISEI_SKIP_TO_DIALOG", 0); + _skip_to_bookmark = env_get_string_nonempty("TAISEI_SKIP_TO_BOOKMARK", NULL); + taisei_set_skip_mode(_stage_should_skip()); #endif + stage->procs->begin(); + player_stage_post_init(&global.plr); + + if(global.stage->type != STAGE_SPELL) { + display_stage_title(stage); + } + eventloop_enter(fstate, stage_logic_frame, stage_render_frame, stage_end_loop, FPS); } @@ -939,12 +1016,15 @@ void stage_end_loop(void* ctx) { stage_draw_shutdown(); stage_free(); player_free(&global.plr); - tsrand_switch(&global.rand_visual); + cosched_finish(&s->sched); + rng_make_active(&global.rand_visual); free_all_refs(); ent_shutdown(); stage_objpools_free(); stop_sounds(); + taisei_commit_persistent_data(); + taisei_set_skip_mode(false); if(taisei_quit_requested()) { global.gameover = GAMEOVER_ABORT; diff --git a/src/stage.h b/src/stage.h index 85a9b76014..324055fc3b 100644 --- a/src/stage.h +++ b/src/stage.h @@ -17,6 +17,7 @@ #include "difficulty.h" #include "util/graphics.h" #include "dialog.h" +#include "coroutine.h" /* taisei's strange macro language. * @@ -149,11 +150,26 @@ bool stage_is_cleared(void); void stage_unlock_bgm(const char *bgm); +bool stage_should_yield(void); +int stage_yield(void); +int stage_wait(int delay); + +#ifdef DEBUG +void _stage_bookmark(const char *name); +#define STAGE_BOOKMARK(name) _stage_bookmark(#name) +DECLARE_EXTERN_TASK(stage_bookmark, { const char *name; }); +#define STAGE_BOOKMARK_DELAYED(delay, name) INVOKE_TASK_DELAYED(delay, stage_bookmark, #name) +#else +#define STAGE_BOOKMARK(name) ((void)0) +#define STAGE_BOOKMARK_DELAYED(delay, name) ((void)0) +#endif + #include "stages/stage1.h" #include "stages/stage2.h" #include "stages/stage3.h" #include "stages/stage4.h" #include "stages/stage5.h" #include "stages/stage6.h" +#include "stages/extra.h" #endif // IGUARD_stage_h diff --git a/src/stagedraw.c b/src/stagedraw.c index 89e388d381..9fcfc4fdef 100644 --- a/src/stagedraw.c +++ b/src/stagedraw.c @@ -54,6 +54,7 @@ static struct { PostprocessShader *viewport_pp; FBPair fb_pairs[NUM_FBPAIRS]; FBPair powersurge_fbpair; + FBPair glow_fbpair; bool framerate_graphs; bool objpool_stats; @@ -169,15 +170,28 @@ static void stage_draw_fbpair_create( fbmgr_group_fbpair_create(stagedraw.mfb_group, name, &fbconf, pair); } +static void stage_draw_setup_glow_framebuffers(void) { + FBAttachmentConfig a = { 0 }; + a.attachment = FRAMEBUFFER_ATTACH_COLOR0; + a.tex_params.filter.min = TEX_FILTER_LINEAR; + a.tex_params.filter.mag = TEX_FILTER_LINEAR; + a.tex_params.width = VIEWPORT_W; + a.tex_params.height = VIEWPORT_H; + a.tex_params.type = TEX_TYPE_RGB_16_FLOAT; + a.tex_params.wrap.s = TEX_WRAP_CLAMP; + a.tex_params.wrap.t = TEX_WRAP_CLAMP; + + FramebufferConfig fbconf = { 0 }; + fbconf.attachments = &a; + fbconf.num_attachments = 1; + fbmgr_group_fbpair_create(stagedraw.mfb_group, "Stage glow", &fbconf, &stagedraw.glow_fbpair); +} + static void stage_draw_setup_framebuffers(void) { - FBAttachmentConfig a[2], *a_color, *a_depth; + FBAttachmentConfig a[2]; memset(a, 0, sizeof(a)); - a_color = &a[0]; - a_depth = &a[1]; - - a_color->attachment = FRAMEBUFFER_ATTACH_COLOR0; - a_depth->attachment = FRAMEBUFFER_ATTACH_DEPTH; + a[0].attachment = FRAMEBUFFER_ATTACH_COLOR0; StageFramebufferResizeParams rp_fg = { .scaling_base = FBPAIR_FG, .scale.best = 1, .scale.worst = 1 }; StageFramebufferResizeParams rp_fg_aux = { .scaling_base = FBPAIR_FG_AUX, .scale.best = 1, .scale.worst = 1 }; @@ -192,31 +206,36 @@ static void stage_draw_setup_framebuffers(void) { .wrap.t = TEX_WRAP_MIRROR, }; - memcpy(&a_color->tex_params, &tex_common, sizeof(tex_common)); - memcpy(&a_depth->tex_params, &tex_common, sizeof(tex_common)); - - a_depth->tex_params.type = TEX_TYPE_DEPTH; + for(int i = 0; i < ARRAY_SIZE(a); ++i) { + a[i].tex_params = tex_common; + } - // Foreground: 1 RGB texture per FB - a_color->tex_params.type = TEX_TYPE_RGB_16; - stage_draw_fbpair_create(stagedraw.fb_pairs + FBPAIR_FG, 1, a, &rp_fg, "Stage FG"); + // Foreground: 1 RGB texture + glow buffer per FB + a[0].tex_params.type = TEX_TYPE_RGB_16; + a[1].attachment = FRAMEBUFFER_ATTACH_COLOR1; + a[1].tex_params.type = TEX_TYPE_RGB_16_FLOAT; + stage_draw_fbpair_create(stagedraw.fb_pairs + FBPAIR_FG, 2, a, &rp_fg, "Stage FG"); // Foreground auxiliary: 1 RGBA texture per FB - a_color->tex_params.type = TEX_TYPE_RGBA_8; + a[0].tex_params.type = TEX_TYPE_RGBA_8; stage_draw_fbpair_create(stagedraw.fb_pairs + FBPAIR_FG_AUX, 1, a, &rp_fg_aux, "Stage FG AUX"); - // Background: 1 RGB texture + depth per FB - a_color->tex_params.type = TEX_TYPE_RGB_8; + // Background: 1 RGB texture + depth buffer per FB + a[0].tex_params.type = TEX_TYPE_RGB_8; + a[1].attachment = FRAMEBUFFER_ATTACH_DEPTH; + a[1].tex_params.type = TEX_TYPE_DEPTH; stage_draw_fbpair_create(stagedraw.fb_pairs + FBPAIR_BG, 2, a, &rp_bg, "Stage BG"); // Background auxiliary: 1 RGBA texture per FB - a_color->tex_params.type = TEX_TYPE_RGBA_8; + a[0].tex_params.type = TEX_TYPE_RGBA_8; stage_draw_fbpair_create(stagedraw.fb_pairs + FBPAIR_BG_AUX, 1, a, &rp_bg_aux, "Stage BG AUX"); // CAUTION: should be at least 16-bit, lest the feedback shader do an oopsie! - a_color->tex_params.type = TEX_TYPE_RGBA_16; - stagedraw.powersurge_fbpair.front = stage_add_background_framebuffer("Powersurge effect FB 1", 0.125, 0.25, 1, a); - stagedraw.powersurge_fbpair.back = stage_add_background_framebuffer("Powersurge effect FB 2", 0.125, 0.25, 1, a); + a[0].tex_params.type = TEX_TYPE_RGBA_16; + stagedraw.powersurge_fbpair.front = stage_add_background_framebuffer("Powersurge effect FB 1", 0.125, 0.25, 1, &a[0]); + stagedraw.powersurge_fbpair.back = stage_add_background_framebuffer("Powersurge effect FB 2", 0.125, 0.25, 1, &a[0]); + + stage_draw_setup_glow_framebuffers(); } static Framebuffer *add_custom_framebuffer( @@ -960,6 +979,50 @@ void stage_draw_viewport(void) { r_mat_mv_pop(); } +static void stage_draw_glow(void) { + r_state_push(); + + Framebuffer *glow_accum = stagedraw.glow_fbpair.front; + Framebuffer *glow_staging = stagedraw.glow_fbpair.back; + FBPair *foreground = stage_get_fbpair(FBPAIR_FG); + + r_shader("glow_feedback"); + r_uniform_vec2("blur_resolution", VIEWPORT_W, VIEWPORT_H); + + r_blend(BLEND_NONE); + r_framebuffer(glow_staging); + r_uniform_vec2("blur_direction", 1, 0); + r_uniform_float("fade", 1); + draw_framebuffer_attachment(foreground->front, VIEWPORT_W, VIEWPORT_H, FRAMEBUFFER_ATTACH_COLOR1); + + r_blend(BLEND_PREMUL_ALPHA); + r_framebuffer(glow_accum); + r_uniform_vec2("blur_direction", 0, 1); + r_uniform_float("fade", 1); + draw_framebuffer_tex(glow_staging, VIEWPORT_W, VIEWPORT_H); + + r_blend(BLEND_NONE); + + for(int i = 0; i < 1; ++i) { + r_framebuffer(glow_staging); + r_uniform_vec2("blur_direction", 1, 0); + r_uniform_float("fade", 1); + draw_framebuffer_tex(glow_accum, VIEWPORT_W, VIEWPORT_H); + + r_framebuffer(glow_accum); + r_uniform_vec2("blur_direction", 0, 1); + r_uniform_float("fade", 0.7); + draw_framebuffer_tex(glow_staging, VIEWPORT_W, VIEWPORT_H); + } + + r_blend(BLEND_PREMUL_ALPHA); + r_shader("glow_apply"); + r_framebuffer(foreground->front); + draw_framebuffer_tex(glow_accum, VIEWPORT_W, VIEWPORT_H); + + r_state_pop(); +} + void stage_draw_scene(StageInfo *stage) { #ifdef DEBUG bool key_nobg = gamekeypressed(KEY_NOBACKGROUND); @@ -986,6 +1049,10 @@ void stage_draw_scene(StageInfo *stage) { begin_viewport_shake(); + if(!key_nobg) { + r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 1), 1); + } + if(draw_bg) { // blit the background r_state_push(); @@ -999,8 +1066,6 @@ void stage_draw_scene(StageInfo *stage) { if(global.plr.mode->procs.bombbg /*&& player_is_bomb_active(&global.plr)*/) { global.plr.mode->procs.bombbg(&global.plr); } - } else if(!key_nobg) { - r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 1), 1); } // draw the 2D objects @@ -1012,6 +1077,8 @@ void stage_draw_scene(StageInfo *stage) { fbpair_swap(foreground); r_blend(BLEND_NONE); + stage_draw_glow(); + // bomb effects shader if present and player bombing if(global.plr.mode->procs.bomb_shader && player_is_bomb_active(&global.plr)) { ShaderRule rules[] = { global.plr.mode->procs.bomb_shader, NULL }; diff --git a/src/stages/corotest.c b/src/stages/corotest.c new file mode 100644 index 0000000000..3e8ea7aefc --- /dev/null +++ b/src/stages/corotest.c @@ -0,0 +1,195 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "corotest.h" +#include "coroutine.h" +#include "global.h" +#include "common_tasks.h" + +TASK(laserproj_death, { Projectile *p; }) { + spawn_projectile_clear_effect(ARGS.p); +} + +TASK(laserize_proj, { BoxedProjectile p; int t; }) { + Projectile *p = TASK_BIND(ARGS.p); + WAIT(ARGS.t); + + cmplx pos = p->pos; + double a = p->angle; + Color clr = p->color; + kill_projectile(p); + + cmplx aim = 12 * cexp(I * a); + create_laserline(pos, aim, 60, 80, &clr); + + p = PROJECTILE( + .pos = pos, + .proto = pp_ball, + .color = &clr, + .timeout = 60, + ); + + INVOKE_TASK_WHEN(&p->events.killed, laserproj_death, p); +} + +TASK(wait_event_test, { BoxedEnemy e; int rounds; int delay; int cnt; int cnt_inc; }) { + Enemy *e = ENT_UNBOX(ARGS.e); + + if(WAIT_EVENT(&e->events.killed).event_status == CO_EVENT_CANCELED) { + // Event canceled? Nothing to do here. + log_debug("[%p] leave (canceled)", (void*)cotask_active()); + return; + } + + // Event signaled. Since this is an enemy death event, e will be invalid + // in the next frame. Let's save its position while we can. + cmplx pos = e->pos; + + while(ARGS.rounds--) { + WAIT(ARGS.delay); + + real angle_ofs = rng_angle(); + + for(int i = 0; i < ARGS.cnt; ++i) { + cmplx aim = cexp(I * (angle_ofs + M_PI * 2.0 * i / (double)ARGS.cnt)); + + PROJECTILE( + .pos = pos, + .proto = pp_crystal, + .color = RGBA(i / (double)ARGS.cnt, 0.0, 1.0 - i / (double)ARGS.cnt, 0.0), + .move = move_asymptotic(12 * aim, 2 * aim, 0.8), + ); + + WAIT(1); + } + + ARGS.cnt += ARGS.cnt_inc; + } +} + +TASK_WITH_FINALIZER(test_enemy, { + double hp; cmplx pos; cmplx dir; + struct { int x; } for_finalizer; +}) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, ARGS.hp, BigFairy, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .life_fragment = 1, + .bomb_fragment = 1, + .power = 3, + .points = 5, + }); + + INVOKE_TASK(wait_event_test, ENT_BOX(e), 3, 10, 15, 3); + + for(;;) { + YIELD; + ARGS.for_finalizer.x++; + + // wander around for a bit... + for(int i = 0; i < 60; ++i) { + e->pos += ARGS.dir; + YIELD; + } + + // stop and... + WAIT(10); + + // pew pew!!! + cmplx aim = 3 * cnormalize(global.plr.pos - e->pos); + int pcount = 120; + + for(int i = 0; i < pcount; ++i) { + for(int j = -1; j < 2; j += 2) { + double a = j * M_PI * 0.1 * psin(20 * (2 * global.frames + i * 10)); + + Projectile *p = PROJECTILE( + .pos = e->pos, + .proto = pp_rice, + .color = RGBA(1.0, 0.2, i / (pcount - 1.0), 0.0), + .move = move_asymptotic(aim * 4 * cdir(a + M_PI), aim * cdir(-a * 2), 0.9), + .max_viewport_dist = 128, + ); + + INVOKE_TASK(laserize_proj, ENT_BOX(p), 40); + } + + WAIT(2); + } + + // keep wandering, randomly + ARGS.dir *= rng_dir(); + } +} + +TASK_FINALIZER(test_enemy) { + log_debug("finalizer called (x = %i)", ARGS.for_finalizer.x); +} + +TASK_WITH_FINALIZER(subtask_test, { int depth; int num; }) { + if(ARGS.depth > 0) { + for(int i = 0; i < 3; ++i) { + INVOKE_SUBTASK(subtask_test, ARGS.depth - 1, i); + } + } + + for(int i = 0;; ++i) { + log_debug("subtask_test: depth=%i; num=%i; iter=%i", ARGS.depth, ARGS.num, i); + YIELD; + } +} + +TASK_FINALIZER(subtask_test) { + log_debug("finalize subtask_test: depth=%i; num=%i", ARGS.depth, ARGS.num); +} + +TASK(subtask_test_init, NO_ARGS) { + log_debug("************ BEGIN ************"); + INVOKE_SUBTASK(subtask_test, 3, -1); + WAIT(300); + log_debug("************ END ************"); +} + +TASK(punching_bag, NO_ARGS) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(0.5*(VIEWPORT_W+VIEWPORT_H*I), 1000, BigFairy, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .life_fragment = 10, + .bomb_fragment = 100, + .power = 3, + .points = 5, + }); +} + +TASK(stage_main, NO_ARGS) { + YIELD; + + stage_wait(30); + log_debug("test 1! %i", global.timer); + stage_wait(60); + log_debug("test 2! %i", global.timer); + + INVOKE_TASK(punching_bag); + return; + + for(;;) { + INVOKE_TASK(subtask_test_init); + INVOKE_TASK_DELAYED(60, test_enemy, 9000, CMPLX(VIEWPORT_W, VIEWPORT_H) * 0.5, 3*I); + stage_wait(1000); + } +} + +static void cotest_begin(void) { + INVOKE_TASK(stage_main); +} + +StageProcs corotest_procs = { + .begin = cotest_begin, +}; diff --git a/src/stages/corotest.h b/src/stages/corotest.h new file mode 100644 index 0000000000..7c4b4ec046 --- /dev/null +++ b/src/stages/corotest.h @@ -0,0 +1,18 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_stages_corotest_h +#define IGUARD_stages_corotest_h + +#include "taisei.h" + +#include "stage.h" + +extern StageProcs corotest_procs; + +#endif // IGUARD_stages_corotest_h diff --git a/src/stages/extra.c b/src/stages/extra.c new file mode 100644 index 0000000000..0e12409df5 --- /dev/null +++ b/src/stages/extra.c @@ -0,0 +1,112 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "extra.h" +#include "coroutine.h" +#include "global.h" +#include "common_tasks.h" + +TASK(glider_bullet, { + cmplx pos; double dir; double spacing; int interval; +}) { + const int nproj = 5; + const int nstep = 4; + BoxedProjectile projs[nproj]; + + const cmplx positions[][5] = { + {1+I, -1, 1, -I, 1-I}, + {2, I, 1, -I, 1-I}, + {2, 1+I, 2-I, -I, 1-I}, + {2, 0, 2-I, 1-I, 1-2*I}, + }; + + cmplx trans = cdir(ARGS.dir+1*M_PI/4)*ARGS.spacing; + + for(int i = 0; i < nproj; i++) { + projs[i] = ENT_BOX(PROJECTILE( + .pos = ARGS.pos+positions[0][i]*trans, + .proto = pp_ball, + .color = RGBA(0,0,1,1), + )); + } + + + for(int step = 0;; step++) { + int cur_step = step%nstep; + int next_step = (step+1)%nstep; + + int dead_count = 0; + for(int i = 0; i < nproj; i++) { + Projectile *p = ENT_UNBOX(projs[i]); + if(p == NULL) { + dead_count++; + } else { + p->move.retention = 1; + p->move.velocity = -(positions[cur_step][i]-(1-I)*(cur_step==3)-positions[next_step][i])*trans/ARGS.interval; + } + } + if(dead_count == nproj) { + return; + } + WAIT(ARGS.interval); + } +} + +TASK(glider_fairy, { + double hp; cmplx pos; cmplx dir; +}) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(VIEWPORT_W/2-10*I, ARGS.hp, BigFairy, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .power = 3, + .points = 5, + }); + + + + YIELD; + + for(int i = 0; i < 80; ++i) { + e->pos += cnormalize(ARGS.pos-e->pos)*2; + YIELD; + } + + + for(int i = 0; i < 3; i++) { + double aim = carg(global.plr.pos - e->pos); + INVOKE_TASK(glider_bullet, e->pos, aim-0.7, 20, 6); + INVOKE_TASK(glider_bullet, e->pos, aim, 25, 3); + INVOKE_TASK(glider_bullet, e->pos, aim+0.7, 20, 6); + WAIT(80-20*i); + } + + for(;;) { + e->pos += 2*(creal(e->pos)-VIEWPORT_W/2 > 0)-1; + YIELD; + } +} + +TASK(stage_main, NO_ARGS) { + YIELD; + + for(int i = 0;;i++) { + INVOKE_TASK_DELAYED(60, glider_fairy, 2000, CMPLX(VIEWPORT_W*(i&1), VIEWPORT_H*0.5), 3*I); + stage_wait(50+100*(i&1)); + } +} + +static void extra_begin(void) { + INVOKE_TASK(stage_main); +} + + +StageProcs extra_procs = { + .begin = extra_begin, +}; diff --git a/src/stages/extra.h b/src/stages/extra.h new file mode 100644 index 0000000000..5e01905ddd --- /dev/null +++ b/src/stages/extra.h @@ -0,0 +1,18 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_stages_extra_h +#define IGUARD_stages_extra_h + +#include "taisei.h" + +#include "stage.h" + +extern StageProcs extra_procs; + +#endif // IGUARD_stages_extra_h diff --git a/src/stages/meson.build b/src/stages/meson.build index c52a523696..c949229458 100644 --- a/src/stages/meson.build +++ b/src/stages/meson.build @@ -12,6 +12,7 @@ stages_src = files( 'stage5_events.c', 'stage6.c', 'stage6_events.c', + 'extra.c', ) if is_developer_build @@ -19,3 +20,5 @@ if is_developer_build 'dpstest.c', ) endif + +stages_src += files('corotest.c') diff --git a/src/stages/stage1.c b/src/stages/stage1.c index f68e959e9a..0828454897 100644 --- a/src/stages/stage1.c +++ b/src/stages/stage1.c @@ -16,6 +16,8 @@ #include "stageutils.h" #include "stagedraw.h" #include "resource/model.h" +#include "util/glm.h" +#include "common_tasks.h" /* * See the definition of AttackInfo in boss.h for information on how to set up the idmaps. @@ -26,7 +28,7 @@ struct stage1_spells_s stage1_spells = { .mid = { .perfect_freeze = { { 0, 1, 2, 3}, AT_Spellcard, "Freeze Sign “Perfect Freeze”", 50, 24000, - cirno_perfect_freeze, cirno_pfreeze_bg, VIEWPORT_W/2.0+100.0*I, 1 + NULL, cirno_pfreeze_bg, VIEWPORT_W/2.0+100.0*I, 1, TASK_INDIRECT_INIT(BossAttack, stage1_spell_perfect_freeze) }, }, @@ -51,7 +53,20 @@ struct stage1_spells_s stage1_spells = { }, }; -static FBPair stage1_bg_fbpair; +static struct { + FBPair water_fbpair; + + struct { + float near, near_target; + float far, far_target; + } fog; + + struct { + float opacity, opacity_target; + } snow; + + float pitch_target; +} stage1_bg; #ifdef SPELL_BENCHMARK AttackInfo stage1_spell_benchmark = { @@ -109,42 +124,54 @@ static void stage1_water_draw(vec3 pos) { r_mat_mv_translate(0, stage_3d_context.cx[1] + 500, 0); r_mat_mv_rotate(M_PI, 1, 0, 0); + static const Color water_color = { 0, 0.08, 0.08, 1 }; + + r_clear(CLEAR_COLOR, &water_color, 1); + + r_mat_mv_push(); + r_mat_mv_translate(0, -9000, 0); + r_mat_mv_rotate(M_PI/2, 1, 0, 0); + r_mat_mv_scale(3640*1.4, 1456*1.4, 1); + r_mat_mv_translate(0, -0.5, 0); + r_shader_standard(); + r_uniform_sampler("tex", "stage1/horizon"); + r_draw_quad(); + r_mat_mv_pop(); + + r_state_push(); + r_enable(RCAP_DEPTH_TEST); + r_depth_func(DEPTH_ALWAYS); r_shader_standard_notex(); r_mat_mv_push(); - r_mat_mv_scale(1200, 3000, 1); - r_color4(0, 0.1, 0.1, 1); + r_mat_mv_scale(80000, 80000, 1); + r_color(&water_color); r_draw_quad(); r_color4(1, 1, 1, 1); r_mat_mv_pop(); + r_state_pop(); - r_disable(RCAP_CULL_FACE); r_disable(RCAP_DEPTH_TEST); + r_disable(RCAP_CULL_FACE); Framebuffer *bg_fb = r_framebuffer_current(); - FBPair *fbpair = &stage1_bg_fbpair; + FBPair *fbpair = &stage1_bg.water_fbpair; r_framebuffer(fbpair->back); r_mat_proj_push(); set_ortho(VIEWPORT_W, VIEWPORT_H); r_mat_mv_push_identity(); - float z = 0.75; - float zo = -0.05; + float hack = (stage_3d_context.crot[0] - 60) / 15.0; + + float z = glm_lerp(0.75, 0.8, hack); + float zo = glm_lerp(-0.05, -0.3, hack); r_mat_mv_translate(VIEWPORT_W * 0.5 * (1 - z), VIEWPORT_H * 0.5 * (1 - z), 0); r_mat_mv_scale(z, z, 1); - r_clear(CLEAR_ALL, RGBA(0, 0.08, 0.08, 1), 1); + r_clear(CLEAR_ALL, RGBA(0, 0, 0, 0), 1); r_shader("sprite_default"); ent_draw(stage1_draw_predicate); - r_mat_mv_push(); - r_shader_standard_notex(); - r_mat_mv_translate(VIEWPORT_W*0.5, VIEWPORT_H*0.5, 0); - r_mat_mv_scale(VIEWPORT_W/z, VIEWPORT_H/z, 1); - r_color4(0, 0.08, 0.08, 0.8); - r_draw_quad(); - r_mat_mv_pop(); - r_mat_mv_pop(); fbpair_swap(fbpair); @@ -152,6 +179,11 @@ static void stage1_water_draw(vec3 pos) { int pp_quality = config_get_int(CONFIG_POSTPROCESS); + ShaderProgram *water_shader = r_shader_get("stage1_water"); + r_uniform_float(r_shader_uniform(water_shader, "time"), 0.5 * global.frames / (float)FPS); + r_uniform_vec4_rgba(r_shader_uniform(water_shader, "water_color"), &water_color); + r_uniform_float(r_shader_uniform(water_shader, "wave_offset"), stage_3d_context.cx[1] / 2400.0); + if(pp_quality > 1) { r_shader("blur5"); r_uniform_vec2("blur_resolution", VIEWPORT_W, VIEWPORT_H); @@ -187,21 +219,14 @@ static void stage1_water_draw(vec3 pos) { } r_mat_mv_push(); - r_mat_mv_translate(0, 70, 0); + r_mat_mv_translate(0, 70 - 140 * hack, 0); r_mat_mv_rotate(10 * DEG2RAD, 1, 0, 0); r_mat_mv_scale(0.85 / (z + zo), -0.85 / (z + zo), 0.85); - r_mat_mv_translate(-VIEWPORT_W/2, 0, 0); + r_mat_mv_translate(-VIEWPORT_W/2, VIEWPORT_H/4 * hack, 0); r_color4(1, 1, 1, 1); draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H); r_mat_mv_pop(); - r_shader_standard_notex(); - r_mat_mv_push(); - r_mat_mv_scale(1200,3000,1); - r_color4(0, 0.08, 0.08, 0.08); - r_draw_quad(); - r_mat_mv_pop(); - r_mat_mv_pop(); r_state_pop(); } @@ -214,37 +239,45 @@ static uint stage1_bg_pos(Stage3D *s3d, vec3 p, float maxrange) { static void stage1_smoke_draw(vec3 pos) { float d = fabsf(pos[1]-stage_3d_context.cx[1]); + float o = ((d-500)*(d-500))/1.5e7; + o *= 5 * pow((5000 - d) / 5000, 3); + + if(o < 1.0f/255.0f) { + return; + } + + float spin = 0.01 * sin(pos[1]*993.11); + r_state_push(); r_shader("sprite_default"); r_disable(RCAP_DEPTH_TEST); r_cull(CULL_BACK); r_mat_mv_push(); - r_mat_mv_translate(pos[0]+200*sin(pos[1]), pos[1], pos[2]+200*sin(pos[1]/25.0)); + r_mat_mv_translate(pos[0]+600*sin(pos[1]), pos[1], pos[2]+600*sin(pos[1]/25.0)); r_mat_mv_rotate(M_PI/2, -1, 0, 0); - r_mat_mv_scale(3.5, 2, 1); - r_mat_mv_rotate(global.frames * DEG2RAD, 0, 0, 1); - float o = ((d-500)*(d-500))/1.5e7; + r_mat_mv_scale(3.5*2, 2*1.5, 1); + r_mat_mv_rotate(global.frames * spin + M_PI * 2 * sin(pos[1]*321.23), 0, 0, 1); r_draw_sprite(&(SpriteParams) { .sprite = "stage1/fog", - .color = RGBA(0.8 * o, 0.8 * o, 0.8 * o, o * 0.5), + .color = RGBA(0.6 * o, 0.7 * o, 0.8 * o, o * 0.5), }); r_mat_mv_pop(); r_state_pop(); } static uint stage1_smoke_pos(Stage3D *s3d, vec3 p, float maxrange) { - vec3 q = {0,0,-300}; - vec3 r = {0,300,0}; + vec3 q = {0,0,800}; + vec3 r = {0,200,0}; return linear3dpos(s3d, p, maxrange/2.0, q, r); } static bool stage1_fog(Framebuffer *fb) { r_shader("zbuf_fog"); r_uniform_sampler("depth", r_framebuffer_get_attachment(fb, FRAMEBUFFER_ATTACH_DEPTH)); - r_uniform_vec4("fog_color", 0.8, 0.8, 0.8, 1.0); - r_uniform_float("start", 0.0); - r_uniform_float("end", 0.8); - r_uniform_float("exponent", 3.0); + r_uniform_vec4("fog_color", 0.78, 0.8, 0.85, 1.0); + r_uniform_float("start", stage1_bg.fog.near); + r_uniform_float("end", stage1_bg.fog.far); + r_uniform_float("exponent", 1.0); r_uniform_float("sphereness", 0.2); draw_framebuffer_tex(fb, VIEWPORT_W, VIEWPORT_H); r_shader_standard(); @@ -252,24 +285,21 @@ static bool stage1_fog(Framebuffer *fb) { } static void stage1_draw(void) { - set_perspective(&stage_3d_context, 500, 5000); - draw_stage3d(&stage_3d_context, 7000); -} - -static void stage1_update(void) { - update_stage3d(&stage_3d_context); + set_perspective(&stage_3d_context, 500, 10000); + draw_stage3d(&stage_3d_context, 10000); } static inline uint32_t floathash(float f) { - return (union { uint32_t i; float f; }){ .f = f }.i; + uint32_t bits = float_to_bits(f); + return splitmix32(&bits); } static void stage1_waterplants_draw(vec3 pos) { r_state_push(); // stateless pseudo-random fuckery - int tile = floathash(pos[1] * 3124312) & 1; - float offs = 200 * sin(2*M_PI*remainder(3214.322211333 * floathash(pos[1]), M_E)); + int tile = floathash(pos[1] * 311.4312) & 1; + float offs = 600 * sin(2*M_PI*remainder(3214.322211333 * floathash(pos[1]), M_E)); float d = -55+50*sin(pos[1]/25.0); r_mat_mv_push(); r_mat_mv_translate(pos[0]+offs, pos[1], d); @@ -305,26 +335,127 @@ static void stage1_waterplants_draw(vec3 pos) { r_state_pop(); } +static uint stage1_waterplants_pos(Stage3D *s3d, vec3 p, float maxrange) { + vec3 q = {0,0,-300}; + vec3 r = {0,150,0}; + return linear3dpos(s3d, p, maxrange/2.0, q, r); +} + +static void stage1_snow_draw(vec3 pos) { + float o = stage1_bg.snow.opacity; + // float appear_time = 2760; + + if(o < 1.0f/256.0f) { + return; + } + + float d = fabsf(pos[1] - stage_3d_context.cx[1]); + + if(fabsf(d) < 500) { + return; + } + + float x = ((d+500)*(d+500))/(5000*5000); + + float h0 = floathash(pos[1]+0) / (double)UINT32_MAX; + float h1 = floathash(pos[1]+1) / (double)UINT32_MAX; + float h2 = floathash(pos[1]+2) / (double)UINT32_MAX; + + float height = 1 + sawtooth(h0 + global.frames/240.0); + + o *= pow(1 - 1.5 * clamp(height - 1, 0, 1), 5) * x; + // o *= min(1, (global.frames - appear_time) / 180.0); + + if(o < 1.0f/256.0f) { + return; + } + + if(height > 1) { + height = 1; + } else { + height = glm_ease_quad_in(height); + } + + if(o > 1) { + o = 1; + } + + r_state_push(); + r_shader("sprite_default"); + r_disable(RCAP_DEPTH_TEST); + r_cull(CULL_BACK); + r_mat_mv_push(); + r_mat_mv_translate(pos[0] + 2200 * sawtooth(h1), pos[1] + 10 * sawtooth(h2), 1200 - 1200 * height); + r_mat_mv_rotate(M_PI/2, -1, 0, 0); + r_draw_sprite(&(SpriteParams) { + .sprite = "part/smoothdot", + .color = RGBA(o, o, o, 0), + }); + r_mat_mv_pop(); + r_state_pop(); + +} + +static uint stage1_snow_pos(Stage3D *s3d, vec3 p, float maxrange) { + vec3 q = {0,0,0}; + vec3 r = {0,15,0}; + return linear3dpos(s3d, p, maxrange, q, r); +} + +void stage1_bg_raise_camera(void) { + stage1_bg.pitch_target = 75; +} + +void stage1_bg_enable_snow(void) { + stage1_bg.snow.opacity_target = 1; +} + +void stage1_bg_disable_snow(void) { + stage1_bg.snow.opacity_target = 0; +} + +static void stage1_update(void) { + update_stage3d(&stage_3d_context); + + stage_3d_context.crot[1] = 2 * sin(global.frames/113.0); + stage_3d_context.crot[2] = 1 * sin(global.frames/132.0); + + fapproach_asymptotic_p(&stage_3d_context.crot[0], stage1_bg.pitch_target, 0.01, 1e-5); + fapproach_asymptotic_p(&stage1_bg.fog.near, stage1_bg.fog.near_target, 0.001, 1e-5); + fapproach_asymptotic_p(&stage1_bg.fog.far, stage1_bg.fog.far_target, 0.001, 1e-5); + fapproach_p(&stage1_bg.snow.opacity, stage1_bg.snow.opacity_target, 1.0 / 180.0); +} + static void stage1_start(void) { - init_stage3d(&stage_3d_context, 16); + init_stage3d(&stage_3d_context, 32); add_model(&stage_3d_context, stage1_water_draw, stage1_bg_pos); - add_model(&stage_3d_context, stage1_waterplants_draw, stage1_smoke_pos); + add_model(&stage_3d_context, stage1_waterplants_draw, stage1_waterplants_pos); + add_model(&stage_3d_context, stage1_snow_draw, stage1_snow_pos); add_model(&stage_3d_context, stage1_smoke_draw, stage1_smoke_pos); - stage_3d_context.crot[0] = 60; - stage_3d_context.cx[2] = 700; - stage_3d_context.cv[1] = 4; - FBAttachmentConfig cfg = { 0 }; cfg.attachment = FRAMEBUFFER_ATTACH_COLOR0; cfg.tex_params.filter.min = TEX_FILTER_LINEAR; cfg.tex_params.filter.mag = TEX_FILTER_LINEAR; - cfg.tex_params.type = TEX_TYPE_RGB; + cfg.tex_params.type = TEX_TYPE_RGBA; cfg.tex_params.wrap.s = TEX_WRAP_CLAMP; cfg.tex_params.wrap.t = TEX_WRAP_CLAMP; - stage1_bg_fbpair.front = stage_add_background_framebuffer("Stage 1 water FB 1", 0.2, 0.5, 1, &cfg); - stage1_bg_fbpair.back = stage_add_background_framebuffer("Stage 1 water FB 2", 0.2, 0.5, 1, &cfg); + stage1_bg.water_fbpair.front = stage_add_background_framebuffer("Stage 1 water FB 1", 0.2, 0.5, 1, &cfg); + stage1_bg.water_fbpair.back = stage_add_background_framebuffer("Stage 1 water FB 2", 0.2, 0.5, 1, &cfg); + + stage1_bg.fog.near_target = 0.5; + stage1_bg.fog.far_target = 1.5; + stage1_bg.snow.opacity_target = 0.0; + stage1_bg.pitch_target = 60; + + stage1_bg.fog.near = 0.2; // stage1_bg.fog.near_target; + stage1_bg.fog.far = 1.0; // stage1_bg.fog.far_target; + stage1_bg.snow.opacity = stage1_bg.snow.opacity_target; + + stage_3d_context.crot[0] = stage1_bg.pitch_target; + stage_3d_context.cx[2] = 700; + stage_3d_context.cv[1] = 8; } static void stage1_preload(void) { @@ -336,6 +467,9 @@ static void stage1_preload(void) { "stage1/waterplants", "dialog/cirno", NULL); + preload_resources(RES_TEXTURE, RESF_DEFAULT, + "stage1/horizon", + NULL); preload_resources(RES_SHADER_PROGRAM, RESF_DEFAULT, "blur5", "lasers/linear", @@ -352,6 +486,7 @@ static void stage1_preload(void) { static void stage1_end(void) { free_stage3d(&stage_3d_context); + memset(&stage1_bg, 0, sizeof(stage1_bg)); } static void stage1_spellpractice_start(void) { @@ -363,6 +498,16 @@ static void stage1_spellpractice_start(void) { global.boss = cirno; stage_start_bgm("stage1boss"); + + stage1_bg_raise_camera(); + stage1_bg_enable_snow(); + + stage1_bg.fog.near = stage1_bg.fog.near_target; + stage1_bg.fog.far = stage1_bg.fog.far_target; + stage1_bg.snow.opacity = stage1_bg.snow.opacity_target; + stage_3d_context.crot[0] = stage1_bg.pitch_target; + + INVOKE_TASK_WHEN(&cirno->events.defeated, common_call_func, stage1_bg_disable_snow); } static void stage1_spellpractice_events(void) { diff --git a/src/stages/stage1.h b/src/stages/stage1.h index c0bedd96a7..88d7ea4051 100644 --- a/src/stages/stage1.h +++ b/src/stages/stage1.h @@ -42,6 +42,10 @@ extern struct stage1_spells_s { extern StageProcs stage1_procs; extern StageProcs stage1_spell_procs; +void stage1_bg_raise_camera(void); +void stage1_bg_enable_snow(void); +void stage1_bg_disable_snow(void); + #ifdef SPELL_BENCHMARK extern AttackInfo stage1_spell_benchmark; #endif diff --git a/src/stages/stage1_events.c b/src/stages/stage1_events.c index c078927c76..f093544fbd 100644 --- a/src/stages/stage1_events.c +++ b/src/stages/stage1_events.c @@ -11,6 +11,7 @@ #include "stage1_events.h" #include "global.h" #include "stagetext.h" +#include "common_tasks.h" static Dialog *stage1_dialog_pre_boss(void) { PlayerMode *pm = global.plr.mode; @@ -31,217 +32,17 @@ static Dialog *stage1_dialog_post_boss(void) { return d; } -static void cirno_intro(Boss *c, int time) { - if(time < 0) - return; - - GO_TO(c, VIEWPORT_W/2.0 + 100.0*I, 0.035); -} - -static int cirno_snowflake_proj(Projectile *p, int time) { - if(time < 0) - return ACTION_ACK; - - int split_time = 200 - 20*global.diff - creal(p->args[1]) * 3; - - if(time < split_time) { - p->pos += p->args[0]; - } else { - if(time == split_time) { - play_sound_ex("redirect", 30, false); - play_sound_ex("shot_special1", 30, false); - color_lerp(&p->color, RGB(0.5, 0.5, 0.5), 0.5); - spawn_projectile_highlight_effect(p); - } - - p->pos -= cabs(p->args[0]) * cexp(I*p->angle); - } - - return 1; -} - -static void cirno_icy(Boss *b, int time) { - int interval = 70 - 8 * global.diff; - int t = time % interval; - int run = time / interval; - int size = 5+3*sin(337*run); - - TIMER(&t); - - if(time < 0) { - return; - } - - cmplx vel = (1+0.125*global.diff)*cexp(I*fmod(200*run,M_PI)); - int c = 6; - double dr = 15; - - FROM_TO_SND("shot1_loop", 0, 3*size, 3) { - for(int i = 0; i < c; i++) { - double ang = 2*M_PI/c*i+run*515; - cmplx phase = cexp(I*ang); - cmplx pos = b->pos+vel*t+dr*_i*phase; - - PROJECTILE( - .proto = pp_crystal, - .pos = pos+6*I*phase, - .color = RGB(0.0, 0.1 + 0.1 * size / 5, 0.8), - .rule = cirno_snowflake_proj, - .args = { vel, _i, }, - .angle = ang+M_PI/4, - .max_viewport_dist = 64, - ); - - PROJECTILE( - .proto = pp_crystal, - .pos = pos-6*I*phase, - .color = RGB(0.0,0.1+0.1*size/5,0.8), - .rule = cirno_snowflake_proj, - .args = { vel, _i }, - .angle = ang-M_PI/4, - .max_viewport_dist = 64, - ); - - int split = 3; - - if(_i > split) { - cmplx pos0 = b->pos+vel*t+dr*split*phase; - - for(int j = -1; j <= 1; j+=2) { - cmplx phase2 = cexp(I*M_PI/4*j)*phase; - cmplx pos2 = pos0+(dr*(_i-split))*phase2; - - PROJECTILE( - .proto = pp_crystal, - .pos = pos2, - .color = RGB(0.0,0.3*size/5,1), - .rule = cirno_snowflake_proj, - .args = { vel, _i }, - .angle = ang+M_PI/4*j, - .max_viewport_dist = 64, - ); - } - } - } - } -} - -static Projectile* spawn_stain(cmplx pos, float angle, int to) { +static Projectile *spawn_stain(cmplx pos, float angle, int to) { return PARTICLE( .sprite = "stain", .pos = pos, - .draw_rule = ScaleFade, + .draw_rule = pdraw_timeout_scalefade(0, 0.8, 1, 0), .timeout = to, .angle = angle, .color = RGBA(0.4, 0.4, 0.4, 0), - .args = {0, 0, 0.8*I} ); } -static int cirno_pfreeze_frogs(Projectile *p, int t) { - if(t < 0) - return ACTION_ACK; - - Boss *parent = global.boss; - - if(parent == NULL) - return ACTION_DESTROY; - - int boss_t = (global.frames - parent->current->starttime) % 320; - - if(boss_t < 110) - linear(p, t); - else if(boss_t == 110) { - p->color = *RGB(0.7, 0.7, 0.7); - spawn_stain(p->pos, p->angle, 30); - spawn_stain(p->pos, p->angle, 30); - spawn_projectile_highlight_effect(p); - play_sound("shot_special1"); - } - - if(t == 240) { - p->prevpos = p->pos; - p->pos0 = p->pos; - p->args[0] = (1.8+0.2*global.diff)*cexp(I*2*M_PI*frand()); - spawn_stain(p->pos, p->angle, 30); - spawn_projectile_highlight_effect(p); - play_sound_ex("shot2", 0, false); - } - - if(t > 240) - linear(p, t-240); - - return 1; -} - -void cirno_perfect_freeze(Boss *c, int time) { - int t = time % 320; - TIMER(&t); - - if(time < 0) - return; - - FROM_TO(-40, 0, 1) - GO_TO(c, VIEWPORT_W/2.0 + 100.0*I, 0.04); - - FROM_TO_SND("shot1_loop",20,80,1) { - float r = frand(); - float g = frand(); - float b = frand(); - - int i; - int n = global.diff; - for(i = 0; i < n; i++) { - PROJECTILE( - .proto = pp_ball, - .pos = c->pos, - .color = RGB(r, g, b), - .rule = cirno_pfreeze_frogs, - .args = { 4*cexp(I*tsrand()) }, - ); - } - } - - GO_AT(c, 160, 190, 2 + 1.0*I); - - int d = max(0, global.diff - D_Normal); - AT(140-50*d) - aniplayer_queue(&c->ani,"(9)",0); - AT(220+30*d) - aniplayer_queue(&c->ani,"main",0); - FROM_TO_SND("shot1_loop", 160 - 50*d, 220 + 30*d, 6-global.diff/2) { - float r1, r2; - - if(global.diff > D_Normal) { - r1 = sin(time/M_PI*5.3) * cos(2*time/M_PI*5.3); - r2 = cos(time/M_PI*5.3) * sin(2*time/M_PI*5.3); - } else { - r1 = nfrand(); - r2 = nfrand(); - } - - PROJECTILE( - .proto = pp_rice, - .pos = c->pos + 60, - .color = RGB(0.3, 0.4, 0.9), - .rule = asymptotic, - .args = { (2.+0.2*global.diff)*cexp(I*(carg(global.plr.pos - c->pos) + 0.5*r1)), 2.5 } - ); - PROJECTILE( - .proto = pp_rice, - .pos = c->pos - 60, - .color = RGB(0.3, 0.4, 0.9), - .rule = asymptotic, - .args = { (2.+0.2*global.diff)*cexp(I*(carg(global.plr.pos - c->pos) + 0.5*r2)), 2.5 } - ); - } - - GO_AT(c, 190, 220, -2); - - FROM_TO(280, 320, 1) - GO_TO(c, VIEWPORT_W/2.0 + 100.0*I, 0.04); -} - void cirno_pfreeze_bg(Boss *c, int time) { r_color4(0.5, 0.5, 0.5, 1.0); fill_viewport(time/700.0, time/700.0, 1, "stage1/cirnobg"); @@ -254,12 +55,6 @@ void cirno_pfreeze_bg(Boss *c, int time) { r_color4(1.0, 1.0, 1.0, 1.0); } -static void cirno_mid_flee(Boss *c, int time) { - if(time >= 0) { - GO_TO(c, -250 + 30 * I, 0.02) - } -} - Boss* stage1_spawn_cirno(cmplx pos) { Boss *cirno = create_boss("Cirno", "cirno", pos); boss_set_portrait(cirno, get_sprite("dialog/cirno"), get_sprite("dialog/cirno_face_normal")); @@ -268,18 +63,6 @@ Boss* stage1_spawn_cirno(cmplx pos) { return cirno; } -static Boss* create_cirno_mid(void) { - Boss *cirno = stage1_spawn_cirno(VIEWPORT_W + 220 + 30.0*I); - - boss_add_attack(cirno, AT_Move, "Introduction", 2, 0, cirno_intro, NULL); - boss_add_attack(cirno, AT_Normal, "Icy Storm", 20, 24000, cirno_icy, NULL); - boss_add_attack_from_info(cirno, &stage1_spells.mid.perfect_freeze, false); - boss_add_attack(cirno, AT_Move, "Flee", 5, 0, cirno_mid_flee, NULL); - - boss_start_attack(cirno, cirno->attacks); - return cirno; -} - static void cirno_intro_boss(Boss *c, int time) { if(time < 0) return; @@ -323,12 +106,12 @@ static void cirno_iceplosion0(Boss *c, int time) { .pos = c->pos, .color = RGB(0.3,0.3,0.8), .rule = accelerated, - .args = { global.diff/4.*cexp(2.0*I*M_PI*frand()) + 2.0*I, 0.002*cexp(I*(M_PI/10.0*(_i%20))) } + .args = { global.diff/4.0*rng_dir() + 2.0*I, 0.002*cdir(M_PI/10.0*(_i%20)) } ); } FROM_TO(150, 300, 30-5*global.diff) { - float dif = M_PI*2*frand(); + float dif = rng_angle(); int i; play_sound("shot1"); for(i = 0; i < 20; i++) { @@ -358,14 +141,14 @@ void cirno_crystal_rain(Boss *c, int time) { int hdiff = max(0, (int)global.diff - D_Normal); - if(frand() > 0.95-0.1*global.diff) { - tsrand_fill(2); + if(rng_chance(0.05 + 0.1 * global.diff)) { + RNG_ARRAY(rng, 2); PROJECTILE( .proto = pp_crystal, - .pos = VIEWPORT_W*afrand(0), + .pos = vrng_range(rng[0], 0, VIEWPORT_W), .color = RGB(0.2,0.2,0.4), .rule = accelerated, - .args = { 1.0*I, 0.01*I + (-0.005+0.005*global.diff)*anfrand(1) } + .args = { 1.0*I, 0.01*I + (-0.005+0.005*global.diff) * vrng_sreal(rng[1]) } ); } @@ -421,7 +204,7 @@ static void cirno_iceplosion1(Boss *c, int time) { .pos = c->pos, .color = RGB(0,0,0.5), .rule = asymptotic, - .args = { (3+_i/3.0)*cexp(I*((2)*M_PI/8.0*i + (0.1+0.03*global.diff)*(1 - 2*frand()))), _i*0.7 } + .args = { (3+_i/3.0)*cexp(I*((2)*M_PI/8.0*i + (0.1+0.03*global.diff) * rng_sreal())), _i*0.7 } ); } } @@ -434,7 +217,7 @@ static void cirno_iceplosion1(Boss *c, int time) { .color = RGB(0.3,0.3,0.8), .rule = accelerated, .args = { - 1.5*cexp(2.0*I*M_PI*frand()) - i * 0.4 + 2.0*I*global.diff/4.0, + 1.5*rng_dir() - i * 0.4 + 2.0*I*global.diff/4.0, 0.002*cexp(I*(M_PI/10.0*(_i%20))) } ); @@ -442,7 +225,7 @@ static void cirno_iceplosion1(Boss *c, int time) { } FROM_TO(150, 300, 30 - 6 * global.diff) { - float dif = M_PI*2*frand(); + float dif = rng_angle(); int i; if(_i > 15) { @@ -456,7 +239,7 @@ static void cirno_iceplosion1(Boss *c, int time) { .pos = c->pos, .color = RGB(0.04*_i,0.04*_i,0.4+0.04*_i), .rule = asymptotic, - .args = { (3+_i/3.0)*cexp(I*(2*M_PI/8.0*i + dif)), 2.5 } + .args = { (3+_i/3.0)*cdir(2*M_PI/8.0*i + dif), 2.5 } ); } } @@ -555,7 +338,7 @@ static int halation_orb(Projectile *p, int time) { }; int pcount = sizeof(colors)/sizeof(Color); - float rot = frand() * 2 * M_PI; + float rot = rng_angle(); for(int i = 0; i < pcount; ++i) { PROJECTILE( @@ -563,7 +346,7 @@ static int halation_orb(Projectile *p, int time) { .pos = p->pos, .color = colors+i, .rule = asymptotic, - .args = { cexp(I*(rot + M_PI * 2 * (float)(i+1)/pcount)), 3 } + .args = { cdir(rot + M_PI * 2 * (float)(i+1)/pcount), 3 } ); } @@ -668,7 +451,7 @@ static int cirno_icicles(Projectile *p, int t) { } else if(t == turn) { p->args[0] = 2.5*cexp(I*(carg(p->args[0])-M_PI/2.0+M_PI*(creal(p->args[0]) > 0))); if(global.diff > D_Normal) - p->args[0] += 0.05*nfrand(); + p->args[0] += rng_range(0, 0.05); play_sound("redirect"); spawn_projectile_highlight_effect(p); } else if(t > turn) { @@ -704,7 +487,7 @@ void cirno_icicle_fall(Boss *c, int time) { if(global.diff > D_Easy) { FROM_TO_SND("shot1_loop",120,200,3) { - float f = frand()*_i; + float f = rng_range(0, _i); PROJECTILE(.proto = pp_ball, .pos = c->pos, .color = RGB(0.,0.,0.3), .rule = accelerated, .args = { 0.2*(-2*I-1.5+f),-0.02*I }); PROJECTILE(.proto = pp_ball, .pos = c->pos, .color = RGB(0.,0.,0.3), .rule = accelerated, .args = { 0.2*(-2*I+1.5+f),-0.02*I }); @@ -715,8 +498,8 @@ void cirno_icicle_fall(Boss *c, int time) { FROM_TO(300,400,10) { play_sound("shot1"); float x = VIEWPORT_W/2+VIEWPORT_W/2*(0.3+_i/10.); - float angle1 = M_PI/10*frand(); - float angle2 = M_PI/10*frand(); + float angle1 = rng_range(0, M_PI/10); + float angle2 = rng_range(0, M_PI/10); for(float i = 1; i < 5; i++) { PROJECTILE( .proto = pp_ball, @@ -724,8 +507,8 @@ void cirno_icicle_fall(Boss *c, int time) { .color = RGB(0.,0.,0.3), .rule = accelerated, .args = { - i*I*0.5*cexp(I*angle1), - 0.001*I-(global.diff == D_Lunatic)*0.001*frand() + i*I*0.5*cdir(angle1), + 0.001*I-(global.diff == D_Lunatic)*0.001*rng_real() } ); @@ -735,15 +518,13 @@ void cirno_icicle_fall(Boss *c, int time) { .color = RGB(0.,0.,0.3), .rule = accelerated, .args = { - i*I*0.5*cexp(-I*angle2), - 0.001*I+(global.diff == D_Lunatic)*0.001*frand() + i*I*0.5*cdir(-angle2), + 0.001*I+(global.diff == D_Lunatic)*0.001*rng_real() } ); } } } - - } static int cirno_crystal_blizzard_proj(Projectile *p, int time) { @@ -795,14 +576,14 @@ void cirno_crystal_blizzard(Boss *c, int time) { GO_TO(c, global.plr.pos, 0.01); if(!(time % (1 + D_Lunatic - global.diff))) { - tsrand_fill(2); + RNG_ARRAY(rng, 2); PROJECTILE( .proto = pp_wave, .pos = c->pos, .color = RGBA(0.2, 0.2, 0.4, 0.0), .rule = cirno_crystal_blizzard_proj, .args = { - 20 * (0.1 + 0.1 * anfrand(0)) * cexp(I*(carg(global.plr.pos - c->pos) + anfrand(1) * 0.2)), + 20 * (0.1 + 0.1 * vrng_sreal(rng[0])) * cexp(I*(carg(global.plr.pos - c->pos) + vrng_sreal(rng[1]) * 0.2)), 5 }, ); @@ -837,7 +618,7 @@ void cirno_benchmark(Boss* b, int t) { double speed = 10; int c = N*speed/VIEWPORT_H; for(int i = 0; i < c; i++) { - double x = frand()*VIEWPORT_W; + double x = rng_range(0, VIEWPORT_W); double plrx = creal(global.plr.pos); x = plrx + sqrt((x-plrx)*(x-plrx)+100)*(1-2*(xflags &= ~PFLAG_NOGRAZE; } - if(t > 700 && frand() > 0.5) + if(t > 700 && rng_chance(0.5)) projectile_set_prototype(p, pp_plainball); - if(t > 1200 && frand() > 0.5) + if(t > 1200 && rng_chance(0.5)) p->color = *RGB(1.0, 0.2, 0.8); - if(t > 350 && frand() > 0.5) + if(t > 350 && rng_chance(0.5)) p->color.a = 0; } } -attr_unused -static void cirno_superhardspellcard(Boss *c, int t) { - // HOWTO: create a super hard spellcard in a few seconds +TASK(burst_fairy, { cmplx pos; cmplx dir; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 700, Fairy, NULL, ARGS.dir)); - cirno_iceplosion0(c, t); - cirno_iceplosion1(c, t); - cirno_crystal_rain(c, t); - cirno_icicle_fall(c, t); - cirno_icy(c, t); - cirno_perfect_freeze(c, t); -} + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 1, + .power = 1, + }); -static Boss *create_cirno(void) { - Boss* cirno = stage1_spawn_cirno(-230 + 100.0*I); + e->move.attraction_point = ARGS.pos + 120*I; + e->move.attraction = 0.03; - boss_add_attack(cirno, AT_Move, "Introduction", 2, 0, cirno_intro_boss, NULL); - boss_add_attack(cirno, AT_Normal, "Iceplosion 0", 20, 23000, cirno_iceplosion0, NULL); - boss_add_attack_from_info(cirno, &stage1_spells.boss.crystal_rain, false); - boss_add_attack(cirno, AT_Normal, "Iceplosion 1", 20, 24000, cirno_iceplosion1, NULL); + WAIT(60); - if(global.diff > D_Normal) { - boss_add_attack_from_info(cirno, &stage1_spells.boss.snow_halation, false); + play_sound("shot1"); + int n = 1.5 * global.diff - 1; + + for(int i = -n; i <= n; i++) { + cmplx aim = cdir(carg(global.plr.pos - e->pos) + 0.2 * i); + + PROJECTILE( + .proto = pp_crystal, + .pos = e->pos, + .color = RGB(0.2, 0.3, 0.5), + .move = move_asymptotic_simple(aim * (2 + 0.5 * global.diff), 5), + ); } - boss_add_attack_from_info(cirno, &stage1_spells.boss.icicle_fall, false); - boss_add_attack_from_info(cirno, &stage1_spells.extra.crystal_blizzard, false); + WAIT(1); - boss_start_attack(cirno, cirno->attacks); - return cirno; + e->move.attraction = 0; + e->move.acceleration = 0.04 * ARGS.dir; + e->move.retention = 1; + + for(;;) { + YIELD; + } } -static int stage1_burst(Enemy *e, int time) { - TIMER(&time); +TASK(circletoss_shoot_circle, { BoxedEnemy e; int duration; int interval; }) { + Enemy *e = TASK_BIND(ARGS.e); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 1, ITEM_POWER, 1); - return ACTION_ACK; - } + int cnt = ARGS.duration / ARGS.interval; + double angle_step = M_TAU / cnt; + + for(int i = 0; i < cnt; ++i) { + play_loop("shot1_loop"); + e->move.velocity *= 0.8; + + cmplx aim = cdir(angle_step * i); + + PROJECTILE( + .proto = pp_rice, + .pos = e->pos, + .color = RGB(0.6, 0.2, 0.7), + .move = move_asymptotic_simple(2 * aim, i * 0.5), + ); - FROM_TO(0, 60, 1) { - // e->pos += 2.0*I; - GO_TO(e, e->pos0 + 120*I, 0.03); + WAIT(ARGS.interval); } +} - AT(60) { - int i = 0; - int n = 1.5*global.diff-1; +TASK(circletoss_shoot_toss, { BoxedEnemy e; int times; int duration; int period; }) { + Enemy *e = TASK_BIND(ARGS.e); + + while(ARGS.times--) { + for(int i = ARGS.duration; i--;) { + play_loop("shot1_loop"); + + double aim_angle = carg(global.plr.pos - e->pos); + aim_angle += 0.05 * global.diff * rng_real(); + + cmplx aim = cdir(aim_angle); + aim *= rng_range(1, 3); - play_sound("shot1"); - for(i = -n; i <= n; i++) { PROJECTILE( - .proto = pp_crystal, + .proto = pp_thickrice, .pos = e->pos, - .color = RGB(0.2, 0.3, 0.5), - .rule = asymptotic, - .args = { - (2+0.1*global.diff)*cexp(I*(carg(global.plr.pos - e->pos) + 0.2*i)), - 5 - } + .color = RGB(0.2, 0.4, 0.8), + .move = move_asymptotic_simple(aim, 3), ); - } - e->moving = true; - e->dir = creal(e->args[0]) < 0; + WAIT(1); + } - e->pos0 = e->pos; + WAIT(ARGS.period - ARGS.duration); } +} - FROM_TO(70, 900, 1) { - e->pos = e->pos0 + (0.04*e->args[0])*_i*_i; +TASK(circletoss_fairy, { cmplx pos; cmplx velocity; cmplx exit_accel; int exit_time; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 1500, BigFairy, NULL, 0)); + + e->move = move_linear(ARGS.velocity); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 2, + .power = 1, + }); + + INVOKE_SUBTASK_DELAYED(60, circletoss_shoot_circle, ENT_BOX(e), + .duration = 40, + .interval = 2 + (global.diff < D_Hard) + ); + + if(global.diff > D_Easy) { + INVOKE_SUBTASK_DELAYED(90, circletoss_shoot_toss, ENT_BOX(e), + .times = 4, + .period = 150, + .duration = 5 + 7 * global.diff + ); } - return 1; + WAIT(ARGS.exit_time); + e->move.acceleration += ARGS.exit_accel; + STALL; } -static int stage1_circletoss(Enemy *e, int time) { - TIMER(&time); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 2, ITEM_POWER, 1); - return 1; +TASK(sinepass_swirl_move, { BoxedEnemy e; cmplx v; cmplx sv; }) { + Enemy *e = TASK_BIND(ARGS.e); + cmplx sv = ARGS.sv; + cmplx v = ARGS.v; + + for(;;) { + sv -= cimag(e->pos - e->pos0) * 0.03 * I; + e->pos += sv * 0.4 + v; + YIELD; } +} + +TASK(sinepass_swirl, { cmplx pos; cmplx vel; cmplx svel; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 100, Swirl, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 1, + }); + + INVOKE_TASK(sinepass_swirl_move, ENT_BOX(e), ARGS.vel, ARGS.svel); + + WAIT(difficulty_value(25, 20, 15, 10)); + + int shot_interval = difficulty_value(120, 40, 30, 20); + + for(;;) { + play_sound("shot1"); - e->pos += e->args[0]; + cmplx aim = cnormalize(global.plr.pos - e->pos); + aim *= difficulty_value(2, 2, 2.5, 3); - int inter = 2+(global.diffargs[0] = 0.8*e->args[0]; PROJECTILE( - .proto = pp_rice, + .proto = pp_ball, .pos = e->pos, - .color = RGB(0.6, 0.2, 0.7), - .rule = asymptotic, - .args = { - 2*cexp(I*2*M_PI*inter/dur*_i), - _i/2.0 - } + .color = RGB(0.8, 0.8, 0.4), + .move = move_asymptotic_simple(aim, 5), ); + + WAIT(shot_interval); } +} + +TASK(circle_fairy, { cmplx pos; cmplx target_pos; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 1400, BigFairy, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 3, + .power = 2, + }); + + e->move.attraction = 0.005; + e->move.retention = 0.8; + e->move.attraction_point = ARGS.target_pos; + + WAIT(120); + + int shot_interval = 2; + int shot_count = difficulty_value(10, 10, 20, 25); + int round_interval = 120 - shot_interval * shot_count; + + for(int round = 0; round < 2; ++round) { + double a_ofs = rng_angle(); + + for(int i = 0; i < shot_count; ++i) { + cmplx aim; + + aim = circle_dir_ofs((round & 1) ? i : shot_count - i, shot_count, a_ofs); + aim *= difficulty_value(1.7, 2.0, 2.5, 2.5); - if(global.diff > D_Easy) { - FROM_TO_INT_SND("shot1_loop",90,500,150,5+7*global.diff,1) { - tsrand_fill(2); PROJECTILE( - .proto = pp_thickrice, + .proto = pp_rice, .pos = e->pos, - .color = RGB(0.2, 0.4, 0.8), - .rule = asymptotic, - .args = { - (1+afrand(0)*2)*cexp(I*carg(global.plr.pos - e->pos)+0.05*I*global.diff*anfrand(1)), - 3 - } + .color = RGB(0.6, 0.2, 0.7), + .move = move_asymptotic_simple(aim, i * 0.5), ); + + play_loop("shot1_loop"); + WAIT(shot_interval); } - } - FROM_TO(global.diff > D_Easy ? 500 : 240, 900, 1) - e->args[0] += 0.03*e->args[1] - 0.04*I; + e->move.attraction_point += 30 * rng_dir(); + WAIT(round_interval); + } - return 1; + WAIT(10); + e->move.attraction = 0; + e->move.retention = 1; + e->move.acceleration = -0.04 * I * cdir(rng_range(0, M_TAU / 12)); + STALL; } -static int stage1_sinepass(Enemy *e, int time) { - TIMER(&time); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 1); - return 1; - } +TASK(drop_swirl, { cmplx pos; cmplx vel; cmplx accel; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 100, Swirl, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 2, + }); + + e->move = move_accelerated(ARGS.vel, ARGS.accel); + + int shot_interval = difficulty_value(120, 40, 30, 20); - e->args[1] -= cimag(e->pos-e->pos0)*0.03*I; - e->pos += e->args[1]*0.4 + e->args[0]; + WAIT(20); + + while(true) { + cmplx aim = cnormalize(global.plr.pos - e->pos); + aim *= 1 + 0.3 * global.diff + rng_real(); - if(frand() > 0.997-0.005*(global.diff-1)) { play_sound("shot1"); PROJECTILE( .proto = pp_ball, .pos = e->pos, - .color = RGB(0.8,0.8,0.4), - .rule = linear, - .args = { - (1+0.2*global.diff+frand())*cexp(I*carg(global.plr.pos - e->pos)) - } + .color = RGB(0.8, 0.8, 0.4), + .move = move_linear(aim), ); - } - return 1; + WAIT(shot_interval); + } } -static int stage1_drop(Enemy *e, int t) { - TIMER(&t); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 2); - return 1; - } - if(t < 0) - return 1; +TASK(multiburst_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 1000, Fairy, NULL, 0)); - e->pos = e->pos0 + e->args[0]*t + e->args[1]*t*t; + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 3, + .power = 2, + }); + + e->move.attraction = 0.05; + // e->move.retention = 0.8; + e->move.attraction_point = ARGS.target_pos; + + WAIT(60); + + int burst_interval = difficulty_value(22, 20, 18, 16); + int bursts = 4; + + for(int i = 0; i < bursts; ++i) { + play_sound("shot1"); + int n = global.diff - 1; + + for(int j = -n; j <= n; j++) { + cmplx aim = cdir(carg(global.plr.pos - e->pos) + j / 5.0); + aim *= 2.5; - FROM_TO(10,1000,1) { - if(frand() > 0.997-0.007*(global.diff-1)) { - play_sound("shot1"); PROJECTILE( - .proto = pp_ball, + .proto = pp_crystal, .pos = e->pos, - .color = RGB(0.8,0.8,0.4), - .rule = linear, - .args = { - (1+0.3*global.diff+frand())*cexp(I*carg(global.plr.pos - e->pos)) - } + .color = RGB(0.2, 0.3, 0.5), + .move = move_linear(aim), ); } + + WAIT(burst_interval); } - return 1; + WAIT(10); + e->move.attraction = 0; + e->move.retention = 1; + e->move.acceleration = ARGS.exit_accel; } -static int stage1_circle(Enemy *e, int t) { - TIMER(&t); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 3, ITEM_POWER, 2); - return 1; - } +TASK(instantcircle_fairy_shoot, { BoxedEnemy e; int cnt; double speed; double boost; }) { + Enemy *e = TASK_BIND(ARGS.e); + play_sound("shot_special1"); - FROM_TO(0, 150, 1) - e->pos += (e->args[0] - e->pos)*0.02; + for(int i = 0; i < ARGS.cnt; ++i) { + cmplx vel = ARGS.speed * circle_dir(i, ARGS.cnt); - FROM_TO_INT_SND("shot1_loop",150, 550, 40, 40, 2+2*(global.diffpos, .color = RGB(0.6, 0.2, 0.7), - .rule = asymptotic, - .args = { - (1.7+0.2*global.diff)*cexp(I*M_PI/10*_ni), - _ni/2.0 - } + .move = move_asymptotic_simple(vel, ARGS.boost), ); } +} - FROM_TO(560,1000,1) - e->pos += e->args[1]; +TASK(instantcircle_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 1200, Fairy, NULL, 0)); - return 1; -} + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 2, + .power = 4, + }); -static int stage1_multiburst(Enemy *e, int t) { - TIMER(&t); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 3, ITEM_POWER, 2); - return 1; + e->move = move_towards(ARGS.target_pos, 0.04); + BoxedEnemy be = ENT_BOX(e); + + INVOKE_TASK_DELAYED(75, instantcircle_fairy_shoot, be, + .cnt = difficulty_value(22, 24, 26, 28), + .speed = 1.5, + .boost = 2.0 + ); + + if(global.diff > D_Easy) { + INVOKE_TASK_DELAYED(95, instantcircle_fairy_shoot, be, + .cnt = difficulty_value(0, 26, 29, 32), + .speed = 3, + .boost = 3.0 + ); } - FROM_TO(0, 100, 1) { - GO_TO(e, e->pos0 + 100*I , 0.02); + WAIT(200); + e->move.attraction = 0; + e->move.retention = 1; + e->move.acceleration = ARGS.exit_accel; +} + +TASK(waveshot, { cmplx pos; real angle; real spread; real freq; int shots; int interval; } ) { + for(int i = 0; i < ARGS.shots; ++i) { + cmplx v = 4 * cdir(ARGS.angle + ARGS.spread * triangle(ARGS.freq * i)); + + play_loop("shot1_loop"); + PROJECTILE( + .proto = pp_thickrice, + .pos = ARGS.pos, + .color = RGBA(0.0, 0.5 * (1.0 - i / (ARGS.shots - 1.0)), 1.0, 1), + // .move = move_asymptotic(-(8-0.1*i) * v, v, 0.8), + .move = move_accelerated(-v, 0.02 * v), + ); + + WAIT(ARGS.interval); } +} - FROM_TO_INT(60, 300, 70, 40, 18-2*global.diff) { - play_sound("shot1"); - int n = global.diff-1; - for(int i = -n; i <= n; i++) { +TASK(waveshot_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 4200, BigFairy, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 4, + .power = 2, + }); + + e->move = move_towards(ARGS.target_pos, 0.03); + + WAIT(120); + + cmplx orig_pos = e->pos; + real angle = carg(global.plr.pos - orig_pos); + cmplx pos = orig_pos - 24 * cdir(angle); + INVOKE_SUBTASK(waveshot, pos, angle, rng_sign() * M_PI/14, 1.0/12.0, 61, 1); + + WAIT(120); + + e->move.attraction = 0; + e->move.retention = 0.8; + e->move.acceleration = ARGS.exit_accel; +} + +TASK(explosion_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 6000, BigFairy, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 8, + }); + + e->move = move_towards(ARGS.target_pos, 0.04); + + WAIT(60); + + int cnt = 60; + real ofs = rng_angle(); + + play_sound("shot_special1"); + + for(int i = 0; i < cnt; ++i) { + cmplx aim = 4 * circle_dir_ofs(i, cnt, ofs); + real s = 0.5 + 0.5 * triangle(6.0 * i / (real)cnt); + + Color clr; + + if(s == 1) { + clr = *RGB(1, 0, 0); + } else { + clr = *color_lerp( + RGB(0.1, 0.6, 1.0), + RGB(1.0, 0.0, 0.3), + s * s + ); + color_mul(&clr, &clr); + } + + PROJECTILE( + .proto = s == 1 ? pp_bigball : pp_ball, + .pos = e->pos, + .color = &clr, + .move = move_asymptotic_simple(aim, 1 + 8 * s), + ); + + for(int j = 0; j < 4; ++j) { + aim *= 0.8; PROJECTILE( - .proto = pp_crystal, + .proto = pp_rice, .pos = e->pos, - .color = RGB(0.2, 0.3, 0.5), - .rule = linear, - .args = { - 2.5*cexp(I*(carg(global.plr.pos - e->pos) + i/5.0)) - } + .color = &clr, + // .move = move_asymptotic_simple(aim * (0.8 - 0.2 * j), 1 + 4 * s), + .move = move_asymptotic_simple(aim, 1 + 8 * s), ); } } - FROM_TO(320, 700, 1) { - e->args[1] += 0.03; - e->pos += e->args[0]*e->args[1] + 1.4*I; + WAIT(10); + + e->move.attraction = 0; + e->move.retention = 0.8; + e->move.acceleration = ARGS.exit_accel; +} + +// opening. projectile bursts +TASK(burst_fairies_1, NO_ARGS) { + for(int i = 3; i--;) { + INVOKE_TASK(burst_fairy, VIEWPORT_W/2 + 70, 1 + 0.6*I); + INVOKE_TASK(burst_fairy, VIEWPORT_W/2 - 70, -1 + 0.6*I); + stage_wait(25); } +} - return 1; +// more bursts. fairies move / \ like +TASK(burst_fairies_2, NO_ARGS) { + for(int i = 3; i--;) { + double ofs = 70 + i * 40; + INVOKE_TASK(burst_fairy, ofs, 1 + 0.6*I); + stage_wait(15); + INVOKE_TASK(burst_fairy, VIEWPORT_W - ofs, -1 + 0.6*I); + stage_wait(15); + } } -static int stage1_instantcircle(Enemy *e, int t) { - TIMER(&t); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 2, ITEM_POWER, 4); - return 1; +TASK(burst_fairies_3, NO_ARGS) { + for(int i = 10; i--;) { + cmplx pos = VIEWPORT_W/2 - 200 * sin(1.17 * global.frames); + INVOKE_TASK(burst_fairy, pos, rng_sign()); + stage_wait(60); } +} - AT(75) { - play_sound("shot_special1"); - for(int i = 0; i < 20+2*global.diff; i++) { - PROJECTILE( - .proto = pp_rice, - .pos = e->pos, - .color = RGB(0.6, 0.2, 0.7), - .rule = asymptotic, - .args = { - 1.5*cexp(I*2*M_PI/(20.0+global.diff)*i), - 2.0, - }, - ); +// swirl, sine pass +TASK(sinepass_swirls, { int duration; double level; double dir; }) { + int duration = ARGS.duration; + double dir = ARGS.dir; + cmplx pos = CMPLX(ARGS.dir < 0 ? VIEWPORT_W : 0, ARGS.level); + int delay = difficulty_value(30, 20, 15, 10); + + for(int t = 0; t < duration; t += delay) { + INVOKE_TASK(sinepass_swirl, pos, 3.5 * dir, 7.0 * I); + stage_wait(delay); + } +} + +// big fairies, circle + projectile toss +TASK(circletoss_fairies_1, NO_ARGS) { + for(int i = 0; i < 2; ++i) { + INVOKE_TASK(circletoss_fairy, + .pos = VIEWPORT_W * i + VIEWPORT_H / 3 * I, + .velocity = 2 - 4 * i - 0.3 * I, + .exit_accel = 0.03 * (1 - 2 * i) - 0.04 * I , + .exit_time = (global.diff > D_Easy) ? 500 : 240 + ); + + stage_wait(50); + } +} + +TASK(drop_swirls, { int cnt; cmplx pos; cmplx vel; cmplx accel; }) { + for(int i = 0; i < ARGS.cnt; ++i) { + INVOKE_TASK(drop_swirl, ARGS.pos, ARGS.vel, ARGS.accel); + stage_wait(20); + } +} + +TASK(schedule_swirls, NO_ARGS) { + INVOKE_TASK(drop_swirls, 25, VIEWPORT_W/3, 2*I, 0.06); + stage_wait(400); + INVOKE_TASK(drop_swirls, 25, 200*I, 4, -0.06*I); +} + +TASK(circle_fairies_1, NO_ARGS) { + for(int i = 0; i < 3; ++i) { + for(int j = 0; j < 3; ++j) { + INVOKE_TASK(circle_fairy, VIEWPORT_W - 64, VIEWPORT_W/2 - 100 + 200 * I + 128 * j); + stage_wait(60); + } + + stage_wait(90); + + for(int j = 0; j < 3; ++j) { + INVOKE_TASK(circle_fairy, 64, VIEWPORT_W/2 + 100 + 200 * I - 128 * j); + stage_wait(60); } + + stage_wait(240); + } +} + +TASK(multiburst_fairies_1, NO_ARGS) { + for(int row = 0; row < 3; ++row) { + for(int col = 0; col < 5; ++col) { + log_debug("WTF %i %i", row, col); + cmplx pos = rng_range(0, VIEWPORT_W); + cmplx target_pos = 64 + 64 * col + I * (64 * row + 100); + cmplx exit_accel = 0.02 * I + 0.03; + INVOKE_TASK(multiburst_fairy, pos, target_pos, exit_accel); + + WAIT(10); + } + + WAIT(120); + } +} + +TASK(instantcircle_fairies, { int duration; }) { + int interval = difficulty_value(160, 130, 100, 70); + + for(int t = ARGS.duration; t > 0; t -= interval) { + double x = VIEWPORT_W/2 + 205 * sin(2.13*global.frames); + double y = VIEWPORT_H/2 + 120 * cos(1.91*global.frames); + INVOKE_TASK(instantcircle_fairy, x, x+y*I, 0.2 * I); + WAIT(interval); } +} + +TASK(waveshot_fairies, { int duration; }) { + int interval = 200; + + for(int t = ARGS.duration; t > 0; t -= interval) { + double x = VIEWPORT_W/2 + round(rng_sreal() * 69); + double y = rng_range(200, 240); + INVOKE_TASK(waveshot_fairy, x, x+y*I, 0.15 * I); + WAIT(interval); + } +} + +TASK_WITH_INTERFACE(midboss_intro, BossAttack) { + Boss *boss = INIT_BOSS_ATTACK(); + BEGIN_BOSS_ATTACK(); + boss->move = move_towards(VIEWPORT_W/2.0 + 200.0*I, 0.035); +} + +#define SNOWFLAKE_ARMS 6 + +static int snowflake_bullet_limit(int size) { + // >= number of bullets spawned per snowflake of this size + return SNOWFLAKE_ARMS * 4 * size; +} + +TASK(make_snowflake, { cmplx pos; MoveParams move; int size; double rot_angle; BoxedProjectileArray *array; }) { + const double spacing = 12; + const int split = 3; + int t = 0; + + for(int j = 0; j < ARGS.size; j++) { + play_loop("shot1_loop"); + + for(int i = 0; i < SNOWFLAKE_ARMS; i++) { + double ang = M_TAU / SNOWFLAKE_ARMS * i + ARGS.rot_angle; + cmplx phase = cdir(ang); + cmplx pos0 = ARGS.pos + spacing * j * phase; + + Projectile *p; + + for(int side = -1; side <= 1; side += 2) { + p = PROJECTILE( + .proto = pp_crystal, + .pos = pos0 + side * 5 * I * phase, + .color = RGB(0.0 + 0.05 * j, 0.1 + 0.1 * j, 0.9), + .move = ARGS.move, + .angle = ang + side * M_PI / 4, + .max_viewport_dist = 128, + .flags = PFLAG_MANUALANGLE, + ); + move_update_multiple(t, &p->pos, &p->move); + p->pos0 = p->prevpos = p->pos; + ENT_ARRAY_ADD(ARGS.array, p); + } + + WAIT(1); + ++t; + + if(j > split) { + cmplx pos1 = ARGS.pos + spacing * split * phase; + + for(int side = -1; side <= 1; side += 2) { + cmplx phase2 = cdir(M_PI / 4 * side) * phase; + cmplx pos2 = pos1 + (spacing * (j - split)) * phase2; + + p = PROJECTILE( + .proto = pp_crystal, + .pos = pos2, + .color = RGB(0.0, 0.3 * ARGS.size / 5, 1), + .move = ARGS.move, + .angle = ang + side * M_PI / 4, + .max_viewport_dist = 128, + .flags = PFLAG_MANUALANGLE, + ); + move_update_multiple(t, &p->pos, &p->move); + p->pos0 = p->prevpos = p->pos; + ENT_ARRAY_ADD(ARGS.array, p); + } + + WAIT(1); + ++t; + } + } + } +} + +TASK_WITH_INTERFACE(icy_storm, BossAttack) { + Boss *boss = INIT_BOSS_ATTACK(); + boss->move = move_towards(CMPLX(VIEWPORT_W/2, 200), 0.02); + BEGIN_BOSS_ATTACK(); + boss->move = move_stop(0.8); + + int flake_spawn_interval = difficulty_value(11, 10, 9, 8); + int flakes_per_burst = difficulty_value(3, 5, 7, 9); + double launch_speed = difficulty_value(5, 6.25, 6.875, 8.75); + int size_base = 5; + int size_oscillation = 3; + int size_max = size_base + size_oscillation; + int burst_interval = difficulty_value(120, 80, 80, 80); + + int flakes_limit = flakes_per_burst * snowflake_bullet_limit(size_max); + DECLARE_ENT_ARRAY(Projectile, snowflake_projs, flakes_limit); + + for(int burst = 0;; ++burst) { + double angle_ofs = carg(global.plr.pos - boss->pos); + int size = size_base + size_oscillation * sin(burst * 2.21); + + for(int flake = 0; flake < flakes_per_burst; ++flake) { + double angle = circle_angle(flake + flakes_per_burst / 2, flakes_per_burst) + angle_ofs; + MoveParams move = move_asymptotic(launch_speed * cdir(angle), 0, 0.95); + INVOKE_SUBTASK(make_snowflake, boss->pos, move, size, angle, &snowflake_projs); + WAIT(flake_spawn_interval); + } + + WAIT(65 - 4 * (size_base + size_oscillation - size)); - AT(95) { - if(global.diff > D_Easy) { + play_sound("redirect"); + // play_sound("shot_special1"); + + ENT_ARRAY_FOREACH(&snowflake_projs, Projectile *p, { + spawn_projectile_highlight_effect(p)->opacity = 0.25; + color_lerp(&p->color, RGB(0.5, 0.5, 0.5), 0.5); + p->move.velocity = 2 * cdir(p->angle); + p->move.acceleration = -cdir(p->angle) * difficulty_value(0.1, 0.15, 0.2, 0.2); + }); + + ENT_ARRAY_CLEAR(&snowflake_projs); + WAIT(burst_interval); + } +} + +DEFINE_EXTERN_TASK(stage1_spell_perfect_freeze) { + Boss *boss = INIT_BOSS_ATTACK(); + BEGIN_BOSS_ATTACK(); + + for(int run = 1;;run++) { + boss->move = move_towards(VIEWPORT_W/2.0 + 100.0*I, 0.04); + + int n = global.diff; + int nfrog = n*60; + + DECLARE_ENT_ARRAY(Projectile, projs, nfrog); + + WAIT(20); + for(int i = 0; i < nfrog/n; i++) { + play_loop("shot1_loop"); + + float r = rng_f32(); + float g = rng_f32(); + float b = rng_f32(); + + for(int j = 0; j < n; j++) { + float speed = rng_range(1.0f, 5.0f + 0.5f * global.diff); + + ENT_ARRAY_ADD(&projs, PROJECTILE( + .proto = pp_ball, + .pos = boss->pos, + .color = RGB(r, g, b), + .move = move_linear(speed * rng_dir()), + )); + } + YIELD; + } + WAIT(20); + + ENT_ARRAY_FOREACH(&projs, Projectile *p, { + spawn_stain(p->pos, p->angle, 30); + spawn_stain(p->pos, p->angle, 30); + spawn_projectile_highlight_effect(p); play_sound("shot_special1"); - for(int i = 0; i < 20+3*global.diff; i++) { + + p->color = *RGB(0.9, 0.9, 0.9); + p->move.retention = 0.8 * rng_dir(); + if(rng_chance(0.2)) { + YIELD; + } + }); + + WAIT(60); + double dir = rng_sign(); + boss->move = (MoveParams){ .velocity = dir*2.7+I, .retention = 0.99, .acceleration = -dir*0.017 }; + + aniplayer_queue(&boss->ani,"(9)",0); + int d = max(0, global.diff - D_Normal); + WAIT(60-5*global.diff); + + ENT_ARRAY_FOREACH(&projs, Projectile *p, { + p->color = *RGB(0.9, 0.9, 0.9); + p->move.retention = 1 + 0.002 * global.diff * rng_f64(); + p->move.velocity = 2 * rng_dir(); + spawn_stain(p->pos, p->angle, 30); + spawn_projectile_highlight_effect(p); + play_sound_ex("shot2", 0, false); + + if(rng_chance(0.4)) { + YIELD; + } + }); + + for(int i = 0; i < 30+10*d; i++) { + play_loop("shot1_loop"); + float r1, r2; + + if(global.diff > D_Normal) { + r1 = sin(i/M_PI*5.3) * cos(2*i/M_PI*5.3); + r2 = cos(i/M_PI*5.3) * sin(2*i/M_PI*5.3); + } else { + r1 = rng_f32(); + r2 = rng_f32(); + } + + cmplx aim = cnormalize(global.plr.pos - boss->pos); + float speed = 2+0.2*global.diff; + + for(int sign = -1; sign <= 1; sign += 2) { PROJECTILE( .proto = pp_rice, - .pos = e->pos, - .color = RGB(0.6, 0.2, 0.7), - .rule = asymptotic, - .args = { - 3*cexp(I*2*M_PI/(20.0+global.diff)*i), - 3.0, - }, + .pos = boss->pos + sign*60, + .color = RGB(0.3, 0.4, 0.9), + .move = move_asymptotic_simple(speed*aim*cdir(0.5*(sign > 0 ? r1 : r2)), 2.5+(global.diff>D_Normal)*0.1*sign*I), ); } + WAIT(6-global.diff/2); } - } + aniplayer_queue(&boss->ani,"main",0); - if(t > 200) { - e->pos += e->args[1]; - } else { - GO_TO(e, e->pos0 + e->args[0] * 110 , 0.04); + WAIT(40-5*global.diff); } +} - return 1; +TASK_WITH_INTERFACE(midboss_flee, BossAttack) { + Boss *boss = INIT_BOSS_ATTACK(); + BEGIN_BOSS_ATTACK(); + boss->move = move_towards(-250 + 30 * I, 0.02); } -static int stage1_tritoss(Enemy *e, int t) { - TIMER(&t); - AT(EVENT_KILLED) { - spawn_items(e->pos, ITEM_POINTS, 5, ITEM_POWER, 2); - return 1; - } +TASK(spawn_midboss, NO_ARGS) { + STAGE_BOOKMARK_DELAYED(120, midboss); - FROM_TO(0, 100, 1) { - e->pos += e->args[0]; - } + Boss *boss = global.boss = stage1_spawn_cirno(VIEWPORT_W + 220 + 30.0*I); - FROM_TO(120, 800,8-global.diff) { + boss_add_attack_task(boss, AT_Move, "Introduction", 2, 0, TASK_INDIRECT(BossAttack, midboss_intro), NULL); + boss_add_attack_task(boss, AT_Normal, "Icy Storm", 20, 24000, TASK_INDIRECT(BossAttack, icy_storm), NULL); + boss_add_attack_from_info(boss, &stage1_spells.mid.perfect_freeze, false); + boss_add_attack_task(boss, AT_Move, "Introduction", 2, 0, TASK_INDIRECT(BossAttack, midboss_flee), NULL); + + boss_start_attack(boss, boss->attacks); + + WAIT(60); + stage1_bg_enable_snow(); +} + +TASK(tritoss_fairy, { cmplx pos; cmplx velocity; cmplx end_velocity; }) { + Enemy *e = TASK_BIND_UNBOXED(create_enemy1c(ARGS.pos, 16000, BigFairy, NULL, 0)); + + INVOKE_TASK_WHEN(&e->events.killed, common_drop_items, &e->pos, { + .points = 5, + .power = 5, + }); + + e->move = move_linear(ARGS.velocity); + WAIT(60); + e->move.retention = 0.9; + WAIT(20); + + int interval = difficulty_value(7,6,5,3); + int rounds = 680/interval; + for(int k = 0; k < rounds; k++) { play_sound("shot1"); - float a = M_PI/30.0*((_i/7)%30)+0.1*nfrand(); - int i; - int n = 3+global.diff/2; - for(i = 0; i < n; i++){ + float a = M_PI / 30.0 * ((k/7) % 30) + 0.1 * rng_f32(); + int n = difficulty_value(3,4,4,5); + + for(int i = 0; i < n; i++) { PROJECTILE( .proto = pp_thickrice, .pos = e->pos, .color = RGB(0.2, 0.4, 0.8), - .rule = asymptotic, - .args = { - 2*cexp(I*a+2.0*I*M_PI/n*i), - 3, - }, + .move = move_asymptotic_simple(2*cdir(a+2.0*M_PI/n*i), 3), ); } - } - FROM_TO(480, 800, 300) { - play_sound("shot_special1"); - int i, n = 15 + global.diff*3; - for(i = 0; i < n; i++) { - PROJECTILE( - .proto = pp_rice, - .pos = e->pos, - .color = RGB(0.6, 0.2, 0.7), - .rule = asymptotic, - .args = { - 1.5*cexp(I*2*M_PI/n*i), - 2.0, - }, - ); - - if(global.diff > D_Easy) { + if(k == rounds/2 || k == rounds-1) { + play_sound("shot_special1"); + int n2 = difficulty_value(20, 23, 26, 30); + for(int i = 0; i < n2; i++) { PROJECTILE( .proto = pp_rice, .pos = e->pos, .color = RGB(0.6, 0.2, 0.7), - .rule = asymptotic, - .args = { - 3*cexp(I*2*M_PI/n*i), - 3.0, - }, + .move = move_asymptotic_simple(1.5*cexp(I*2*M_PI/n2*i),2), ); + + if(global.diff > D_Easy) { + PROJECTILE( + .proto = pp_rice, + .pos = e->pos, + .color = RGB(0.6, 0.2, 0.7), + .move = move_asymptotic_simple(3*cexp(I*2*M_PI/n2*i), 3.0), + ); + } } } + WAIT(interval); } - if(t > 820) - e->pos += e->args[1]; - - return 1; + WAIT(20); + e->move = move_asymptotic_simple(ARGS.end_velocity, -1); } -// #define BULLET_TEST +TASK(spawn_boss, NO_ARGS) { + STAGE_BOOKMARK_DELAYED(120, boss); -#ifdef BULLET_TEST -static int proj_rotate(Projectile *p, int t) { - if(t < 0) { - return ACTION_ACK; + Boss *boss = global.boss = stage1_spawn_cirno(-230 + 100.0*I); + + boss_add_attack(boss, AT_Move, "Introduction", 2, 0, cirno_intro_boss, NULL); + boss_add_attack(boss, AT_Normal, "Iceplosion 0", 20, 23000, cirno_iceplosion0, NULL); + boss_add_attack_from_info(boss, &stage1_spells.boss.crystal_rain, false); + boss_add_attack(boss, AT_Normal, "Iceplosion 1", 20, 24000, cirno_iceplosion1, NULL); + + if(global.diff > D_Normal) { + boss_add_attack_from_info(boss, &stage1_spells.boss.snow_halation, false); } - p->angle = global.frames / 60.0; - // p->angle = M_PI/2; - return ACTION_NONE; + boss_add_attack_from_info(boss, &stage1_spells.boss.icicle_fall, false); + boss_add_attack_from_info(boss, &stage1_spells.extra.crystal_blizzard, false); + + boss_start_attack(boss, boss->attacks); } -#endif -void stage1_events(void) { - TIMER(&global.timer); +TASK(stage_timeline, NO_ARGS) { + stage_start_bgm("stage1"); + stage_set_voltage_thresholds(50, 125, 300, 600); - AT(0) { - stage_start_bgm("stage1"); - stage_set_voltage_thresholds(50, 125, 300, 600); - } + // INVOKE_TASK(waveshot_fairy, VIEWPORT_W/2, (VIEWPORT_W+VIEWPORT_H*I)*0.5, 0.5*I); + // return; -#ifdef BULLET_TEST - if(!global.projs) { - PROJECTILE( - .proto = pp_rice, - .pos = (VIEWPORT_W + VIEWPORT_H * I) * 0.5, - .color = hsl(0.5, 1.0, 0.5), - .rule = proj_rotate, - ); - } + INVOKE_TASK_DELAYED(100, burst_fairies_1); + INVOKE_TASK_DELAYED(240, burst_fairies_2); + INVOKE_TASK_DELAYED(440, sinepass_swirls, 180, 100, 1); + INVOKE_TASK_DELAYED(480, circletoss_fairies_1); + INVOKE_TASK_DELAYED(660, circle_fairies_1); + INVOKE_TASK_DELAYED(900, schedule_swirls); + INVOKE_TASK_DELAYED(1500, burst_fairies_3); + INVOKE_TASK_DELAYED(2200, multiburst_fairies_1); - if(!(global.frames % 36)) { - ProjPrototype *projs[] = { - pp_thickrice, - pp_rice, - // pp_ball, - // pp_plainball, - // pp_bigball, - // pp_soul, - pp_wave, - pp_card, - pp_bigball, - pp_plainball, - pp_ball, - }; - int numprojs = sizeof(projs)/sizeof(*projs); + INVOKE_TASK_DELAYED(2200, common_call_func, stage1_bg_raise_camera); + STAGE_BOOKMARK_DELAYED(2500, pre-midboss); + INVOKE_TASK_DELAYED(2700, spawn_midboss); - for(int i = 0; i < numprojs; ++i) { - PROJECTILE( - .proto = projs[i], - .pos = ((0.5 + i) * VIEWPORT_W/numprojs + 0 * I), - .color = hsl(0.5, 1.0, 0.5), - .rule = linear, - .args = { 1*I }, - ); - } + while(!global.boss) YIELD; + int midboss_time = WAIT_EVENT(&global.boss->events.defeated).frames; + int filler_time = 2180; + int time_ofs = 500 - midboss_time; - } + log_debug("midboss_time = %i; filler_time = %i; time_ofs = %i", midboss_time, filler_time, time_ofs); - return; -#endif + STAGE_BOOKMARK(post-midboss); - // opening. projectile bursts - FROM_TO(100, 160, 25) { - create_enemy1c(VIEWPORT_W/2 + 70, 700, Fairy, stage1_burst, 1 + 0.6*I); - create_enemy1c(VIEWPORT_W/2 - 70, 700, Fairy, stage1_burst, -1 + 0.6*I); - } + int swirl_spam_time = 760; - // more bursts. fairies move / \ like - FROM_TO(240, 300, 30) { - create_enemy1c(70 + _i*40, 700, Fairy, stage1_burst, -1 + 0.6*I); - create_enemy1c(VIEWPORT_W - (70 + _i*40), 700, Fairy, stage1_burst, 1 + 0.6*I); + for(int i = 0; i < swirl_spam_time; i += 30) { + int o = ((int[]) { 0, 1, 0, -1 })[(i / 60) % 4]; + INVOKE_TASK_DELAYED(i + time_ofs, sinepass_swirls, 40, 132 + 32 * o, 1 - 2 * ((i / 60) & 1)); } - // big fairies, circle + projectile toss - FROM_TO(400, 460, 50) - create_enemy2c(VIEWPORT_W*_i + VIEWPORT_H/3*I, 1500, BigFairy, stage1_circletoss, 2-4*_i-0.3*I, 1-2*_i); + time_ofs += swirl_spam_time; - // swirl, sine pass - FROM_TO(380, 1000, 20) { - tsrand_fill(2); - create_enemy2c(VIEWPORT_W*(_i&1) + afrand(0)*100.0*I + 70.0*I, 100, Swirl, stage1_sinepass, 3.5*(1-2*(_i&1)), afrand(1)*7.0*I); + INVOKE_TASK_DELAYED(40 + time_ofs, burst_fairies_1); + + int instacircle_time = filler_time - swirl_spam_time - 600; + + for(int i = 0; i < instacircle_time; i += 180) { + INVOKE_TASK_DELAYED(i + time_ofs, sinepass_swirls, 80, 132, 1); + INVOKE_TASK_DELAYED(120 + i + time_ofs, instantcircle_fairies, 120); } - // swirl, drops - FROM_TO(1100, 1600, 20) - create_enemy2c(VIEWPORT_W/3, 100, Swirl, stage1_drop, 4.0*I, 0.06); + WAIT(filler_time - midboss_time); + STAGE_BOOKMARK(post-midboss-filler); - FROM_TO(1500, 2000, 20) - create_enemy2c(VIEWPORT_W+200.0*I, 100, Swirl, stage1_drop, -2, -0.04-0.03*I); + INVOKE_TASK_DELAYED(100, circletoss_fairy, -25 + VIEWPORT_H/3*I, 1 - 0.5*I, 0.01 * ( 1 - I), 200); + INVOKE_TASK_DELAYED(125, circletoss_fairy, VIEWPORT_W+25 + VIEWPORT_H/3*I, -1 - 0.5*I, 0.01 * (-1 - I), 200); - // bursts - FROM_TO(1250, 1800, 60) { - create_enemy1c(VIEWPORT_W/2 - 200 * sin(1.17*global.frames), 500, Fairy, stage1_burst, nfrand()); + if(global.diff > D_Normal) { + INVOKE_TASK_DELAYED(100, circletoss_fairy, -25 + 2*VIEWPORT_H/3*I, 1 - 0.5*I, 0.01 * ( 1 - I), 200); + INVOKE_TASK_DELAYED(125, circletoss_fairy, VIEWPORT_W+25 + 2*VIEWPORT_H/3*I, -1 - 0.5*I, 0.01 * (-1 - I), 200); } - // circle - multi burst combo - FROM_TO(1700, 2300, 300) { - tsrand_fill(3); - create_enemy2c(VIEWPORT_W/2, 1400, BigFairy, stage1_circle, VIEWPORT_W/4 + VIEWPORT_W/2*afrand(0)+200.0*I, 3-6*(afrand(1)>0.5)+afrand(2)*2.0*I); - } + INVOKE_TASK_DELAYED(240, waveshot_fairies, 600); + INVOKE_TASK_DELAYED(400, burst_fairies_3); - FROM_TO(2000, 2500, 200) { - int t = global.diff + 1; - for(int i = 0; i < t; i++) - create_enemy1c(VIEWPORT_W/2 - 40*t + 80*i, 1000, Fairy, stage1_multiburst, i - 2.5); - } + STAGE_BOOKMARK_DELAYED(1000, post-midboss-filler-2); - AT(2700) - global.boss = create_cirno_mid(); + INVOKE_TASK_DELAYED(1000, burst_fairies_1); + INVOKE_TASK_DELAYED(1120, explosion_fairy, 120*I, VIEWPORT_W-80 + 120*I, -0.2+0.1*I); + INVOKE_TASK_DELAYED(1280, explosion_fairy, VIEWPORT_W + 220*I, 80 + 220*I, 0.2+0.1*I); - // some chaotic swirls + instant circle combo - FROM_TO(2760, 3800, 20) { - tsrand_fill(2); - create_enemy2c(VIEWPORT_W/2 - 200*anfrand(0), 250+40*global.diff, Swirl, stage1_drop, 1.0*I, 0.001*I + 0.02 + 0.06*anfrand(1)); - } + STAGE_BOOKMARK_DELAYED(1400, post-midboss-filler-3); - FROM_TO(2900, 3750, 190-30*global.diff) { - create_enemy2c(VIEWPORT_W/2 + 205 * sin(2.13*global.frames), 1200, Fairy, stage1_instantcircle, 2.0*I, 3.0 - 6*frand() - 1.0*I); - } + INVOKE_TASK_DELAYED(1400, drop_swirls, 25, 2*VIEWPORT_W/3, 2*I, -0.06); + INVOKE_TASK_DELAYED(1600, drop_swirls, 25, VIEWPORT_W/3, 2*I, 0.06); - // multiburst + normal circletoss, later tri-toss - FROM_TO(3900, 4800, 200) { - create_enemy1c(VIEWPORT_W/2 - 195 * cos(2.43*global.frames), 1000, Fairy, stage1_multiburst, 2.5*frand()); - } + INVOKE_TASK_DELAYED(1520, tritoss_fairy, VIEWPORT_W / 2 - 30*I, 3 * I, -2.6 * I); - FROM_TO(4000, 4100, 20) - create_enemy2c(VIEWPORT_W*_i + VIEWPORT_H/3*I, 1700, Fairy, stage1_circletoss, 2-4*_i-0.3*I, 1-2*_i); + INVOKE_TASK_DELAYED(1820, circle_fairy, VIEWPORT_W + 42 + 300*I, VIEWPORT_W - 130 + 240*I); + INVOKE_TASK_DELAYED(1820, circle_fairy, - 42 + 300*I, 130 + 240*I); - AT(4200) - create_enemy2c(VIEWPORT_W/2.0, 4000, BigFairy, stage1_tritoss, 2.0*I, -2.6*I); + INVOKE_TASK_DELAYED(1880, instantcircle_fairy, VIEWPORT_W + 42 + 300*I, VIEWPORT_W - 84 + 260*I, 0.2 * (-2 - I)); + INVOKE_TASK_DELAYED(1880, instantcircle_fairy, - 42 + 300*I, 84 + 260*I, 0.2 * ( 2 - I)); - AT(5000) { - enemy_kill_all(&global.enemies); - stage_unlock_bgm("stage1"); - global.boss = create_cirno(); - } + INVOKE_TASK_DELAYED(2120, waveshot_fairy, VIEWPORT_W + 42 + 300*I, 130 + 140*I, 0.2 * (-2 - I)); + INVOKE_TASK_DELAYED(2120, waveshot_fairy, - 42 + 300*I, VIEWPORT_W - 130 + 140*I, 0.2 * ( 2 - I)); + + STAGE_BOOKMARK_DELAYED(2300, pre-boss); + + WAIT(2560); + INVOKE_TASK(spawn_boss); + while(!global.boss) YIELD; + WAIT_EVENT(&global.boss->events.defeated); - AT(5100) { - stage_unlock_bgm("stage1boss"); - global.dialog = stage1_dialog_post_boss(); + stage_unlock_bgm("stage1boss"); + + WAIT(120); + stage1_bg_disable_snow(); + WAIT(120); + + global.dialog = stage1_dialog_post_boss(); + while(dialog_is_active(global.dialog)) { + YIELD; } - AT(5105) { - stage_finish(GAMEOVER_SCORESCREEN); + WAIT(5); + stage_finish(GAMEOVER_SCORESCREEN); +} + +void stage1_events(void) { + TIMER(&global.timer); + + AT(0) { + INVOKE_TASK(stage_timeline); } + + return; } diff --git a/src/stages/stage1_events.h b/src/stages/stage1_events.h index f7241a8e8a..3ab1d0bba5 100644 --- a/src/stages/stage1_events.h +++ b/src/stages/stage1_events.h @@ -13,7 +13,6 @@ #include "boss.h" -void cirno_perfect_freeze(Boss*, int); void cirno_crystal_rain(Boss*, int); void cirno_snow_halation(Boss*, int); void cirno_icicle_fall(Boss*, int); @@ -24,4 +23,6 @@ void cirno_benchmark(Boss*, int); void stage1_events(void); Boss* stage1_spawn_cirno(cmplx pos); +DECLARE_EXTERN_TASK_WITH_INTERFACE(stage1_spell_perfect_freeze, BossAttack); + #endif // IGUARD_stages_stage1_events_h diff --git a/src/stages/stage2.c b/src/stages/stage2.c index c5a487020f..5ca9ba2e4f 100644 --- a/src/stages/stage2.c +++ b/src/stages/stage2.c @@ -15,6 +15,9 @@ #include "stage.h" #include "stageutils.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + /* * See the definition of AttackInfo in boss.h for information on how to set up the idmaps. * To add, remove, or reorder spells, see this stage's header file. diff --git a/src/stages/stage2_events.c b/src/stages/stage2_events.c index ccca950c95..cdfbedc4e4 100644 --- a/src/stages/stage2_events.c +++ b/src/stages/stage2_events.c @@ -13,6 +13,9 @@ #include "stage.h" #include "enemy.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + static Dialog *stage2_dialog_pre_boss(void) { PlayerMode *pm = global.plr.mode; Dialog *d = dialog_create(); diff --git a/src/stages/stage3.c b/src/stages/stage3.c index 0977199920..111b660ee2 100644 --- a/src/stages/stage3.c +++ b/src/stages/stage3.c @@ -15,6 +15,9 @@ #include "stage.h" #include "stageutils.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + /* * See the definition of AttackInfo in boss.h for information on how to set up the idmaps. * To add, remove, or reorder spells, see this stage's header file. diff --git a/src/stages/stage3_events.c b/src/stages/stage3_events.c index 5f7a538b3d..9eeaf58b08 100644 --- a/src/stages/stage3_events.c +++ b/src/stages/stage3_events.c @@ -13,6 +13,9 @@ #include "stage.h" #include "enemy.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + static Dialog *stage3_dialog_pre_boss(void) { PlayerMode *pm = global.plr.mode; Dialog *d = dialog_create(); @@ -861,7 +864,8 @@ static int wriggle_rocket_laserbullet(Projectile *p, int time) { return 1; } -static void wriggle_slave_part_draw(Projectile *p, int t) { +DEPRECATED_DRAW_RULE +static void wriggle_slave_part_draw(Projectile *p, int t, ProjDrawRuleArgs args) { float b = 1 - t / (double)p->timeout; r_mat_mv_push(); r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0); @@ -1203,7 +1207,8 @@ void wriggle_light_singularity(Boss *boss, int time) { } -static void wriggle_fstorm_proj_draw(Projectile *p, int time) { +DEPRECATED_DRAW_RULE +static void wriggle_fstorm_proj_draw(Projectile *p, int time, ProjDrawRuleArgs args) { float f = 1-min(time/60.0,1); r_mat_mv_push(); r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0); @@ -1249,7 +1254,7 @@ static int wriggle_fstorm_proj(Projectile *p, int time) { p->args[1] *= 2/cabs(p->args[1]); p->angle = carg(p->args[1]); p->birthtime = global.frames; - p->draw_rule = wriggle_fstorm_proj_draw; + p->draw_rule = (ProjDrawRule) { wriggle_fstorm_proj_draw }; p->sprite = NULL; projectile_set_prototype(p, pp_rice); spawn_projectile_highlight_effect(p); diff --git a/src/stages/stage4.c b/src/stages/stage4.c index 336ff7c9a1..a585d4ebff 100644 --- a/src/stages/stage4.c +++ b/src/stages/stage4.c @@ -17,6 +17,9 @@ #include "util/glm.h" #include "resource/model.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + /* * See the definition of AttackInfo in boss.h for information on how to set up the idmaps. * To add, remove, or reorder spells, see this stage's header file. diff --git a/src/stages/stage4_events.c b/src/stages/stage4_events.c index a8add93299..b7dbb64b89 100644 --- a/src/stages/stage4_events.c +++ b/src/stages/stage4_events.c @@ -15,6 +15,9 @@ #include "enemy.h" #include "laser.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + void kurumi_spell_bg(Boss*, int); void kurumi_slaveburst(Boss*, int); void kurumi_redspike(Boss*, int); @@ -1223,7 +1226,8 @@ static int kurumi_extra_bigfairy1(Enemy *e, int time) { return 1; } -static void kurumi_extra_drainer_draw(Projectile *p, int time) { +DEPRECATED_DRAW_RULE +static void kurumi_extra_drainer_draw(Projectile *p, int time, ProjDrawRuleArgs args) { cmplx org = p->pos; cmplx targ = p->args[1]; double a = 0.5 * creal(p->args[2]); diff --git a/src/stages/stage5.c b/src/stages/stage5.c index 3fe704f384..7b394bcc09 100644 --- a/src/stages/stage5.c +++ b/src/stages/stage5.c @@ -16,6 +16,9 @@ #include "global.h" #include "resource/model.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + /* * See the definition of AttackInfo in boss.h for information on how to set up the idmaps. * To add, remove, or reorder spells, see this stage's header file. diff --git a/src/stages/stage5_events.c b/src/stages/stage5_events.c index 98d1bd62b2..90908c7a91 100644 --- a/src/stages/stage5_events.c +++ b/src/stages/stage5_events.c @@ -12,6 +12,9 @@ #include "stage5.h" #include "global.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + static Dialog *stage5_dialog_post_midboss(void) { PlayerMode *pm = global.plr.mode; Dialog *d = dialog_create(); diff --git a/src/stages/stage6.c b/src/stages/stage6.c index 7967392ba3..c49c6343f2 100644 --- a/src/stages/stage6.c +++ b/src/stages/stage6.c @@ -17,6 +17,9 @@ #include "resource/model.h" #include "stagedraw.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + /* * See the definition of AttackInfo in boss.h for information on how to set up the idmaps. * To add, remove, or reorder spells, see this stage's header file. diff --git a/src/stages/stage6_events.c b/src/stages/stage6_events.c index 2ee99dc3b9..ce98cc9866 100644 --- a/src/stages/stage6_events.c +++ b/src/stages/stage6_events.c @@ -14,6 +14,9 @@ #include "stagetext.h" #include "stagedraw.h" +PRAGMA(message "Remove when this stage is modernized") +DIAGNOSTIC(ignored "-Wdeprecated-declarations") + static Dialog *stage6_dialog_pre_boss(void) { PlayerMode *pm = global.plr.mode; Dialog *d = dialog_create(); @@ -197,7 +200,8 @@ static int scythe_mid(Enemy *e, int t) { return 1; } -static void ScytheTrail(Projectile *p, int t) { +DEPRECATED_DRAW_RULE +static void ScytheTrail(Projectile *p, int t, ProjDrawRuleArgs args) { r_mat_mv_push(); r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0); r_mat_mv_rotate(p->angle + (M_PI * 0.5), 0, 0, 1); @@ -1085,14 +1089,6 @@ static int broglie_particle(Projectile *p, int t) { return ACTION_ACK; } - /* - if(t == EVENT_BIRTH) { - // hidden and no collision detection until scattertime - p->type = FakeProj; - p->draw = ProjNoDraw; - } - */ - if(t < 0) { return ACTION_ACK; } @@ -1112,7 +1108,7 @@ static int broglie_particle(Projectile *p, int t) { } } else { if(t == scattertime && p->type != PROJ_DEAD) { - p->draw_rule = ProjDraw; + projectile_set_layer(p, LAYER_BULLET); p->flags &= ~(PFLAG_NOCLEARBONUS | PFLAG_NOCLEAREFFECT | PFLAG_NOCOLLISION); double angle_ampl = creal(p->args[3]); @@ -1215,7 +1211,7 @@ static int broglie_charge(Projectile *p, int t) { fast ? 2.0 : 1.5, (1 + 2 * ((global.diff - 1) / (double)(D_Lunatic - 1))) * M_PI/11 + s_freq*10*I }, - .draw_rule = ProjNoDraw, + .layer = LAYER_NODRAW, .flags = PFLAG_NOCLEARBONUS | PFLAG_NOCLEAREFFECT | PFLAG_NOSPAWNEFFECTS | PFLAG_NOCOLLISION, ); } diff --git a/src/stageutils.c b/src/stageutils.c index 37de7f248c..80d7e6652c 100644 --- a/src/stageutils.c +++ b/src/stageutils.c @@ -102,6 +102,12 @@ uint linear3dpos(Stage3D *s3d, vec3 q, float maxrange, vec3 p, vec3 r) { } ++size; + + if(size == s3d->pos_buffer_size) { + s3d->pos_buffer_size *= 2; + log_debug("pos_buffer exhausted, reallocating %u -> %u", size, s3d->pos_buffer_size); + s3d->pos_buffer = realloc(s3d->pos_buffer, sizeof(vec3) * s3d->pos_buffer_size); + } } else if(mod == 1) { mod = -1; num = t; @@ -112,6 +118,8 @@ uint linear3dpos(Stage3D *s3d, vec3 q, float maxrange, vec3 p, vec3 r) { num += mod; } + assert(size < s3d->pos_buffer_size); + return size; } diff --git a/src/util/compat.h b/src/util/compat.h index 51cce92897..bb54111033 100644 --- a/src/util/compat.h +++ b/src/util/compat.h @@ -184,35 +184,8 @@ typedef cmplx64 cmplx; typedef struct { alignas(TAISEI_BUILDCONF_MALLOC_ALIGNMENT) char a; } max_align_t; #endif -// In case the C11 CMPLX macro is not present, try our best to provide a substitute -#if !defined CMPLX - #undef HAS_BUILTIN_COMPLEX - - #if defined __has_builtin - #if __has_builtin(__builtin_complex) - #define HAS_BUILTIN_COMPLEX - #endif - #else - #if defined __GNUC__ && defined __GNUC_MINOR__ - #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) - #define HAS_BUILTIN_COMPLEX - #endif - #endif - #endif - - #if defined HAS_BUILTIN_COMPLEX - #define CMPLX(re,im) __builtin_complex((double)(re), (double)(im)) - #elif defined __clang__ - #define CMPLX(re,im) (__extension__ (_Complex double){(double)(re), (double)(im)}) - #elif defined _Imaginary_I - #define CMPLX(re,im) (_Complex double)((double)(re) + _Imaginary_I * (double)(im)) - #else - #define CMPLX(re,im) (_Complex double)((double)(re) + _Complex_I * (double)(im)) - #endif -#elif defined __EMSCRIPTEN__ && defined __clang__ - // CMPLX from emscripten headers uses the clang-specific syntax without __extension__ - #pragma clang diagnostic ignored "-Wcomplex-component-init" -#endif +// polyfill CMPLX macros +#include "compat_cmplx.h" /* * Abstract away the nasty GNU attribute syntax. @@ -309,4 +282,8 @@ typedef cmplx64 cmplx; #define abort nxAbort #endif +#ifdef RNG_API_CHECK + #define _Generic(ignore, ...) _Generic(0, __VA_ARGS__) +#endif + #endif // IGUARD_util_compat_h diff --git a/src/util/compat_cmplx.h b/src/util/compat_cmplx.h new file mode 100644 index 0000000000..1763fd2436 --- /dev/null +++ b/src/util/compat_cmplx.h @@ -0,0 +1,62 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#ifndef IGUARD_util_compat_cmplx_h +#define IGUARD_util_compat_cmplx_h + +#include "taisei.h" + +#undef HAS_BUILTIN_COMPLEX + +#if defined __has_builtin + #if __has_builtin(__builtin_complex) + #define HAS_BUILTIN_COMPLEX + #endif +#else + #if defined __GNUC__ && defined __GNUC_MINOR__ + #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) + #define HAS_BUILTIN_COMPLEX + #endif + #endif +#endif + +// In case the C11 CMPLX macro is not present, try our best to provide a substitute + +#if !defined CMPLX + #if defined HAS_BUILTIN_COMPLEX + #define CMPLX(re,im) __builtin_complex((double)(re), (double)(im)) + #elif defined __clang__ + #define CMPLX(re,im) (__extension__ (_Complex double){ (double)(re), (double)(im) }) + #elif defined _Imaginary_I + #define CMPLX(re,im) (_Complex double)((double)(re) + _Imaginary_I * (double)(im)) + #else + #define CMPLX(re,im) (_Complex double)((double)(re) + _Complex_I * (double)(im)) + #endif +#elif defined __EMSCRIPTEN__ && defined __clang__ + // CMPLX from emscripten headers uses the clang-specific syntax without __extension__ + #pragma clang diagnostic ignored "-Wcomplex-component-init" +#endif + +// same for CMPLXF + +#if !defined CMPLXF + #if defined HAS_BUILTIN_COMPLEX + #define CMPLXF(re,im) __builtin_complex((float)(re), (float)(im)) + #elif defined __clang__ + #define CMPLXF(re,im) (__extension__ (_Complex float){ (float)(re), (float)(im) }) + #elif defined _Imaginary_I + #define CMPLXF(re,im) (_Complex float)((float)(re) + _Imaginary_I * (float)(im)) + #else + #define CMPLXF(re,im) (_Complex float)((float)(re) + _Complex_I * (float)(im)) + #endif +#elif defined __EMSCRIPTEN__ && defined __clang__ + // CMPLXF from emscripten headers uses the clang-specific syntax without __extension__ + #pragma clang diagnostic ignored "-Wcomplex-component-init" +#endif + +#endif // IGUARD_util_compat_cmplx_h diff --git a/src/util/crap.h b/src/util/crap.h index b294142ad0..45c7c4be94 100644 --- a/src/util/crap.h +++ b/src/util/crap.h @@ -17,16 +17,30 @@ void* memdup(const void *src, size_t size) attr_returns_allocated attr_nonnull(1 void inherit_missing_pointers(uint num, void *dest[num], void *const base[num]) attr_nonnull(2, 3); bool is_main_thread(void); +typedef union FloatBits { + float val; + uint32_t bits; +} FloatBits; + +typedef union DoubleBits { + double val; + uint64_t bits; +} DoubleBits; + INLINE uint32_t float_to_bits(float f) { - union { uint32_t i; float f; } u; - u.f = f; - return u.i; + return ((FloatBits) { .val = f }).bits; } INLINE float bits_to_float(uint32_t i) { - union { uint32_t i; float f; } u; - u.i = i; - return u.f; + return ((FloatBits) { .bits = i }).val; +} + +INLINE uint64_t double_to_bits(double d) { + return ((DoubleBits) { .val = d }).bits; +} + +INLINE double bits_to_double(uint64_t i) { + return ((DoubleBits) { .bits = i }).val; } extern SDL_threadID main_thread_id; diff --git a/src/util/fbmgr.c b/src/util/fbmgr.c index 3200d3e399..3c2ed6b503 100644 --- a/src/util/fbmgr.c +++ b/src/util/fbmgr.c @@ -31,9 +31,15 @@ struct ManagedFramebufferGroup { static ManagedFramebufferData *framebuffers; -static inline void fbmgr_framebuffer_get_metrics(ManagedFramebuffer *mfb, IntExtent *fb_size, FloatRect *fb_viewport) { +static inline bool fbmgr_framebuffer_get_metrics(ManagedFramebuffer *mfb, IntExtent *fb_size, FloatRect *fb_viewport) { ManagedFramebufferData *mfb_data = GET_DATA(mfb); + + if(!mfb_data->resize_strategy.resize_func) { + return false; + } + mfb_data->resize_strategy.resize_func(mfb_data->resize_strategy.userdata, fb_size, fb_viewport); + return true; } static void fbmgr_framebuffer_update(ManagedFramebuffer *mfb) { @@ -41,7 +47,9 @@ static void fbmgr_framebuffer_update(ManagedFramebuffer *mfb) { FloatRect fb_viewport; Framebuffer *fb = mfb->fb; - fbmgr_framebuffer_get_metrics(mfb, &fb_size, &fb_viewport); + if(!fbmgr_framebuffer_get_metrics(mfb, &fb_size, &fb_viewport)) { + return; + } for(uint i = 0; i < FRAMEBUFFER_MAX_ATTACHMENTS; ++i) { fbutil_resize_attachment(fb, i, fb_size.w, fb_size.h); @@ -57,7 +65,6 @@ static void fbmgr_framebuffer_update_all(void) { } ManagedFramebuffer *fbmgr_framebuffer_create(const char *name, const FramebufferConfig *cfg) { - assert(cfg->resize_strategy.resize_func != NULL); assert(cfg->attachments != NULL); assert(cfg->num_attachments >= 1); @@ -72,11 +79,17 @@ ManagedFramebuffer *fbmgr_framebuffer_create(const char *name, const Framebuffer IntExtent fb_size; FloatRect fb_viewport; - fbmgr_framebuffer_get_metrics(mfb, &fb_size, &fb_viewport); - for(int i = 0; i < cfg->num_attachments; ++i) { - ac[i].tex_params.width = fb_size.w; - ac[i].tex_params.height = fb_size.h; + if(fbmgr_framebuffer_get_metrics(mfb, &fb_size, &fb_viewport)) { + for(int i = 0; i < cfg->num_attachments; ++i) { + ac[i].tex_params.width = fb_size.w; + ac[i].tex_params.height = fb_size.h; + } + } else { + fb_viewport.x = 0; + fb_viewport.y = 0; + fb_viewport.w = ac[0].tex_params.width; + fb_viewport.h = ac[0].tex_params.height; } fbutil_create_attachments(mfb->fb, cfg->num_attachments, ac); @@ -173,4 +186,3 @@ void fbmgr_group_fbpair_create(ManagedFramebufferGroup *group, const char *name, snprintf(buf, sizeof(buf), "%s FB 2", name); fbpair->back = fbmgr_group_framebuffer_create(group, buf, cfg); } - diff --git a/src/util/glm.h b/src/util/glm.h index 2105e303ff..02c4580b53 100644 --- a/src/util/glm.h +++ b/src/util/glm.h @@ -29,4 +29,10 @@ PRAGMA(GCC diagnostic pop) #endif +typedef float (*glm_ease_t)(float); + +INLINE float glm_ease_apply(glm_ease_t ease, float f) { + return ease ? ease(f) : f; +} + #endif // IGUARD_util_glm_h diff --git a/src/util/macrohax.h b/src/util/macrohax.h new file mode 100644 index 0000000000..ee4283f0b7 --- /dev/null +++ b/src/util/macrohax.h @@ -0,0 +1,24 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#ifndef IGUARD_util_macrohax_h +#define IGUARD_util_macrohax_h + +#include "taisei.h" + +#define MACROHAX_FIRST_(f, ...) f +#define MACROHAX_FIRST(...) MACROHAX_FIRST_(__VA_ARGS__, _) +#define MACROHAX_EMPTY() +#define MACROHAX_DEFER(id) id MACROHAX_EMPTY() +#define MACROHAX_OBSTRUCT(...) __VA_ARGS__ MACROHAX_DEFER(MACROHAX_EMPTY)() +#define MACROHAX_EXPAND(...) __VA_ARGS__ + +#define MACROHAX_CONCAT(a, b) a ## b +#define MACROHAX_ADDLINENUM(a) MACROHAX_EXPAND(MACROHAX_DEFER(MACROHAX_CONCAT)(a, __LINE__)) + +#endif // IGUARD_util_macrohax_h diff --git a/src/util/miscmath.c b/src/util/miscmath.c index 1fade2556b..e44a9bfbec 100644 --- a/src/util/miscmath.c +++ b/src/util/miscmath.c @@ -15,10 +15,18 @@ double lerp(double v0, double v1, double f) { return f * (v1 - v0) + v0; } +float lerpf(float v0, float v1, float f) { + return f * (v1 - v0) + v0; +} + cmplx clerp(cmplx v0, cmplx v1, double f) { return f * (v1 - v0) + v0; } +cmplx32 clerpf(cmplx32 v0, cmplx32 v1, float32 f) { + return f * (v1 - v0) + v0; +} + double approach(double v, double t, double d) { if(v < t) { v += d; @@ -91,6 +99,40 @@ void capproach_asymptotic_p(cmplx *val, cmplx target, double rate, double epsilo *val = capproach_asymptotic(*val, target, rate, epsilon); } +cmplx cnormalize(cmplx c) { + return c / cabs(c); +} + +cmplx cclampabs(cmplx c, double maxabs) { + double a = cabs(c); + + if(a > maxabs) { + return maxabs * c / a; + } + + return c; +} + +cmplx cdir(double angle) { + // this is faster than cexp(I*angle) + + #ifdef TAISEI_BUILDCONF_HAVE_SINCOS + double s, c; + sincos(angle, &s, &c); + return CMPLX(c, s); + #else + return CMPLX(cos(angle), sin(angle)); + #endif +} + +cmplx cwmul(cmplx c0, cmplx c1) { + return CMPLX(creal(c0)*creal(c1), cimag(c0)*cimag(c1)); +} + +cmplx32 cwmulf(cmplx32 c0, cmplx32 c1) { + return CMPLXF(crealf(c0)*crealf(c1), cimagf(c0)*cimagf(c1)); +} + double psin(double x) { return 0.5 + 0.5 * sin(x); } @@ -153,8 +195,8 @@ double smoothstep(double edge0, double edge1, double x) { } double smoothmin(double a, double b, double k) { - float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); - return lerp(b, a, h) - k * h * (1.0 - h); + float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); + return lerp(b, a, h) - k * h * (1.0 - h); } int sign(double x) { @@ -173,6 +215,14 @@ double swing(double x, double s) { return x * x * ((s + 1) * x + s) / 2 + 1; } +double sawtooth(double x) { + return 2 * (x - floor(x + 0.5)); +} + +double triangle(double x) { + return 2 * fabs(sawtooth(x)) - 1; +} + uint32_t topow2_u32(uint32_t x) { x -= 1; x |= (x >> 1); @@ -214,6 +264,18 @@ float sanitize_scale(float scale) { return max(0.1, scale); } +double circle_angle(double index, double max_elements) { + return (index * (M_PI * 2.0)) / max_elements; +} + +cmplx circle_dir(double index, double max_elements) { + return cdir(circle_angle(index, max_elements)); +} + +cmplx circle_dir_ofs(double index, double max_elements, double ofs) { + return cdir(circle_angle(index, max_elements) + ofs); +} + float normpdf(float x, float sigma) { return 0.39894 * exp(-0.5 * pow(x, 2) / pow(sigma, 2)) / sigma; } diff --git a/src/util/miscmath.h b/src/util/miscmath.h index 4cfa3cb955..9ddce47080 100644 --- a/src/util/miscmath.h +++ b/src/util/miscmath.h @@ -11,12 +11,15 @@ #include "taisei.h" -#define DEG2RAD (M_PI/180.0) -#define RAD2DEG (180.0/M_PI) +#define DEG2RAD (M_PI / 180.0) +#define RAD2DEG (180.0 / M_PI) #define GOLDEN_RATIO 1.618033988749895 +#define M_TAU (M_PI * 2) double lerp(double v0, double v1, double f) attr_const; +float lerpf(float v0, float v1, float f) attr_const; cmplx clerp(cmplx v0, cmplx v1, double f) attr_const; +cmplx32 clerpf(cmplx32 v0, cmplx32 v1, float32 f) attr_const; intmax_t imin(intmax_t, intmax_t) attr_const; intmax_t imax(intmax_t, intmax_t) attr_const; uintmax_t umin(uintmax_t, uintmax_t) attr_const; @@ -37,15 +40,25 @@ cmplx capproach_asymptotic(cmplx val, cmplx target, double rate, double epsilon) void approach_asymptotic_p(double *val, double target, double rate, double epsilon); void fapproach_asymptotic_p(float *val, float target, float rate, float epsilon); void capproach_asymptotic_p(cmplx *val, cmplx target, double rate, double epsilon); +cmplx cnormalize(cmplx c) attr_const; +cmplx cclampabs(cmplx c, double maxabs) attr_const; +cmplx cdir(double angle) attr_const; +cmplx cwmul(cmplx c0, cmplx c1) attr_const; +cmplx32 cwmulf(cmplx32 c0, cmplx32 c1) attr_const; double psin(double) attr_const; int sign(double) attr_const; double swing(double x, double s) attr_const; +double sawtooth(double x) attr_const; +double triangle(double x) attr_const; uint32_t topow2_u32(uint32_t x) attr_const; uint64_t topow2_u64(uint64_t x) attr_const; float ftopow2(float x) attr_const; float smooth(float x) attr_const; float smoothreclamp(float x, float old_min, float old_max, float new_min, float new_max) attr_const; float sanitize_scale(float scale) attr_const; +double circle_angle(double index, double max_elements) attr_const; +cmplx circle_dir(double index, double max_elements) attr_const; +cmplx circle_dir_ofs(double index, double max_elements, double ofs) attr_const; uint64_t upow10(uint n) attr_const; uint digitcnt(uint64_t x) attr_const; float normpdf(float x, float sigma) attr_const; diff --git a/subprojects/koishi b/subprojects/koishi new file mode 120000 index 0000000000..9245396dab --- /dev/null +++ b/subprojects/koishi @@ -0,0 +1 @@ +../external/koishi \ No newline at end of file