From 84c3a02b9a5d9139f7a14c4ff8a6afa4c322f268 Mon Sep 17 00:00:00 2001 From: falsycat Date: Mon, 14 Sep 2020 00:00:00 +0000 Subject: [PATCH] [RELEASE] u22-v03 This version is submitted to U22 breau. --- .gitignore | 1 + .gitmodules | 3 + CMakeLists.txt | 40 + app/CMakeLists.txt | 1 + app/sdl/CMakeLists.txt | 14 + app/sdl/args.c | 104 +++ app/sdl/args.h | 24 + app/sdl/event.c | 52 ++ app/sdl/event.h | 13 + app/sdl/main.c | 172 ++++ cmake/anysrc.cmake | 20 + cmake/sos.cmake | 20 + core/CMakeLists.txt | 13 + core/lobullet/CMakeLists.txt | 20 + core/lobullet/base.c | 235 +++++ core/lobullet/base.h | 81 ++ core/lobullet/bomb.c | 205 ++++ core/lobullet/bomb.h | 82 ++ core/lobullet/linear.c | 197 ++++ core/lobullet/linear.h | 81 ++ core/lobullet/misc.c | 37 + core/lobullet/misc.h | 31 + core/lobullet/pool.c | 104 +++ core/lobullet/pool.h | 42 + core/locharacter/CMakeLists.txt | 27 + core/locharacter/base.c | 434 +++++++++ core/locharacter/base.h | 100 ++ core/locharacter/big_warder.c | 787 ++++++++++++++++ core/locharacter/big_warder.h | 37 + core/locharacter/big_warder.private.h | 157 ++++ core/locharacter/cavia.c | 265 ++++++ core/locharacter/cavia.h | 36 + core/locharacter/encephalon.c | 138 +++ core/locharacter/encephalon.h | 34 + core/locharacter/greedy_scientist.c | 607 ++++++++++++ core/locharacter/greedy_scientist.h | 37 + core/locharacter/greedy_scientist.private.h | 228 +++++ core/locharacter/misc.c | 68 ++ core/locharacter/misc.h | 76 ++ core/locharacter/pool.c | 122 +++ core/locharacter/pool.h | 48 + core/locharacter/scientist.c | 305 ++++++ core/locharacter/scientist.h | 34 + core/locharacter/theists_child.c | 763 +++++++++++++++ core/locharacter/theists_child.h | 37 + core/locharacter/theists_child.private.h | 150 +++ core/locharacter/util.c | 139 +++ core/locharacter/util.h | 54 ++ core/locharacter/warder.c | 299 ++++++ core/locharacter/warder.h | 33 + core/locommon/CMakeLists.txt | 15 + core/locommon/counter.c | 45 + core/locommon/counter.h | 38 + core/locommon/easing.c | 40 + core/locommon/easing.h | 24 + core/locommon/input.h | 27 + core/locommon/msgpack.h | 118 +++ core/locommon/null.c | 20 + core/locommon/null.h | 20 + core/locommon/physics.c | 94 ++ core/locommon/physics.h | 28 + core/locommon/position.c | 83 ++ core/locommon/position.h | 59 ++ core/locommon/ticker.c | 46 + core/locommon/ticker.h | 44 + core/loeffect/CMakeLists.txt | 14 + core/loeffect/effect.c | 99 ++ core/loeffect/effect.h | 87 ++ core/loeffect/generic.c | 81 ++ core/loeffect/generic.h | 40 + core/loeffect/recipient.c | 132 +++ core/loeffect/recipient.h | 76 ++ core/loeffect/stance.c | 143 +++ core/loeffect/stance.h | 84 ++ core/loentity/CMakeLists.txt | 16 + core/loentity/bullet.c | 43 + core/loentity/bullet.h | 45 + core/loentity/character.c | 28 + core/loentity/character.h | 40 + core/loentity/decl.private.h | 13 + core/loentity/entity.c | 44 + core/loentity/entity.h | 88 ++ core/loentity/ground.h | 18 + core/loentity/store.c | 211 +++++ core/loentity/store.h | 90 ++ core/loground/CMakeLists.txt | 17 + core/loground/base.c | 228 +++++ core/loground/base.h | 54 ++ core/loground/island.c | 35 + core/loground/island.h | 31 + core/loground/misc.c | 37 + core/loground/misc.h | 25 + core/loground/pool.c | 89 ++ core/loground/pool.h | 39 + core/loplayer/CMakeLists.txt | 28 + core/loplayer/action.c | 880 ++++++++++++++++++ core/loplayer/action.h | 64 ++ core/loplayer/camera.c | 273 ++++++ core/loplayer/camera.h | 101 ++ core/loplayer/combat.c | 503 ++++++++++ core/loplayer/combat.h | 107 +++ core/loplayer/controller.c | 79 ++ core/loplayer/controller.h | 51 + core/loplayer/entity.c | 311 +++++++ core/loplayer/entity.h | 93 ++ core/loplayer/event.c | 146 +++ core/loplayer/event.h | 74 ++ core/loplayer/hud.c | 409 ++++++++ core/loplayer/hud.h | 53 ++ core/loplayer/menu.c | 565 +++++++++++ core/loplayer/menu.h | 86 ++ core/loplayer/player.c | 288 ++++++ core/loplayer/player.h | 122 +++ core/loplayer/status.c | 193 ++++ core/loplayer/status.h | 87 ++ core/loresource/CMakeLists.txt | 40 + core/loresource/font.c | 41 + core/loresource/font.h | 21 + core/loresource/font/LICENSE-SIL.txt | 93 ++ core/loresource/font/README.md | 13 + core/loresource/font/sans.woff | Bin 0 -> 664288 bytes core/loresource/font/serif.woff | Bin 0 -> 861972 bytes core/loresource/language.c | 40 + core/loresource/language.h | 20 + core/loresource/music.c | 101 ++ core/loresource/music.h | 38 + core/loresource/music/README.md | 30 + core/loresource/music/biome_boss.mp3 | Bin 0 -> 1789680 bytes core/loresource/music/biome_cavias_camp.mp3 | Bin 0 -> 256872 bytes core/loresource/music/biome_laboratory.mp3 | Bin 0 -> 480480 bytes .../music/biome_metaphysical_gate.mp3 | Bin 0 -> 849024 bytes core/loresource/music/boss_big_warder.mp3 | Bin 0 -> 1937239 bytes .../music/boss_greedy_scientist.mp3 | Bin 0 -> 976896 bytes core/loresource/music/boss_theists_child.mp3 | Bin 0 -> 1772544 bytes core/loresource/music/title.mp3 | Bin 0 -> 1816867 bytes core/loresource/set.c | 46 + core/loresource/set.h | 36 + core/loresource/sound.c | 263 ++++++ core/loresource/sound.h | 34 + core/loresource/sound/README.md | 16 + core/loresource/sound/bomb.mp3 | Bin 0 -> 48768 bytes core/loresource/sound/damage.mp3 | Bin 0 -> 2055 bytes core/loresource/sound/dodge.mp3 | Bin 0 -> 16896 bytes core/loresource/sound/dying.mp3 | Bin 0 -> 64896 bytes core/loresource/sound/enemy_shoot.mp3 | Bin 0 -> 1561 bytes core/loresource/sound/enemy_trigger.mp3 | Bin 0 -> 5428 bytes core/loresource/sound/guard.mp3 | Bin 0 -> 24960 bytes core/loresource/sound/player_shoot.mp3 | Bin 0 -> 24630 bytes core/loresource/sound/player_trigger.mp3 | Bin 0 -> 5845 bytes core/loresource/sound/reflection.mp3 | Bin 0 -> 8064 bytes core/loresource/sound/touch_gate.mp3 | Bin 0 -> 20670 bytes core/loresource/text.c | 32 + core/loresource/text.h | 14 + core/loresource/text/jp.h | 88 ++ core/loscene/CMakeLists.txt | 23 + core/loscene/context.c | 127 +++ core/loscene/context.h | 35 + core/loscene/game.c | 398 ++++++++ core/loscene/game.h | 17 + core/loscene/param.h | 26 + core/loscene/scene.c | 28 + core/loscene/scene.h | 43 + core/loscene/title.c | 333 +++++++ core/loscene/title.h | 16 + core/loshader/CMakeLists.txt | 63 ++ core/loshader/backwall.c | 118 +++ core/loshader/backwall.fshader | 220 +++++ core/loshader/backwall.h | 60 ++ core/loshader/backwall.vshader | 11 + core/loshader/bullet.c | 191 ++++ core/loshader/bullet.fshader | 53 ++ core/loshader/bullet.h | 68 ++ core/loshader/bullet.vshader | 52 ++ core/loshader/character.c | 205 ++++ core/loshader/character.fshader | 7 + core/loshader/character.h | 85 ++ core/loshader/character.vshader | 375 ++++++++ core/loshader/cinescope.c | 116 +++ core/loshader/cinescope.fshader | 10 + core/loshader/cinescope.h | 48 + core/loshader/cinescope.vshader | 22 + core/loshader/combat_ring.c | 183 ++++ core/loshader/combat_ring.fshader | 91 ++ core/loshader/combat_ring.h | 58 ++ core/loshader/combat_ring.vshader | 31 + core/loshader/event_line.c | 91 ++ core/loshader/event_line.fshader | 11 + core/loshader/event_line.h | 63 ++ core/loshader/event_line.vshader | 22 + core/loshader/fog.c | 131 +++ core/loshader/fog.fshader | 104 +++ core/loshader/fog.h | 59 ++ core/loshader/fog.vshader | 15 + core/loshader/ground.c | 174 ++++ core/loshader/ground.fshader | 9 + core/loshader/ground.h | 62 ++ core/loshader/ground.vshader | 20 + core/loshader/header.shader | 22 + core/loshader/hud_bar.c | 187 ++++ core/loshader/hud_bar.fshader | 21 + core/loshader/hud_bar.h | 60 ++ core/loshader/hud_bar.vshader | 56 ++ core/loshader/hud_text.c | 95 ++ core/loshader/hud_text.fshader | 28 + core/loshader/hud_text.h | 66 ++ core/loshader/hud_text.vshader | 33 + core/loshader/menu_background.c | 97 ++ core/loshader/menu_background.fshader | 35 + core/loshader/menu_background.h | 44 + core/loshader/menu_background.vshader | 11 + core/loshader/menu_stance.c | 185 ++++ core/loshader/menu_stance.fshader | 9 + core/loshader/menu_stance.h | 68 ++ core/loshader/menu_stance.vshader | 93 ++ core/loshader/menu_text.c | 97 ++ core/loshader/menu_text.fshader | 12 + core/loshader/menu_text.h | 66 ++ core/loshader/menu_text.vshader | 24 + core/loshader/pixsort.c | 109 +++ core/loshader/pixsort.fshader | 71 ++ core/loshader/pixsort.h | 51 + core/loshader/pixsort.vshader | 12 + core/loshader/posteffect.c | 144 +++ core/loshader/posteffect.fshader | 139 +++ core/loshader/posteffect.h | 57 ++ core/loshader/posteffect.vshader | 11 + core/loshader/set.c | 250 +++++ core/loshader/set.h | 103 ++ core/loshader/uniblock.c | 117 +++ core/loshader/uniblock.h | 56 ++ core/loworld/CMakeLists.txt | 27 + core/loworld/chunk.c | 159 ++++ core/loworld/chunk.h | 98 ++ core/loworld/environment.c | 254 +++++ core/loworld/environment.h | 66 ++ core/loworld/generator.c | 245 +++++ core/loworld/generator.h | 47 + core/loworld/poolset.c | 38 + core/loworld/poolset.h | 29 + core/loworld/store.c | 265 ++++++ core/loworld/store.h | 52 ++ core/loworld/template.c | 287 ++++++ core/loworld/template.h | 138 +++ core/loworld/test.h | 146 +++ core/loworld/view.c | 171 ++++ core/loworld/view.h | 49 + thirdparty/CMakeLists.txt | 2 + thirdparty/miniaudio/CMakeLists.txt | 8 + thirdparty/miniaudio/miniaudio.c | 2 + thirdparty/miniaudio/miniaudio.h | 10 + thirdparty/miniaudio/repo | 1 + tool/CMakeLists.txt | 0 tool/bin2c.sh | 14 + tool/leak-check.sh | 35 + util/CMakeLists.txt | 12 + util/chaos/CMakeLists.txt | 7 + util/chaos/abchash.c | 32 + util/chaos/abchash.h | 19 + util/chaos/xorshift.c | 19 + util/chaos/xorshift.h | 14 + util/coly2d/CMakeLists.txt | 7 + util/coly2d/README.md | 4 + util/coly2d/hittest.c | 171 ++++ util/coly2d/hittest.h | 53 ++ util/coly2d/shape.c | 54 ++ util/coly2d/shape.h | 51 + util/container/CMakeLists.txt | 11 + util/container/array.c | 90 ++ util/container/array.h | 31 + util/container/test.c | 48 + util/conv/CMakeLists.txt | 3 + util/conv/charcode.c | 36 + util/conv/charcode.h | 11 + util/dictres/CMakeLists.txt | 11 + util/dictres/dictres.c | 87 ++ util/dictres/dictres.h | 27 + util/dictres/test.c | 27 + util/gleasy/CMakeLists.txt | 14 + util/gleasy/atlas.c | 194 ++++ util/gleasy/atlas.h | 56 ++ util/gleasy/buffer.h | 7 + util/gleasy/framebuffer.c | 96 ++ util/gleasy/framebuffer.h | 47 + util/gleasy/misc.h | 18 + util/gleasy/program.c | 66 ++ util/gleasy/program.h | 24 + util/gleasy/shader.c | 57 ++ util/gleasy/shader.h | 23 + util/gleasy/texture.h | 5 + util/glyphas/CMakeLists.txt | 33 + util/glyphas/README.md | 4 + util/glyphas/aligner.c | 78 ++ util/glyphas/aligner.h | 52 ++ util/glyphas/block.c | 216 +++++ util/glyphas/block.h | 102 ++ util/glyphas/cache.c | 111 +++ util/glyphas/cache.h | 47 + util/glyphas/context.c | 21 + util/glyphas/context.h | 18 + util/glyphas/drawer.c | 164 ++++ util/glyphas/drawer.fshader | 14 + util/glyphas/drawer.h | 51 + util/glyphas/drawer.vshader | 25 + util/glyphas/face.c | 78 ++ util/glyphas/face.h | 52 ++ util/glyphas/glyph.c | 36 + util/glyphas/glyph.h | 32 + util/glyphas/test.c | 179 ++++ util/jukebox/CMakeLists.txt | 23 + util/jukebox/amp.c | 83 ++ util/jukebox/amp.h | 38 + util/jukebox/beep.c | 77 ++ util/jukebox/beep.h | 42 + util/jukebox/composite.c | 113 +++ util/jukebox/composite.h | 36 + util/jukebox/decoder.c | 217 +++++ util/jukebox/decoder.h | 71 ++ util/jukebox/delay.c | 94 ++ util/jukebox/delay.h | 23 + util/jukebox/effect.c | 13 + util/jukebox/effect.h | 31 + util/jukebox/format.c | 9 + util/jukebox/format.h | 14 + util/jukebox/lowpass.c | 57 ++ util/jukebox/lowpass.h | 31 + util/jukebox/mixer.c | 98 ++ util/jukebox/mixer.h | 26 + util/jukebox/sound.c | 180 ++++ util/jukebox/sound.h | 53 ++ util/jukebox/test.c | 53 ++ util/math/CMakeLists.txt | 16 + util/math/algorithm.h | 63 ++ util/math/algorithm.sos.c | 88 ++ util/math/constant.h | 8 + util/math/matrix.h | 123 +++ util/math/matrix.sos.c | 240 +++++ util/math/rational.c | 110 +++ util/math/rational.h | 84 ++ util/math/test.c | 111 +++ util/math/vector.c | 275 ++++++ util/math/vector.h | 78 ++ util/memory/CMakeLists.txt | 10 + util/memory/memory.c | 81 ++ util/memory/memory.h | 29 + util/memory/test.c | 8 + util/mpkutil/CMakeLists.txt | 10 + util/mpkutil/README.md | 4 + util/mpkutil/file.c | 58 ++ util/mpkutil/file.h | 19 + util/mpkutil/get.c | 143 +++ util/mpkutil/get.h | 92 ++ util/mpkutil/pack.c | 49 + util/mpkutil/pack.h | 39 + util/parsarg/CMakeLists.txt | 12 + util/parsarg/parsarg.c | 79 ++ util/parsarg/parsarg.h | 42 + util/parsarg/test.c | 45 + 357 files changed, 29223 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 app/CMakeLists.txt create mode 100644 app/sdl/CMakeLists.txt create mode 100644 app/sdl/args.c create mode 100644 app/sdl/args.h create mode 100644 app/sdl/event.c create mode 100644 app/sdl/event.h create mode 100644 app/sdl/main.c create mode 100644 cmake/anysrc.cmake create mode 100644 cmake/sos.cmake create mode 100644 core/CMakeLists.txt create mode 100644 core/lobullet/CMakeLists.txt create mode 100644 core/lobullet/base.c create mode 100644 core/lobullet/base.h create mode 100644 core/lobullet/bomb.c create mode 100644 core/lobullet/bomb.h create mode 100644 core/lobullet/linear.c create mode 100644 core/lobullet/linear.h create mode 100644 core/lobullet/misc.c create mode 100644 core/lobullet/misc.h create mode 100644 core/lobullet/pool.c create mode 100644 core/lobullet/pool.h create mode 100644 core/locharacter/CMakeLists.txt create mode 100644 core/locharacter/base.c create mode 100644 core/locharacter/base.h create mode 100644 core/locharacter/big_warder.c create mode 100644 core/locharacter/big_warder.h create mode 100644 core/locharacter/big_warder.private.h create mode 100644 core/locharacter/cavia.c create mode 100644 core/locharacter/cavia.h create mode 100644 core/locharacter/encephalon.c create mode 100644 core/locharacter/encephalon.h create mode 100644 core/locharacter/greedy_scientist.c create mode 100644 core/locharacter/greedy_scientist.h create mode 100644 core/locharacter/greedy_scientist.private.h create mode 100644 core/locharacter/misc.c create mode 100644 core/locharacter/misc.h create mode 100644 core/locharacter/pool.c create mode 100644 core/locharacter/pool.h create mode 100644 core/locharacter/scientist.c create mode 100644 core/locharacter/scientist.h create mode 100644 core/locharacter/theists_child.c create mode 100644 core/locharacter/theists_child.h create mode 100644 core/locharacter/theists_child.private.h create mode 100644 core/locharacter/util.c create mode 100644 core/locharacter/util.h create mode 100644 core/locharacter/warder.c create mode 100644 core/locharacter/warder.h create mode 100644 core/locommon/CMakeLists.txt create mode 100644 core/locommon/counter.c create mode 100644 core/locommon/counter.h create mode 100644 core/locommon/easing.c create mode 100644 core/locommon/easing.h create mode 100644 core/locommon/input.h create mode 100644 core/locommon/msgpack.h create mode 100644 core/locommon/null.c create mode 100644 core/locommon/null.h create mode 100644 core/locommon/physics.c create mode 100644 core/locommon/physics.h create mode 100644 core/locommon/position.c create mode 100644 core/locommon/position.h create mode 100644 core/locommon/ticker.c create mode 100644 core/locommon/ticker.h create mode 100644 core/loeffect/CMakeLists.txt create mode 100644 core/loeffect/effect.c create mode 100644 core/loeffect/effect.h create mode 100644 core/loeffect/generic.c create mode 100644 core/loeffect/generic.h create mode 100644 core/loeffect/recipient.c create mode 100644 core/loeffect/recipient.h create mode 100644 core/loeffect/stance.c create mode 100644 core/loeffect/stance.h create mode 100644 core/loentity/CMakeLists.txt create mode 100644 core/loentity/bullet.c create mode 100644 core/loentity/bullet.h create mode 100644 core/loentity/character.c create mode 100644 core/loentity/character.h create mode 100644 core/loentity/decl.private.h create mode 100644 core/loentity/entity.c create mode 100644 core/loentity/entity.h create mode 100644 core/loentity/ground.h create mode 100644 core/loentity/store.c create mode 100644 core/loentity/store.h create mode 100644 core/loground/CMakeLists.txt create mode 100644 core/loground/base.c create mode 100644 core/loground/base.h create mode 100644 core/loground/island.c create mode 100644 core/loground/island.h create mode 100644 core/loground/misc.c create mode 100644 core/loground/misc.h create mode 100644 core/loground/pool.c create mode 100644 core/loground/pool.h create mode 100644 core/loplayer/CMakeLists.txt create mode 100644 core/loplayer/action.c create mode 100644 core/loplayer/action.h create mode 100644 core/loplayer/camera.c create mode 100644 core/loplayer/camera.h create mode 100644 core/loplayer/combat.c create mode 100644 core/loplayer/combat.h create mode 100644 core/loplayer/controller.c create mode 100644 core/loplayer/controller.h create mode 100644 core/loplayer/entity.c create mode 100644 core/loplayer/entity.h create mode 100644 core/loplayer/event.c create mode 100644 core/loplayer/event.h create mode 100644 core/loplayer/hud.c create mode 100644 core/loplayer/hud.h create mode 100644 core/loplayer/menu.c create mode 100644 core/loplayer/menu.h create mode 100644 core/loplayer/player.c create mode 100644 core/loplayer/player.h create mode 100644 core/loplayer/status.c create mode 100644 core/loplayer/status.h create mode 100644 core/loresource/CMakeLists.txt create mode 100644 core/loresource/font.c create mode 100644 core/loresource/font.h create mode 100644 core/loresource/font/LICENSE-SIL.txt create mode 100644 core/loresource/font/README.md create mode 100755 core/loresource/font/sans.woff create mode 100755 core/loresource/font/serif.woff create mode 100644 core/loresource/language.c create mode 100644 core/loresource/language.h create mode 100644 core/loresource/music.c create mode 100644 core/loresource/music.h create mode 100644 core/loresource/music/README.md create mode 100644 core/loresource/music/biome_boss.mp3 create mode 100644 core/loresource/music/biome_cavias_camp.mp3 create mode 100644 core/loresource/music/biome_laboratory.mp3 create mode 100644 core/loresource/music/biome_metaphysical_gate.mp3 create mode 100644 core/loresource/music/boss_big_warder.mp3 create mode 100644 core/loresource/music/boss_greedy_scientist.mp3 create mode 100644 core/loresource/music/boss_theists_child.mp3 create mode 100644 core/loresource/music/title.mp3 create mode 100644 core/loresource/set.c create mode 100644 core/loresource/set.h create mode 100644 core/loresource/sound.c create mode 100644 core/loresource/sound.h create mode 100644 core/loresource/sound/README.md create mode 100644 core/loresource/sound/bomb.mp3 create mode 100644 core/loresource/sound/damage.mp3 create mode 100644 core/loresource/sound/dodge.mp3 create mode 100644 core/loresource/sound/dying.mp3 create mode 100644 core/loresource/sound/enemy_shoot.mp3 create mode 100644 core/loresource/sound/enemy_trigger.mp3 create mode 100644 core/loresource/sound/guard.mp3 create mode 100644 core/loresource/sound/player_shoot.mp3 create mode 100644 core/loresource/sound/player_trigger.mp3 create mode 100644 core/loresource/sound/reflection.mp3 create mode 100644 core/loresource/sound/touch_gate.mp3 create mode 100644 core/loresource/text.c create mode 100644 core/loresource/text.h create mode 100644 core/loresource/text/jp.h create mode 100644 core/loscene/CMakeLists.txt create mode 100644 core/loscene/context.c create mode 100644 core/loscene/context.h create mode 100644 core/loscene/game.c create mode 100644 core/loscene/game.h create mode 100644 core/loscene/param.h create mode 100644 core/loscene/scene.c create mode 100644 core/loscene/scene.h create mode 100644 core/loscene/title.c create mode 100644 core/loscene/title.h create mode 100644 core/loshader/CMakeLists.txt create mode 100644 core/loshader/backwall.c create mode 100644 core/loshader/backwall.fshader create mode 100644 core/loshader/backwall.h create mode 100644 core/loshader/backwall.vshader create mode 100644 core/loshader/bullet.c create mode 100644 core/loshader/bullet.fshader create mode 100644 core/loshader/bullet.h create mode 100644 core/loshader/bullet.vshader create mode 100644 core/loshader/character.c create mode 100644 core/loshader/character.fshader create mode 100644 core/loshader/character.h create mode 100644 core/loshader/character.vshader create mode 100644 core/loshader/cinescope.c create mode 100644 core/loshader/cinescope.fshader create mode 100644 core/loshader/cinescope.h create mode 100644 core/loshader/cinescope.vshader create mode 100644 core/loshader/combat_ring.c create mode 100644 core/loshader/combat_ring.fshader create mode 100644 core/loshader/combat_ring.h create mode 100644 core/loshader/combat_ring.vshader create mode 100644 core/loshader/event_line.c create mode 100644 core/loshader/event_line.fshader create mode 100644 core/loshader/event_line.h create mode 100644 core/loshader/event_line.vshader create mode 100644 core/loshader/fog.c create mode 100644 core/loshader/fog.fshader create mode 100644 core/loshader/fog.h create mode 100644 core/loshader/fog.vshader create mode 100644 core/loshader/ground.c create mode 100644 core/loshader/ground.fshader create mode 100644 core/loshader/ground.h create mode 100644 core/loshader/ground.vshader create mode 100644 core/loshader/header.shader create mode 100644 core/loshader/hud_bar.c create mode 100644 core/loshader/hud_bar.fshader create mode 100644 core/loshader/hud_bar.h create mode 100644 core/loshader/hud_bar.vshader create mode 100644 core/loshader/hud_text.c create mode 100644 core/loshader/hud_text.fshader create mode 100644 core/loshader/hud_text.h create mode 100644 core/loshader/hud_text.vshader create mode 100644 core/loshader/menu_background.c create mode 100644 core/loshader/menu_background.fshader create mode 100644 core/loshader/menu_background.h create mode 100644 core/loshader/menu_background.vshader create mode 100644 core/loshader/menu_stance.c create mode 100644 core/loshader/menu_stance.fshader create mode 100644 core/loshader/menu_stance.h create mode 100644 core/loshader/menu_stance.vshader create mode 100644 core/loshader/menu_text.c create mode 100644 core/loshader/menu_text.fshader create mode 100644 core/loshader/menu_text.h create mode 100644 core/loshader/menu_text.vshader create mode 100644 core/loshader/pixsort.c create mode 100644 core/loshader/pixsort.fshader create mode 100644 core/loshader/pixsort.h create mode 100644 core/loshader/pixsort.vshader create mode 100644 core/loshader/posteffect.c create mode 100644 core/loshader/posteffect.fshader create mode 100644 core/loshader/posteffect.h create mode 100644 core/loshader/posteffect.vshader create mode 100644 core/loshader/set.c create mode 100644 core/loshader/set.h create mode 100644 core/loshader/uniblock.c create mode 100644 core/loshader/uniblock.h create mode 100644 core/loworld/CMakeLists.txt create mode 100644 core/loworld/chunk.c create mode 100644 core/loworld/chunk.h create mode 100644 core/loworld/environment.c create mode 100644 core/loworld/environment.h create mode 100644 core/loworld/generator.c create mode 100644 core/loworld/generator.h create mode 100644 core/loworld/poolset.c create mode 100644 core/loworld/poolset.h create mode 100644 core/loworld/store.c create mode 100644 core/loworld/store.h create mode 100644 core/loworld/template.c create mode 100644 core/loworld/template.h create mode 100644 core/loworld/test.h create mode 100644 core/loworld/view.c create mode 100644 core/loworld/view.h create mode 100644 thirdparty/CMakeLists.txt create mode 100644 thirdparty/miniaudio/CMakeLists.txt create mode 100644 thirdparty/miniaudio/miniaudio.c create mode 100644 thirdparty/miniaudio/miniaudio.h create mode 160000 thirdparty/miniaudio/repo create mode 100644 tool/CMakeLists.txt create mode 100755 tool/bin2c.sh create mode 100755 tool/leak-check.sh create mode 100644 util/CMakeLists.txt create mode 100644 util/chaos/CMakeLists.txt create mode 100644 util/chaos/abchash.c create mode 100644 util/chaos/abchash.h create mode 100644 util/chaos/xorshift.c create mode 100644 util/chaos/xorshift.h create mode 100644 util/coly2d/CMakeLists.txt create mode 100644 util/coly2d/README.md create mode 100644 util/coly2d/hittest.c create mode 100644 util/coly2d/hittest.h create mode 100644 util/coly2d/shape.c create mode 100644 util/coly2d/shape.h create mode 100644 util/container/CMakeLists.txt create mode 100644 util/container/array.c create mode 100644 util/container/array.h create mode 100644 util/container/test.c create mode 100644 util/conv/CMakeLists.txt create mode 100644 util/conv/charcode.c create mode 100644 util/conv/charcode.h create mode 100644 util/dictres/CMakeLists.txt create mode 100644 util/dictres/dictres.c create mode 100644 util/dictres/dictres.h create mode 100644 util/dictres/test.c create mode 100644 util/gleasy/CMakeLists.txt create mode 100644 util/gleasy/atlas.c create mode 100644 util/gleasy/atlas.h create mode 100644 util/gleasy/buffer.h create mode 100644 util/gleasy/framebuffer.c create mode 100644 util/gleasy/framebuffer.h create mode 100644 util/gleasy/misc.h create mode 100644 util/gleasy/program.c create mode 100644 util/gleasy/program.h create mode 100644 util/gleasy/shader.c create mode 100644 util/gleasy/shader.h create mode 100644 util/gleasy/texture.h create mode 100644 util/glyphas/CMakeLists.txt create mode 100644 util/glyphas/README.md create mode 100644 util/glyphas/aligner.c create mode 100644 util/glyphas/aligner.h create mode 100644 util/glyphas/block.c create mode 100644 util/glyphas/block.h create mode 100644 util/glyphas/cache.c create mode 100644 util/glyphas/cache.h create mode 100644 util/glyphas/context.c create mode 100644 util/glyphas/context.h create mode 100644 util/glyphas/drawer.c create mode 100644 util/glyphas/drawer.fshader create mode 100644 util/glyphas/drawer.h create mode 100644 util/glyphas/drawer.vshader create mode 100644 util/glyphas/face.c create mode 100644 util/glyphas/face.h create mode 100644 util/glyphas/glyph.c create mode 100644 util/glyphas/glyph.h create mode 100644 util/glyphas/test.c create mode 100644 util/jukebox/CMakeLists.txt create mode 100644 util/jukebox/amp.c create mode 100644 util/jukebox/amp.h create mode 100644 util/jukebox/beep.c create mode 100644 util/jukebox/beep.h create mode 100644 util/jukebox/composite.c create mode 100644 util/jukebox/composite.h create mode 100644 util/jukebox/decoder.c create mode 100644 util/jukebox/decoder.h create mode 100644 util/jukebox/delay.c create mode 100644 util/jukebox/delay.h create mode 100644 util/jukebox/effect.c create mode 100644 util/jukebox/effect.h create mode 100644 util/jukebox/format.c create mode 100644 util/jukebox/format.h create mode 100644 util/jukebox/lowpass.c create mode 100644 util/jukebox/lowpass.h create mode 100644 util/jukebox/mixer.c create mode 100644 util/jukebox/mixer.h create mode 100644 util/jukebox/sound.c create mode 100644 util/jukebox/sound.h create mode 100644 util/jukebox/test.c create mode 100644 util/math/CMakeLists.txt create mode 100644 util/math/algorithm.h create mode 100644 util/math/algorithm.sos.c create mode 100644 util/math/constant.h create mode 100644 util/math/matrix.h create mode 100644 util/math/matrix.sos.c create mode 100644 util/math/rational.c create mode 100644 util/math/rational.h create mode 100644 util/math/test.c create mode 100644 util/math/vector.c create mode 100644 util/math/vector.h create mode 100644 util/memory/CMakeLists.txt create mode 100644 util/memory/memory.c create mode 100644 util/memory/memory.h create mode 100644 util/memory/test.c create mode 100644 util/mpkutil/CMakeLists.txt create mode 100644 util/mpkutil/README.md create mode 100644 util/mpkutil/file.c create mode 100644 util/mpkutil/file.h create mode 100644 util/mpkutil/get.c create mode 100644 util/mpkutil/get.h create mode 100644 util/mpkutil/pack.c create mode 100644 util/mpkutil/pack.h create mode 100644 util/parsarg/CMakeLists.txt create mode 100644 util/parsarg/parsarg.c create mode 100644 util/parsarg/parsarg.h create mode 100644 util/parsarg/test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ec76ed --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..29a5cfe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/miniaudio"] + path = thirdparty/miniaudio/repo + url = https://github.com/dr-soft/miniaudio diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a0f467a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.16) + +project(leftone C) + +if (BUILD_TESTING) + enable_testing() +endif() + +set(LEFTONE_TOOL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tool") + +set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers") +set(CMAKE_C_FLAGS_RELEASE + "${CMAKE_C_FLAGS_RELEASE} -Wno-unused-parameter") + +if (WIN32) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mconsole") +endif() + +find_package(Freetype REQUIRED) +find_package(GLEW REQUIRED) +find_package(OpenGL REQUIRED) +find_package(SDL2 REQUIRED) +find_package(msgpack REQUIRED) + +include_directories(SYSTEM + ${FREETYPE_INCLUDE_DIRS} + ${GLEW_INCLUDE_DIRS} + ${OPENGL_INCLUDE_DIR} +) +include_directories(.) + +include(cmake/anysrc.cmake) +include(cmake/sos.cmake) + +add_subdirectory(app) +add_subdirectory(core) +add_subdirectory(thirdparty) +add_subdirectory(tool) +add_subdirectory(util) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..0340b1a --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(sdl) diff --git a/app/sdl/CMakeLists.txt b/app/sdl/CMakeLists.txt new file mode 100644 index 0000000..a3a7805 --- /dev/null +++ b/app/sdl/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable(app-sdl + args.c + event.c + main.c +) +target_link_libraries(app-sdl + GLEW::GLEW + SDL2::SDL2 + + conv + parsarg + + loscene +) diff --git a/app/sdl/args.c b/app/sdl/args.c new file mode 100644 index 0000000..86de299 --- /dev/null +++ b/app/sdl/args.c @@ -0,0 +1,104 @@ +#include "./args.h" + +#include +#include +#include +#include +#include +#include + +#include "util/parsarg/parsarg.h" + +#include "core/loscene/param.h" + +void app_args_parse(app_args_t* args, int argc, char** argv) { + assert(args != NULL); + assert(argc > 0); + + parsarg_t pa; + parsarg_initialize(&pa, argc-1, argv+1); + + while (!parsarg_finished(&pa)) { + size_t nlen; + char* n = parsarg_pop_name(&pa, &nlen); + + char* v; + parsarg_pop_value(&pa, &v); + + if (n == NULL && v == NULL) continue; + + bool ok = false; + +# define bool_(name, var) do { \ + if (strncmp(name, n, nlen) == 0 && name[nlen] == 0) { \ + if (v == NULL) { \ + var = true; \ + ok = true; \ + } else { \ + fprintf(stderr, "option '"name"' cannot take any values"); \ + abort(); \ + } \ + } \ + } while (0) + +# define int_(name, var, min, max) do { \ + if (strncmp(name, n, nlen) == 0 && name[nlen] == 0) { \ + char* end; \ + const intmax_t i = strtoimax(v, &end, 0); \ + if (*end == 0 && min <= i && i < max) { \ + var = i; \ + ok = true; \ + } else { \ + fprintf(stderr, \ + "option '"name"' requires " \ + "an integer value (%"PRIdMAX"~%"PRIdMAX")\n", \ + (intmax_t) min, (intmax_t) max); \ + abort(); \ + } \ + continue; \ + } \ + } while (0) + + /* ---- scene parameters ---- */ + int_("width", args->scene.width, 640, INT32_MAX); + int_("height", args->scene.height, 360, INT32_MAX); + int_("dpi-x", args->scene.dpi.x, 1, INT32_MAX); + int_("dpi-y", args->scene.dpi.y, 1, INT32_MAX); + + int_("max-msaa", args->scene.max_msaa, 1, INT32_MAX); + int_("brightness", args->scene.brightness, 0, 2000); + + bool_("disable-heavy-backwall", + args->scene.environment.disable_heavy_backwall); + bool_("disable-heavy-fog", + args->scene.environment.disable_heavy_fog); + + bool_("skip-title", args->scene.skip_title); + + bool_("test-poolset-packing", args->scene.test.loworld_poolset_packing); + bool_("test-player-packing", args->scene.test.loplayer_packing); + + /* ---- app parameters ---- */ + int_("max-fps", args->max_fps, 1, INT32_MAX); + + bool_("override-dpi", args->override_dpi); + + bool_("force-window", args->force_window); + bool_("force-desktop-fullscreen", args->force_desktop_fullscreen); + bool_("force-fullscreen", args->force_fullscreen); + +# undef int_ +# undef bool_ + + if (!ok) { + if (n == NULL) { + fprintf(stderr, "missing option name for the value '%s'\n", v); + } else { + fprintf(stderr, "unknown option '%.*s'\n", (int) nlen, n); + } + abort(); + } + } + + parsarg_deinitialize(&pa); +} diff --git a/app/sdl/args.h b/app/sdl/args.h new file mode 100644 index 0000000..9ae1c65 --- /dev/null +++ b/app/sdl/args.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "core/loscene/param.h" + +typedef struct { + loscene_param_t scene; + + int32_t max_fps; + + bool override_dpi; + + bool force_window; + bool force_desktop_fullscreen; + bool force_fullscreen; +} app_args_t; + +void +app_args_parse( + app_args_t* args, + int argc, + char** argv +); diff --git a/app/sdl/event.c b/app/sdl/event.c new file mode 100644 index 0000000..05be8bd --- /dev/null +++ b/app/sdl/event.c @@ -0,0 +1,52 @@ +#include "./event.h" + +#include +#include + +#include + +#include "core/locommon/input.h" + +#define APP_EVENT_GET_BUTTON_BIT_FROM_KEY(k) ( \ + (k) == SDLK_a? LOCOMMON_INPUT_BUTTON_LEFT: \ + (k) == SDLK_d? LOCOMMON_INPUT_BUTTON_RIGHT: \ + (k) == SDLK_w? LOCOMMON_INPUT_BUTTON_UP: \ + (k) == SDLK_s? LOCOMMON_INPUT_BUTTON_DOWN: \ + (k) == SDLK_SPACE? LOCOMMON_INPUT_BUTTON_JUMP: \ + (k) == SDLK_LSHIFT? LOCOMMON_INPUT_BUTTON_DASH: \ + (k) == SDLK_ESCAPE? LOCOMMON_INPUT_BUTTON_MENU: \ + 0) + +#define APP_EVENT_GET_BUTTON_BIT_FROM_MOUSE(m) ( \ + (m) == SDL_BUTTON_LEFT? LOCOMMON_INPUT_BUTTON_ATTACK: \ + (m) == SDL_BUTTON_RIGHT? LOCOMMON_INPUT_BUTTON_GUARD: \ + 0) + +bool app_event_handle(locommon_input_t* input, const SDL_Event* e) { + assert(input != NULL); + assert(e != NULL); + + switch (e->type) { + case SDL_MOUSEMOTION: + input->cursor = vec2( + e->motion.x/input->resolution.x, e->motion.y/input->resolution.y); + input->cursor.x = input->cursor.x*2 - 1; + input->cursor.y = 1 - input->cursor.y*2; + break; + case SDL_MOUSEBUTTONDOWN: + input->buttons |= APP_EVENT_GET_BUTTON_BIT_FROM_MOUSE(e->button.button); + break; + case SDL_MOUSEBUTTONUP: + input->buttons &= ~APP_EVENT_GET_BUTTON_BIT_FROM_MOUSE(e->button.button); + break; + case SDL_KEYDOWN: + input->buttons |= APP_EVENT_GET_BUTTON_BIT_FROM_KEY(e->key.keysym.sym); + break; + case SDL_KEYUP: + input->buttons &= ~APP_EVENT_GET_BUTTON_BIT_FROM_KEY(e->key.keysym.sym); + break; + case SDL_QUIT: + return false; + } + return true; +} diff --git a/app/sdl/event.h b/app/sdl/event.h new file mode 100644 index 0000000..9d4c881 --- /dev/null +++ b/app/sdl/event.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include + +#include "core/locommon/input.h" + +bool +app_event_handle( + locommon_input_t* input, + const SDL_Event* e +); diff --git a/app/sdl/main.c b/app/sdl/main.c new file mode 100644 index 0000000..f0f92ae --- /dev/null +++ b/app/sdl/main.c @@ -0,0 +1,172 @@ +#define SDL_MAIN_HANDLED +#define NO_STDIO_REDIRECT + +#include +#include +#include + +#include +#include + +#include "core/loscene/context.h" + +#include "./args.h" +#include "./event.h" + +static const app_args_t app_default_args_ = { + .scene = { + .width = 960, + .height = 540, + .dpi = vec2(96, 96), + + .max_msaa = 8, + .brightness = 1000, + }, + .max_fps = 60, +}; + +typedef struct { + SDL_Window* win; + SDL_GLContext gl; +} libs_t; + +static void app_initialize_libraries_(libs_t* libs, app_args_t* args) { + assert(libs != NULL); + assert(args != NULL); + + SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0"); + if (SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "failed to initialize SDL: %s\n", SDL_GetError()); + abort(); + } + + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute( + SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + uint32_t win_flags = + SDL_WINDOW_ALLOW_HIGHDPI | + SDL_WINDOW_OPENGL | + SDL_WINDOW_SHOWN; + if (args->force_window) { + } else if (args->force_desktop_fullscreen) { + win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } else if (args->force_fullscreen) { + win_flags |= SDL_WINDOW_FULLSCREEN; + } else { +# ifdef NDEBUG + win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; +# endif + } + + libs->win = SDL_CreateWindow( + "LEFTONE", /* title */ + SDL_WINDOWPOS_CENTERED, /* X position */ + SDL_WINDOWPOS_CENTERED, /* Y position */ + args->scene.width, + args->scene.height, + win_flags); + if (libs->win == NULL) { + fprintf(stderr, "failed to create window: %s\n", SDL_GetError()); + abort(); + } + + int w, h; + SDL_GetWindowSize(libs->win, &w, &h); + args->scene.width = w; + args->scene.height = h; + + libs->gl = SDL_GL_CreateContext(libs->win); + + glewExperimental = 1; + if (glewInit() != GLEW_OK) { + fprintf(stderr, "failed to init GLEW\n"); + abort(); + } + + glViewport(0, 0, args->scene.width, args->scene.height); +} +static void app_deinitialize_libraries_(libs_t* libs) { + assert(libs != NULL); + + SDL_GL_DeleteContext(libs->gl); + SDL_DestroyWindow(libs->win); + SDL_Quit(); +} + +static void app_get_dpi_(libs_t* libs, app_args_t* args) { + assert(libs != NULL); + assert(args != NULL); + + if (args->override_dpi) return; + + const int32_t disp = SDL_GetWindowDisplayIndex(libs->win); + + float x, y; + if (SDL_GetDisplayDPI(disp, NULL, &x, &y) == 0) { + args->scene.dpi = vec2(x, y); + } else { + fprintf(stderr, "failed to get display DPI: %s\n", SDL_GetError()); + fprintf(stderr, "Anti-aliasing may not work properly.\n"); + } +} + +int main(int argc, char** argv) { + (void) argc, (void) argv; + + app_args_t args = app_default_args_; + app_args_parse(&args, argc, argv); + + libs_t libs; + app_initialize_libraries_(&libs, &args); + + app_get_dpi_(&libs, &args); + + loscene_context_t* ctx = loscene_context_new(&args.scene); + + locommon_input_t input = { + .resolution = vec2(args.scene.width, args.scene.height), + .dpi = args.scene.dpi, + .cursor = vec2(0, 0), + }; + + glClearColor(0, 0, 0, 0); + + const uint64_t min_frame_time = 1000 / args.max_fps; + for (;;) { + const uint64_t base_time = SDL_GetTicks(); + + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (!app_event_handle(&input, &e)) goto EXIT; + } + + if (!loscene_context_update(ctx, &input, base_time)) goto EXIT; + + glClear(GL_COLOR_BUFFER_BIT); + loscene_context_draw(ctx); + SDL_GL_SwapWindow(libs.win); + +# ifndef NDEBUG + /* for debugging in MSYS */ + fflush(stdout); + fflush(stderr); +# endif + + const uint64_t elapsed = SDL_GetTicks() - base_time; + if (elapsed < min_frame_time) { + SDL_Delay(min_frame_time - elapsed); + } + } + +EXIT: + loscene_context_delete(ctx); + app_deinitialize_libraries_(&libs); + return 0; +} diff --git a/cmake/anysrc.cmake b/cmake/anysrc.cmake new file mode 100644 index 0000000..517d757 --- /dev/null +++ b/cmake/anysrc.cmake @@ -0,0 +1,20 @@ +function(target_any_sources target) + set(bin2c ${LEFTONE_TOOL_DIR}/bin2c.sh) + + target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + + foreach (path ${ARGN}) + get_filename_component(dirname ${path} DIRECTORY) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/anysrc/${dirname}) + + set(name ${target}_${path}_) + set(in ${CMAKE_CURRENT_SOURCE_DIR}/${path}) + set(out ${CMAKE_CURRENT_BINARY_DIR}/anysrc/${path}) + add_custom_command( + OUTPUT ${out}.c ${out}.h + COMMAND cat ${in} | ${bin2c} ${name} ${out} + DEPENDS ${path} ${bin2c} + COMMENT "converting ${path} to C header") + target_sources(${target} PRIVATE ${out}.c ${out}.h) + endforeach() +endfunction() diff --git a/cmake/sos.cmake b/cmake/sos.cmake new file mode 100644 index 0000000..08974e2 --- /dev/null +++ b/cmake/sos.cmake @@ -0,0 +1,20 @@ +function(target_source_of_source target) + target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sos) + foreach (file ${ARGN}) + get_filename_component(name ${file} NAME_WE) + + set(sos_target sos-${target}-${name}) + set(in ${CMAKE_CURRENT_SOURCE_DIR}/${file}) + set(out ${CMAKE_CURRENT_BINARY_DIR}/sos/${file}) + + add_executable(${sos_target} ${in}) + add_custom_command( + OUTPUT ${out} + COMMAND ${sos_target} > ${out} + DEPENDS ${sos_target} + COMMENT "generating ${file}") + target_sources(${target} PRIVATE ${out}) + endforeach() +endfunction() diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..0bba832 --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(.) + +add_subdirectory(lobullet) +add_subdirectory(locharacter) +add_subdirectory(locommon) +add_subdirectory(loeffect) +add_subdirectory(loentity) +add_subdirectory(loground) +add_subdirectory(loplayer) +add_subdirectory(loresource) +add_subdirectory(loscene) +add_subdirectory(loshader) +add_subdirectory(loworld) diff --git a/core/lobullet/CMakeLists.txt b/core/lobullet/CMakeLists.txt new file mode 100644 index 0000000..7c773d8 --- /dev/null +++ b/core/lobullet/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(lobullet + base.c + bomb.c + linear.c + misc.c + pool.c +) +target_link_libraries(lobullet + msgpackc + + math + memory + mpkutil + coly2d + + locommon + loeffect + loentity + loshader +) diff --git a/core/lobullet/base.c b/core/lobullet/base.c new file mode 100644 index 0000000..b01e28d --- /dev/null +++ b/core/lobullet/base.c @@ -0,0 +1,235 @@ +#include "./base.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loentity/bullet.h" +#include "core/loentity/character.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./bomb.h" +#include "./linear.h" +#include "./misc.h" + +static void lobullet_base_delete_(loentity_t* entity) { + assert(entity != NULL); + + lobullet_base_t* base = (typeof(base)) entity; + if (!base->used) return; + + base->used = false; + +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + lobullet_##name##_tear_down(base); \ + return; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static void lobullet_base_die_(loentity_t* entity) { + assert(entity != NULL); + +} + +static bool lobullet_base_update_(loentity_t* entity) { + assert(entity != NULL); + + lobullet_base_t* base = (typeof(base)) entity; + base->cache = (typeof(base->cache)) {0}; + +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + return lobullet_##name##_update(base); \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + return false; + +# undef each_ +} + +static void lobullet_base_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + lobullet_base_t* base = (typeof(base)) entity; + + vec2_t p; + locommon_position_sub(&p, &base->super.super.pos, basepos); + vec2_addeq(&base->cache.instance.pos, &p); + + loshader_bullet_drawer_add_instance(base->drawer, &base->cache.instance); +} + +static void lobullet_base_pack_( + const loentity_t* entity, msgpack_packer* packer) { + assert(entity != NULL); + assert(packer != NULL); + + const lobullet_base_t* base = (typeof(base)) entity; + + msgpack_pack_map(packer, 4); + + mpkutil_pack_str(packer, "subclass"); + mpkutil_pack_str(packer, "bullet"); + + mpkutil_pack_str(packer, "type"); + mpkutil_pack_str(packer, lobullet_type_stringify(base->type)); + + mpkutil_pack_str(packer, "id"); + msgpack_pack_uint64(packer, base->super.super.id); + + mpkutil_pack_str(packer, "data"); +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + lobullet_##name##_pack_data(base, packer); \ + return; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static bool lobullet_base_affect_( + loentity_bullet_t* bullet, loentity_character_t* chara) { + assert(bullet != NULL); + + lobullet_base_t* base = (typeof(base)) bullet; + + vec2_t v = vec2(0, 0); + switch (base->cache.knockback.algorithm) { + case LOBULLET_BASE_KNOCKBACK_ALGORITHM_VELOCITY: + v = base->super.velocity; + break; + case LOBULLET_BASE_KNOCKBACK_ALGORITHM_POSITION: + locommon_position_sub(&v, &chara->super.pos, &base->super.super.pos); + break; + } + const float plen = vec2_pow_length(&v); + if (plen != 0) { + vec2_diveq(&v, sqrtf(plen)); + vec2_muleq(&v, base->cache.knockback.acceleration); + loentity_character_knockback(chara, &v); + } + + if (base->cache.toxic) { + loentity_character_apply_effect(chara, &base->cache.effect); + } + return base->cache.toxic; +} + +void lobullet_base_initialize( + lobullet_base_t* base, + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities) { + assert(base != NULL); + assert(res != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(entities != NULL); + + *base = (typeof(*base)) { + .super = { + .super = { + .vtable = { + .delete = lobullet_base_delete_, + .die = lobullet_base_die_, + .update = lobullet_base_update_, + .draw = lobullet_base_draw_, + .pack = lobullet_base_pack_, + }, + .subclass = LOENTITY_SUBCLASS_BULLET, + }, + .vtable = { + .affect = lobullet_base_affect_, + }, + }, + .res = res, + .drawer = drawer, + .ticker = ticker, + .entities = entities, + }; +} + +void lobullet_base_reinitialize(lobullet_base_t* base, loentity_id_t id) { + assert(base != NULL); + + base->super.super.id = id; +} + +void lobullet_base_deinitialize(lobullet_base_t* base) { + assert(base != NULL); + + lobullet_base_delete_(&base->super.super); +} + +bool lobullet_base_unpack(lobullet_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + lobullet_base_reinitialize(base, 0); + + const char* v; + size_t vlen; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define streq_(v1, len, v2) \ + (strncmp(v1, v2, len) == 0 && v2[len] == 0) + if (!mpkutil_get_str(item_("subclass"), &v, &vlen) || + !streq_(v, vlen, "bullet")) { + return false; + } +# undef streq_ + + if (!mpkutil_get_str(item_("type"), &v, &vlen) || + !lobullet_type_unstringify(&base->type, v, vlen)) { + return false; + } + + if (!mpkutil_get_uint64(item_("id"), &base->super.super.id)) { + return false; + } + + const msgpack_object* data = item_("data"); +# define each_(NAME, name) do { \ + if (base->type == LOBULLET_TYPE_##NAME) { \ + if (!lobullet_##name##_unpack_data(base, data)) return false; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + +# undef each_ + +# undef item_ + return true; +} diff --git a/core/lobullet/base.h b/core/lobullet/base.h new file mode 100644 index 0000000..5656b47 --- /dev/null +++ b/core/lobullet/base.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/ticker.h" +#include "core/loentity/bullet.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./misc.h" + +typedef enum { + LOBULLET_BASE_KNOCKBACK_ALGORITHM_VELOCITY, + LOBULLET_BASE_KNOCKBACK_ALGORITHM_POSITION, +} lobullet_base_knockback_algorithm_t; + +typedef struct { + loentity_bullet_t super; + bool used; + + /* injected deps */ + loresource_set_t* res; + loshader_bullet_drawer_t* drawer; + const locommon_ticker_t* ticker; + loentity_store_t* entities; + + /* params not to be packed */ + struct { + bool toxic; + loeffect_t effect; + /* When toxic is true, apply this effect to characters hit. */ + + struct { + float acceleration; + lobullet_base_knockback_algorithm_t algorithm; + } knockback; + + loshader_bullet_drawer_instance_t instance; + /* instance pos is added to draw pos */ + } cache; + + /* params to be packed (includes id) */ + lobullet_type_t type; + +# define LOBULLET_BASE_DATA_MAX_SIZE 256 + uint8_t data[LOBULLET_BASE_DATA_MAX_SIZE]; + /* pack function for the type is used */ +} lobullet_base_t; + +void +lobullet_base_initialize( + lobullet_base_t* base, + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities +); + +void +lobullet_base_reinitialize( + lobullet_base_t* base, + loentity_id_t id +); + +void +lobullet_base_deinitialize( + lobullet_base_t* base +); + +bool +lobullet_base_unpack( + lobullet_base_t* base, + const msgpack_object* obj +); diff --git a/core/lobullet/bomb.c b/core/lobullet/bomb.c new file mode 100644 index 0000000..5451f44 --- /dev/null +++ b/core/lobullet/bomb.c @@ -0,0 +1,205 @@ +#include "./bomb.h" + +#include +#include +#include +#include +#include + +#include "util/coly2d/shape.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/math/vector.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loentity/entity.h" +#include "core/loresource/sound.h" +#include "core/loshader/bullet.h" + +#include "./base.h" +#include "./misc.h" + +#define LOBULLET_BOMB_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("owner", owner); \ + PROC("pos", pos); \ + PROC("size", size); \ + PROC("angle", angle); \ + PROC("color", color); \ + PROC("silent", silent); \ + PROC("beat", beat); \ + PROC("step", step); \ + PROC("knockback", knockback); \ + PROC("effect", effect); \ + PROC("since", since); \ +} while (0) +#define LOBULLET_BOMB_PARAM_TO_PACK_COUNT 11 + +_Static_assert(sizeof(lobullet_bomb_param_t) <= LOBULLET_BASE_DATA_MAX_SIZE); + +static bool lobullet_bomb_update_(lobullet_base_t* base) { + assert(base != NULL); + + const lobullet_bomb_param_t* p = (typeof(p)) base->data; + + base->super.super.pos = p->pos; + base->super.owner = p->owner; + base->super.velocity = vec2(0, 0); + base->super.shape.size = p->size; + base->super.shape.angle = p->angle; + + const uint64_t st = (p->step-1) * p->beat; + const uint64_t ed = st + 100; + + const uint64_t t = base->ticker->time - p->since; + const uint64_t pt = + (int64_t) t >= base->ticker->delta? t - base->ticker->delta: 0; + + if (!p->silent && pt < st && t >= st) { + loresource_sound_play(base->res->sound, "bomb"); + } + + base->cache.toxic = st <= t && t < ed; + base->cache.effect = p->effect; + return t < p->step*p->beat; +} + +bool lobullet_bomb_param_valid(const lobullet_bomb_param_t* param) { + return + param != NULL && + locommon_position_valid(¶m->pos) && + vec2_valid(¶m->size) && + MATH_FLOAT_VALID(param->angle) && + vec4_valid(¶m->color) && + MATH_FLOAT_VALID(param->beat) && + param->step > 0 && + MATH_FLOAT_VALID(param->knockback); +} +void lobullet_bomb_param_pack( + const lobullet_bomb_param_t* p, msgpack_packer* packer) { + assert(lobullet_bomb_param_valid(p)); + assert(packer != NULL); + + msgpack_pack_map(packer, LOBULLET_BOMB_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOBULLET_BOMB_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} +bool lobullet_bomb_param_unpack( + lobullet_bomb_param_t* p, const msgpack_object* obj) { + assert(p != NULL); + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOBULLET_BOMB_PARAM_TO_PACK_EACH_(unpack_); + return lobullet_bomb_param_valid(p); + +# undef unpack_ + +# undef item_ +} + +void lobullet_bomb_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_bomb_param_t* param) { + assert(base != NULL); + assert(lobullet_bomb_param_valid(param)); + + base->type = type; + + lobullet_bomb_param_t* p = (typeof(p)) base->data; + *p = *param; + p->since = base->ticker->time; +} + +bool lobullet_bomb_square_update(lobullet_base_t* base) { + assert(base != NULL); + + if (!lobullet_bomb_update_(base)) return false; + + const lobullet_bomb_param_t* p = (typeof(p)) base->data; + + /* ---- calculate motion ---- */ + const float beats = (base->ticker->time - p->since) / p->beat; + + float time = 0; + float angle = p->angle; + float alpha = 1; + if (beats < p->step-1) { + time = beats - (int64_t) beats; + alpha = 1-time; + time = time*time; + time = (1-time)*.05f; + } else { + time = 1 - powf(1-(beats - (int64_t) beats), 2); + angle += time * MATH_PI/4; + time = 1-time; + } + + /* ---- apply motion ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_RECT; + base->super.shape.angle = angle; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_SQUARE, + .size = p->size, + .theta = angle, + .color = p->color, + .time = time, + }; + base->cache.instance.color.w *= alpha; + return true; +} + +bool lobullet_bomb_triangle_update(lobullet_base_t* base) { + assert(base != NULL); + + if (!lobullet_bomb_update_(base)) return false; + + const lobullet_bomb_param_t* p = (typeof(p)) base->data; + + /* ---- calculate motion ---- */ + const float beats = (base->ticker->time - p->since) / p->beat; + + float time = 0; + float alpha = 1; + if (beats < p->step-1) { + time = beats - (int64_t) beats; + alpha = 1-time; + time = time*time; + time = (1-time)*.05f; + } else { + time = 1 - powf(1-(beats - (int64_t) beats), 2); + time = 1-time; + } + + /* ---- apply motion ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_TRIANGLE; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_TRIANGLE, + .size = p->size, + .theta = base->super.shape.angle, + .color = p->color, + .time = time, + }; + base->cache.instance.color.w *= alpha; + return true; +} diff --git a/core/lobullet/bomb.h b/core/lobullet/bomb.h new file mode 100644 index 0000000..ad2b979 --- /dev/null +++ b/core/lobullet/bomb.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loeffect/effect.h" +#include "core/loentity/entity.h" + +#include "./base.h" +#include "./misc.h" + +typedef struct { + loentity_id_t owner; + locommon_position_t pos; + vec2_t size; + float angle; + vec4_t color; + bool silent; + + float beat; + int32_t step; + float knockback; + + loeffect_t effect; + + uint64_t since; /* set by build function */ +} lobullet_bomb_param_t; + +bool +lobullet_bomb_param_valid( + const lobullet_bomb_param_t* param /* NULLABLE */ +); +void +lobullet_bomb_param_pack( + const lobullet_bomb_param_t* param, + msgpack_packer* packer +); +bool +lobullet_bomb_param_unpack( + lobullet_bomb_param_t* param, + const msgpack_object* obj /* NULLABLE */ +); + +void +lobullet_bomb_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_bomb_param_t* param +); + +bool +lobullet_bomb_square_update( + lobullet_base_t* base +); +#define lobullet_bomb_square_build(base, param) \ + lobullet_bomb_build(base, LOBULLET_TYPE_BOMB_SQUARE, param) +#define lobullet_bomb_square_tear_down(base) +#define lobullet_bomb_square_pack_data(base, packer) \ + lobullet_bomb_param_pack( \ + (const lobullet_bomb_param_t*) base->data, packer) +#define lobullet_bomb_square_unpack_data(base, obj) \ + lobullet_bomb_param_unpack( \ + (lobullet_bomb_param_t*) base->data, obj) + +bool +lobullet_bomb_triangle_update( + lobullet_base_t* base +); +#define lobullet_bomb_triangle_build(base, param) \ + lobullet_bomb_build(base, LOBULLET_TYPE_BOMB_TRIANGLE, param) +#define lobullet_bomb_triangle_tear_down(base) +#define lobullet_bomb_triangle_pack_data(base, packer) \ + lobullet_bomb_param_pack( \ + (const lobullet_bomb_param_t*) base->data, packer) +#define lobullet_bomb_triangle_unpack_data(base, obj) \ + lobullet_bomb_param_unpack( \ + (lobullet_bomb_param_t*) base->data, obj) diff --git a/core/lobullet/linear.c b/core/lobullet/linear.c new file mode 100644 index 0000000..67a4b63 --- /dev/null +++ b/core/lobullet/linear.c @@ -0,0 +1,197 @@ +#include "./linear.h" + +#include +#include +#include +#include + +#include + +#include "util/coly2d/shape.h" +#include "util/math/constant.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/loeffect/effect.h" +#include "core/loentity/entity.h" + +#include "./base.h" +#include "./misc.h" + +#define LOBULLET_LINEAR_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("owner", owner); \ + PROC("pos", pos); \ + PROC("size", size); \ + PROC("velocity", velocity); \ + PROC("acceleration", acceleration); \ + PROC("color", color); \ + PROC("duration", duration); \ + PROC("knockback", knockback); \ + PROC("effect", effect); \ + PROC("since", since); \ +} while (0) +#define LOBULLET_LINEAR_PARAM_TO_PACK_COUNT 10 + +_Static_assert(sizeof(lobullet_linear_param_t) <= LOBULLET_BASE_DATA_MAX_SIZE); + +static bool lobullet_linear_update_(lobullet_base_t* base) { + assert(base != NULL); + + const lobullet_linear_param_t* p = (typeof(p)) base->data; + + const float t = (base->ticker->time - p->since)/1000.f; + + base->super.owner = p->owner; + + /* ---- movement ---- */ + vec2_t v1; + vec2_mul(&v1, &p->velocity, t); + + vec2_t v2; + vec2_mul(&v2, &p->acceleration, t*t/2); + + base->super.super.pos = p->pos; + vec2_addeq(&base->super.super.pos.fract, &v1); + vec2_addeq(&base->super.super.pos.fract, &v2); + locommon_position_reduce(&base->super.super.pos); + + /* ---- velocity ---- */ + vec2_mul(&base->super.velocity, &p->acceleration, t); + vec2_addeq(&base->super.velocity, &p->velocity); + + /* ---- angle ---- */ + const float theta = vec2_pow_length(&base->super.velocity) != 0? + atan2f(base->super.velocity.y, base->super.velocity.x): 0; + base->super.shape.size = p->size; + base->super.shape.angle = theta; + + /* ---- parameter update ---- */ + base->cache.toxic = true; + base->cache.effect = p->effect; + + base->cache.knockback = (typeof(base->cache.knockback)) { + .acceleration = p->knockback, + .algorithm = LOBULLET_BASE_KNOCKBACK_ALGORITHM_VELOCITY, + }; + + return p->since + p->duration > base->ticker->time; +} + +bool lobullet_linear_param_valid(const lobullet_linear_param_t* param) { + return + param != NULL && + locommon_position_valid(¶m->pos) && + vec2_valid(¶m->size) && + vec2_valid(¶m->velocity) && + vec2_valid(¶m->acceleration) && + param->duration > 0; +} +void lobullet_linear_param_pack( + const lobullet_linear_param_t* p, msgpack_packer* packer) { + assert(lobullet_linear_param_valid(p)); + assert(packer != NULL); + + msgpack_pack_map(packer, LOBULLET_LINEAR_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOBULLET_LINEAR_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} +bool lobullet_linear_param_unpack( + lobullet_linear_param_t* p, const msgpack_object* obj) { + assert(p != NULL); + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOBULLET_LINEAR_PARAM_TO_PACK_EACH_(unpack_); + return lobullet_linear_param_valid(p); + +# undef unpack_ + +# undef item_ +} + +void lobullet_linear_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_linear_param_t* param) { + assert(base != NULL); + assert(lobullet_linear_param_valid(param)); + + base->type = type; + + lobullet_linear_param_t* p = (typeof(p)) base->data; + *p = *param; + p->since = base->ticker->time; +} + +bool lobullet_linear_light_update(lobullet_base_t* base) { + assert(base != NULL); + + static const uint64_t fadedur = 500; + + if (!lobullet_linear_update_(base)) return false; + + const lobullet_linear_param_t* p = (typeof(p)) base->data; + + /* ---- calculation ---- */ + vec2_t size = p->size; + vec2_muleq(&size, 1.2f); + + float alpha = 1; + const uint64_t remain = p->duration - (base->ticker->time - p->since); + if (remain <= fadedur) alpha = remain*1.f / fadedur; + + /* ---- apply result ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_ELLIPSE; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_LIGHT, + .size = size, + .theta = base->super.shape.angle, + .color = p->color, + }; + base->cache.instance.color.w = alpha; + return true; +} + +bool lobullet_linear_triangle_update(lobullet_base_t* base) { + assert(base != NULL); + + if (!lobullet_linear_update_(base)) return false; + + const lobullet_linear_param_t* p = (typeof(p)) base->data; + + /* ---- calculation ---- */ + vec2_t size = p->size; + size.x *= 1-(1-cos(MATH_PI/3))/2; + size.y *= 1-(1-sin(MATH_PI/3))/2; + + /* ---- apply result ---- */ + base->super.shape.type = COLY2D_SHAPE_TYPE_TRIANGLE; + + base->cache.instance = (loshader_bullet_drawer_instance_t) { + .bullet_id = LOSHADER_BULLET_ID_TRIANGLE, + .size = size, + .theta = base->super.shape.angle, + .color = p->color, + .time = 1, + }; + return true; +} diff --git a/core/lobullet/linear.h b/core/lobullet/linear.h new file mode 100644 index 0000000..90dc51d --- /dev/null +++ b/core/lobullet/linear.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loeffect/effect.h" +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t owner; + + locommon_position_t pos; + vec2_t size; + vec2_t velocity; + vec2_t acceleration; + vec4_t color; + + uint64_t duration; + float knockback; + + loeffect_t effect; + + uint64_t since; /* set by build function */ +} lobullet_linear_param_t; + +bool +lobullet_linear_param_valid( + const lobullet_linear_param_t* param +); +void +lobullet_linear_param_pack( + const lobullet_linear_param_t* param, + msgpack_packer* packer +); +bool +lobullet_linear_param_unpack( + lobullet_linear_param_t* param, + const msgpack_object* obj /* NULLABLE */ +); + +void +lobullet_linear_build( + lobullet_base_t* base, + lobullet_type_t type, + const lobullet_linear_param_t* param +); + +bool +lobullet_linear_light_update( + lobullet_base_t* base +); +#define lobullet_linear_light_build(base, param) \ + lobullet_linear_build(base, LOBULLET_TYPE_LINEAR_LIGHT, param) +#define lobullet_linear_light_tear_down(base) +#define lobullet_linear_light_pack_data(base, packer) \ + lobullet_linear_param_pack( \ + (const lobullet_linear_param_t*) base->data, packer) +#define lobullet_linear_light_unpack_data(base, obj) \ + lobullet_linear_param_unpack( \ + (lobullet_linear_param_t*) base->data, obj) + +bool +lobullet_linear_triangle_update( + lobullet_base_t* base +); +#define lobullet_linear_triangle_build(base, param) \ + lobullet_linear_build(base, LOBULLET_TYPE_LINEAR_TRIANGLE, param) +#define lobullet_linear_triangle_tear_down(base) +#define lobullet_linear_triangle_pack_data(base, packer) \ + lobullet_linear_param_pack( \ + (const lobullet_linear_param_t*) base->data, packer) +#define lobullet_linear_triangle_unpack_data(base, obj) \ + lobullet_linear_param_unpack( \ + (lobullet_linear_param_t*) base->data, obj) diff --git a/core/lobullet/misc.c b/core/lobullet/misc.c new file mode 100644 index 0000000..766f0a9 --- /dev/null +++ b/core/lobullet/misc.c @@ -0,0 +1,37 @@ +#include "./misc.h" + +#include +#include +#include +#include + +const char* lobullet_type_stringify(lobullet_type_t type) { +# define each_(NAME, name) do { \ + if (type == LOBULLET_TYPE_##NAME) return #name; \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool lobullet_type_unstringify( + lobullet_type_t* type, const char* v, size_t len) { + assert(type != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *type = LOBULLET_TYPE_##NAME; \ + return true; \ + } \ + } while (0) + + LOBULLET_TYPE_EACH_(each_); + return false; + +# undef each_ +} diff --git a/core/lobullet/misc.h b/core/lobullet/misc.h new file mode 100644 index 0000000..7d56b6b --- /dev/null +++ b/core/lobullet/misc.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +/* dont forget to update EACH macro */ +typedef enum { + LOBULLET_TYPE_LINEAR_LIGHT, + LOBULLET_TYPE_LINEAR_TRIANGLE, + LOBULLET_TYPE_BOMB_SQUARE, + LOBULLET_TYPE_BOMB_TRIANGLE, +} lobullet_type_t; + +#define LOBULLET_TYPE_EACH_(PROC) do { \ + PROC(LINEAR_LIGHT, linear_light); \ + PROC(LINEAR_TRIANGLE, linear_triangle); \ + PROC(BOMB_SQUARE, bomb_square); \ + PROC(BOMB_TRIANGLE, bomb_triangle); \ +} while (0) + +const char* +lobullet_type_stringify( + lobullet_type_t type +); + +bool +lobullet_type_unstringify( + lobullet_type_t* type, + const char* v, + size_t len +); diff --git a/core/lobullet/pool.c b/core/lobullet/pool.c new file mode 100644 index 0000000..a30eb23 --- /dev/null +++ b/core/lobullet/pool.c @@ -0,0 +1,104 @@ +#include "./pool.h" + +#include + +#include + +#include "util/memory/memory.h" + +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./base.h" + +struct lobullet_pool_t { + loresource_set_t* res; + loshader_bullet_drawer_t* drawer; + locommon_counter_t* idgen; + const locommon_ticker_t* ticker; + loentity_store_t* entities; + + size_t length; + lobullet_base_t items[1]; +}; + +static size_t lobullet_pool_find_unused_item_index_( + const lobullet_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + if (!pool->items[i].used) return i; + } + fprintf(stderr, "bullet pool overflow\n"); + abort(); +} + +lobullet_pool_t* lobullet_pool_new( + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + size_t length) { + assert(res != NULL); + assert(drawer != NULL); + assert(idgen != NULL); + assert(ticker != NULL); + assert(entities != NULL); + assert(length > 0); + + lobullet_pool_t* pool = + memory_new(sizeof(*pool) + (length-1)*sizeof(pool->items[0])); + *pool = (typeof(*pool)) { + .res = res, + .drawer = drawer, + .idgen = idgen, + .ticker = ticker, + .entities = entities, + .length = length, + }; + for (size_t i = 0; i < pool->length; ++i) { + lobullet_base_initialize( + &pool->items[i], + res, + drawer, + ticker, + entities); + } + return pool; +} + +void lobullet_pool_delete(lobullet_pool_t* pool) { + if (pool == NULL) return; + + for (size_t i = 0; i < pool->length; ++i) { + lobullet_base_deinitialize(&pool->items[i]); + } + memory_delete(pool); +} + +lobullet_base_t* lobullet_pool_create(lobullet_pool_t* pool) { + assert(pool != NULL); + + const size_t i = lobullet_pool_find_unused_item_index_(pool); + + pool->items[i].used = true; + lobullet_base_reinitialize( + &pool->items[i], locommon_counter_count(pool->idgen)); + return &pool->items[i]; +} + +lobullet_base_t* lobullet_pool_unpack_item( + lobullet_pool_t* pool, const msgpack_object* obj) { + assert(pool != NULL); + + const size_t i = lobullet_pool_find_unused_item_index_(pool); + + if (!lobullet_base_unpack(&pool->items[i], obj)) return NULL; + + pool->items[i].used = true; + return &pool->items[i]; +} diff --git a/core/lobullet/pool.h b/core/lobullet/pool.h new file mode 100644 index 0000000..66d9557 --- /dev/null +++ b/core/lobullet/pool.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include + +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/bullet.h" + +#include "./base.h" + +struct lobullet_pool_t; +typedef struct lobullet_pool_t lobullet_pool_t; + +lobullet_pool_t* /* OWNERSHIP */ +lobullet_pool_new( + loresource_set_t* res, + loshader_bullet_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + size_t length +); + +void +lobullet_pool_delete( + lobullet_pool_t* pool /* OWNERSHIP */ +); + +lobullet_base_t* +lobullet_pool_create( + lobullet_pool_t* pool +); + +lobullet_base_t* +lobullet_pool_unpack_item( + lobullet_pool_t* pool, + const msgpack_object* obj +); diff --git a/core/locharacter/CMakeLists.txt b/core/locharacter/CMakeLists.txt new file mode 100644 index 0000000..67c4fad --- /dev/null +++ b/core/locharacter/CMakeLists.txt @@ -0,0 +1,27 @@ +add_library(locharacter + base.c + big_warder.c + cavia.c + encephalon.c + greedy_scientist.c + misc.c + pool.c + scientist.c + theists_child.c + util.c + warder.c +) +target_link_libraries(locharacter + msgpackc + + math + memory + mpkutil + + lobullet + locommon + loeffect + loentity + loplayer + loshader +) diff --git a/core/locharacter/base.c b/core/locharacter/base.c new file mode 100644 index 0000000..bf84cf2 --- /dev/null +++ b/core/locharacter/base.c @@ -0,0 +1,434 @@ +#include "./base.h" + +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./big_warder.h" +#include "./cavia.h" +#include "./encephalon.h" +#include "./greedy_scientist.h" +#include "./scientist.h" +#include "./theists_child.h" +#include "./misc.h" +#include "./warder.h" + +#define LOCHARACTER_BASE_PARAM_TO_PACK_EACH_( \ + PROC, PROC_type, PROC_state, PROC_str) do { \ + PROC_str ("subclass", "character"); \ + PROC_type ("type", type); \ + PROC ("id", super.super.id); \ + PROC ("ground", ground); \ + PROC ("pos", pos); \ + PROC ("direction", direction); \ + PROC ("knockback", knockback); \ + PROC ("gravity", gravity); \ + PROC ("madness", recipient.madness); \ + PROC ("effects", recipient.effects); \ + PROC_state("state", state); \ + PROC ("since", since); \ + PROC ("last-update-time", last_update_time); \ + PROC ("last-knockback-time", last_knockback_time); \ + PROC ("last-hit-time", last_hit_time); \ +} while (0) +#define LOCHARACTER_BASE_PARAM_TO_PACK_COUNT 15 + +static void locharacter_base_convert_to_world_pos_( + const loentity_ground_t* g, locommon_position_t* wpos, const vec2_t* pos) { + assert(g != NULL); + assert(wpos != NULL); + assert(vec2_valid(pos)); + + vec2_t p = *pos; + p.x *= g->size.x; + p.y += g->size.y; + + *wpos = g->super.pos; + vec2_addeq(&wpos->fract, &p); + locommon_position_reduce(wpos); +} + +static void locharacter_base_convert_from_world_pos_( + const loentity_ground_t* g, vec2_t* pos, const locommon_position_t* wpos) { + assert(g != NULL); + assert(pos != NULL); + assert(locommon_position_valid(wpos)); + + locommon_position_sub(pos, wpos, &g->super.pos); + pos->x /= g->size.x; + pos->y -= g->size.y; +} + +static loentity_ground_t* locharacter_base_get_ground_(locharacter_base_t* base) { + assert(base != NULL); + + loentity_store_iterator_t itr; + if (loentity_store_find_item_by_id(base->entities, &itr, base->ground)) { + return itr.ground; + } + return NULL; +} + +static void locharacter_base_handle_knockback_(locharacter_base_t* base) { + assert(base != NULL); + + vec2_t v = base->knockback; + v.x /= base->cache.ground->size.x; + vec2_muleq(&v, base->ticker->delta_f); + vec2_addeq(&base->pos, &v); + + locommon_easing_linear_float(&base->knockback.x, 0, base->ticker->delta_f/2); + locommon_easing_linear_float(&base->knockback.y, 0, base->ticker->delta_f/2); +} + +static void locharacter_base_calculate_world_position_( + locharacter_base_t* base) { + assert(base != NULL); + + base->pos.x = MATH_CLAMP(base->pos.x, -1, 1); + base->pos.y = MATH_CLAMP(base->pos.y, 0, 1); + + if (base->pos.y < base->cache.height) { + if (base->cache.gravity) base->gravity = 0; + base->pos.y = base->cache.height; + } + locharacter_base_convert_to_world_pos_( + base->cache.ground, &base->super.super.pos, &base->pos); +} + +static void locharacter_base_calculate_velocity_( + locharacter_base_t* base, vec2_t* v, const locommon_position_t* oldpos) { + assert(base != NULL); + assert(v != NULL); + assert(locommon_position_valid(oldpos)); + + locommon_position_sub(v, &base->super.super.pos, oldpos); + vec2_diveq(v, base->ticker->delta_f); +} + +static void locharacter_base_execute_bullet_hittest_( + locharacter_base_t* base, const vec2_t* velocity) { + assert(base != NULL); + assert(vec2_valid(velocity)); + + if (base->last_hit_time + 200 > base->ticker->time) return; + + if (loentity_store_affect_bullets_shot_by_one( + base->entities, + &base->super, + base->player->entity.super.super.id, + velocity, + base->ticker->delta_f)) { + base->last_hit_time = base->ticker->time; + } +} + +static void locharacter_base_delete_(loentity_t* entity) { + assert(entity != NULL); + + locharacter_base_t* base = (typeof(base)) entity; + if (!base->used) return; + + base->used = false; + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + locharacter_##name##_tear_down(base); \ + return; \ + } \ + } while (0) + + LOCHARACTER_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static void locharacter_base_die_(loentity_t* entity) { + assert(entity != NULL); + +} + +static bool locharacter_base_update_(loentity_t* entity) { + assert(entity != NULL); + + static const float gravity_acceleration = 2.f; + + locharacter_base_t* base = (typeof(base)) entity; + + base->cache = (typeof(base->cache)) { + .time = base->ticker->time, + }; + + base->cache.ground = locharacter_base_get_ground_(base); + if (base->cache.ground == NULL) return false; + + locharacter_base_convert_from_world_pos_( + base->cache.ground, + &base->cache.player_pos, + &base->player->entity.super.super.pos); + + locharacter_base_handle_knockback_(base); + + locommon_position_t oldpos = base->super.super.pos; + + base->pos.y += base->gravity * base->ticker->delta_f; + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + if (!locharacter_##name##_update(base)) return false; \ + } \ + } while (0) + + LOCHARACTER_TYPE_EACH_(each_); + +# undef each_ + + locharacter_base_calculate_world_position_(base); + + if (base->cache.gravity) { + base->gravity -= base->ticker->delta_f * gravity_acceleration; + } else { + base->gravity = 0; + } + if (base->cache.bullet_hittest) { + vec2_t velocity; + locharacter_base_calculate_velocity_(base, &velocity, &oldpos); + locharacter_base_execute_bullet_hittest_(base, &velocity); + } + + base->cache.ground = NULL; + base->last_update_time = base->cache.time; + return true; +} + +static void locharacter_base_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + locharacter_base_t* base = (typeof(base)) entity; + + vec2_t v; + locommon_position_sub(&v, &base->super.super.pos, basepos); + vec2_addeq(&base->cache.instance.pos, &v); + + loshader_character_drawer_add_instance(base->drawer, &base->cache.instance); +} + +static void locharacter_base_apply_effect_( + loentity_character_t* entity, const loeffect_t* effect) { + assert(entity != NULL); + assert(effect != NULL); + + locharacter_base_t* base = (typeof(base)) entity; + loeffect_recipient_apply_effect(&base->recipient, effect); +} + +static void locharacter_base_knockback_( + loentity_character_t* chara, const vec2_t* knockback) { + assert(chara != NULL); + assert(vec2_valid(knockback)); + + locharacter_base_t* base = (typeof(base)) chara; + + static const float r = .05f; + if (vec2_pow_length(knockback) > r*r) { + base->last_knockback_time = base->ticker->time; + } + vec2_addeq(&base->knockback, knockback); +} + +static void locharacter_base_pack_( + const loentity_t* chara, msgpack_packer* packer) { + assert(chara != NULL); + assert(packer != NULL); + + const locharacter_base_t* base = (typeof(base)) chara; + + msgpack_pack_map(packer, LOCHARACTER_BASE_PARAM_TO_PACK_COUNT+1); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &base->var); \ + } while (0) +# define pack_type_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, locharacter_type_stringify(base->var)); \ + } while (0) +# define pack_state_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, locharacter_state_stringify(base->var)); \ + } while (0) +# define pack_str_(name, str) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, str); \ + } while (0) + + LOCHARACTER_BASE_PARAM_TO_PACK_EACH_(pack_, pack_type_, pack_state_, pack_str_); + +# undef pack_str_ +# undef pack_state_ +# undef pack_type_ +# undef pack_ + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + locharacter_##name##_pack_data(base, packer); \ + return; \ + } \ + } while (0) + + mpkutil_pack_str(packer, "data"); + LOCHARACTER_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +void locharacter_base_initialize( + locharacter_base_t* base, + loresource_set_t* res, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player) { + assert(base != NULL); + assert(res != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(player != NULL); + + *base = (typeof(*base)) { + .super = { + .super = { + .vtable = { + .delete = locharacter_base_delete_, + .die = locharacter_base_die_, + .update = locharacter_base_update_, + .draw = locharacter_base_draw_, + .pack = locharacter_base_pack_, + }, + .subclass = LOENTITY_SUBCLASS_CHARACTER, + }, + .vtable = { + .apply_effect = locharacter_base_apply_effect_, + .knockback = locharacter_base_knockback_, + }, + }, + .res = res, + .drawer = drawer, + .ticker = ticker, + .bullets = bullets, + .entities = entities, + .player = player, + }; + loeffect_recipient_initialize(&base->recipient, ticker); +} + +void locharacter_base_reinitialize(locharacter_base_t* base, loentity_id_t id) { + assert(base != NULL); + +# define reset_(name, var) do { \ + base->var = (typeof(base->var)) {0}; \ + } while (0) +# define reset_str_(name, str) + + LOCHARACTER_BASE_PARAM_TO_PACK_EACH_( + reset_, reset_, reset_, reset_str_); + +# undef reset_str_ +# undef reset_ + + loeffect_recipient_reset(&base->recipient); + base->super.super.id = id; +} + +void locharacter_base_deinitialize(locharacter_base_t* base) { + assert(base != NULL); + + if (base->used) locharacter_base_delete_(&base->super.super); + loeffect_recipient_deinitialize(&base->recipient); +} + +bool locharacter_base_unpack( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + assert(obj != NULL); + + locharacter_base_reinitialize(base, 0); + /* id will be overwritten below */ + + const char* v; + size_t vlen; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &base->var)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_type_(name, var) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !locharacter_type_unstringify(&base->var, v, vlen)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_state_(name, var) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !locharacter_state_unstringify(&base->var, v, vlen)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_str_(name, str) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !(strncmp(v, str, vlen) == 0 && str[vlen] == 0)) { \ + return NULL; \ + } \ + } while (0) + + LOCHARACTER_BASE_PARAM_TO_PACK_EACH_( + unpack_, unpack_type_, unpack_state_, unpack_str_); + +# undef unpack_str_ +# undef unpack_state_ +# undef unpack_type_ +# undef unpack_ + + const msgpack_object* data = item_("data"); + +# define each_(NAME, name) do { \ + if (base->type == LOCHARACTER_TYPE_##NAME) { \ + return locharacter_##name##_unpack_data(base, data); \ + } \ + } while (0) + + LOCHARACTER_TYPE_EACH_(each_); + return false; + +# undef each_ + +# undef item_ +} diff --git a/core/locharacter/base.h b/core/locharacter/base.h new file mode 100644 index 0000000..7e0e0c5 --- /dev/null +++ b/core/locharacter/base.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include + +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./misc.h" + +typedef struct { + loentity_character_t super; + bool used; + + /* injected deps */ + loresource_set_t* res; + loshader_character_drawer_t* drawer; + const locommon_ticker_t* ticker; + lobullet_pool_t* bullets; + loentity_store_t* entities; + loplayer_t* player; + + /* temporary params for update */ + struct { + /* set before calling update function */ + loentity_ground_t* ground; + vec2_t player_pos; + + uint64_t time; + /* Defaultly equals to ticker->time. + But characters who have an event with music + overwrites this value for synchronization */ + + /* set by update function */ + float height; + bool bullet_hittest; + bool gravity; + + loshader_character_drawer_instance_t instance; + } cache; + + /* params to be packed (includes id) */ + locharacter_type_t type; + + loentity_id_t ground; + vec2_t pos; + float direction; + vec2_t knockback; + float gravity; + + loeffect_recipient_t recipient; + + locharacter_state_t state; + uint64_t since; + + uint64_t last_update_time; + uint64_t last_knockback_time; + uint64_t last_hit_time; + +# define LOCHARACTER_BASE_DATA_MAX_SIZE 256 + uint8_t data[LOCHARACTER_BASE_DATA_MAX_SIZE]; +} locharacter_base_t; + +void +locharacter_base_initialize( + locharacter_base_t* base, + loresource_set_t* res, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player +); + +void +locharacter_base_reinitialize( + locharacter_base_t* base, + loentity_id_t id +); + +void +locharacter_base_deinitialize( + locharacter_base_t* base +); + +bool +locharacter_base_unpack( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/big_warder.c b/core/locharacter/big_warder.c new file mode 100644 index 0000000..3b86a0d --- /dev/null +++ b/core/locharacter/big_warder.c @@ -0,0 +1,787 @@ +#include "./big_warder.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/loentity/entity.h" +#include "core/loplayer/combat.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/text.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" +#include "./util.h" + +typedef struct { + locharacter_event_holder_t event; + + int32_t phase; + vec2_t from; + vec2_t to; +} locharacter_big_warder_param_t; + +_Static_assert( + sizeof(locharacter_big_warder_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE); + +#define LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("event-start-time", event.start_time); \ + PROC("phase", phase); \ + PROC("from", from); \ + PROC("to", to); \ +} while (0) +#define LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_COUNT 4 + +static const vec2_t locharacter_big_warder_size_ = vec2(.04f, .07f); + +static const loeffect_recipient_status_t +locharacter_big_warder_base_status_ = { + .attack = .1f, + .defence = .85f, + .speed = .1f, + .jump = .1f, +}; + +#define LOCHARACTER_BIG_WARDER_BEAT (60000/80.f) /* 80 BPM */ +#define LOCHARACTER_BIG_WARDER_MUSIC_DURATION \ + ((uint64_t) LOCHARACTER_BIG_WARDER_BEAT*144) + +#define LOCHARACTER_BIG_WARDER_MELODY_B_BEAT 80 + +#include "./big_warder.private.h" + +static void +locharacter_big_warder_start_wait_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_walk_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_shoot_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_combo_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_thrust_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_cooldown_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_stunned_state_( + locharacter_base_t* c +); +static void +locharacter_big_warder_start_dead_state_( + locharacter_base_t* c +); + +static void locharacter_big_warder_finalize_event_(locharacter_base_t* c) { + assert(c != NULL); + /* This function must start next state. */ + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + assert(p != NULL); + + locharacter_event_holder_release_control(&p->event); + + if (c->recipient.madness > 0) { + loentity_character_apply_effect( + &c->player->entity.super, &loeffect_curse_trigger()); + locharacter_big_warder_start_wait_state_(c); + } else { + loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_UNFINISHER); + locharacter_big_warder_start_dead_state_(c); + } +} + +static bool locharacter_big_warder_reset_if_player_left_( + locharacter_base_t* c) { + assert(c != NULL); + + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + return false; + } + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + locharacter_event_holder_release_control(&p->event); + + locharacter_big_warder_start_wait_state_(c); + return true; +} + +static void locharacter_big_warder_update_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t period = 1000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)%period*1.f/period; + t = (t*2) - 1; + t = MATH_ABS(t); + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t*t*(3-2*t); + + /* ---- state transition ---- */ + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < .5f*.5f) { + locharacter_big_warder_start_walk_state_(c); + return; + } + } +} +static void locharacter_big_warder_start_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_big_warder_update_walk_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const float linedur = beat*4; + static const uint64_t period = 800; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + const bool event = locharacter_event_holder_has_control(&p->event); + + const uint64_t min_duration = event? LOCHARACTER_BIG_WARDER_BEAT*16: 0; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (c->pos.x != 0) { + float t = (c->cache.time - c->since)%period*1.f/period; + t = (t*2) - 1; + t = MATH_ABS(t); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->motion_time = t; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + } + + /* ---- position ---- */ + if (c->pos.x != 0) c->direction = -MATH_SIGN(c->pos.x); + + c->pos.y = 0; + locommon_easing_linear_float(&c->pos.x, 0, c->ticker->delta_f/5); + + /* ---- event ---- */ + if (event) { + p->event.param->cinescope = true; + p->event.param->hide_hud = true; + + if (c->since+(p->phase+1)*linedur < c->cache.time) { + static const char* text[] = { + "boss_big_warder_line0", + "boss_big_warder_line1", + }; + if (p->phase < (int32_t) (sizeof(text)/sizeof(text[0]))) { + const char* v = loresource_text_get( + c->res->lang, text[(size_t) p->phase]); + loplayer_event_param_set_line(p->event.param, v, strlen(v)); + } else { + loplayer_event_param_set_line(p->event.param, "", 0); + } + ++p->phase; + } + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->pos.x == 0 && c->since + min_duration <= c->cache.time) { + if (event) { + p->event.param->hide_hud = false; + p->event.param->cinescope = false; + loplayer_event_param_set_line(p->event.param, "", 0); + } + locharacter_big_warder_start_shoot_state_(c); + return; + } +} +static void locharacter_big_warder_start_walk_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WALK; + + p->phase = 0; + + loeffect_recipient_reset(&c->recipient); + + if (!loeffect_stance_set_has( + &c->player->status.stances, LOEFFECT_STANCE_ID_UNFINISHER)) { + locharacter_event_holder_take_control(&p->event); + } +} + +static void locharacter_big_warder_update_shoot_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat*3; + + const uint64_t t = c->cache.time - c->since; + + c->cache.bullet_hittest = true; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- shooting ---- */ + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + if (p->phase < 4 && p->phase*beat/2 <= c->cache.time - c->since) { + ++p->phase; + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = c->super.super.pos, + .size = vec2(.04f, .04f), + .velocity = vec2(c->direction*.5f, 0), + .color = vec4(.6f, .6f, .6f, .8f), + .acceleration = vec2(0, 0), + .duration = 2000, + .knockback = .4f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + locharacter_big_warder_start_combo_state_(c); + return; + } +} +static void locharacter_big_warder_start_shoot_state_(locharacter_base_t* c) { + assert(c != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_SHOOT; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + p->phase = 0; +} + +static void locharacter_big_warder_update_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t step_dur = beat; + static const uint64_t attack_dur = beat*2; + + const locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (c->since + step_dur > c->cache.time) { + const float t = (c->cache.time - c->since)*1.f/step_dur; + + /* ---- position ---- */ + vec2_t dist; + vec2_sub(&dist, &p->to, &p->from); + vec2_muleq(&dist, t*t*(3-2*t)); + c->pos = p->from; + vec2_addeq(&c->pos, &dist); + + /* ---- motion ---- */ + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = 1-powf(2*t-1, 4); + } else { + float t = (c->cache.time - c->since - step_dur)*1.f/attack_dur; + t *= 4; + t -= (uint64_t) t; + + /* ---- motion ---- */ + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = t*t; + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + step_dur + attack_dur <= c->cache.time) { + locharacter_big_warder_start_thrust_state_(c); + return; + } +} +static void locharacter_big_warder_start_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t parry = 400; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time+parry > c->cache.time) { + locharacter_big_warder_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COMBO; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + c->gravity = 0; + + p->from = c->pos; + p->to = c->cache.player_pos; + p->to.x -= c->direction * locharacter_big_warder_size_.x; + p->to.y -= locharacter_big_warder_size_.y*.2f; + + for (size_t i = 0; i < 4; ++i) { + loplayer_attack(c->player, &(loplayer_combat_attack_t) { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*(i/2.f+1)), + .duration = beat/4, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack), + }); + } +} + +static void locharacter_big_warder_update_thrust_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + + const uint64_t ti = c->cache.time - c->since; + if (p->phase <= 0) { + /* ---- disappear ---- */ + instance->color.w *= 1 - ti/beat/4; + if (ti > beat/4) { + c->pos = p->to; + if (p->phase < 0) { /* backattack */ + c->direction *= -1; + p->phase = 0; + } + ++p->phase; + } + + } else if (p->phase == 1) { + /* ---- appear ---- */ + float t = (ti/beat/2 - .5f)*2; + if (t > 1) t = 1; + + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t; + instance->color.w *= t; + + if (ti > beat/2) ++p->phase; + + } else { + /* ---- attack ---- */ + float t = (ti/beat - .5f)*2; + if (t > 1) t = 1; + + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + duration < c->cache.time) { + locharacter_big_warder_start_cooldown_state_(c); + return; + } +} +static void locharacter_big_warder_start_thrust_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const float bmelo = LOCHARACTER_BIG_WARDER_MELODY_B_BEAT; + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_THRUST; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + c->gravity = 0; + + const bool backattack = + locharacter_event_holder_has_control(&p->event) && + (c->cache.time - p->event.start_time >= beat*bmelo); + const float backattack_f = backattack? 1: -1; + + p->to = c->cache.player_pos; + p->to.x += c->direction*locharacter_big_warder_size_.x*backattack_f; + p->to.y -= locharacter_big_warder_size_.y*.2f; + + p->phase = backattack? -1: 0; + + loplayer_attack(c->player, &(loplayer_combat_attack_t) { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat/2), + .duration = beat/2, + .knockback = vec2(-backattack_f*c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack), + }); +} + +static void locharacter_big_warder_update_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat*4; + + const uint64_t ti = c->cache.time - c->since; + + const locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = ti*1.f/duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- position ---- */ + if (ti < beat*2) { + t = ti/beat/2; + vec2_sub(&c->pos, &p->to, &p->from); + vec2_muleq(&c->pos, t*t*(3-2*t)); + vec2_addeq(&c->pos, &p->from); + + t = t*2-1; + t = 1-powf(MATH_ABS(t), 2); + c->pos.y += t*c->recipient.status.jump/2; + } else { + c->pos = vec2(0, 0); + } + + /* ---- state transition ---- */ + if (locharacter_big_warder_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + locharacter_big_warder_start_shoot_state_(c); + return; + } +} +static void locharacter_big_warder_start_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) c->data; + + if (locharacter_event_holder_has_control(&p->event)) { + static const uint64_t dur = LOCHARACTER_BIG_WARDER_MUSIC_DURATION; + if (p->event.start_time+dur < c->cache.time) { + locharacter_big_warder_finalize_event_(c); + return; + } + } else { + if (c->recipient.madness <= 0) { + locharacter_big_warder_start_dead_state_(c); + return; + } + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COOLDOWN; + + p->from = c->pos; + p->to = vec2(0, 0); +} + +static void locharacter_big_warder_update_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + static const uint64_t duration = beat*4; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/duration; + t *= 6; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < 1) { + t = 1-powf(1-t, 6); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t; + } else { + t = (t-1)/5; + if (t > 1) t = 1; + t = t*t*(3-2*t); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (c->since + duration <= c->cache.time) { + locharacter_big_warder_start_cooldown_state_(c); + return; + } +} +static void locharacter_big_warder_start_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STUNNED; + + loeffect_recipient_apply_effect( + &c->recipient, &loeffect_immediate_damage(1.f)); +} + +static void locharacter_big_warder_update_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t anime_duration = 4000; + static const uint64_t duration = 30000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/anime_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t*t; + instance->color.w *= 1-t; + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + locharacter_big_warder_start_wait_state_(c); + return; + } +} +static void locharacter_big_warder_start_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(c->player, .8f); +} + +bool locharacter_big_warder_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_big_warder_size_; + static const float height = size.y * 1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + + loeffect_recipient_update( + &base->recipient, &locharacter_big_warder_base_status_); + + if (!locharacter_event_holder_update(&p->event)) { + locharacter_big_warder_start_wait_state_(base); + } + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_WARDER, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = vec4(.2f, 0, 0, 1), + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_big_warder_update_wait_state_(base); + break; + case LOCHARACTER_STATE_WALK: + locharacter_big_warder_update_walk_state_(base); + break; + case LOCHARACTER_STATE_SHOOT: + locharacter_big_warder_update_shoot_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_big_warder_update_combo_state_(base); + break; + case LOCHARACTER_STATE_THRUST: + locharacter_big_warder_update_thrust_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_big_warder_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_STUNNED: + locharacter_big_warder_update_stunned_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_big_warder_update_dead_state_(base); + break; + default: + locharacter_big_warder_start_wait_state_(base); + } + locharacter_big_warder_update_passive_action_(base); + + base->cache.height = height; + + base->cache.instance.marker = !!base->cache.bullet_hittest; + base->cache.instance.size.x *= base->direction; + return true; +} + +void locharacter_big_warder_build( + locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_BIG_WARDER; + + base->ground = ground; + + base->pos = vec2(.7f, 0); + base->direction = 1; + + base->state = LOCHARACTER_STATE_WAIT; + base->since = base->cache.time; + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + *p = (typeof(*p)) {0}; + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_big_warder, + base, + LOCHARACTER_BIG_WARDER_MUSIC_DURATION, + 0); +} + +void locharacter_big_warder_tear_down(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + locharacter_event_holder_deinitialize(&p->event); +} + +void locharacter_big_warder_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_big_warder_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_big_warder_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_big_warder_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(unpack_); + +# undef unpack_ + +# undef item_ + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_big_warder, + base, + LOCHARACTER_BIG_WARDER_MUSIC_DURATION, + p->event.start_time); + return true; +} diff --git a/core/locharacter/big_warder.h b/core/locharacter/big_warder.h new file mode 100644 index 0000000..5e7a02a --- /dev/null +++ b/core/locharacter/big_warder.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_big_warder_update( + locharacter_base_t* base +); + +void +locharacter_big_warder_build( + locharacter_base_t* base, + loentity_id_t ground +); + +void +locharacter_big_warder_tear_down( + locharacter_base_t* base +); + +void +locharacter_big_warder_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_big_warder_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/big_warder.private.h b/core/locharacter/big_warder.private.h new file mode 100644 index 0000000..39d8bb3 --- /dev/null +++ b/core/locharacter/big_warder.private.h @@ -0,0 +1,157 @@ +static void locharacter_big_warder_update_passive_action_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_BIG_WARDER_BEAT; + + const locharacter_big_warder_param_t* p = (typeof(p)) c->data; + if (!locharacter_event_holder_has_control(&p->event)) return; + + const uint64_t dt = c->cache.time - c->last_update_time; + const uint64_t t = c->cache.time - p->event.start_time; + + const float beats = t/beat; + const float last_beats = t > dt? (t-dt)/beat: 0; + +# define name_pos_(name, x, y) \ + locommon_position_t name = c->cache.ground->super.pos; \ + vec2_addeq(&name.fract, &vec2(x, y)); \ + locommon_position_reduce(&name); + + name_pos_(top, 0, .8f); + name_pos_(center, 0, .25f); + name_pos_(left, -.3f, .2f); + name_pos_(right, .3f, .2f); + name_pos_(leftbottom, -.4f, .1f); + name_pos_(rightbottom, .4f, .1f); + name_pos_(leftbottom_off, -.6f, .1f); + name_pos_(rightbottom_off, .6f, .1f); + +# undef name_pos_ + +# define trigger_on_(x) (last_beats < (x) && beats >= (x)) + + /* ---- intro -> A melody ---- */ + if (trigger_on_(12)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = center, + .size = vec2(.4f, .4f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + + /* ---- A melody ---- */ + for (size_t i = 48; i < 80; i+=8) { + for (size_t j = 0; j < 2; ++j) { + if (trigger_on_(i-4 + j*4)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = j? leftbottom: rightbottom, + .size = vec2(.05f, .15f), + .angle = j? 0: MATH_PI, + .color = vec4(1, 1, 1, .6f), + .silent = true, + .beat = beat, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + if (trigger_on_(i + j*4)) { + static const float speed = 1.4f; + static const float accel = .7f / (beat*2); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = j? leftbottom_off: rightbottom_off, + .size = vec2(.05f, .15f), + .velocity = vec2(j? speed: -speed, 0), + .acceleration = vec2(j? -accel: accel, 0), + .color = vec4(1, 1, 1, .8f), + .duration = beat*2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + + /* ---- B melody ---- */ + static const int32_t bmelo_trigger_beats[] = {92, 108}; + static const size_t bmelo_trigger_counts = + sizeof(bmelo_trigger_beats)/sizeof(bmelo_trigger_beats[0]); + for (size_t i = 0; i < bmelo_trigger_counts; ++i) { + const int32_t st = bmelo_trigger_beats[i]; + for (int32_t j = 0; j < 4; ++j) { + if (trigger_on_(st + j/2.f)) { + for (int32_t x = -2; x <= 2; ++x) { + locommon_position_t pos = center; + vec2_addeq(&pos.fract, &vec2(x/2.f*.45f, (j-1)/4.f*.3f)); + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.1f, .1f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .6f), + .silent = true, + .beat = beat*2, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + } + + /* ---- C melody ---- */ + for (int32_t i = 0; i < 8; ++i) { + for (int32_t x = -10; x <= 10; ++x) { + if (trigger_on_(112 + i*4 + (x+10)/100.f)) { + locommon_position_t pos = center; + pos.fract.x += x/10.f*.47f; + pos.fract.y -= .13f; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.06f, .06f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + +# undef trigger_on_ +} diff --git a/core/locharacter/cavia.c b/core/locharacter/cavia.c new file mode 100644 index 0000000..6a8b412 --- /dev/null +++ b/core/locharacter/cavia.c @@ -0,0 +1,265 @@ +#include "./cavia.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/loeffect/recipient.h" +#include "core/loentity/entity.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +static const vec2_t locharacter_cavia_size_ = vec2(.02f, .05f); + +static const loeffect_recipient_status_t locharacter_cavia_base_status_ = { + .attack = .2f, + .defence = .1f, + .speed = .05f, +}; + +static void +locharacter_cavia_start_walk_state_( + locharacter_base_t* base +); +static bool +locharacter_cavia_start_thrust_state_( + locharacter_base_t* base +); +static void +locharacter_cavia_start_cooldown_state_( + locharacter_base_t* base +); +static void +locharacter_cavia_start_dead_state_( + locharacter_base_t* base +); + +static void locharacter_cavia_update_walk_state_(locharacter_base_t* base) { + assert(base != NULL); + + /* ---- movement ---- */ + const vec2_t* gsize = &base->cache.ground->size; + const float s = base->recipient.status.speed; + base->pos.x += base->ticker->delta_f * base->direction * s / gsize->x; + + if (MATH_ABS(base->pos.x) > 1) base->direction *= -1; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + + const int32_t p = 70/s; + const float t = (base->ticker->time - base->since)%p*2.0f/p - 1; + instance->motion_time = MATH_ABS(t); + + /* ---- dead ---- */ + if (base->recipient.madness <= 0) { + locharacter_cavia_start_dead_state_(base); + return; + } + + /* ---- trigger thrust ---- */ + if (loplayer_event_get_param(base->player->event) == NULL) { + vec2_t dist; + locommon_position_sub( + &dist, &base->player->entity.super.super.pos, &base->super.super.pos); + const float sdist_x = dist.x * base->direction; + if (MATH_ABS(dist.y) < locharacter_cavia_size_.y && + sdist_x >= locharacter_cavia_size_.x && + sdist_x <= locharacter_cavia_size_.x*2) { + if (locharacter_cavia_start_thrust_state_(base)) return; + } + } +} +static void locharacter_cavia_start_walk_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_WALK; +} + +static void locharacter_cavia_update_thrust_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t premotion = 1200; + static const uint64_t duration = 1500; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + + float t = (base->ticker->time - base->since)*1.0f/premotion; + if (t > 1) t = 1; + instance->motion_time = t*t*t*t; + + /* ---- cooldown ---- */ + if (base->since+duration <= base->ticker->time) { + /* TODO(catfoot): go to cooldown */ + locharacter_cavia_start_cooldown_state_(base); + return; + } +} +static bool locharacter_cavia_start_thrust_state_(locharacter_base_t* base) { + assert(base != NULL); + + const loplayer_combat_attack_t attack = { + .attacker = base->super.super.id, + .start = base->ticker->time + 1000, + .duration = 500, + .knockback = vec2(base->direction*.1f, 0), + .effect = loeffect_immediate_damage(base->recipient.status.attack), + }; + if (!loplayer_attack(base->player, &attack)) return false; + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_THRUST; + return true; +} + +static void locharacter_cavia_update_cooldown_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 500; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + + float t = (base->ticker->time - base->since)*1.0f/duration; + if (t > 1) t = 1; + instance->motion_time = t*t*(3-2*t); + + /* ---- cooldown ---- */ + if (base->since+duration <= base->ticker->time) { + if (base->recipient.madness <= 0) { + locharacter_cavia_start_dead_state_(base); + return; + } else { + locharacter_cavia_start_walk_state_(base); + return; + } + } +} +static void locharacter_cavia_start_cooldown_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_COOLDOWN; +} + +static void locharacter_cavia_update_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t anime = 500; + static const uint64_t duration = 30000; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (base->since+anime > base->ticker->time) { + const float t = (base->ticker->time - base->since)*1.0f/anime; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t*t; + + } else if (base->since+anime*2 > base->ticker->time) { + const float t = (base->ticker->time - anime - base->since)*1.0f/anime; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t*t; + + } else if (base->ticker->time+anime > base->since+duration) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = + 1 - (base->since + duration - base->ticker->time)*1.0f/anime; + + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + } + + /* ---- revive ---- */ + if (base->since+duration <= base->ticker->time) { + loeffect_recipient_reset(&base->recipient); + locharacter_cavia_start_walk_state_(base); + return; + } +} +static void locharacter_cavia_start_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(base->player, .1f); +} + +bool locharacter_cavia_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_cavia_size_; + static const vec4_t color = vec4(.1f, .1f, .1f, 1); + static const float height = size.y*1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + loeffect_recipient_update(&base->recipient, &locharacter_cavia_base_status_); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_CAVIA, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + switch (base->state) { + case LOCHARACTER_STATE_WALK: + locharacter_cavia_update_walk_state_(base); + break; + case LOCHARACTER_STATE_THRUST: + locharacter_cavia_update_thrust_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_cavia_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_cavia_update_dead_state_(base); + break; + default: + locharacter_cavia_start_walk_state_(base); + } + + base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD; + + base->cache.height = height; + + base->cache.instance.size.x *= base->direction; + base->cache.instance.marker = !!base->cache.bullet_hittest; + return true; +} + +void locharacter_cavia_build( + locharacter_base_t* base, const locharacter_cavia_param_t* param) { + assert(base != NULL); + assert(param != NULL); + + base->type = LOCHARACTER_TYPE_CAVIA; + + base->ground = param->ground; + + base->pos = vec2(param->pos, 0); + base->direction = param->direction == 1? 1: -1; + + base->state = LOCHARACTER_STATE_WALK; + base->since = base->ticker->time; +} diff --git a/core/locharacter/cavia.h b/core/locharacter/cavia.h new file mode 100644 index 0000000..0d65292 --- /dev/null +++ b/core/locharacter/cavia.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t ground; + float pos; + float direction; +} locharacter_cavia_param_t; + +bool +locharacter_cavia_update( + locharacter_base_t* base +); + +void +locharacter_cavia_build( + locharacter_base_t* base, + const locharacter_cavia_param_t* param +); + +#define locharacter_cavia_tear_down(base) + +#define locharacter_cavia_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define locharacter_cavia_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/locharacter/encephalon.c b/core/locharacter/encephalon.c new file mode 100644 index 0000000..cfe9ad4 --- /dev/null +++ b/core/locharacter/encephalon.c @@ -0,0 +1,138 @@ +#include "./encephalon.h" + +#include +#include +#include + +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/loentity/entity.h" +#include "core/loplayer/player.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +typedef struct { + float progress; +} locharacter_encephalon_param_t; + +_Static_assert( + sizeof(locharacter_encephalon_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE); + +static const vec2_t locharacter_encephalon_size_ = vec2(.1f, .1f); + +#define LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("progress", progress); \ +} while (0) +#define LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_COUNT 1 + +static void locharacter_encephalon_update_progress_(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + const vec2_t* player = &base->cache.player_pos; + const bool near = + MATH_ABS(player->x) < 1 && + 0 <= player->y && player->y < locharacter_encephalon_size_.y; + + if (near && base->state == LOCHARACTER_STATE_WAIT) { + p->progress += base->ticker->delta_f; + if (p->progress >= 1) { + loplayer_touch_encephalon(base->player); + base->state = LOCHARACTER_STATE_COOLDOWN; + loresource_sound_play(base->res->sound, "touch_gate"); + } + } else { + p->progress -= base->ticker->delta_f * 2; + if (!near) base->state = LOCHARACTER_STATE_WAIT; + } + p->progress = MATH_CLAMP(p->progress, 0, 1); +} + +bool locharacter_encephalon_update(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_encephalon_update_progress_(base); + + const locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + base->pos = vec2(0, locharacter_encephalon_size_.y); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_ENCEPHALON, + .from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1, + .to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1, + .color = vec4(p->progress*.5f, 0, 0, .95f), + .size = locharacter_encephalon_size_, + }; + if (base->state == LOCHARACTER_STATE_COOLDOWN && p->progress > 0) { + base->cache.instance.color.w *= + chaos_xorshift(base->ticker->time)%100/100.f; + } + return true; +} + +void locharacter_encephalon_build( + locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_ENCEPHALON; + + base->ground = ground; + base->pos = vec2(0, 0); + + base->state = LOCHARACTER_STATE_WAIT; +} + +void locharacter_encephalon_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_encephalon_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_encephalon_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ + +# undef item_ +} diff --git a/core/locharacter/encephalon.h b/core/locharacter/encephalon.h new file mode 100644 index 0000000..d8657a8 --- /dev/null +++ b/core/locharacter/encephalon.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_encephalon_update( + locharacter_base_t* base +); + +void +locharacter_encephalon_build( + locharacter_base_t* base, + loentity_id_t ground +); + +#define locharacter_encephalon_tear_down(base) + +void +locharacter_encephalon_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_encephalon_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/greedy_scientist.c b/core/locharacter/greedy_scientist.c new file mode 100644 index 0000000..838cf1c --- /dev/null +++ b/core/locharacter/greedy_scientist.c @@ -0,0 +1,607 @@ +#include "./greedy_scientist.h" + +#include +#include +#include +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/loentity/entity.h" +#include "core/loplayer/combat.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/text.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" +#include "./util.h" + +typedef struct { + locharacter_event_holder_t event; + + int32_t phase; + vec2_t from; + vec2_t to; +} locharacter_greedy_scientist_param_t; + +_Static_assert( + sizeof(locharacter_greedy_scientist_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE); + +#define LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("event-start-time", event.start_time); \ + PROC("phase", phase); \ + PROC("from", from); \ + PROC("to", to); \ +} while (0) +#define LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_COUNT 4 + +static const vec2_t locharacter_greedy_scientist_size_ = vec2(.03f, .07f); + +static const loeffect_recipient_status_t +locharacter_greedy_scientist_base_status_ = { + .attack = .2f, + .defence = .85f, + .speed = .1f, + .jump = .1f, +}; + +#define LOCHARACTER_GREEDY_SCIENTIST_BEAT (60000/140.f) /* 140 BPM */ +#define LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION \ + ((uint64_t) LOCHARACTER_GREEDY_SCIENTIST_BEAT*128) + +#define LOCHARACTER_GREEDY_SCIENTIST_MELODY_B_BEAT 52 + +#include "./greedy_scientist.private.h" + +static void +locharacter_greedy_scientist_start_wait_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_standup_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_combo_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_cooldown_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_stunned_state_( + locharacter_base_t* c +); +static void +locharacter_greedy_scientist_start_dead_state_( + locharacter_base_t* c +); + +static void locharacter_greedy_scientist_finalize_event_(locharacter_base_t* c) { + assert(c != NULL); + /* This function must start next state. */ + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + assert(p != NULL); + + locharacter_event_holder_release_control(&p->event); + + if (c->recipient.madness > 0) { + loentity_character_apply_effect( + &c->player->entity.super, &loeffect_curse_trigger()); + locharacter_greedy_scientist_start_wait_state_(c); + } else { + loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_PHILOSOPHER); + locharacter_greedy_scientist_start_dead_state_(c); + } +} + +static bool locharacter_greedy_scientist_reset_if_player_left_( + locharacter_base_t* c) { + assert(c != NULL); + + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + return false; + } + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + locharacter_event_holder_release_control(&p->event); + + locharacter_greedy_scientist_start_wait_state_(c); + return true; +} + +static void locharacter_greedy_scientist_update_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t min_duration = LOCHARACTER_GREEDY_SCIENTIST_BEAT*4; + + c->cache.gravity = true; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = 0; + + /* ---- state transition ---- */ + if (c->since + min_duration <= c->cache.time) { + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < .5f*.5f) { + locharacter_greedy_scientist_start_standup_state_(c); + return; + } + } + } +} +static void locharacter_greedy_scientist_start_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_greedy_scientist_update_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t period = beat*4; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + const bool event = locharacter_event_holder_has_control(&p->event); + const uint64_t duration = event? beat*20: beat*8; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)%period*1.f/period; + t = t*2-1; + t = MATH_ABS(t); + t = t*t*(3-2*t); + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + instance->motion_time = t; + + /* ---- event ---- */ + if (event) { + p->event.param->cinescope = true; + p->event.param->hide_hud = true; + + if (c->since+(p->phase*8+4)*beat < c->cache.time) { + static const char* text[] = { + "boss_greedy_scientist_line0", + "boss_greedy_scientist_line1", + }; + if (p->phase < (int32_t) (sizeof(text)/sizeof(text[0]))) { + const char* v = loresource_text_get( + c->res->lang, text[(size_t) p->phase]); + loplayer_event_param_set_line(p->event.param, v, strlen(v)); + } else { + loplayer_event_param_set_line(p->event.param, "", 0); + } + ++p->phase; + } + } + + /* ---- state transition ---- */ + if (locharacter_greedy_scientist_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + if (event) { + p->event.param->cinescope = false; + p->event.param->hide_hud = false; + loplayer_event_param_set_line(p->event.param, "", 0); + } + locharacter_greedy_scientist_start_combo_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STANDUP; + + loeffect_recipient_reset(&c->recipient); + + if (!loeffect_stance_set_has( + &c->player->status.stances, LOEFFECT_STANCE_ID_PHILOSOPHER)) { + locharacter_event_holder_take_control(&p->event); + } + p->phase = 0; +} + +static void locharacter_greedy_scientist_update_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float offset_y = locharacter_greedy_scientist_size_.y*1.5f; + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t step_dur = beat; + static const uint64_t attack_dur = beat*3; + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + if (c->since + step_dur > c->cache.time) { + const float t = (c->cache.time - c->since)*1.f/step_dur; + + vec2_t to = p->to; + to.y += offset_y; + + /* ---- position ---- */ + vec2_t dist; + vec2_sub(&dist, &to, &p->from); + vec2_muleq(&dist, t*t*(3-2*t)); + c->pos = p->from; + vec2_addeq(&c->pos, &dist); + + /* ---- motion ---- */ + instance->motion_time = 1-powf(2*t-1, 4); + } else { + float t = (c->cache.time - c->since - step_dur)*1.f/attack_dur; + t *= 3; + t -= (uint64_t) t; + t = t*t; + + /* ---- position ---- */ + c->pos.y -= c->ticker->delta_f*t/3 * offset_y; + + /* ---- motion ---- */ + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (locharacter_greedy_scientist_reset_if_player_left_(c)) return; + + if (c->since + step_dur + attack_dur <= c->cache.time) { + locharacter_greedy_scientist_start_cooldown_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t parry = 100; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time+parry > c->cache.time) { + locharacter_greedy_scientist_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COMBO; + + c->direction = c->cache.player_pos.x - c->pos.x; + c->direction = c->direction > 0? 1: -1; + + c->gravity = 0; + + p->from = c->pos; + p->to = c->cache.player_pos; + + const size_t delay_index = chaos_xorshift(c->cache.time)&1? 2: 3; + for (size_t i = 1; i < 4; ++i) { + const uint64_t delay = i >= delay_index? beat/2: 0; + loplayer_attack(c->player, &(loplayer_combat_attack_t) { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*i) + delay, + .duration = beat/2, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + }); + } +} + +static void locharacter_greedy_scientist_update_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t duration = beat*4; + + const uint64_t ti = c->cache.time - c->since; + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = ti*1.f/duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (locharacter_greedy_scientist_reset_if_player_left_(c)) return; + + if (c->since + duration <= c->cache.time) { + locharacter_greedy_scientist_start_combo_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t bmelo = LOCHARACTER_GREEDY_SCIENTIST_MELODY_B_BEAT*beat; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + + if (locharacter_event_holder_has_control(&p->event)) { + static const uint64_t dur = LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION; + if (p->event.start_time+dur < c->cache.time) { + locharacter_greedy_scientist_finalize_event_(c); + return; + } + } else { + if (c->recipient.madness <= 0) { + locharacter_greedy_scientist_start_dead_state_(c); + return; + } + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COOLDOWN; + + if (locharacter_event_holder_has_control(&p->event)) { + locharacter_greedy_scientist_trigger_chained_mines_(c); + if (c->cache.time - p->event.start_time > bmelo) { + locharacter_greedy_scientist_shoot_amnesia_bullet_(c); + } + } +} + +static void locharacter_greedy_scientist_update_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + static const uint64_t duration = beat*4; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/duration; + t *= 6; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < 1) { + t = 1-powf(1-t, 6); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t; + } else { + t = (t-1)/5; + if (t > 1) t = 1; + t = t*t*(3-2*t); + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (c->since + duration <= c->cache.time) { + locharacter_greedy_scientist_start_cooldown_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STUNNED; + + loeffect_recipient_apply_effect( + &c->recipient, &loeffect_immediate_damage(1.f)); +} + +static void locharacter_greedy_scientist_update_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t anime_duration = 4000; + static const uint64_t duration = 30000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/anime_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t*t; + instance->color.w *= 1-t; + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + locharacter_greedy_scientist_start_wait_state_(c); + return; + } +} +static void locharacter_greedy_scientist_start_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(c->player, .8f); +} + +bool locharacter_greedy_scientist_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_greedy_scientist_size_; + static const float height = size.y * 1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + + loeffect_recipient_update( + &base->recipient, &locharacter_greedy_scientist_base_status_); + + if (!locharacter_event_holder_update(&p->event)) { + locharacter_greedy_scientist_start_wait_state_(base); + } + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_SCIENTIST, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = vec4(.2f, 0, 0, 1), + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_greedy_scientist_update_wait_state_(base); + break; + case LOCHARACTER_STATE_STANDUP: + locharacter_greedy_scientist_update_standup_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_greedy_scientist_update_combo_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_greedy_scientist_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_STUNNED: + locharacter_greedy_scientist_update_stunned_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_greedy_scientist_update_dead_state_(base); + break; + default: + locharacter_greedy_scientist_start_wait_state_(base); + } + locharacter_greedy_scientist_update_passive_action_(base); + + base->cache.height = height; + + base->cache.instance.marker = !!base->cache.bullet_hittest; + base->cache.instance.size.x *= base->direction; + return true; +} + +void locharacter_greedy_scientist_build( + locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_GREEDY_SCIENTIST; + + base->ground = ground; + + base->pos = vec2(.7f, 0); + base->direction = 1; + + base->state = LOCHARACTER_STATE_WAIT; + base->since = base->cache.time; + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + *p = (typeof(*p)) {0}; + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_greedy_scientist, + base, + LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION, + 0); +} + +void locharacter_greedy_scientist_tear_down(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + locharacter_event_holder_deinitialize(&p->event); +} + +void locharacter_greedy_scientist_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_greedy_scientist_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(unpack_); + +# undef unpack_ + +# undef item_ + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_greedy_scientist, + base, + LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION, + p->event.start_time); + return true; +} diff --git a/core/locharacter/greedy_scientist.h b/core/locharacter/greedy_scientist.h new file mode 100644 index 0000000..85aaded --- /dev/null +++ b/core/locharacter/greedy_scientist.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_greedy_scientist_update( + locharacter_base_t* base +); + +void +locharacter_greedy_scientist_build( + locharacter_base_t* base, + loentity_id_t ground +); + +void +locharacter_greedy_scientist_tear_down( + locharacter_base_t* base +); + +void +locharacter_greedy_scientist_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_greedy_scientist_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/greedy_scientist.private.h b/core/locharacter/greedy_scientist.private.h new file mode 100644 index 0000000..527e65a --- /dev/null +++ b/core/locharacter/greedy_scientist.private.h @@ -0,0 +1,228 @@ +static void locharacter_greedy_scientist_trigger_chained_mines_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + + locommon_position_t center = c->cache.ground->super.pos; + center.fract.y += .1f; + locommon_position_reduce(¢er); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = center, + .size = vec2(.07f, .07f), + .angle = 0, + .color = vec4(1, .9f, .9f, .8f), + .beat = beat, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + + for (int32_t i = -6; i <= 6; ++i) { + locommon_position_t pos = center; + pos.fract.x += i/6.f*.5f; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.05f, .05f), + .angle = 0, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*1.5f, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } +} + +static void locharacter_greedy_scientist_shoot_amnesia_bullet_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + + locommon_position_t pos = c->super.super.pos; + pos.fract.y += locharacter_greedy_scientist_size_.y*1.5f; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.06f, .06f), + .velocity = vec2(0, .1f), + .color = vec4(.8f, .8f, .6f, .8f), + .acceleration = vec2(0, -2), + .duration = 1000, + .effect = loeffect_amnesia(c->ticker->time, beat*8), + })); + loentity_store_add(c->entities, &b->super.super); +} + +static void locharacter_greedy_scientist_update_passive_action_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT; + + const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data; + if (!locharacter_event_holder_has_control(&p->event)) return; + + const uint64_t dt = c->cache.time - c->last_update_time; + const uint64_t t = c->cache.time - p->event.start_time; + + const float beats = t/beat; + const float last_beats = t > dt? (t-dt)/beat: 0; + +# define name_pos_(name, x, y) \ + locommon_position_t name = c->cache.ground->super.pos; \ + vec2_addeq(&name.fract, &vec2(x, y)); \ + locommon_position_reduce(&name); + + name_pos_(top, 0, .8f); + name_pos_(lefttop, -.3f, .8f); + name_pos_(righttop, .3f, .8f); + name_pos_(center, 0, .4f); + name_pos_(left, -.3f, .2f); + name_pos_(right, .3f, .2f); + +# undef name_pos_ + +# define trigger_on_(x) (last_beats < (x) && beats >= (x)) + + /* ---- intro -> A melody ---- */ + for (size_t i = 0; i < 2; ++i) { + if (trigger_on_(16)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.1f*cos(MATH_PI/6), .1f), + .angle = -MATH_PI/2, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*2, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + if (trigger_on_(16.5f)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.1f*cos(MATH_PI/6), .1f), + .angle = MATH_PI/2, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*2, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + if (trigger_on_(17)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.12f, .12f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat*2, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/4), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + for (size_t i = 0; i < 4; ++i) { + if (trigger_on_(18 + i*.5f)) { + locommon_position_t pos = center; + pos.fract.y -= .1f * i; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.05f, .2f), + .angle = -MATH_PI/2, + .color = vec4(1, 1, 1, .4f), + .silent = true, + .beat = beat, + .step = 1, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- B melody ---- */ + for (size_t i = 52, cnt = 0; i < 84; i+=4, ++cnt) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = cnt%2? lefttop: righttop, + .size = vec2(.1f, .3f), + .velocity = vec2(0, -1/(beat*4)*1000), + .color = vec4(1, 1, 1, .8f), + .duration = beat*4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- C melody ---- */ + for (size_t i = 84, cnt = 0; i < 156; i+=8, ++cnt) { + if (trigger_on_(i)) { + for (int32_t x = -1-cnt%2; x <= 2; x+=2) { + locommon_position_t pos = top; + pos.fract.x += .18f*x; + locommon_position_reduce(&pos); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.05f, .1f), + .velocity = vec2(0, -1/(beat*4)*1000), + .color = vec4(1, 1, 1, .8f), + .duration = beat*4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + } + +# undef trigger_on_ +} diff --git a/core/locharacter/misc.c b/core/locharacter/misc.c new file mode 100644 index 0000000..7caeaed --- /dev/null +++ b/core/locharacter/misc.c @@ -0,0 +1,68 @@ +#include "./misc.h" + +#include +#include +#include +#include + +const char* locharacter_type_stringify(locharacter_type_t type) { +# define each_(NAME, name) do { \ + if (type == LOCHARACTER_TYPE_##NAME) return #name; \ + } while(0) + + LOCHARACTER_TYPE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool locharacter_type_unstringify( + locharacter_type_t* type, const char* v, size_t len) { + assert(type != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *type = LOCHARACTER_TYPE_##NAME; \ + return true; \ + } \ + } while(0) + + LOCHARACTER_TYPE_EACH_(each_); + return false; + +# undef each_ +} + +const char* locharacter_state_stringify(locharacter_state_t state) { +# define each_(NAME, name) do { \ + if (state == LOCHARACTER_STATE_##NAME) return #name; \ + } while(0) + + LOCHARACTER_STATE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool locharacter_state_unstringify( + locharacter_state_t* state, const char* v, size_t len) { + assert(state != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *state = LOCHARACTER_STATE_##NAME; \ + return true; \ + } \ + } while(0) + + LOCHARACTER_STATE_EACH_(each_); + return false; + +# undef each_ +} diff --git a/core/locharacter/misc.h b/core/locharacter/misc.h new file mode 100644 index 0000000..7668492 --- /dev/null +++ b/core/locharacter/misc.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +/* dont forget to update EACH macro */ +typedef enum { + LOCHARACTER_TYPE_ENCEPHALON, + LOCHARACTER_TYPE_CAVIA, + LOCHARACTER_TYPE_SCIENTIST, + LOCHARACTER_TYPE_WARDER, + LOCHARACTER_TYPE_THEISTS_CHILD, + LOCHARACTER_TYPE_BIG_WARDER, + LOCHARACTER_TYPE_GREEDY_SCIENTIST, +} locharacter_type_t; + +#define LOCHARACTER_TYPE_EACH_(PROC) do { \ + PROC(ENCEPHALON, encephalon); \ + PROC(CAVIA, cavia); \ + PROC(SCIENTIST, scientist); \ + PROC(WARDER, warder); \ + PROC(THEISTS_CHILD, theists_child); \ + PROC(BIG_WARDER, big_warder); \ + PROC(GREEDY_SCIENTIST, greedy_scientist); \ +} while (0) + +/* dont forget to update EACH macro */ +typedef enum { + LOCHARACTER_STATE_WAIT, + LOCHARACTER_STATE_STANDUP, + LOCHARACTER_STATE_WALK, + LOCHARACTER_STATE_SHOOT, + LOCHARACTER_STATE_RUSH, + LOCHARACTER_STATE_THRUST, + LOCHARACTER_STATE_COMBO, + LOCHARACTER_STATE_COOLDOWN, + LOCHARACTER_STATE_STUNNED, + LOCHARACTER_STATE_DEAD, +} locharacter_state_t; + +#define LOCHARACTER_STATE_EACH_(PROC) do { \ + PROC(WAIT, wait); \ + PROC(STANDUP, standup); \ + PROC(WALK, walk); \ + PROC(SHOOT, shoot); \ + PROC(RUSH, rush); \ + PROC(THRUST, thrust); \ + PROC(COMBO, combo); \ + PROC(COOLDOWN, cooldown); \ + PROC(STUNNED, stunned); \ + PROC(DEAD, dead); \ +} while (0) + +const char* +locharacter_type_stringify( + locharacter_type_t type +); + +bool +locharacter_type_unstringify( + locharacter_type_t* type, + const char* v, + size_t len +); + +const char* +locharacter_state_stringify( + locharacter_state_t state +); + +bool +locharacter_state_unstringify( + locharacter_state_t* state, + const char* v, + size_t len +); diff --git a/core/locharacter/pool.c b/core/locharacter/pool.c new file mode 100644 index 0000000..4b38e7d --- /dev/null +++ b/core/locharacter/pool.c @@ -0,0 +1,122 @@ +#include "./pool.h" + +#include +#include +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./base.h" + +struct locharacter_pool_t { + loresource_set_t* res; + loshader_character_drawer_t* drawer; + locommon_counter_t* idgen; + const locommon_ticker_t* ticker; + lobullet_pool_t* bullets; + loentity_store_t* entities; + loplayer_t* player; + + size_t length; + locharacter_base_t items[1]; +}; + +static size_t locharacter_pool_find_unused_item_index_( + const locharacter_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + if (!pool->items[i].used) return i; + } + fprintf(stderr, "character pool overflow\n"); + abort(); +} + +locharacter_pool_t* locharacter_pool_new( + loresource_set_t* res, + loshader_character_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player, + size_t length) { + assert(res != NULL); + assert(drawer != NULL); + assert(idgen != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(player != NULL); + assert(length > 0); + + locharacter_pool_t* pool = memory_new( + sizeof(*pool) + (length-1)*sizeof(pool->items[0])); + *pool = (typeof(*pool)) { + .res = res, + .drawer = drawer, + .idgen = idgen, + .ticker = ticker, + .bullets = bullets, + .entities = entities, + .player = player, + .length = length, + }; + + for (size_t i = 0; i < pool->length; ++i) { + locharacter_base_initialize( + &pool->items[i], + res, + drawer, + ticker, + bullets, + entities, + player); + } + return pool; +} + +void locharacter_pool_delete(locharacter_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + locharacter_base_deinitialize(&pool->items[i]); + } + memory_delete(pool); +} + +locharacter_base_t* locharacter_pool_create(locharacter_pool_t* pool) { + assert(pool != NULL); + + const size_t i = locharacter_pool_find_unused_item_index_(pool); + + locharacter_base_reinitialize( + &pool->items[i], locommon_counter_count(pool->idgen)); + + pool->items[i].used = true; + return &pool->items[i]; +} + +locharacter_base_t* locharacter_pool_unpack_item( + locharacter_pool_t* pool, const msgpack_object* obj) { + assert(pool != NULL); + + const size_t i = locharacter_pool_find_unused_item_index_(pool); + + if (!locharacter_base_unpack(&pool->items[i], obj)) return NULL; + + pool->items[i].used = true; + return &pool->items[i]; +} diff --git a/core/locharacter/pool.h b/core/locharacter/pool.h new file mode 100644 index 0000000..b573f47 --- /dev/null +++ b/core/locharacter/pool.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/counter.h" +#include "core/locommon/ticker.h" +#include "core/loentity/store.h" +#include "core/loplayer/player.h" +#include "core/loresource/set.h" +#include "core/loshader/character.h" + +#include "./base.h" + +struct locharacter_pool_t; +typedef struct locharacter_pool_t locharacter_pool_t; + +locharacter_pool_t* /* OWNERSHIP */ +locharacter_pool_new( + loresource_set_t* res, + loshader_character_drawer_t* drawer, + locommon_counter_t* idgen, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_t* player, + size_t length +); + +void +locharacter_pool_delete( + locharacter_pool_t* pool /* OWNERSHIP */ +); + +locharacter_base_t* +locharacter_pool_create( + locharacter_pool_t* pool +); + +locharacter_base_t* +locharacter_pool_unpack_item( + locharacter_pool_t* pool, + const msgpack_object* obj +); diff --git a/core/locharacter/scientist.c b/core/locharacter/scientist.c new file mode 100644 index 0000000..35aeda3 --- /dev/null +++ b/core/locharacter/scientist.c @@ -0,0 +1,305 @@ +#include "./scientist.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +static const vec2_t locharacter_scientist_size_ = vec2(.02f, .05f); + +static const loeffect_recipient_status_t locharacter_scientist_base_status_ = { + .attack = .2f, + .defence = .1f, +}; + +static void locharacter_scientist_trigger_bomb_(locharacter_base_t* c) { + assert(c != NULL); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = c->player->entity.super.super.pos, + .size = vec2(.15f, .15f), + .angle = 0, + .color = vec4(1, 1, 1, .6f), + .beat = 500, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + + loresource_sound_play(c->res->sound, "enemy_trigger"); +} + +static void +locharacter_scientist_start_wait_state_( + locharacter_base_t* base +); +static void +locharacter_scientist_start_shoot_state_( + locharacter_base_t* base +); +static void +locharacter_scientist_start_combo_state_( + locharacter_base_t* base +); +static void +locharacter_scientist_start_dead_state_( + locharacter_base_t* base +); + +static void locharacter_scientist_update_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + static const uint64_t period = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed%period*1.f/period; + t = t*2 - 1; + t = MATH_ABS(t); + t = t*t*(3-2*t); + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (elapsed >= duration) { + if (base->recipient.madness <= 0) { + locharacter_scientist_start_dead_state_(base); + return; + } + + if (loplayer_event_get_param(base->player->event) == NULL) { + vec2_t disp; + vec2_sub(&disp, &base->cache.player_pos, &base->pos); + disp.x *= base->cache.ground->size.x; + + const float pdist = vec2_pow_length(&disp); + if (MATH_ABS(disp.y) < locharacter_scientist_size_.y && + pdist < .2f*.2f) { + static const float r = locharacter_scientist_size_.x*3; + if (pdist < r*r) { + locharacter_scientist_start_combo_state_(base); + } else if (disp.x*base->direction > 0) { + locharacter_scientist_start_shoot_state_(base); + } + } + return; + } + } +} +static void locharacter_scientist_start_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_scientist_update_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 500; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f / duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_scientist_trigger_bomb_(base); + locharacter_scientist_start_wait_state_(base); + return; + } +} +static void locharacter_scientist_start_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_SHOOT; +} + +static void locharacter_scientist_update_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + base->cache.gravity = false; + + /* ---- motion ---- */ + float t = elapsed*1.f/duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_scientist_start_wait_state_(base); + return; + } +} +static void locharacter_scientist_start_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_COMBO; + + const float diff = base->cache.player_pos.x - base->pos.x; + base->direction = MATH_SIGN(diff); + + const loplayer_combat_attack_t attack = { + .attacker = base->super.super.id, + .start = base->ticker->time + 600, + .duration = 400, + .knockback = vec2(base->direction*.1f, 0), + .effect = loeffect_immediate_damage(base->recipient.status.attack), + }; + loplayer_attack(base->player, &attack); +} + +static void locharacter_scientist_update_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t anime_duration = 500; + static const uint64_t duration = 30000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (elapsed > duration - anime_duration) { /* wake up */ + float t = 1-(duration - elapsed)*1.f/anime_duration; + if (t < 0) t = 0; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = 1-powf(1-t, 2); + } else { /* down */ + float t = elapsed*1.f/anime_duration; + if (t > 1) t = 1; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = powf(t, 2.); + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + loeffect_recipient_reset(&base->recipient); + locharacter_scientist_start_wait_state_(base); + return; + } +} +static void locharacter_scientist_start_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(base->player, .3f); +} + +bool locharacter_scientist_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_scientist_size_; + static const vec4_t color = vec4(.1f, .1f, .1f, 1); + static const float height = size.y*1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + loeffect_recipient_update(&base->recipient, &locharacter_scientist_base_status_); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_SCIENTIST, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + base->cache.gravity = true; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_scientist_update_wait_state_(base); + break; + case LOCHARACTER_STATE_SHOOT: + locharacter_scientist_update_shoot_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_scientist_update_combo_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_scientist_update_dead_state_(base); + break; + default: + locharacter_scientist_start_wait_state_(base); + } + + base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD; + + base->cache.height = height; + + base->cache.instance.size.x *= base->direction; + base->cache.instance.marker = !!base->cache.bullet_hittest; + return true; +} + +void locharacter_scientist_build( + locharacter_base_t* base, const locharacter_scientist_param_t* param) { + assert(base != NULL); + assert(param != NULL); + + base->type = LOCHARACTER_TYPE_SCIENTIST; + + base->ground = param->ground; + + base->pos = vec2(param->pos, 0); + base->direction = param->direction == 1? 1: -1; + + locharacter_scientist_start_wait_state_(base); +} diff --git a/core/locharacter/scientist.h b/core/locharacter/scientist.h new file mode 100644 index 0000000..5ae2c40 --- /dev/null +++ b/core/locharacter/scientist.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t ground; + float pos; + float direction; +} locharacter_scientist_param_t; + +bool +locharacter_scientist_update( + locharacter_base_t* base +); + +void +locharacter_scientist_build( + locharacter_base_t* base, + const locharacter_scientist_param_t* param +); + +#define locharacter_scientist_tear_down(base) + +#define locharacter_scientist_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define locharacter_scientist_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/locharacter/theists_child.c b/core/locharacter/theists_child.c new file mode 100644 index 0000000..c233027 --- /dev/null +++ b/core/locharacter/theists_child.c @@ -0,0 +1,763 @@ +#include "./theists_child.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "util/chaos/xorshift.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/math/rational.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/bomb.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loentity/bullet.h" +#include "core/loentity/store.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/music.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" +#include "./util.h" + +typedef struct { + locharacter_event_holder_t event; + + uint64_t phase; + vec2_t from; + vec2_t to; +} locharacter_theists_child_param_t; + +#define LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("event-start-time", event.start_time); \ + PROC("phase", phase); \ + PROC("from", from); \ + PROC("to", to); \ +} while (0) +#define LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_COUNT 4 + +static const vec2_t locharacter_theists_child_size_ = vec2(.02f, .06f); + +static const loeffect_recipient_status_t +locharacter_theists_child_base_status_ = { + .attack = .1f, + .defence = .9f, + .speed = .1f, + .jump = .2f, +}; + +#define LOCHARACTER_THEISTS_CHILD_BEAT (60000/140.f) /* 140 BPM */ +#define LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION \ + ((uint64_t) LOCHARACTER_THEISTS_CHILD_BEAT*236) + +#define LOCHARACTER_THEISTS_CHILD_MELODY_B_START_BEAT 128 +#define LOCHARACTER_THEISTS_CHILD_MELODY_B_END_BEAT 192 + +#include "./theists_child.private.h" + +static void +locharacter_theists_child_start_wait_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_standup_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_rush_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_combo_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_cooldown_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_stunned_state_( + locharacter_base_t* c +); +static void +locharacter_theists_child_start_dead_state_( + locharacter_base_t* c +); + +static void locharacter_theists_child_finalize_event_(locharacter_base_t* c) { + assert(c != NULL); + /* This function must start next state. */ + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + assert(p != NULL); + + locharacter_event_holder_release_control(&p->event); + + if (c->recipient.madness > 0) { + loentity_character_apply_effect( + &c->player->entity.super, &loeffect_curse_trigger()); + locharacter_theists_child_start_wait_state_(c); + } else { + loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_REVOLUTIONER); + locharacter_theists_child_start_dead_state_(c); + } +} + +static bool locharacter_theists_child_reset_if_player_left_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + return false; + } + locharacter_event_holder_release_control(&p->event); + locharacter_theists_child_start_wait_state_(c); + return true; +} + +static void locharacter_theists_child_fire_bullets_(locharacter_base_t* c) { + assert(c != NULL); + + static const float len = .3f; + static const float accel = .6f; + static const uint64_t dur = 1000; + + for (size_t i = 0; i < 30; ++i) { + const float t = MATH_PI/15*i; + + const vec2_t v = vec2(cos(t), sin(t)); + + locommon_position_t pos = c->super.super.pos; + pos.fract.x += v.x*len; + pos.fract.y += v.y*len; + locommon_position_reduce(&pos); + + lobullet_base_t* bullet = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(bullet, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = pos, + .size = vec2(.015f, .015f), + .acceleration = vec2(-v.x*accel, -v.y*accel), + .color = vec4(1, 1, 1, .8f), + .duration = dur, + .knockback = .1f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &bullet->super.super); + } +} + +static void locharacter_theists_child_update_wait_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float standup_range = .5f; + static const int32_t sit_duration = 4000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/sit_duration; + if (t > 1) t = 1; + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (c->since+sit_duration <= c->cache.time) { + if (MATH_ABS(c->cache.player_pos.x) < 1 && + 0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) { + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < standup_range*standup_range) { + locharacter_theists_child_start_standup_state_(c); + return; + } + } + } +} +static void locharacter_theists_child_start_wait_state_(locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_theists_child_update_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t line_duration = beat*10; + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + const bool event = locharacter_event_holder_has_control(&p->event); + const uint64_t standup_duration = event? beat*64: 1000; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.0f/standup_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < .5f) { + t *= 2; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = t*t*(3-2*t); + } else { + t = (t-.5f)*2; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t*t*(3-2*t); + } + + /* ---- event ---- */ + if (event) { + p->event.param->cinescope = true; + p->event.param->hide_hud = true; + + if (c->since+(p->phase+2)*line_duration < c->cache.time) { + static const char* text[] = { + "boss_theists_child_line0", + "boss_theists_child_line1", + "boss_theists_child_line2", + }; + if (p->phase < sizeof(text)/sizeof(text[0])) { + const char* v = loresource_text_get( + c->res->lang, text[(size_t) p->phase]); + loplayer_event_param_set_line(p->event.param, v, strlen(v)); + } else { + loplayer_event_param_set_line(p->event.param, "", 0); + } + ++p->phase; + } + } + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (c->since+standup_duration < c->cache.time) { + if (event) { + p->event.param->hide_hud = false; + p->event.param->cinescope = false; + loplayer_event_param_set_line(p->event.param, "", 0); + } + locharacter_theists_child_start_rush_state_(c); + return; + } +} +static void locharacter_theists_child_start_standup_state_( + locharacter_base_t* c) { + assert(c != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STANDUP; + + p->phase = 0; + loeffect_recipient_reset(&c->recipient); + + if (!loeffect_stance_set_has( + &c->player->status.stances, LOEFFECT_STANCE_ID_REVOLUTIONER)) { + locharacter_event_holder_take_control(&p->event); + } +} + +static void locharacter_theists_child_update_rush_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + static const uint64_t premotion_duration = beat*2; + static const uint64_t whole_duration = beat*4; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + const uint64_t elapsed = c->cache.time - c->since; + + /* ---- motion ---- */ + float t = elapsed*1.f/premotion_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < .1f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->motion_time = 1-powf(1-t*10, 2); + } else if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = 1-powf(1-(t-.1f)/4*10, 2); + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = powf((t-.5f)*2, 4); + } + + /* ---- position ---- */ + vec2_sub(&c->pos, &p->to, &p->from); + c->direction = MATH_SIGN(c->pos.x); + vec2_muleq(&c->pos, powf(t, 2)); + c->pos.y += (1-MATH_ABS(t*2-1))*c->recipient.status.jump*.1f; + vec2_addeq(&c->pos, &p->from); + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (c->since+whole_duration < c->cache.time) { + locharacter_theists_child_start_cooldown_state_(c); + return; + } +} +static void locharacter_theists_child_start_rush_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + static const uint64_t parry = 300; + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time + parry > c->cache.time) { + locharacter_theists_child_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_RUSH; + + const vec2_t* player = &c->cache.player_pos; + + const float diffx = player->x - c->pos.x; + p->from = c->pos; + p->to = vec2( + player->x - MATH_SIGN(diffx)*locharacter_theists_child_size_.x*2, + player->y - .02f); + + const loplayer_combat_attack_t attack = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) beat, + .duration = beat*3, + .knockback = vec2(MATH_SIGN(player->x)*.2f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*2), + }; + loplayer_attack(c->player, &attack); +} + +static void locharacter_theists_child_update_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t premotion_duration = beat; + static const uint64_t attack_duration = beat; + static const uint64_t whole_duration = beat*4; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + const uint64_t elapsed = c->cache.time - c->since; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (elapsed < premotion_duration) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = elapsed*1.f / premotion_duration; + } else { + const uint64_t attack_elapsed = elapsed - premotion_duration; + + float t = 1; + if (attack_elapsed < attack_duration*p->phase) { + t = attack_elapsed%attack_duration*1.f / attack_duration; + } + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = 1-powf(1-t, 4); + } + + /* ---- position ---- */ + if (elapsed < premotion_duration) { + const float t = elapsed*1.f/premotion_duration; + vec2_sub(&c->pos, &p->to, &p->from); + vec2_muleq(&c->pos, t*t); + vec2_addeq(&c->pos, &p->from); + } + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (elapsed >= whole_duration) { + locharacter_theists_child_start_cooldown_state_(c); + return; + } +} +static void locharacter_theists_child_start_combo_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t parry = 200; + + locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + if (c->last_knockback_time + parry > c->cache.time) { + locharacter_theists_child_start_stunned_state_(c); + return; + } + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COMBO; + + const float diffx = c->cache.player_pos.x - c->pos.x; + c->direction = MATH_SIGN(diffx); + + p->phase = 2 + chaos_xorshift(c->since)%2; + p->from = c->pos; + p->to = c->cache.player_pos; + p->to.x -= c->direction*locharacter_theists_child_size_.x*2; + p->to.y -= .02f; + + const loplayer_combat_attack_t attack1 = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) beat, + .duration = beat/2, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*.8f), + }; + loplayer_attack(c->player, &attack1); + + const loplayer_combat_attack_t attack2 = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*2), + .duration = p->phase == 2? beat*1.5: beat/2, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*1.1f), + }; + loplayer_attack(c->player, &attack2); + + if (p->phase >= 3) { + const loplayer_combat_attack_t attack3 = { + .attacker = c->super.super.id, + .start = c->ticker->time + (uint64_t) (beat*3), + .duration = beat/2, + .knockback = vec2(c->direction*.1f, 0), + .effect = loeffect_immediate_damage(c->recipient.status.attack*1.3f), + }; + loplayer_attack(c->player, &attack3); + } +} + +static void locharacter_theists_child_update_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + static const uint64_t duration = beat*4; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + c->cache.bullet_hittest = true; + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.0f/duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t; + + /* ---- state transition ---- */ + if (locharacter_theists_child_reset_if_player_left_(c)) return; + + if (c->since+duration < c->cache.time) { + if (locharacter_event_holder_has_control(&p->event)) { + static const uint64_t dur = LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION; + if (p->event.start_time+dur < c->cache.time) { + locharacter_theists_child_finalize_event_(c); + return; + } + } else { + if (c->recipient.madness <= 0) { + locharacter_theists_child_start_dead_state_(c); + return; + } + } + + vec2_t diff; + vec2_sub(&diff, &c->cache.player_pos, &c->pos); + if (vec2_pow_length(&diff) < .5f*.5f) { + locharacter_theists_child_start_combo_state_(c); + return; + } + + locharacter_theists_child_start_rush_state_(c); + return; + } +} +static void locharacter_theists_child_start_cooldown_state_( + locharacter_base_t* c) { + assert(c != NULL); + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_COOLDOWN; + + bool skip_firing = false; + if (locharacter_event_holder_has_control(&p->event)) { + const float beat = + (c->cache.time - p->event.start_time)/LOCHARACTER_THEISTS_CHILD_BEAT; + skip_firing = + LOCHARACTER_THEISTS_CHILD_MELODY_B_START_BEAT <= beat && + beat < LOCHARACTER_THEISTS_CHILD_MELODY_B_END_BEAT; + } + if (!skip_firing) locharacter_theists_child_fire_bullets_(c); +} + +static void locharacter_theists_child_update_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + static const uint64_t duration = beat*4; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/duration; + t *= 6; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + if (t < 1) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->motion_time = 1-powf(1-t, 6); + } else { + t = (t-1)/5; + if (t > 1) t = 1; + t = t*t*(3-2*t); + + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + locharacter_theists_child_start_cooldown_state_(c); + return; + } +} +static void locharacter_theists_child_start_stunned_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_STUNNED; + + loeffect_recipient_apply_effect( + &c->recipient, &loeffect_immediate_damage(1.f)); +} + +static void locharacter_theists_child_update_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + static const uint64_t anime_duration = 4000; + static const uint64_t duration = 30000; + + c->cache.gravity = true; + + /* ---- motion ---- */ + float t = (c->cache.time - c->since)*1.f/anime_duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &c->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t*t; + instance->color.w = 1-t; + + /* ---- state transition ---- */ + if (c->since+duration < c->cache.time) { + c->pos = vec2(0, 0); + locharacter_theists_child_start_wait_state_(c); + return; + } +} +static void locharacter_theists_child_start_dead_state_( + locharacter_base_t* c) { + assert(c != NULL); + + c->since = c->cache.time; + c->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(c->player, .5f); +} + +bool locharacter_theists_child_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_theists_child_size_; + static const vec4_t color = vec4(.05f, 0, 0, 1); + static const float height = size.y * 1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + + loeffect_recipient_update( + &base->recipient, &locharacter_theists_child_base_status_); + + if (!locharacter_event_holder_update(&p->event)) { + locharacter_theists_child_start_wait_state_(base); + } + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_CAVIA, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_theists_child_update_wait_state_(base); + break; + case LOCHARACTER_STATE_STANDUP: + locharacter_theists_child_update_standup_state_(base); + break; + case LOCHARACTER_STATE_RUSH: + locharacter_theists_child_update_rush_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_theists_child_update_combo_state_(base); + break; + case LOCHARACTER_STATE_COOLDOWN: + locharacter_theists_child_update_cooldown_state_(base); + break; + case LOCHARACTER_STATE_STUNNED: + locharacter_theists_child_update_stunned_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_theists_child_update_dead_state_(base); + break; + default: + locharacter_theists_child_start_wait_state_(base); + } + locharacter_theists_child_update_passive_action_(base); + + base->cache.height = height; + + base->cache.instance.marker = !!base->cache.bullet_hittest; + base->cache.instance.size.x *= base->direction; + return true; +} + +void locharacter_theists_child_build(locharacter_base_t* base, loentity_id_t ground) { + assert(base != NULL); + + base->type = LOCHARACTER_TYPE_THEISTS_CHILD; + + base->ground = ground; + + base->pos = vec2(0, 0); + base->direction = 1; + + base->state = LOCHARACTER_STATE_WAIT; + base->since = base->cache.time; + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + *p = (typeof(*p)) {0}; + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_theists_child, + base, + LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION, + 0); +} + +void locharacter_theists_child_tear_down(locharacter_base_t* base) { + assert(base != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + locharacter_event_holder_deinitialize(&p->event); +} + +void locharacter_theists_child_pack_data( + const locharacter_base_t* base, msgpack_packer* packer) { + assert(base != NULL); + assert(packer != NULL); + + const locharacter_theists_child_param_t* p = (typeof(p)) base->data; + + msgpack_pack_map(packer, LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \ + } while (0) + + LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool locharacter_theists_child_unpack_data( + locharacter_base_t* base, const msgpack_object* obj) { + assert(base != NULL); + + locharacter_theists_child_param_t* p = (typeof(p)) base->data; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \ + return false; \ + } \ + } while (0) + + LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(unpack_); + +# undef unpack_ + +# undef item_ + + locharacter_event_holder_initialize( + &p->event, + &base->res->music.boss_theists_child, + base, + LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION, + p->event.start_time); + return true; +} diff --git a/core/locharacter/theists_child.h b/core/locharacter/theists_child.h new file mode 100644 index 0000000..3b796b2 --- /dev/null +++ b/core/locharacter/theists_child.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +bool +locharacter_theists_child_update( + locharacter_base_t* base +); + +void +locharacter_theists_child_build( + locharacter_base_t* base, + loentity_id_t ground +); + +void +locharacter_theists_child_tear_down( + locharacter_base_t* base +); + +void +locharacter_theists_child_pack_data( + const locharacter_base_t* base, + msgpack_packer* packer +); + +bool +locharacter_theists_child_unpack_data( + locharacter_base_t* base, + const msgpack_object* obj +); diff --git a/core/locharacter/theists_child.private.h b/core/locharacter/theists_child.private.h new file mode 100644 index 0000000..5ca34c2 --- /dev/null +++ b/core/locharacter/theists_child.private.h @@ -0,0 +1,150 @@ +static void locharacter_theists_child_update_passive_action_( + locharacter_base_t* c) { + assert(c != NULL); + + static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT; + + const locharacter_theists_child_param_t* p = (typeof(p)) c->data; + if (!locharacter_event_holder_has_control(&p->event)) return; + + const uint64_t dt = c->cache.time - c->last_update_time; + const uint64_t t = c->cache.time - p->event.start_time; + + const float beats = t/beat; + const float last_beats = t > dt? (t-dt)/beat: 0; + +# define name_pos_(name, x, y) \ + locommon_position_t name = c->cache.ground->super.pos; \ + vec2_addeq(&name.fract, &vec2(x, y)); \ + locommon_position_reduce(&name); + + name_pos_(top, 0, .8f); + name_pos_(lefttop, -.25f, .8f); + name_pos_(righttop, .25f, .8f); + name_pos_(center, 0, .25f); + name_pos_(left, -.3f, .2f); + name_pos_(right, .3f, .2f); + +# undef name_pos_ + +# define trigger_on_(x) (last_beats < (x) && beats >= (x)) + + /* ---- intro -> A melody ---- */ + if (trigger_on_(56)) { + for (size_t i = 0; i < 2; ++i) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = i? left: right, + .size = vec2(.05f, .15f), + .angle = -MATH_PI/2, + .color = vec4(1, 1, 1, .8f), + .silent = true, + .beat = beat, + .step = 8, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + if (trigger_on_(64)) { + for (size_t i = 0; i < 2; ++i) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = i? lefttop: righttop, + .size = vec2(.05f, .15f), + .velocity = vec2(0, -1.4f/(beat/1000*2)), + .acceleration = vec2(0, 1/(beat/1000*2)), + .color = vec4(1, 1, 1, .8f), + .duration = beat*2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- B melody ---- */ + for (size_t i = 128, cnt = 0; i < 192; i+=4, ++cnt) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = cnt%2 == 0? left: right, + .size = vec2(.13f, .13f), + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .8f), + .silent = true, + .beat = LOCHARACTER_THEISTS_CHILD_BEAT, + .step = 4, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack/2), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + for (size_t i = 128; i < 192; i+=4) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = top, + .size = vec2(.05f, .2f), + .velocity = vec2(0, -1.4f/(beat/1000*2)), + .acceleration = vec2(0, 1/(beat/1000*2)), + .color = vec4(1, 1, 1, .8f), + .duration = beat*2, + .knockback = .1f, + .effect = loeffect_immediate_damage( + c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + + /* ---- fill-in ---- */ + if (trigger_on_(192)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .pos = center, + .size = vec2(.2f, .2f), + .angle = MATH_PI/4, + .color = vec4(1, 1, .4f, .8f), + .silent = true, + .beat = LOCHARACTER_THEISTS_CHILD_BEAT, + .step = 8, + .knockback = .1f, + .effect = loeffect_amnesia( + c->ticker->time + (uint64_t) (8*beat), beat*4), + })); + loentity_store_add(c->entities, &b->super.super); + } + + /* ---- C melody ---- */ + for (size_t i = 200, cnt = 0; i < 232; i+=2, ++cnt) { + if (trigger_on_(i)) { + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) { + .owner = c->super.super.id, + .size = vec2(.16f, .16f), + .pos = cnt%2 == 0? left: right, + .angle = MATH_PI/4, + .color = vec4(1, 1, 1, .8f), + .silent = true, + .beat = LOCHARACTER_THEISTS_CHILD_BEAT, + .step = 2, + .knockback = .1f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + } + } + +# undef trigger_on_ +} diff --git a/core/locharacter/util.c b/core/locharacter/util.c new file mode 100644 index 0000000..d69acac --- /dev/null +++ b/core/locharacter/util.c @@ -0,0 +1,139 @@ +#include "./util.h" + +#include +#include +#include +#include + +#include "util/math/rational.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loresource/music.h" +#include "core/loplayer/event.h" + +#include "./base.h" + +static void locharacter_event_holder_handle_control_lost_( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + + if (holder->music != NULL) { + jukebox_amp_change_volume(&holder->music->amp, 0, &rational(1, 1)); + jukebox_decoder_stop_after(holder->music->decoder, &rational(1, 1)); + } + + holder->param = NULL; + holder->start_time = 0; +} + +void locharacter_event_holder_initialize( + locharacter_event_holder_t* holder, + loresource_music_player_t* music, + locharacter_base_t* owner, + uint64_t duration, + uint64_t start_time) { + assert(holder != NULL); + assert(music != NULL); + assert(owner != NULL); + assert(duration > 0); + + *holder = (typeof(*holder)) { + .music = music, + .owner = owner, + .duration = duration, + .start_time = start_time, + }; +} + +void locharacter_event_holder_deinitialize( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + + locharacter_event_holder_release_control(holder); +} + +bool locharacter_event_holder_take_control( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + assert(holder->owner != NULL); + assert(holder->owner->cache.ground != NULL); + + const locharacter_base_t* owner = holder->owner; + + const bool recover = holder->start_time > 0; + + const uint64_t t = recover? owner->cache.time - holder->start_time: 0; + if (recover && t >= holder->duration) return false; + + holder->param = loplayer_event_take_control( + owner->player->event, owner->super.super.id); + if (holder->param == NULL) return false; + + loplayer_event_param_t* p = holder->param; + + p->area_pos = owner->cache.ground->super.pos; + p->area_pos.fract.y += .4f; + locommon_position_reduce(&p->area_pos); + + p->area_size = vec2(.45f, .45f); + p->music = holder->music; + + if (!recover) { + loentity_character_apply_effect( + &owner->player->entity.super, + &loeffect_curse(owner->ticker->time, holder->duration)); + holder->start_time = owner->cache.time; + } + if (holder->music != NULL) { + jukebox_decoder_play(holder->music->decoder, &rational(t, 1000), false); + jukebox_amp_change_volume(&holder->music->amp, .8f, &rational(1, 1)); + } + return true; +} + +void locharacter_event_holder_release_control( + locharacter_event_holder_t* holder) { + assert(holder != NULL); + + if (holder->param == NULL) return; + + loplayer_event_param_release_control(holder->param); + locharacter_event_holder_handle_control_lost_(holder); +} + +bool locharacter_event_holder_update(locharacter_event_holder_t* holder) { + assert(holder != NULL); + + if (holder->start_time > holder->owner->ticker->time) { + holder->start_time = 0; + } + + loplayer_event_param_t* p = holder->param; + if (p == NULL) { + if (holder->start_time > 0) { + return locharacter_event_holder_take_control(holder); + } + return true; + } + + if (!p->controlled || p->controlled_by != holder->owner->super.super.id) { + locharacter_event_holder_handle_control_lost_(holder); + return false; + } + + if (holder->music != NULL) { + rational_t r; + jukebox_decoder_get_seek_position(holder->music->decoder, &r); + rational_normalize(&r, 1000); + holder->owner->cache.time = r.num + holder->start_time; + } + return true; +} + +bool locharacter_event_holder_has_control( + const locharacter_event_holder_t* holder) { + assert(holder != NULL); + + return holder->param != NULL; +} diff --git a/core/locharacter/util.h b/core/locharacter/util.h new file mode 100644 index 0000000..464ab2a --- /dev/null +++ b/core/locharacter/util.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include "core/loentity/entity.h" +#include "core/loresource/music.h" +#include "core/loplayer/event.h" + +#include "./base.h" + +typedef struct { + loresource_music_player_t* music; + locharacter_base_t* owner; + + uint64_t duration; + + loplayer_event_param_t* param; + uint64_t start_time; +} locharacter_event_holder_t; + +void +locharacter_event_holder_initialize( + locharacter_event_holder_t* holder, + loresource_music_player_t* music, + locharacter_base_t* owner, + uint64_t duration, + uint64_t start_time +); + +void +locharacter_event_holder_deinitialize( + locharacter_event_holder_t* holder +); + +bool +locharacter_event_holder_take_control( + locharacter_event_holder_t* holder +); + +void +locharacter_event_holder_release_control( + locharacter_event_holder_t* holder +); + +bool /* false: event was aborted by others */ +locharacter_event_holder_update( + locharacter_event_holder_t* holder +); + +bool +locharacter_event_holder_has_control( + const locharacter_event_holder_t* holder +); diff --git a/core/locharacter/warder.c b/core/locharacter/warder.c new file mode 100644 index 0000000..784c17a --- /dev/null +++ b/core/locharacter/warder.c @@ -0,0 +1,299 @@ +#include "./warder.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/linear.h" +#include "core/loeffect/recipient.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loplayer/event.h" +#include "core/loplayer/player.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./base.h" +#include "./misc.h" + +static const vec2_t locharacter_warder_size_ = vec2(.02f, .05f); + +static const loeffect_recipient_status_t locharacter_warder_base_status_ = { + .attack = .1f, + .defence = -.8f, +}; + +static void locharacter_warder_shoot_(locharacter_base_t* c) { + assert(c != NULL); + + lobullet_base_t* b = lobullet_pool_create(c->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = c->super.super.id, + .pos = c->super.super.pos, + .size = vec2(.04f, .04f), + .velocity = vec2(c->direction*.5f, 0), + .color = vec4(.6f, .6f, .6f, .8f), + .acceleration = vec2(0, 0), + .duration = 2000, + .knockback = .4f, + .effect = loeffect_immediate_damage(c->recipient.status.attack), + })); + loentity_store_add(c->entities, &b->super.super); + + loresource_sound_play(c->res->sound, "enemy_shoot"); +} + +static void +locharacter_warder_start_wait_state_( + locharacter_base_t* base +); +static void +locharacter_warder_start_shoot_state_( + locharacter_base_t* base +); +static void +locharacter_warder_start_combo_state_( + locharacter_base_t* base +); +static void +locharacter_warder_start_dead_state_( + locharacter_base_t* base +); + +static void locharacter_warder_update_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f / duration; + if (t > 1) t = 1; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = t*t*(3-2*t); + + /* ---- state transition ---- */ + if (elapsed >= duration) { + if (base->recipient.madness <= 0) { + locharacter_warder_start_dead_state_(base); + return; + } + + if (loplayer_event_get_param(base->player->event) == NULL) { + vec2_t disp; + vec2_sub(&disp, &base->cache.player_pos, &base->pos); + disp.x *= base->cache.ground->size.x; + + const float pdist = vec2_pow_length(&disp); + if (MATH_ABS(disp.y) < locharacter_warder_size_.y && pdist < .4f*.4f) { + static const float r = locharacter_warder_size_.x*3; + if (pdist < r*r) { + locharacter_warder_start_combo_state_(base); + } else if (disp.x*base->direction > 0) { + locharacter_warder_start_shoot_state_(base); + } + } + return; + } + } +} +static void locharacter_warder_start_wait_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_WAIT; +} + +static void locharacter_warder_update_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 500; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f / duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_warder_shoot_(base); + locharacter_warder_start_wait_state_(base); + return; + } +} +static void locharacter_warder_start_shoot_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_SHOOT; +} + +static void locharacter_warder_update_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t duration = 1000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + float t = elapsed*1.f/duration; + if (t > 1) t = 1; + t = t*t; + + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (t < .5f) { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->motion_time = t*2; + } else { + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->motion_time = (t-.5f)*2; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + locharacter_warder_start_wait_state_(base); + return; + } +} +static void locharacter_warder_start_combo_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_COMBO; + + const float diff = base->cache.player_pos.x - base->pos.x; + base->direction = MATH_SIGN(diff); + + const loplayer_combat_attack_t attack = { + .attacker = base->super.super.id, + .start = base->ticker->time + 500, + .duration = 500, + .knockback = vec2(base->direction*.1f, 0), + .effect = loeffect_immediate_damage(base->recipient.status.attack), + }; + loplayer_attack(base->player, &attack); +} + +static void locharacter_warder_update_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + static const uint64_t anime_duration = 500; + static const uint64_t duration = 30000; + + const uint64_t elapsed = base->ticker->time - base->since; + + /* ---- motion ---- */ + loshader_character_drawer_instance_t* instance = &base->cache.instance; + if (elapsed > duration - anime_duration) { /* wake up */ + float t = 1-(duration - elapsed)*1.f/anime_duration; + if (t < 0) t = 0; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1; + instance->motion_time = 1-powf(1-t, 2); + } else { /* down */ + float t = elapsed*1.f/anime_duration; + if (t > 1) t = 1; + instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2; + instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN; + instance->motion_time = t; + } + + /* ---- state transition ---- */ + if (elapsed >= duration) { + loeffect_recipient_reset(&base->recipient); + locharacter_warder_start_wait_state_(base); + return; + } +} +static void locharacter_warder_start_dead_state_(locharacter_base_t* base) { + assert(base != NULL); + + base->since = base->ticker->time; + base->state = LOCHARACTER_STATE_DEAD; + + loplayer_gain_faith(base->player, .1f); +} + +bool locharacter_warder_update(locharacter_base_t* base) { + assert(base != NULL); + + static const vec2_t size = locharacter_warder_size_; + static const vec4_t color = vec4(.1f, .1f, .1f, 1); + static const float height = size.y*1.4f; + static const float drawsz = MATH_MAX(size.x, size.y); + + loeffect_recipient_update(&base->recipient, &locharacter_warder_base_status_); + + base->cache.instance = (loshader_character_drawer_instance_t) { + .character_id = LOSHADER_CHARACTER_ID_WARDER, + .marker_offset = vec2(0, height - drawsz), + .pos = vec2(0, drawsz - height), + .size = vec2(drawsz, drawsz), + .color = color, + }; + + switch (base->state) { + case LOCHARACTER_STATE_WAIT: + locharacter_warder_update_wait_state_(base); + break; + case LOCHARACTER_STATE_SHOOT: + locharacter_warder_update_shoot_state_(base); + break; + case LOCHARACTER_STATE_COMBO: + locharacter_warder_update_combo_state_(base); + break; + case LOCHARACTER_STATE_DEAD: + locharacter_warder_update_dead_state_(base); + break; + default: + locharacter_warder_start_wait_state_(base); + } + + base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD; + base->cache.gravity = true; + + base->cache.height = height; + + base->cache.instance.size.x *= base->direction; + base->cache.instance.marker = !!base->cache.bullet_hittest; + return true; +} + +void locharacter_warder_build( + locharacter_base_t* base, const locharacter_warder_param_t* param) { + assert(base != NULL); + assert(param != NULL); + + base->type = LOCHARACTER_TYPE_WARDER; + + base->ground = param->ground; + + base->pos = vec2(param->pos, 0); + base->direction = -MATH_SIGN(param->pos); + if (base->direction == 0) base->direction = 1; + + locharacter_warder_start_wait_state_(base); +} diff --git a/core/locharacter/warder.h b/core/locharacter/warder.h new file mode 100644 index 0000000..b73c498 --- /dev/null +++ b/core/locharacter/warder.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include + +#include "core/loentity/entity.h" + +#include "./base.h" + +typedef struct { + loentity_id_t ground; + float pos; +} locharacter_warder_param_t; + +bool +locharacter_warder_update( + locharacter_base_t* base +); + +void +locharacter_warder_build( + locharacter_base_t* base, + const locharacter_warder_param_t* param +); + +#define locharacter_warder_tear_down(base) + +#define locharacter_warder_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define locharacter_warder_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/locommon/CMakeLists.txt b/core/locommon/CMakeLists.txt new file mode 100644 index 0000000..7b282aa --- /dev/null +++ b/core/locommon/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(locommon + counter.c + easing.c + null.c + physics.c + position.c + ticker.c +) +target_link_libraries(locommon + msgpackc + + coly2d + math + mpkutil +) diff --git a/core/locommon/counter.c b/core/locommon/counter.c new file mode 100644 index 0000000..aaaa571 --- /dev/null +++ b/core/locommon/counter.c @@ -0,0 +1,45 @@ +#include "./counter.h" + +#include +#include + +#include + +#include "util/mpkutil/get.h" + +void locommon_counter_initialize(locommon_counter_t* counter, size_t first) { + assert(counter != NULL); + + *counter = (typeof(*counter)) { + .next = first, + }; +} + +void locommon_counter_deinitialize(locommon_counter_t* counter) { + assert(counter != NULL); + +} + +size_t locommon_counter_count(locommon_counter_t* counter) { + assert(counter != NULL); + + return counter->next++; +} + +void locommon_counter_pack( + const locommon_counter_t* counter, msgpack_packer* packer) { + assert(counter != NULL); + assert(packer != NULL); + + msgpack_pack_uint64(packer, counter->next); +} + +bool locommon_counter_unpack( + locommon_counter_t* counter, const msgpack_object* obj) { + assert(counter != NULL); + + if (!mpkutil_get_uint64(obj, &counter->next)) { + return false; + } + return true; +} diff --git a/core/locommon/counter.h b/core/locommon/counter.h new file mode 100644 index 0000000..38ee923 --- /dev/null +++ b/core/locommon/counter.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +typedef struct { + size_t next; +} locommon_counter_t; + +void +locommon_counter_initialize( + locommon_counter_t* counter, + size_t first +); + +void +locommon_counter_deinitialize( + locommon_counter_t* counter +); + +size_t +locommon_counter_count( + locommon_counter_t* counter +); + +void +locommon_counter_pack( + const locommon_counter_t* counter, + msgpack_packer* packer +); + +bool +locommon_counter_unpack( + locommon_counter_t* counter, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/locommon/easing.c b/core/locommon/easing.c new file mode 100644 index 0000000..dd23010 --- /dev/null +++ b/core/locommon/easing.c @@ -0,0 +1,40 @@ +#include "./easing.h" + +#include +#include + +#include "util/math/algorithm.h" + +void locommon_easing_linear_float(float* v, float ed, float delta) { + assert(v != NULL); + assert(MATH_FLOAT_VALID(*v)); + assert(MATH_FLOAT_VALID(delta)); + assert(delta >= 0); + + const float flag = MATH_SIGN(ed - *v); + *v += flag * delta; + if ((ed-*v)*flag < 0) *v = ed; +} + +void locommon_easing_smooth_float(float* v, float ed, float delta) { + assert(v != NULL); + assert(MATH_FLOAT_VALID(*v)); + assert(MATH_FLOAT_VALID(delta)); + assert(delta >= 0); + + *v += (ed - *v) * MATH_MIN(delta, 1); +} + +void locommon_easing_smooth_position( + locommon_position_t* pos, const locommon_position_t* ed, float delta) { + assert(locommon_position_valid(pos)); + assert(locommon_position_valid(ed)); + assert(MATH_FLOAT_VALID(delta)); + + vec2_t diff; + locommon_position_sub(&diff, ed, pos); + vec2_muleq(&diff, MATH_MIN(delta, 1)); + + vec2_addeq(&pos->fract, &diff); + locommon_position_reduce(pos); +} diff --git a/core/locommon/easing.h b/core/locommon/easing.h new file mode 100644 index 0000000..ecd1073 --- /dev/null +++ b/core/locommon/easing.h @@ -0,0 +1,24 @@ +#pragma once + +#include "./position.h" + +void +locommon_easing_linear_float( + float* v, + float ed, + float delta +); + +void +locommon_easing_smooth_float( + float* v, + float ed, + float delta +); + +void +locommon_easing_smooth_position( + locommon_position_t* pos, + const locommon_position_t* ed, + float delta +); diff --git a/core/locommon/input.h b/core/locommon/input.h new file mode 100644 index 0000000..09edc5a --- /dev/null +++ b/core/locommon/input.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "util/math/vector.h" + +typedef enum { + LOCOMMON_INPUT_BUTTON_LEFT = 1 << 0, + LOCOMMON_INPUT_BUTTON_RIGHT = 1 << 1, + LOCOMMON_INPUT_BUTTON_UP = 1 << 2, + LOCOMMON_INPUT_BUTTON_DOWN = 1 << 3, + LOCOMMON_INPUT_BUTTON_DASH = 1 << 4, + LOCOMMON_INPUT_BUTTON_JUMP = 1 << 5, + LOCOMMON_INPUT_BUTTON_ATTACK = 1 << 6, + LOCOMMON_INPUT_BUTTON_GUARD = 1 << 7, + LOCOMMON_INPUT_BUTTON_MENU = 1 << 8, +} locommon_input_button_t; + +typedef uint16_t locommon_input_buttons_t; + +typedef struct { + locommon_input_buttons_t buttons; + + vec2_t resolution; /* in pixels */ + vec2_t dpi; + vec2_t cursor; /* -1~1 */ +} locommon_input_t; diff --git a/core/locommon/msgpack.h b/core/locommon/msgpack.h new file mode 100644 index 0000000..cd380df --- /dev/null +++ b/core/locommon/msgpack.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/counter.h" +#include "core/locommon/null.h" + +/* THE FOLLOWING INCLUDES DESTROY DEPENDENCY STRUCTURE BETWEEN MODULES. :( */ +#include "core/loeffect/effect.h" +#include "core/loeffect/generic.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loentity/entity.h" + +#define LOCOMMON_MSGPACK_PACK_ANY_(packer, v) _Generic((v), \ + const int32_t*: locommon_msgpack_pack_int32_, \ + const uint64_t*: locommon_msgpack_pack_uint64_, \ + const float*: locommon_msgpack_pack_float_, \ + const bool*: locommon_msgpack_pack_bool_, \ + const char*: locommon_msgpack_pack_str_, \ + const vec2_t*: locommon_msgpack_pack_vec2_, \ + const vec4_t*: locommon_msgpack_pack_vec4_, \ + \ + const locommon_counter_t*: locommon_counter_pack, \ + const locommon_null_t*: locommon_null_pack, \ + const locommon_position_t*: locommon_position_pack, \ + const locommon_ticker_t*: locommon_ticker_pack, \ + \ + const loeffect_t*: loeffect_pack, \ + const loeffect_generic_immediate_param_t*: loeffect_generic_immediate_param_pack, \ + const loeffect_generic_lasting_param_t*: loeffect_generic_lasting_param_pack, \ + const loeffect_recipient_effect_param_t*: loeffect_recipient_effect_param_pack, \ + const loeffect_stance_set_t*: loeffect_stance_set_pack \ + )(v, packer) + +#define LOCOMMON_MSGPACK_PACK_ANY(packer, v) \ + LOCOMMON_MSGPACK_PACK_ANY_(packer, (const typeof(*v)*) (v)) + +#define LOCOMMON_MSGPACK_UNPACK_ANY(obj, v) _Generic((v), \ + int32_t*: locommon_msgpack_unpack_int32_, \ + uint64_t*: locommon_msgpack_unpack_uint64_, \ + float*: locommon_msgpack_unpack_float_, \ + bool*: locommon_msgpack_unpack_bool_, \ + vec2_t*: locommon_msgpack_unpack_vec2_, \ + vec4_t*: locommon_msgpack_unpack_vec4_, \ + \ + locommon_counter_t*: locommon_counter_unpack, \ + locommon_null_t*: locommon_null_unpack, \ + locommon_position_t*: locommon_position_unpack, \ + locommon_ticker_t*: locommon_ticker_unpack, \ + \ + loeffect_t*: loeffect_unpack, \ + loeffect_generic_immediate_param_t*: loeffect_generic_immediate_param_unpack, \ + loeffect_generic_lasting_param_t*: loeffect_generic_lasting_param_unpack, \ + loeffect_recipient_effect_param_t*: loeffect_recipient_effect_param_unpack, \ + loeffect_stance_set_t*: loeffect_stance_set_unpack \ + )(v, obj) + +static inline void locommon_msgpack_pack_int32_( + const int32_t* v, msgpack_packer* packer) { + msgpack_pack_int32(packer, *v); +} +static inline void locommon_msgpack_pack_uint64_( + const uint64_t* v, msgpack_packer* packer) { + msgpack_pack_uint64(packer, *v); +} +static inline void locommon_msgpack_pack_float_( + const float* v, msgpack_packer* packer) { + msgpack_pack_double(packer, *v); +} +static inline void locommon_msgpack_pack_bool_( + const bool* v, msgpack_packer* packer) { + mpkutil_pack_bool(packer, *v); +} +static inline void locommon_msgpack_pack_str_( + const char* str, msgpack_packer* packer) { + mpkutil_pack_str(packer, str); +} +static inline void locommon_msgpack_pack_vec2_( + const vec2_t* v, msgpack_packer* packer) { + mpkutil_pack_vec2(packer, v); +} +static inline void locommon_msgpack_pack_vec4_( + const vec4_t* v, msgpack_packer* packer) { + mpkutil_pack_vec4(packer, v); +} + +static inline bool locommon_msgpack_unpack_int32_( + int32_t* v, const msgpack_object* obj) { + return mpkutil_get_int32(obj, v); +} +static inline bool locommon_msgpack_unpack_uint64_( + uint64_t* v, const msgpack_object* obj) { + return mpkutil_get_uint64(obj, v); +} +static inline bool locommon_msgpack_unpack_float_( + float* v, const msgpack_object* obj) { + return mpkutil_get_float(obj, v); +} +static inline bool locommon_msgpack_unpack_bool_( + bool* v, const msgpack_object* obj) { + return mpkutil_get_bool(obj, v); +} +static inline bool locommon_msgpack_unpack_vec2_( + vec2_t* v, const msgpack_object* obj) { + return mpkutil_get_vec2(obj, v); +} +static inline bool locommon_msgpack_unpack_vec4_( + vec4_t* v, const msgpack_object* obj) { + return mpkutil_get_vec4(obj, v); +} diff --git a/core/locommon/null.c b/core/locommon/null.c new file mode 100644 index 0000000..ee4ec61 --- /dev/null +++ b/core/locommon/null.c @@ -0,0 +1,20 @@ +#include "./null.h" + +#include +#include +#include + +#include + +void locommon_null_pack(const locommon_null_t* null, msgpack_packer* packer) { + assert(null != NULL); + assert(null != NULL); + + msgpack_pack_nil(packer); +} + +bool locommon_null_unpack(locommon_null_t* null, const msgpack_object* obj) { + assert(null != NULL); + + return obj != NULL; +} diff --git a/core/locommon/null.h b/core/locommon/null.h new file mode 100644 index 0000000..8317114 --- /dev/null +++ b/core/locommon/null.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +/* used in some macro templates */ +typedef struct {} locommon_null_t; + +void +locommon_null_pack( + const locommon_null_t* null, + msgpack_packer* packer +); + +bool +locommon_null_unpack( + locommon_null_t* null, + const msgpack_object* obj +); diff --git a/core/locommon/physics.c b/core/locommon/physics.c new file mode 100644 index 0000000..7a22e06 --- /dev/null +++ b/core/locommon/physics.c @@ -0,0 +1,94 @@ +#include "./physics.h" + +#include +#include +#include + +#include "util/coly2d/hittest.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "./position.h" + +bool locommon_physics_entity_valid(const locommon_physics_entity_t* e) { + return + e != NULL && + locommon_position_valid(&e->pos) && + vec2_valid(&e->velocity) && + vec2_valid(&e->size); +} + +bool locommon_physics_solve_collision_with_fixed_one( + locommon_physics_entity_t* e1, + const locommon_physics_entity_t* e2, + float dt) { + assert(locommon_physics_entity_valid(e1)); + assert(locommon_physics_entity_valid(e2)); + assert(vec2_pow_length(&e2->velocity) == 0); + assert(MATH_FLOAT_VALID(dt)); + + vec2_t size; + vec2_add(&size, &e1->size, &e2->size); + + vec2_t pos; + locommon_position_sub(&pos, &e1->pos, &e2->pos); + pos.x /= size.x; + pos.y /= size.y; + + vec2_t velocity = e1->velocity; + velocity.x /= size.x; + velocity.y /= size.y; + + vec2_t disp; + vec2_mul(&disp, &velocity, dt); + + vec2_t spos; + vec2_sub(&spos, &pos, &disp); + + static const vec2_t origin = vec2(0, 0); + static const vec2_t sz = vec2(1, 1); + if (!coly2d_hittest_lineseg_and_rect(&spos, &pos, &origin, &sz)) { + return false; + } + + if (MATH_ABS(spos.x) < 1 && MATH_ABS(spos.y) < 1) { + float* f = MATH_ABS(spos.x) > MATH_ABS(spos.y)? &spos.x: &spos.y; + *f = MATH_SIGN(*f); + } + + vec2_t vt = vec2(MATH_INF, MATH_INF); + if (velocity.x != 0) { + vt.x = -(MATH_SIGN(velocity.x)+spos.x) / velocity.x; + } + if (velocity.y != 0) { + vt.y = -(MATH_SIGN(velocity.y)+spos.y) / velocity.y; + } + + /* ---- simulation ---- */ + float t = MATH_MIN(vt.x, vt.y); + if (t < 0) t = MATH_MAX(vt.x, vt.y); + if (t < 0 || t >= dt) return false; + + vec2_t d, v = velocity, p = spos; + vec2_mul(&d, &v, t); + vec2_addeq(&p, &d); + + if (t == vt.x) v.x = 0; + if (t == vt.y) v.y = 0; + vec2_mul(&d, &v, dt-t); + vec2_addeq(&p, &d); + + /* ---- return result ---- */ + p.x *= size.x; + p.y *= size.y; + v.x *= size.x; + v.y *= size.y; + + e1->pos = e2->pos; + vec2_addeq(&e1->pos.fract, &p); + locommon_position_reduce(&e1->pos); + + e1->velocity = v; + + return true; +} diff --git a/core/locommon/physics.h b/core/locommon/physics.h new file mode 100644 index 0000000..771a8c1 --- /dev/null +++ b/core/locommon/physics.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "util/math/vector.h" + +#include "./position.h" + +typedef struct { + /* input */ + vec2_t size; + + /* input/output */ + locommon_position_t pos; + vec2_t velocity; +} locommon_physics_entity_t; + +bool +locommon_physics_entity_valid( + const locommon_physics_entity_t* e +); + +bool /* whether they were collided */ +locommon_physics_solve_collision_with_fixed_one( + locommon_physics_entity_t* e1, + const locommon_physics_entity_t* e2, + float dt +); diff --git a/core/locommon/position.c b/core/locommon/position.c new file mode 100644 index 0000000..728348a --- /dev/null +++ b/core/locommon/position.c @@ -0,0 +1,83 @@ +#include "./position.h" + +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +int32_t locommon_position_get_district_from_chunk(int32_t x) { + static const int32_t cpd = LOCOMMON_POSITION_CHUNKS_PER_DISTRICT; + return x >= 0? x/cpd: x/cpd-1; +} + +int32_t locommon_position_get_chunk_index_in_district(int32_t x) { + static const int32_t cpd = LOCOMMON_POSITION_CHUNKS_PER_DISTRICT; + return x >= 0? x%cpd: cpd-1-(-x-1)%cpd; +} + +bool locommon_position_valid(const locommon_position_t* a) { + return a != NULL && vec2_valid(&a->fract); +} + +void locommon_position_sub( + vec2_t* a, const locommon_position_t* b, const locommon_position_t* c) { + assert(a != NULL); + assert(locommon_position_valid(b)); + assert(locommon_position_valid(c)); + + vec2_sub(a, &b->fract, &c->fract); + a->x += b->chunk.x - c->chunk.x; + a->y += b->chunk.y - c->chunk.y; +} + +void locommon_position_reduce(locommon_position_t* a) { + assert(locommon_position_valid(a)); + +# define reduce_(e) do { \ + a->chunk.e += (int) a->fract.e; \ + a->fract.e -= (int) a->fract.e; \ + if (a->fract.e < 0) { \ + --a->chunk.e; \ + ++a->fract.e; \ + } \ + } while (0) + + reduce_(x); + reduce_(y); + +# undef reduce_ +} + +void locommon_position_pack( + const locommon_position_t* pos, msgpack_packer* packer) { + assert(locommon_position_valid(pos)); + assert(packer != NULL); + + msgpack_pack_array(packer, 4); + + msgpack_pack_int32(packer, pos->chunk.x); + msgpack_pack_int32(packer, pos->chunk.y); + msgpack_pack_double(packer, pos->fract.x); + msgpack_pack_double(packer, pos->fract.y); +} + +bool locommon_position_unpack( + locommon_position_t* pos, const msgpack_object* obj) { + assert(pos != NULL); + + const msgpack_object_array* root = mpkutil_get_array(obj); + if (root == NULL || root->size != 4 || + !mpkutil_get_int32(&root->ptr[0], &pos->chunk.x) || + !mpkutil_get_int32(&root->ptr[1], &pos->chunk.y) || + !mpkutil_get_float(&root->ptr[2], &pos->fract.x) || + !mpkutil_get_float(&root->ptr[3], &pos->fract.y)) { + return false; + } + return true; +} diff --git a/core/locommon/position.h b/core/locommon/position.h new file mode 100644 index 0000000..c2a01fc --- /dev/null +++ b/core/locommon/position.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +typedef struct { + struct { + int32_t x, y; + } chunk; + vec2_t fract; +} locommon_position_t; + +#define locommon_position(chunk_x, chunk_y, fract) \ + ((locommon_position_t) { {chunk_x, chunk_y, }, fract }) + +#define LOCOMMON_POSITION_CHUNKS_PER_DISTRICT 7 + +int32_t +locommon_position_get_district_from_chunk( + int32_t x +); + +int32_t +locommon_position_get_chunk_index_in_district( + int32_t x +); + +bool +locommon_position_valid( + const locommon_position_t* a +); + +void +locommon_position_sub( + vec2_t* a, + const locommon_position_t* b, + const locommon_position_t* c +); + +void +locommon_position_reduce( + locommon_position_t* a +); + +void +locommon_position_pack( + const locommon_position_t* pos, + msgpack_packer* packer +); + +bool +locommon_position_unpack( + locommon_position_t* pos, + const msgpack_object* obj +); diff --git a/core/locommon/ticker.c b/core/locommon/ticker.c new file mode 100644 index 0000000..92957c8 --- /dev/null +++ b/core/locommon/ticker.c @@ -0,0 +1,46 @@ +#include "./ticker.h" + +#include +#include +#include + +#include + +#include "util/mpkutil/get.h" + +void locommon_ticker_initialize(locommon_ticker_t* ticker, uint64_t time) { + assert(ticker != NULL); + + *ticker = (typeof(*ticker)) { .time = time, }; +} + +void locommon_ticker_deinitialize(locommon_ticker_t* ticker) { + (void) ticker; +} + +void locommon_ticker_tick(locommon_ticker_t* ticker, uint64_t time) { + assert(ticker != NULL); + assert(ticker->time <= time); + + ticker->delta = time - ticker->time; + ticker->delta_f = ticker->delta*1.f / LOCOMMON_TICKER_UNIT; + ticker->time = time; +} + +void locommon_ticker_pack( + const locommon_ticker_t* ticker, msgpack_packer* packer) { + assert(ticker != NULL); + assert(packer != NULL); + + msgpack_pack_uint64(packer, ticker->time); +} + +bool locommon_ticker_unpack( + locommon_ticker_t* ticker, const msgpack_object* obj) { + assert(ticker != NULL); + + if (!mpkutil_get_uint64(obj, &ticker->time)) { + return false; + } + return true; +} diff --git a/core/locommon/ticker.h b/core/locommon/ticker.h new file mode 100644 index 0000000..7c79e1a --- /dev/null +++ b/core/locommon/ticker.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +#define LOCOMMON_TICKER_UNIT 1000 + +typedef struct { + uint64_t time; + + int64_t delta; + float delta_f; +} locommon_ticker_t; + +void +locommon_ticker_initialize( + locommon_ticker_t* ticker, + uint64_t time +); + +void +locommon_ticker_deinitialize( + locommon_ticker_t* ticker +); + +void +locommon_ticker_tick( + locommon_ticker_t* ticker, + uint64_t time +); + +void +locommon_ticker_pack( + const locommon_ticker_t* ticker, + msgpack_packer* packer +); + +bool +locommon_ticker_unpack( + locommon_ticker_t* ticker, + const msgpack_object* obj +); diff --git a/core/loeffect/CMakeLists.txt b/core/loeffect/CMakeLists.txt new file mode 100644 index 0000000..66f415e --- /dev/null +++ b/core/loeffect/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(loeffect + effect.c + generic.c + recipient.c + stance.c +) +target_link_libraries(loeffect + msgpackc + + mpkutil + + locommon + loshader +) diff --git a/core/loeffect/effect.c b/core/loeffect/effect.c new file mode 100644 index 0000000..ea6557d --- /dev/null +++ b/core/loeffect/effect.c @@ -0,0 +1,99 @@ +#include "./effect.h" + +#include +#include +#include +#include + +#include + +#include "core/locommon/msgpack.h" + +#include "./generic.h" + +#define LOEFFECT_ID_EACH_(PROC) do { \ + PROC(IMMEDIATE_DAMAGE, "imm-damage", imm); \ + PROC(CURSE, "curse", lasting); \ + PROC(CURSE_TRIGGER, "curse-trigger", null); \ + PROC(AMNESIA, "amnesia", lasting); \ +} while (0) + +const char* loeffect_id_stringify(loeffect_id_t id) { +# define each_(NAME, s, d) do { \ + if (LOEFFECT_ID_##NAME == id) return s; \ + } while(0) + + LOEFFECT_ID_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loeffect_id_unstringify(loeffect_id_t* id, const char* str, size_t len) { + assert(id != NULL); + assert(str != NULL || len == 0); + +# define each_(NAME, s, d) do { \ + if (strncmp(str, s, len) == 0 && s[len] == 0) { \ + *id = LOEFFECT_ID_##NAME; \ + return true; \ + } \ + } while (0) + + LOEFFECT_ID_EACH_(each_); + return false; + +# undef each_ +} + +void loeffect_pack(const loeffect_t* effect, msgpack_packer* packer) { + assert(effect != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "id"); + mpkutil_pack_str(packer, loeffect_id_stringify(effect->id)); + + mpkutil_pack_str(packer, "data"); + +# define each_(NAME, s, d) do { \ + if (effect->id == LOEFFECT_ID_##NAME) { \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &effect->data.d); \ + } \ + } while (0) + + LOEFFECT_ID_EACH_(each_); + +# undef each_ +} + +bool loeffect_unpack(loeffect_t* effect, const msgpack_object* obj) { + assert(effect != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + + const char* idstr; + size_t idstr_len; + if (!mpkutil_get_str(item_("id"), &idstr, &idstr_len) || + !loeffect_id_unstringify(&effect->id, idstr, idstr_len)) { + return false; + } + +# define each_(NAME, s, d) do { \ + if (effect->id == LOEFFECT_ID_##NAME) { \ + return LOCOMMON_MSGPACK_UNPACK_ANY(item_("data"), &effect->data.d); \ + } \ + } while (0) + + LOEFFECT_ID_EACH_(each_); + +# undef each_ + return false; +} diff --git a/core/loeffect/effect.h b/core/loeffect/effect.h new file mode 100644 index 0000000..e4c2256 --- /dev/null +++ b/core/loeffect/effect.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include + +#include + +#include "core/locommon/null.h" + +#include "./generic.h" + +typedef enum { + LOEFFECT_ID_IMMEDIATE_DAMAGE, + + LOEFFECT_ID_CURSE, + /* The curse effect actually does nothing and is just for HUD. + * To kill player immediately, use curse trigger effect.*/ + LOEFFECT_ID_CURSE_TRIGGER, + + LOEFFECT_ID_AMNESIA, + LOEFFECT_ID_LOST, +} loeffect_id_t; + +typedef struct { + loeffect_id_t id; + union { + locommon_null_t null; + loeffect_generic_immediate_param_t imm; + loeffect_generic_lasting_param_t lasting; + } data; +} loeffect_t; + +#define loeffect_immediate_damage(a) \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_IMMEDIATE_DAMAGE, \ + .data = { .imm = { \ + .amount = a, \ + }, }, \ + } ) + +#define loeffect_curse(b, dur) \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_CURSE, \ + .data = { .lasting = { \ + .begin = b, \ + .duration = dur, \ + }, }, \ + } ) + +#define loeffect_curse_trigger() \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_CURSE_TRIGGER, \ + } ) + +#define loeffect_amnesia(b, dur) \ + ((loeffect_t) { \ + .id = LOEFFECT_ID_AMNESIA, \ + .data = { .lasting = { \ + .begin = b, \ + .duration = dur, \ + }, }, \ + } ) + +const char* +loeffect_id_stringify( + loeffect_id_t id +); + +bool +loeffect_id_unstringify( + loeffect_id_t* id, + const char* str, + size_t len +); + +void +loeffect_pack( + const loeffect_t* effect, + msgpack_packer* packer +); + +bool +loeffect_unpack( + loeffect_t* effect, + const msgpack_object* obj +); diff --git a/core/loeffect/generic.c b/core/loeffect/generic.c new file mode 100644 index 0000000..4aaeb99 --- /dev/null +++ b/core/loeffect/generic.c @@ -0,0 +1,81 @@ +#include "./generic.h" + +#include +#include +#include +#include + +#include + +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +void loeffect_generic_immediate_param_pack( + const loeffect_generic_immediate_param_t* param, + msgpack_packer* packer) { + assert(param != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "amount"); + msgpack_pack_double(packer, param->amount); +} + +bool loeffect_generic_immediate_param_unpack( + loeffect_generic_immediate_param_t* param, + const msgpack_object* obj) { + assert(param != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + + const msgpack_object* amount = mpkutil_get_map_item_by_str(root, "amount"); + if (!mpkutil_get_float(amount, ¶m->amount)) { + return false; + } + return true; +} + +void loeffect_generic_lasting_param_pack( + const loeffect_generic_lasting_param_t* param, msgpack_packer* packer) { + assert(param != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 3); + + mpkutil_pack_str(packer, "begin"); + msgpack_pack_uint64(packer, param->begin); + + mpkutil_pack_str(packer, "duration"); + msgpack_pack_uint64(packer, param->duration); + + mpkutil_pack_str(packer, "amount"); + msgpack_pack_double(packer, param->amount); +} + +bool loeffect_generic_lasting_param_unpack( + loeffect_generic_lasting_param_t* param, const msgpack_object* obj) { + assert(param != NULL); + assert(obj != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + + if (!mpkutil_get_uint64(item_("begin"), ¶m->begin)) { + return false; + } + if (!mpkutil_get_uint64(item_("duration"), ¶m->duration)) { + return false; + } + if (!mpkutil_get_float(item_("amount"), ¶m->amount)) { + return false; + } + +# undef item_ + return true; +} diff --git a/core/loeffect/generic.h b/core/loeffect/generic.h new file mode 100644 index 0000000..a66f0da --- /dev/null +++ b/core/loeffect/generic.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +typedef struct { + float amount; +} loeffect_generic_immediate_param_t; + +typedef struct { + uint64_t begin; + uint64_t duration; + float amount; +} loeffect_generic_lasting_param_t; + +void +loeffect_generic_immediate_param_pack( + const loeffect_generic_immediate_param_t* param, + msgpack_packer* packer +); + +bool +loeffect_generic_immediate_param_unpack( + loeffect_generic_immediate_param_t* param, + const msgpack_object* obj +); + +void +loeffect_generic_lasting_param_pack( + const loeffect_generic_lasting_param_t* param, + msgpack_packer* packer +); + +bool +loeffect_generic_lasting_param_unpack( + loeffect_generic_lasting_param_t* param, + const msgpack_object* obj +); diff --git a/core/loeffect/recipient.c b/core/loeffect/recipient.c new file mode 100644 index 0000000..5b7bccd --- /dev/null +++ b/core/loeffect/recipient.c @@ -0,0 +1,132 @@ +#include "./recipient.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/ticker.h" + +#include "./generic.h" + +#define LOEFFECT_RECIPIENT_EFFECT_PARAM_EACH_(PROC) do { \ + PROC(curse); \ +} while (0) +#define LOEFFECT_RECIPIENT_EFFECT_PARAM_COUNT 1 + +void loeffect_recipient_initialize( + loeffect_recipient_t* recipient, const locommon_ticker_t* ticker) { + assert(recipient != NULL); + assert(ticker != NULL); + + *recipient = (typeof(*recipient)) { + .ticker = ticker, + }; + loeffect_recipient_reset(recipient); +} + +void loeffect_recipient_deinitialize(loeffect_recipient_t* recipient) { + assert(recipient != NULL); + +} + +void loeffect_recipient_reset(loeffect_recipient_t* recipient) { + assert(recipient != NULL); + + recipient->madness = 1; + recipient->faith = 1; + + recipient->effects = (typeof(recipient->effects)) {0}; +} + +void loeffect_recipient_apply_effect( + loeffect_recipient_t* recipient, const loeffect_t* effect) { + assert(recipient != NULL); + assert(effect != NULL); + + if (recipient->madness <= 0) return; + + switch (effect->id) { + case LOEFFECT_ID_IMMEDIATE_DAMAGE: + recipient->madness -= + effect->data.imm.amount * (1-recipient->status.defence); + recipient->last_damage = LOEFFECT_ID_IMMEDIATE_DAMAGE; + break; + case LOEFFECT_ID_CURSE: + recipient->effects.curse = effect->data.lasting; + break; + case LOEFFECT_ID_CURSE_TRIGGER: + recipient->madness = 0; + recipient->last_damage = LOEFFECT_ID_CURSE; + break; + case LOEFFECT_ID_AMNESIA: + recipient->effects.amnesia = effect->data.lasting; + break; + default: + ; + } +} + +void loeffect_recipient_update( + loeffect_recipient_t* recipient, const loeffect_recipient_status_t* base) { + assert(recipient != NULL); + assert(base != NULL); + + recipient->status = *base; + + if (recipient->madness > 0 && recipient->faith <= 0) { + recipient->madness -= recipient->ticker->delta_f / 30; + recipient->last_damage = LOEFFECT_ID_LOST; + } + + recipient->madness = MATH_CLAMP(recipient->madness, 0, 1); + recipient->faith = MATH_CLAMP(recipient->faith, 0, 1); +} + +void loeffect_recipient_effect_param_pack( + const loeffect_recipient_effect_param_t* param, + msgpack_packer* packer) { + assert(param != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOEFFECT_RECIPIENT_EFFECT_PARAM_COUNT); + +# define each_(name) do { \ + mpkutil_pack_str(packer, #name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, ¶m->name); \ + } while (0) + + LOEFFECT_RECIPIENT_EFFECT_PARAM_EACH_(each_); + +# undef each_ +} + +bool loeffect_recipient_effect_param_unpack( + loeffect_recipient_effect_param_t* param, const msgpack_object* obj) { + assert(param != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define each_(name) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(#name), ¶m->name)) { \ + param->name = (typeof(param->name)) {0}; \ + } \ + } while (0) + + LOEFFECT_RECIPIENT_EFFECT_PARAM_EACH_(each_); + +# undef each_ +# undef item_ + return true; +} diff --git a/core/loeffect/recipient.h b/core/loeffect/recipient.h new file mode 100644 index 0000000..57f0397 --- /dev/null +++ b/core/loeffect/recipient.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +#include + +#include "core/locommon/ticker.h" + +#include "./effect.h" +#include "./generic.h" + +typedef struct { + float attack; + float defence; + + float speed; /* [chunks/sec] */ + float jump; /* [chunks/sec^2] */ +} loeffect_recipient_status_t; + +typedef struct { + loeffect_generic_lasting_param_t curse; + loeffect_generic_lasting_param_t amnesia; +} loeffect_recipient_effect_param_t; + +typedef struct { + const locommon_ticker_t* ticker; + + float madness; + float faith; + loeffect_id_t last_damage; + + loeffect_recipient_effect_param_t effects; + + loeffect_recipient_status_t status; + +} loeffect_recipient_t; + +void +loeffect_recipient_initialize( + loeffect_recipient_t* recipient, + const locommon_ticker_t* ticker +); + +void +loeffect_recipient_deinitialize( + loeffect_recipient_t* recipient +); + +void +loeffect_recipient_reset( + loeffect_recipient_t* recipient +); + +void +loeffect_recipient_apply_effect( + loeffect_recipient_t* recipient, + const loeffect_t* effect +); + +void +loeffect_recipient_update( + loeffect_recipient_t* recipient, + const loeffect_recipient_status_t* base +); + +void +loeffect_recipient_effect_param_pack( + const loeffect_recipient_effect_param_t* recipient, + msgpack_packer* packer +); + +bool +loeffect_recipient_effect_param_unpack( + loeffect_recipient_effect_param_t* recipient, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loeffect/stance.c b/core/loeffect/stance.c new file mode 100644 index 0000000..3d7a340 --- /dev/null +++ b/core/loeffect/stance.c @@ -0,0 +1,143 @@ +#include "./stance.h" + +#include +#include +#include +#include +#include + +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/loshader/menu_stance.h" + +#include "./recipient.h" + +const char* loeffect_stance_stringify(loeffect_stance_id_t id) { +# define each_(NAME, name) \ + if (id == LOEFFECT_STANCE_ID_##NAME) return #name; + + LOEFFECT_STANCE_EACH(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loeffect_stance_unstringify( + loeffect_stance_id_t* id, const char* str, size_t len) { + assert(id != NULL); + assert(str != NULL || len == 0); + +# define each_(NAME, name) do {\ + if (strncmp(str, #name, len) == 0 && #name[len] == 0) { \ + *id = LOEFFECT_STANCE_ID_##NAME; \ + return true; \ + } \ + } while (0) + + LOEFFECT_STANCE_EACH(each_); + return false; + +# undef each_ +} + +loshader_menu_stance_id_t loeffect_stance_get_id_for_menu_shader( + loeffect_stance_id_t id) { +# define each_(NAME, name) do {\ + if (id == LOEFFECT_STANCE_ID_##NAME) { \ + return LOSHADER_MENU_STANCE_ID_##NAME; \ + } \ + } while (0) + + LOEFFECT_STANCE_EACH(each_); + + assert(false); + return LOSHADER_MENU_STANCE_ID_EMPTY; + +# undef each_ +} + +void loeffect_stance_set_initialize(loeffect_stance_set_t* set) { + assert(set != NULL); + + *set = 1 << LOEFFECT_STANCE_ID_MISSIONARY; +} + +void loeffect_stance_set_deinitialize(loeffect_stance_set_t* set) { + assert(set != NULL); + +} + +void loeffect_stance_set_add( + loeffect_stance_set_t* set, loeffect_stance_id_t id) { + assert(set != NULL); + + *set |= 1 << id; +} + +void loeffect_stance_set_remove( + loeffect_stance_set_t* set, loeffect_stance_id_t id) { + assert(set != NULL); + + *set &= ~(1 << id); +} + +bool loeffect_stance_set_has( + const loeffect_stance_set_t* set, loeffect_stance_id_t id) { + assert(set != NULL); + + return *set & (1 << id); +} + +void loeffect_stance_set_affect_base_status( + const loeffect_stance_set_t* set, + loeffect_recipient_status_t* status) { + assert(set != NULL); + assert(status != NULL); + +} + +void loeffect_stance_set_pack( + const loeffect_stance_set_t* set, msgpack_packer* packer) { + assert(set != NULL); + assert(packer != NULL); + + loeffect_stance_id_t mask = 1; + size_t len = 0; + while (mask <= *set) { + len += !!(*set & mask); + mask <<= 1; + } + msgpack_pack_array(packer, len); + + mask = 1; + size_t i = 0; + while (*set >= mask) { + if (*set & mask) { + mpkutil_pack_str(packer, loeffect_stance_stringify(i)); + } + ++i; + mask <<= 1; + } +} + +bool loeffect_stance_set_unpack( + loeffect_stance_set_t* set, const msgpack_object* obj) { + assert(set != NULL); + + const msgpack_object_array* array = mpkutil_get_array(obj); + if (array == NULL) return false; + + for (size_t i = 0; i < array->size; ++i) { + size_t len; + const char* name; + if (!mpkutil_get_str(&array->ptr[i], &name, &len)) continue; + + loeffect_stance_id_t stance; + if (!loeffect_stance_unstringify(&stance, name, len)) continue; + *set |= 1 << stance; + } + return true; +} diff --git a/core/loeffect/stance.h b/core/loeffect/stance.h new file mode 100644 index 0000000..d9bd933 --- /dev/null +++ b/core/loeffect/stance.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +#include + +#include "core/loshader/menu_stance.h" + +typedef enum { + LOEFFECT_STANCE_ID_MISSIONARY, + LOEFFECT_STANCE_ID_REVOLUTIONER, + LOEFFECT_STANCE_ID_UNFINISHER, + LOEFFECT_STANCE_ID_PHILOSOPHER, + LOEFFECT_STANCE_ID_LENGTH_, +} loeffect_stance_id_t; +_Static_assert(LOEFFECT_STANCE_ID_LENGTH_ < 16); + +typedef uint16_t loeffect_stance_set_t; + +#define LOEFFECT_STANCE_EACH(PROC) do { \ + PROC(MISSIONARY, missionary); \ + PROC(REVOLUTIONER, revolutioner); \ + PROC(UNFINISHER, unfinisher); \ + PROC(PHILOSOPHER, philosopher); \ +} while (0) + +const char* +loeffect_stance_stringify( + loeffect_stance_id_t id +); + +bool +loeffect_stance_unstringify( + loeffect_stance_id_t* id, + const char* str, + size_t len +); + +loshader_menu_stance_id_t +loeffect_stance_get_id_for_menu_shader( + loeffect_stance_id_t id +); + +void +loeffect_stance_set_initialize( + loeffect_stance_set_t* set +); + +void +loeffect_stance_set_deinitialize( + loeffect_stance_set_t* set +); + +void +loeffect_stance_set_add( + loeffect_stance_set_t* set, + loeffect_stance_id_t id +); + +void +loeffect_stance_set_remove( + loeffect_stance_set_t* set, + loeffect_stance_id_t id +); + +bool +loeffect_stance_set_has( + const loeffect_stance_set_t* set, + loeffect_stance_id_t id +); + +void +loeffect_stance_set_pack( + const loeffect_stance_set_t* set, + msgpack_packer* packer +); + +bool +loeffect_stance_set_unpack( + loeffect_stance_set_t* set, + const msgpack_object* obj +); diff --git a/core/loentity/CMakeLists.txt b/core/loentity/CMakeLists.txt new file mode 100644 index 0000000..1e7272a --- /dev/null +++ b/core/loentity/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(loentity + bullet.c + character.c + entity.c + store.c +) +target_link_libraries(loentity + msgpackc + + coly2d + container + math + memory + + locommon +) diff --git a/core/loentity/bullet.c b/core/loentity/bullet.c new file mode 100644 index 0000000..759ebbc --- /dev/null +++ b/core/loentity/bullet.c @@ -0,0 +1,43 @@ +#include "./bullet.h" + +#include +#include +#include + +#include "util/coly2d/shape.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./decl.private.h" + +bool loentity_bullet_affect( + loentity_bullet_t* bullet, loentity_character_t* chara) { + assert(bullet != NULL); + assert(chara != NULL); + + assert(bullet->vtable.affect != NULL); + return bullet->vtable.affect(bullet, chara); +} + +bool loentity_bullet_hittest( + const loentity_bullet_t* bullet, + const locommon_position_t* point, + const vec2_t* velocity, + float dt) { + assert(bullet != NULL); + assert(locommon_position_valid(point)); + assert(vec2_valid(velocity)); + assert(MATH_FLOAT_VALID(dt)); + + vec2_t st; + locommon_position_sub(&st, point, &bullet->super.pos); + + vec2_t ed; + vec2_sub(&ed, velocity, &bullet->velocity); + vec2_muleq(&ed, dt); + vec2_addeq(&ed, &st); + + return coly2d_shape_hittest_lineseg(&bullet->shape, &st, &ed); +} diff --git a/core/loentity/bullet.h b/core/loentity/bullet.h new file mode 100644 index 0000000..b353597 --- /dev/null +++ b/core/loentity/bullet.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "util/coly2d/shape.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./entity.h" + +#include "./decl.private.h" + +typedef struct { + bool + (*affect)( + loentity_bullet_t* bullet, + loentity_character_t* chara + ); +} loentity_bullet_vtable_t; + +struct loentity_bullet_t { + loentity_t super; + + loentity_bullet_vtable_t vtable; + + loentity_id_t owner; + vec2_t velocity; + + coly2d_shape_t shape; +}; + +bool +loentity_bullet_affect( + loentity_bullet_t* bullet, + loentity_character_t* chara +); + +bool +loentity_bullet_hittest( + const loentity_bullet_t* bullet, + const locommon_position_t* point, + const vec2_t* velocity, + float dt +); diff --git a/core/loentity/character.c b/core/loentity/character.c new file mode 100644 index 0000000..c72d873 --- /dev/null +++ b/core/loentity/character.c @@ -0,0 +1,28 @@ +#include "./character.h" + +#include +#include + +#include "core/loeffect/effect.h" + +#include "./entity.h" + +#include "./decl.private.h" + +void loentity_character_apply_effect( + loentity_character_t* chara, const loeffect_t* effect) { + assert(chara != NULL); + assert(effect != NULL); + + assert(chara->vtable.apply_effect != NULL); + chara->vtable.apply_effect(chara, effect); +} + +void loentity_character_knockback( + loentity_character_t* chara, const vec2_t* v) { + assert(chara != NULL); + assert(v != NULL); + + assert(chara->vtable.knockback != NULL); + chara->vtable.knockback(chara, v); +} diff --git a/core/loentity/character.h b/core/loentity/character.h new file mode 100644 index 0000000..650e063 --- /dev/null +++ b/core/loentity/character.h @@ -0,0 +1,40 @@ +#pragma once + +#include "util/math/vector.h" + +#include "core/loeffect/effect.h" + +#include "./entity.h" + +#include "./decl.private.h" + +typedef struct { + void + (*apply_effect)( + loentity_character_t* chara, + const loeffect_t* effect + ); + void + (*knockback)( + loentity_character_t* chara, + const vec2_t* v + ); +} loentity_character_vtable_t; + +struct loentity_character_t { + loentity_t super; + + loentity_character_vtable_t vtable; +}; + +void +loentity_character_apply_effect( + loentity_character_t* chara, + const loeffect_t* effect +); + +void +loentity_character_knockback( + loentity_character_t* chara, + const vec2_t* v +); diff --git a/core/loentity/decl.private.h b/core/loentity/decl.private.h new file mode 100644 index 0000000..8e09592 --- /dev/null +++ b/core/loentity/decl.private.h @@ -0,0 +1,13 @@ +#pragma once + +struct loentity_t; +typedef struct loentity_t loentity_t; + +struct loentity_ground_t; +typedef struct loentity_ground_t loentity_ground_t; + +struct loentity_bullet_t; +typedef struct loentity_bullet_t loentity_bullet_t; + +struct loentity_character_t; +typedef struct loentity_character_t loentity_character_t; diff --git a/core/loentity/entity.c b/core/loentity/entity.c new file mode 100644 index 0000000..026797f --- /dev/null +++ b/core/loentity/entity.c @@ -0,0 +1,44 @@ +#include "./entity.h" + +#include +#include +#include + +#include "./decl.private.h" + +void loentity_delete(loentity_t* entity) { + assert(entity != NULL); + + assert(entity->vtable.delete != NULL); + entity->vtable.delete(entity); +} + +void loentity_die(loentity_t* entity) { + assert(entity != NULL); + + assert(entity->vtable.die != NULL); + entity->vtable.die(entity); +} + +bool loentity_update(loentity_t* entity) { + assert(entity != NULL); + + assert(entity->vtable.update != NULL); + return entity->vtable.update(entity); +} + +void loentity_draw(loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + assert(entity->vtable.draw != NULL); + entity->vtable.draw(entity, basepos); +} + +void loentity_pack(const loentity_t* entity, msgpack_packer* packer) { + assert(entity != NULL); + assert(packer != NULL); + + assert(entity->vtable.pack != NULL); + entity->vtable.pack(entity, packer); +} diff --git a/core/loentity/entity.h b/core/loentity/entity.h new file mode 100644 index 0000000..c6d280e --- /dev/null +++ b/core/loentity/entity.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./decl.private.h" + +typedef struct { + void + (*delete)( + loentity_t* entity + ); + + void + (*die)( + loentity_t* entity + ); + + bool + (*update)( + loentity_t* entity + ); + void + (*draw)( + loentity_t* entity, + const locommon_position_t* basepos + ); + + void + (*pack)( + const loentity_t* entity, + msgpack_packer* packer + ); +} loentity_vtable_t; + +typedef enum { + LOENTITY_SUBCLASS_NONE, + LOENTITY_SUBCLASS_GROUND, + LOENTITY_SUBCLASS_BULLET, + LOENTITY_SUBCLASS_CHARACTER, +} loentity_subclass_t; + +typedef uint64_t loentity_id_t; + +struct loentity_t { + loentity_vtable_t vtable; + loentity_subclass_t subclass; + + loentity_id_t id; + + locommon_position_t pos; + + bool dont_save; +}; + +void +loentity_delete( + loentity_t* entity /* OWNERSHIP */ +); + +void +loentity_die( + loentity_t* entity +); + +bool +loentity_update( + loentity_t* entity +); + +void +loentity_draw( + loentity_t* entity, + const locommon_position_t* basepos +); + +void +loentity_pack( + const loentity_t* entity, + msgpack_packer* packer +); diff --git a/core/loentity/ground.h b/core/loentity/ground.h new file mode 100644 index 0000000..12e79f1 --- /dev/null +++ b/core/loentity/ground.h @@ -0,0 +1,18 @@ +#pragma once + +#include "util/math/vector.h" + +#include "./entity.h" + +#include "./decl.private.h" + +typedef struct { +} loentity_ground_vtable_t; + +struct loentity_ground_t { + loentity_t super; + + loentity_ground_vtable_t vtable; + + vec2_t size; +}; diff --git a/core/loentity/store.c b/core/loentity/store.c new file mode 100644 index 0000000..2716816 --- /dev/null +++ b/core/loentity/store.c @@ -0,0 +1,211 @@ +#include "./store.h" + +#include +#include +#include + +#include "util/container/array.h" +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/physics.h" +#include "core/locommon/position.h" + +#include "./bullet.h" +#include "./character.h" +#include "./entity.h" +#include "./ground.h" + +struct loentity_store_t { + CONTAINER_ARRAY loentity_t** items; +}; + +static void loentity_store_iterator_assign_by_index_( + loentity_store_t* store, loentity_store_iterator_t* itr) { + assert(store != NULL); + assert(itr != NULL); + assert(itr->index < container_array_get_length(store->items)); + + itr->entity = NULL; + itr->ground = NULL; + itr->bullet = NULL; + itr->character = NULL; + + itr->entity = store->items[itr->index]; + switch (itr->entity->subclass) { + case LOENTITY_SUBCLASS_GROUND: + itr->ground = (loentity_ground_t*) itr->entity; + break; + case LOENTITY_SUBCLASS_BULLET: + itr->bullet = (loentity_bullet_t*) itr->entity; + break; + case LOENTITY_SUBCLASS_CHARACTER: + itr->character = (loentity_character_t*) itr->entity; + break; + default: + assert(false); + } +} + +loentity_store_t* loentity_store_new(size_t reserve) { + loentity_store_t* store = memory_new(sizeof(*store)); + *store = (typeof(*store)) {0}; + + container_array_reserve(store->items, reserve); + return store; +} + +void loentity_store_delete(loentity_store_t* store) { + if (store == NULL) return; + + container_array_delete(store->items); + memory_delete(store); +} + +void loentity_store_add( + loentity_store_t* store, loentity_t* entity) { + assert(store != NULL); + assert(entity != NULL); + + const size_t len = container_array_get_length(store->items); + size_t index = 0; + for (; index < len; ++index) { + if (store->items[index] == NULL) break; + } + if (index == len) container_array_insert(store->items, index); + store->items[index] = entity; +} + +loentity_t* loentity_store_remove( + loentity_store_t* store, const loentity_store_iterator_t* itr) { + assert(store != NULL); + assert(itr != NULL); + + assert(itr->index < container_array_get_length(store->items)); + assert(itr->entity != NULL); + assert(itr->entity == store->items[itr->index]); + + store->items[itr->index] = NULL; + return itr->entity; +} + +void loentity_store_clear(loentity_store_t* store) { + assert(store != NULL); + + const size_t len = container_array_get_length(store->items); + for (size_t i = 0; i < len; ++i) { + loentity_t** e = &store->items[i]; + if (*e == NULL) continue; + loentity_delete(*e); + *e = NULL; + } +} + +bool loentity_store_iterate_next( + loentity_store_t* store, loentity_store_iterator_t* itr) { + assert(store != NULL); + assert(itr != NULL); + + ++itr->index; + if (itr->entity == NULL) { + itr->index = 0; + } + itr->entity = NULL; + itr->ground = NULL; + itr->bullet = NULL; + itr->character = NULL; + + const size_t len = container_array_get_length(store->items); + for (; itr->index < len; ++itr->index) { + if (store->items[itr->index] != NULL) break; + } + if (itr->index >= len) return false; + + loentity_store_iterator_assign_by_index_(store, itr); + return true; +} + +bool loentity_store_find_item_by_id( + loentity_store_t* store, loentity_store_iterator_t* itr, loentity_id_t id) { + assert(store != NULL); + assert(itr != NULL); + + const size_t len = container_array_get_length(store->items); + for (itr->index = 0; itr->index < len; ++itr->index) { + loentity_t* e = store->items[itr->index]; + if (e != NULL && e->id == id) { + loentity_store_iterator_assign_by_index_(store, itr); + return true; + } + } + return false; +} + +bool loentity_store_solve_collision_between_ground( + loentity_store_t* store, + locommon_physics_entity_t* e, + float dt) { + assert(store != NULL); + assert(e != NULL); + + bool solved = false; + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(store, &itr)) { + if (itr.ground == NULL) continue; + + const locommon_physics_entity_t g = { + .pos = itr.entity->pos, + .velocity = vec2(0, 0), + .size = itr.ground->size, + }; + if (locommon_physics_solve_collision_with_fixed_one(e, &g, dt)) { + solved = true; + } + } + return solved; +} + +bool loentity_store_affect_bullets_shot_by_others( + loentity_store_t* store, + loentity_character_t* chara, + const vec2_t* velocity, + float dt) { + assert(store != NULL); + assert(chara != NULL); + assert(vec2_valid(velocity)); + assert(MATH_FLOAT_VALID(dt)); + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(store, &itr)) { + if (itr.bullet == NULL || itr.bullet->owner == chara->super.id) continue; + + if (loentity_bullet_hittest(itr.bullet, &chara->super.pos, velocity, dt)) { + if (loentity_bullet_affect(itr.bullet, chara)) return true; + } + } + return false; +} + +bool loentity_store_affect_bullets_shot_by_one( + loentity_store_t* store, + loentity_character_t* chara, + loentity_id_t shooter, + const vec2_t* velocity, + float dt) { + assert(store != NULL); + assert(chara != NULL); + assert(vec2_valid(velocity)); + assert(MATH_FLOAT_VALID(dt)); + + loentity_store_iterator_t itr = {0}; + while (loentity_store_iterate_next(store, &itr)) { + if (itr.bullet == NULL || itr.bullet->owner != shooter) continue; + + if (loentity_bullet_hittest(itr.bullet, &chara->super.pos, velocity, dt)) { + if (loentity_bullet_affect(itr.bullet, chara)) return true; + } + } + return false; +} diff --git a/core/loentity/store.h b/core/loentity/store.h new file mode 100644 index 0000000..f92edd4 --- /dev/null +++ b/core/loentity/store.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include "util/math/vector.h" + +#include "core/locommon/physics.h" +#include "core/locommon/position.h" + +#include "./bullet.h" +#include "./character.h" +#include "./entity.h" +#include "./ground.h" + +struct loentity_store_t; +typedef struct loentity_store_t loentity_store_t; + +typedef struct { + loentity_t* entity; + loentity_ground_t* ground; + loentity_bullet_t* bullet; + loentity_character_t* character; + + size_t index; +} loentity_store_iterator_t; + +loentity_store_t* /* OWNERSHIP */ +loentity_store_new( + size_t reserve +); + +void +loentity_store_delete( + loentity_store_t* store /* OWNERSHIP */ +); + +void +loentity_store_add( + loentity_store_t* store, + loentity_t* entity /* OWNERSHIP */ +); + +loentity_t* /* OWNERSHIP */ +loentity_store_remove( + loentity_store_t* store, + const loentity_store_iterator_t* itr +); + +void +loentity_store_clear( + loentity_store_t* store +); + +bool +loentity_store_iterate_next( + loentity_store_t* store, + loentity_store_iterator_t* itr +); + +bool +loentity_store_find_item_by_id( + loentity_store_t* store, + loentity_store_iterator_t* itr, + loentity_id_t id +); + +bool /* whether the entitiy was collided */ +loentity_store_solve_collision_between_ground( + loentity_store_t* store, + locommon_physics_entity_t* e, + float dt +); + +bool +loentity_store_affect_bullets_shot_by_others( + loentity_store_t* store, + loentity_character_t* chara, + const vec2_t* velocity, + float dt +); + +bool +loentity_store_affect_bullets_shot_by_one( + loentity_store_t* store, + loentity_character_t* chara, + loentity_id_t shooter, + const vec2_t* velocity, + float dt +); diff --git a/core/loground/CMakeLists.txt b/core/loground/CMakeLists.txt new file mode 100644 index 0000000..e0f0c4f --- /dev/null +++ b/core/loground/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(loground + base.c + island.c + misc.c + pool.c +) +target_link_libraries(loground + msgpackc + + math + memory + mpkutil + + locommon + loentity + loshader +) diff --git a/core/loground/base.c b/core/loground/base.c new file mode 100644 index 0000000..b2a84ea --- /dev/null +++ b/core/loground/base.c @@ -0,0 +1,228 @@ +#include "./base.h" + +#include +#include +#include +#include + +#include + +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/loentity/entity.h" +#include "core/loentity/ground.h" +#include "core/loshader/ground.h" + +#include "./island.h" +#include "./misc.h" + +#define LOGROUND_BASE_PARAM_TO_PACK_EACH_(PROC, PROC_str, PROC_type) do { \ + PROC_str ("subclass", "ground"); \ + PROC_type ("type", type); \ + PROC ("id", super.super.id); \ + PROC ("pos", super.super.pos); \ + PROC ("size", super.size); \ +} while (0) +#define LOGROUND_BASE_PARAM_TO_PACK_COUNT 5 + +static void loground_base_delete_(loentity_t* entity) { + assert(entity != NULL); + + loground_base_t* base = (typeof(base)) entity; + if (!base->used) return; + + base->used = false; + +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + loground_##name##_tear_down(base); \ + return; \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +static void loground_base_die_(loentity_t* entity) { + assert(entity != NULL); + +} + +static bool loground_base_update_(loentity_t* entity) { + assert(entity != NULL); + + loground_base_t* base = (typeof(base)) entity; + base->cache = (typeof(base->cache)) {0}; + +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + return loground_##name##_update(base); \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + return false; + +# undef each_ +} + +static void loground_base_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(locommon_position_valid(basepos)); + + loground_base_t* base = (typeof(base)) entity; + + vec2_t p; + locommon_position_sub(&p, &base->super.super.pos, basepos); + vec2_addeq(&base->cache.instance.pos, &p); + + loshader_ground_drawer_add_instance(base->drawer, &base->cache.instance); +} + +static void loground_base_pack_( + const loentity_t* entity, msgpack_packer* packer) { + assert(entity != NULL); + assert(packer != NULL); + + const loground_base_t* base = (typeof(base)) entity; + + msgpack_pack_map(packer, LOGROUND_BASE_PARAM_TO_PACK_COUNT+1); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &base->var); \ + } while (0) +# define pack_str_(name, str) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, str); \ + } while (0) +# define pack_type_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + mpkutil_pack_str(packer, loground_type_stringify(base->var)); \ + } while (0) + + + LOGROUND_BASE_PARAM_TO_PACK_EACH_(pack_, pack_str_, pack_type_); + +# undef pack_type_ +# undef pack_str_ +# undef pack_ + +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + loground_##name##_pack_data(base, packer); \ + return; \ + } \ + } while (0) + + mpkutil_pack_str(packer, "data"); + LOGROUND_TYPE_EACH_(each_); + assert(false); + +# undef each_ +} + +void loground_base_initialize( + loground_base_t* base, loshader_ground_drawer_t* drawer) { + assert(base != NULL); + assert(drawer != NULL); + + *base = (typeof(*base)) { + .super = { + .super = { + .vtable = { + .delete = loground_base_delete_, + .die = loground_base_die_, + .update = loground_base_update_, + .draw = loground_base_draw_, + .pack = loground_base_pack_, + }, + .subclass = LOENTITY_SUBCLASS_GROUND, + }, + }, + .drawer = drawer, + }; +} + +void loground_base_reinitialize(loground_base_t* base, loentity_id_t id) { + assert(base != NULL); + +# define reset_(name, var) do { \ + base->var = (typeof(base->var)) {0}; \ + } while (0) +# define reset_str_(name, str) + + LOGROUND_BASE_PARAM_TO_PACK_EACH_(reset_, reset_str_, reset_); + +# undef reset_str_ +# undef reset_ + + base->super.super.id = id; +} + +void loground_base_deinitialize(loground_base_t* base) { + assert(base != NULL); + + loground_base_delete_(&base->super.super); +} + +bool loground_base_unpack(loground_base_t* base, const msgpack_object *obj) { + assert(base != NULL); + assert(obj != NULL); + + loground_base_reinitialize(base, 0); + /* id will be overwritten below */ + + const char* v; + size_t vlen; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &base->var)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_type_(name, var) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !loground_type_unstringify(&base->var, v, vlen)) { \ + return NULL; \ + } \ + } while (0) +# define unpack_str_(name, str) do { \ + if (!mpkutil_get_str(item_(name), &v, &vlen) || \ + !(strncmp(v, str, vlen) == 0 && str[vlen] == 0)) { \ + return NULL; \ + } \ + } while (0) + + LOGROUND_BASE_PARAM_TO_PACK_EACH_(unpack_, unpack_str_, unpack_type_); + +# undef unpack_str_ +# undef unpack_type_ +# undef unpack_ + + const msgpack_object* data = item_("data"); +# define each_(NAME, name) do { \ + if (base->type == LOGROUND_TYPE_##NAME) { \ + return loground_##name##_unpack_data(base, data); \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + return false; + +# undef each_ + +# undef item_ +} diff --git a/core/loground/base.h b/core/loground/base.h new file mode 100644 index 0000000..8353174 --- /dev/null +++ b/core/loground/base.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include + +#include "core/loentity/entity.h" +#include "core/loentity/ground.h" +#include "core/loshader/ground.h" + +#include "./misc.h" + +typedef struct { + loentity_ground_t super; + bool used; + + /* injected deps */ + loshader_ground_drawer_t* drawer; + + /* params not to be packed */ + struct { + loshader_ground_drawer_instance_t instance; + } cache; + + /* params to be packed (includes id, pos, and size) */ + loground_type_t type; + +# define LOGROUND_BASE_DATA_MAX_SIZE 256 + uint8_t data[LOGROUND_BASE_DATA_MAX_SIZE]; +} loground_base_t; + +void +loground_base_initialize( + loground_base_t* base, + loshader_ground_drawer_t* drawer +); + +void +loground_base_reinitialize( + loground_base_t* base, + loentity_id_t id +); + +void +loground_base_deinitialize( + loground_base_t* base +); + +bool +loground_base_unpack( + loground_base_t* base, + const msgpack_object *obj +); diff --git a/core/loground/island.c b/core/loground/island.c new file mode 100644 index 0000000..f814afe --- /dev/null +++ b/core/loground/island.c @@ -0,0 +1,35 @@ +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./base.h" +#include "./misc.h" + +bool loground_island_update(loground_base_t* base) { + assert(base != NULL); + + base->cache.instance = (loshader_ground_drawer_instance_t) { + .ground_id = LOSHADER_GROUND_ID_ISLAND, + .size = base->super.size, + }; + return true; +} + +void loground_island_build( + loground_base_t* base, + const locommon_position_t* pos, + const vec2_t* size) { + assert(base != NULL); + assert(locommon_position_valid(pos)); + assert(vec2_valid(size)); + assert(size->x >= 0 && size->y >= 0); + + base->type = LOGROUND_TYPE_ISLAND; + + base->super.super.pos = *pos; + base->super.size = *size; +} diff --git a/core/loground/island.h b/core/loground/island.h new file mode 100644 index 0000000..43cce41 --- /dev/null +++ b/core/loground/island.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" + +#include "./base.h" + +bool +loground_island_update( + loground_base_t* base +); + +void +loground_island_build( + loground_base_t* base, + const locommon_position_t* pos, + const vec2_t* size +); + +#define loground_island_tear_down(base) + +#define loground_island_pack_data(base, packer) \ + msgpack_pack_nil(packer) + +#define loground_island_unpack_data(base, obj) \ + (obj != NULL) diff --git a/core/loground/misc.c b/core/loground/misc.c new file mode 100644 index 0000000..1d990bd --- /dev/null +++ b/core/loground/misc.c @@ -0,0 +1,37 @@ +#include "./misc.h" + +#include +#include +#include +#include + +const char* loground_type_stringify(loground_type_t type) { +# define each_(NAME, name) do { \ + if (type == LOGROUND_TYPE_##NAME) return #name; \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loground_type_unstringify( + loground_type_t* type, const char* v, size_t len) { + assert(type != NULL); + assert(v != NULL || len == 0); + +# define each_(NAME, name) do { \ + if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \ + *type = LOGROUND_TYPE_##NAME; \ + return true; \ + } \ + } while (0) + + LOGROUND_TYPE_EACH_(each_); + return false; + +# undef each_ +} diff --git a/core/loground/misc.h b/core/loground/misc.h new file mode 100644 index 0000000..d839899 --- /dev/null +++ b/core/loground/misc.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +/* dont forget to update EACH macro */ +typedef enum { + LOGROUND_TYPE_ISLAND, +} loground_type_t; + +#define LOGROUND_TYPE_EACH_(PROC) do { \ + PROC(ISLAND, island); \ +} while (0) + +const char* +loground_type_stringify( + loground_type_t type +); + +bool +loground_type_unstringify( + loground_type_t* type, + const char* v, + size_t len +); diff --git a/core/loground/pool.c b/core/loground/pool.c new file mode 100644 index 0000000..e704444 --- /dev/null +++ b/core/loground/pool.c @@ -0,0 +1,89 @@ +#include "./pool.h" + +#include +#include +#include +#include + +#include + +#include "util/memory/memory.h" + +#include "core/locommon/counter.h" +#include "core/loshader/ground.h" + +#include "./base.h" + +struct loground_pool_t { + loshader_ground_drawer_t* drawer; + locommon_counter_t* idgen; + + size_t length; + loground_base_t items[1]; +}; + +static size_t loground_pool_find_unused_item_index_( + const loground_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + if (!pool->items[i].used) return i; + } + fprintf(stderr, "ground pool overflow\n"); + abort(); +} + +loground_pool_t* loground_pool_new( + loshader_ground_drawer_t* drawer, + locommon_counter_t* idgen, + size_t length) { + assert(drawer != NULL); + assert(idgen != NULL); + assert(length > 0); + + loground_pool_t* pool = memory_new( + sizeof(*pool) + (length-1)*sizeof(pool->items[0])); + *pool = (typeof(*pool)) { + .drawer = drawer, + .idgen = idgen, + .length = length, + }; + + for (size_t i = 0; i < pool->length; ++i) { + loground_base_initialize(&pool->items[i], drawer); + } + return pool; +} + +void loground_pool_delete(loground_pool_t* pool) { + assert(pool != NULL); + + for (size_t i = 0; i < pool->length; ++i) { + loground_base_deinitialize(&pool->items[i]); + } + memory_delete(pool); +} + +loground_base_t* loground_pool_create(loground_pool_t* pool) { + assert(pool != NULL); + + const size_t i = loground_pool_find_unused_item_index_(pool); + + loground_base_reinitialize( + &pool->items[i], locommon_counter_count(pool->idgen)); + + pool->items[i].used = true; + return &pool->items[i]; +} + +loground_base_t* loground_pool_unpack_item( + loground_pool_t* pool, const msgpack_object* obj) { + assert(pool != NULL); + + const size_t i = loground_pool_find_unused_item_index_(pool); + + if (!loground_base_unpack(&pool->items[i], obj)) return NULL; + + pool->items[i].used = true; + return &pool->items[i]; +} diff --git a/core/loground/pool.h b/core/loground/pool.h new file mode 100644 index 0000000..b16c734 --- /dev/null +++ b/core/loground/pool.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/counter.h" +#include "core/loshader/ground.h" + +#include "./base.h" + +struct loground_pool_t; +typedef struct loground_pool_t loground_pool_t; + +loground_pool_t* /* OWNERSHIP */ +loground_pool_new( + loshader_ground_drawer_t* drawer, + locommon_counter_t* idgen, + size_t length +); + +void +loground_pool_delete( + loground_pool_t* pool /* OWNERSHIP */ +); + +loground_base_t* /* OWNERSHIP */ +loground_pool_create( + loground_pool_t* pool +); + +loground_base_t* /* OWNERSHIP/NULLABLE */ +loground_pool_unpack_item( + loground_pool_t* pool, + const msgpack_object* obj +); diff --git a/core/loplayer/CMakeLists.txt b/core/loplayer/CMakeLists.txt new file mode 100644 index 0000000..0a5b3f6 --- /dev/null +++ b/core/loplayer/CMakeLists.txt @@ -0,0 +1,28 @@ +add_library(loplayer + action.c + camera.c + combat.c + controller.c + entity.c + event.c + hud.c + menu.c + player.c + status.c +) +target_link_libraries(loplayer + msgpackc + + conv + glyphas + math + memory + mpkutil + + lobullet + locommon + loeffect + loentity + loresource + loshader +) diff --git a/core/loplayer/action.c b/core/loplayer/action.c new file mode 100644 index 0000000..1f1baea --- /dev/null +++ b/core/loplayer/action.c @@ -0,0 +1,880 @@ +#include "./action.h" + +#include +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/base.h" +#include "core/lobullet/linear.h" +#include "core/lobullet/pool.h" +#include "core/locommon/easing.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/effect.h" +#include "core/loeffect/stance.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loresource/sound.h" +#include "core/loresource/text.h" + +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +struct loplayer_action_t { + loresource_set_t* res; + const locommon_ticker_t* ticker; + lobullet_pool_t* bullets; + loentity_store_t* entities; + + loplayer_event_t* event; + loplayer_status_t* status; + loplayer_entity_t* entity; + const loplayer_controller_t* controller; + loplayer_combat_t* combat; + loplayer_camera_t* camera; + loplayer_hud_t* hud; + loplayer_menu_t* menu; + + union { + struct { + } stand; + struct { + float direction; + } moving; + struct { + float direction; + } dodge; + struct { + } combat; + struct { + } shoot; + struct { + } dead; + struct { + bool invincible; + } menu; + } state; + uint64_t since; + + void + (*execute)( + loplayer_action_t* action + ); + void + (*pack)( + const loplayer_action_t* action, + msgpack_packer* packer + ); +}; + +#define LOPLAYER_ACTION_STATE_EACH_(PROC) do { \ + PROC(stand); \ + PROC(moving); \ + PROC(dodge); \ + PROC(combat); \ + PROC(shoot); \ + PROC(dead); \ + PROC(menu); \ +} while (0) + +static void +loplayer_action_start_stand_state_( + loplayer_action_t* action +); +static void +loplayer_action_start_moving_state_( + loplayer_action_t* action, + float direction +); +static void +loplayer_action_start_dodge_state_( + loplayer_action_t* action, + float direction +); +static void +loplayer_action_start_combat_state_( + loplayer_action_t* action +); +static bool +loplayer_action_start_shoot_state_( + loplayer_action_t* action +); +static void +loplayer_action_start_dead_state_( + loplayer_action_t* action +); +static void +loplayer_action_start_menu_state_( + loplayer_action_t* action, + bool invincible +); + +static void loplayer_action_affect_bullet_(loplayer_action_t* action) { + assert(action != NULL); + + if (action->status->bullet_immune_until > action->ticker->time) { + return; + } + if (loplayer_entity_affect_bullet(action->entity)) { + action->status->bullet_immune_until = action->ticker->time + 200; + } +} +static bool loplayer_action_shoot_bullet_(loplayer_action_t* action) { + assert(action != NULL); + + static const float consume = .05f; + + float* f = &action->status->recipient.faith; + if (*f <= 0) return false; + + vec2_t v; + locommon_position_sub( + &v, &action->controller->looking, &action->entity->super.super.pos); + const float vlen = vec2_length(&v); + if (vlen == 0) { + v = vec2(action->entity->direction, 0); + } else { + vec2_diveq(&v, vec2_length(&v)); + } + + /* TODO(catfoot): diffusion */ + vec2_muleq(&v, 1.f); + vec2_addeq(&v, &action->entity->last_velocity); + + lobullet_base_t* b = lobullet_pool_create(action->bullets); + lobullet_linear_light_build(b, (&(lobullet_linear_param_t) { + .owner = action->entity->super.super.id, + .pos = action->entity->super.super.pos, + .size = vec2(.015f, .015f), + .velocity = v, + .acceleration = vec2(0, -.1f), + .color = vec4(.8f, .8f, .8f, .8f), + .duration = 2000, + .knockback = .1f, + .effect = loeffect_immediate_damage( + action->status->recipient.status.attack/2), + })); + loentity_store_add(action->entities, &b->super.super); + + *f -= consume; + if (*f < 0) *f = 0; + return true; +} + +static void loplayer_action_show_tutorial_after_death_(loplayer_action_t* action) { + assert(action != NULL); + +# define text_(name) loresource_text_get(action->res->lang, name) +# define popup_(name) \ + loplayer_menu_popup( \ + action->menu, \ + text_("tutorial_title_"name), \ + text_("tutorial_text_" name)) + + switch (action->status->recipient.last_damage) { + case LOEFFECT_ID_IMMEDIATE_DAMAGE: + popup_("dead_by_combat"); + break; + case LOEFFECT_ID_CURSE: + popup_("dead_by_curse"); + break; + case LOEFFECT_ID_LOST: + popup_("dead_by_lost"); + break; + default: + return; + } + +# undef popup_ +# undef text_ + + loplayer_action_start_menu_popup_state(action); +} + +static void loplayer_action_execute_stand_state_(loplayer_action_t* action) { + assert(action != NULL); + + const float max_acceleration_ = action->entity->on_ground? 2.0f: 0.5f; + + loplayer_entity_aim(action->entity, &action->controller->looking); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_combat_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + if (loplayer_action_start_shoot_state_(action)) return; + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + loplayer_action_start_dodge_state_(action, action->entity->direction); + return; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + if (action->entity->movement.x == 0) { + loplayer_menu_show_status(action->menu); + loplayer_action_start_menu_state_(action, false /* INVINCIBLE */); + return; + } + break; + } + + switch (action->controller->movement) { + case LOPLAYER_CONTROLLER_MOVEMENT_NONE: + break; + case LOPLAYER_CONTROLLER_MOVEMENT_JUMP: + if (action->entity->on_ground) { + action->entity->gravity += action->status->recipient.status.jump; + } + break; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT: + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT: + loplayer_action_start_moving_state_(action, -1); + return; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT: + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT: + loplayer_action_start_moving_state_(action, 1); + return; + } + + const float t = (action->ticker->time - action->since)%2000/1000.0f - 1; + action->entity->motion.time = t*t*(3-2*MATH_ABS(t)); + action->entity->motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1; + action->entity->motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND2; + + locommon_easing_linear_float( + &action->entity->movement.x, + 0, + max_acceleration_ * action->ticker->delta_f); +} +static void loplayer_action_pack_stand_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "stand"); +} +static bool loplayer_action_unpack_stand_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static void loplayer_action_start_stand_state_(loplayer_action_t* action) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_stand_state_; + action->pack = loplayer_action_pack_stand_state_; + + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); +} + +static void loplayer_action_execute_moving_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const float backwalk_attenuation_ = 0.8f; + static const float dash_speed_ = 1.4f; + + const float max_acceleration_ = action->entity->on_ground? 2.4f: 0.8f; + + const float dir = action->state.moving.direction; + + loplayer_entity_aim(action->entity, &action->controller->looking); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_combat_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + if (loplayer_action_start_shoot_state_(action)) return; + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + loplayer_action_start_dodge_state_(action, dir); + return; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + break; + } + + float max_speed = action->status->recipient.status.speed; + float control_dir = dir; + + switch (action->controller->movement) { + case LOPLAYER_CONTROLLER_MOVEMENT_NONE: + loplayer_action_start_stand_state_(action); + return; + case LOPLAYER_CONTROLLER_MOVEMENT_JUMP: + if (action->entity->on_ground) { + action->entity->gravity += action->status->recipient.status.jump; + } + return; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT: + control_dir = -1; + break; + case LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT: + control_dir = 1; + break; + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT: + max_speed *= dash_speed_; + control_dir = -1; + break; + case LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT: + max_speed *= dash_speed_; + control_dir = 1; + break; + } + if (control_dir * dir < 0) { + loplayer_action_start_stand_state_(action); + return; + } + + if (dir * action->entity->direction < 0) { + max_speed *= backwalk_attenuation_; + } + + if (action->entity->on_ground) { + const int32_t p = 70/max_speed; + const float t = (action->ticker->time - action->since)%p*2.0f/p - 1; + action->entity->motion.time = MATH_ABS(t); + action->entity->motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1; + action->entity->motion.to = LOSHADER_CHARACTER_MOTION_ID_WALK; + } + + locommon_easing_linear_float( + &action->entity->movement.x, + max_speed*dir, + max_acceleration_ * action->ticker->delta_f); +} +static void loplayer_action_pack_moving_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "moving"); + + mpkutil_pack_str(packer, "direction"); + msgpack_pack_double(packer, action->state.moving.direction); +} +static bool loplayer_action_unpack_moving_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + const msgpack_object* direction = + mpkutil_get_map_item_by_str(root, "direction"); + if (!mpkutil_get_float(direction, &action->state.moving.direction)) { + return false; + } + return true; +} +static void loplayer_action_start_moving_state_( + loplayer_action_t* action, float dir) { + assert(action != NULL); + assert(MATH_FLOAT_VALID(dir)); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_moving_state_; + action->pack = loplayer_action_pack_moving_state_; + + action->state = (typeof(action->state)) { + .moving = { + .direction = dir, + }, + }; + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); +} + +static void loplayer_action_execute_dodge_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const uint64_t duration_ = 200; + static const float start_speed_ = 0.6f; + static const float end_speed_ = 0.1f; + + if (action->since + duration_ <= action->ticker->time) { + loplayer_combat_drop_all_attacks(action->combat); + loplayer_action_start_stand_state_(action); + return; + } + + const float dir = action->state.dodge.direction; + + vec2_t* v = &action->entity->movement; + + const float t = (action->ticker->time - action->since)*1.0f/duration_; + const float r = 1 - powf(1-t, 1.5); + v->x = (r * (start_speed_-end_speed_) + end_speed_) * dir; + v->y = 0; + + action->entity->motion.time = 1-powf(1-t, 2); + action->entity->motion.from = LOSHADER_CHARACTER_MOTION_ID_WALK; + action->entity->motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND1; +} +static void loplayer_action_pack_dodge_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "dodge"); + + mpkutil_pack_str(packer, "direction"); + msgpack_pack_double(packer, action->state.dodge.direction); +} +static bool loplayer_action_unpack_dodge_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + const msgpack_object* direction = + mpkutil_get_map_item_by_str(root, "direction"); + if (!mpkutil_get_float(direction, &action->state.moving.direction)) { + return false; + } + return true; +} +static void loplayer_action_start_dodge_state_( + loplayer_action_t* action, float dir) { + assert(action != NULL); + assert(MATH_FLOAT_VALID(dir)); + + action->since = action->ticker->time; + action->state = (typeof(action->state)) { + .moving = { + .direction = dir, + }, + }; + action->execute = loplayer_action_execute_dodge_state_; + action->pack = loplayer_action_pack_dodge_state_; + + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); + loplayer_combat_drop_all_attacks(action->combat); + + loresource_sound_play(action->res->sound, "dodge"); +} + +static void loplayer_action_execute_combat_state_(loplayer_action_t* action) { + assert(action != NULL); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (!loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_stand_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + loplayer_action_start_dodge_state_(action, action->entity->direction); + return; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + loplayer_combat_guard(action->combat); + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + loplayer_combat_unguard(action->combat); + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + break; + } + + action->entity->gravity = 0; + + const float klen = vec2_length(&action->entity->knockback); + if (klen > .1f) vec2_muleq(&action->entity->knockback, .1f/klen); +} +static void loplayer_action_pack_combat_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "combat"); +} +static bool loplayer_action_unpack_combat_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static void loplayer_action_start_combat_state_(loplayer_action_t* action) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_combat_state_; + action->pack = loplayer_action_pack_combat_state_; + + action->entity->movement = vec2(0, 0); + + action->camera->state = LOPLAYER_CAMERA_STATE_COMBAT; + loplayer_hud_show(action->hud); +} + +static void loplayer_action_execute_shoot_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const uint64_t duration = 300; + static const float max_acceleration = 1.f; + + loplayer_entity_aim(action->entity, &action->controller->looking); + + if (action->status->recipient.madness <= 0) { + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_action_start_combat_state_(action); + return; + } + + if (action->since+duration <= action->ticker->time) { + if (loplayer_action_shoot_bullet_(action)) { + loresource_sound_play(action->res->sound, "player_shoot"); + } + loplayer_action_start_stand_state_(action); + return; + } + + const float a = max_acceleration * action->ticker->delta_f; + locommon_easing_linear_float(&action->entity->movement.x, 0, a); + locommon_easing_linear_float(&action->entity->movement.y, 0, a); +} +static void loplayer_action_pack_shoot_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "shoot"); +} +static bool loplayer_action_unpack_shoot_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static bool loplayer_action_start_shoot_state_(loplayer_action_t* action) { + assert(action != NULL); + + if (!loeffect_stance_set_has( + &action->status->stances, LOEFFECT_STANCE_ID_REVOLUTIONER)) { + return false; + } + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_shoot_state_; + action->pack = loplayer_action_pack_shoot_state_; + + action->camera->state = LOPLAYER_CAMERA_STATE_DEFAULT; + loplayer_hud_show(action->hud); + + loresource_sound_play(action->res->sound, "player_trigger"); + return true; +} + +static void loplayer_action_execute_dead_state_(loplayer_action_t* action) { + assert(action != NULL); + + static const uint64_t duration_ = 3000; + + if (action->since + duration_ <= action->ticker->time) { + loplayer_entity_move(action->entity, &action->status->respawn_pos); + loplayer_status_reset(action->status); + loplayer_combat_drop_all_attacks(action->combat); + + loplayer_action_start_stand_state_(action); + loplayer_action_show_tutorial_after_death_(action); + loplayer_event_abort(action->event); + return; + } +} +static void loplayer_action_pack_dead_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "dead"); +} +static bool loplayer_action_unpack_dead_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + return root != NULL; +} +static void loplayer_action_start_dead_state_(loplayer_action_t* action) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_dead_state_; + action->pack = loplayer_action_pack_dead_state_; + + action->entity->movement = vec2(0, 0); + + action->camera->state = LOPLAYER_CAMERA_STATE_DEAD; + loplayer_hud_hide(action->hud); + loplayer_combat_drop_all_attacks(action->combat); + + /* Deny all event requests. */ + loplayer_event_abort(action->event); + loplayer_event_take_control(action->event, action->entity->super.super.id); +} + +static void loplayer_action_execute_menu_state_(loplayer_action_t* action) { + assert(action != NULL); + + if (action->status->recipient.madness <= 0) { + loplayer_menu_hide(action->menu); + loplayer_action_start_dead_state_(action); + return; + } + + loplayer_action_affect_bullet_(action); + if (!action->state.menu.invincible && + loplayer_combat_accept_all_attacks(action->combat)) { + loplayer_menu_hide(action->menu); + loplayer_action_start_combat_state_(action); + return; + } + + switch (action->controller->action) { + case LOPLAYER_CONTROLLER_ACTION_NONE: + break; + case LOPLAYER_CONTROLLER_ACTION_ATTACK: + break; + case LOPLAYER_CONTROLLER_ACTION_DODGE: + break; + case LOPLAYER_CONTROLLER_ACTION_GUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_UNGUARD: + break; + case LOPLAYER_CONTROLLER_ACTION_MENU: + loplayer_menu_hide(action->menu); + loplayer_action_start_stand_state_(action); + return; + } +} +static void loplayer_action_pack_menu_state_( + const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "name"); + mpkutil_pack_str(packer, "menu"); + + mpkutil_pack_str(packer, "invincible"); + mpkutil_pack_bool(packer, action->state.menu.invincible); +} +static bool loplayer_action_unpack_menu_state_( + loplayer_action_t* action, const msgpack_object_map* root) { + assert(action != NULL); + + const msgpack_object* invincible = + mpkutil_get_map_item_by_str(root, "invincible"); + if (!mpkutil_get_bool(invincible, &action->state.menu.invincible)) { + return false; + } + return true; +} +static void loplayer_action_start_menu_state_( + loplayer_action_t* action, bool invincible) { + assert(action != NULL); + + action->since = action->ticker->time; + action->execute = loplayer_action_execute_menu_state_; + action->pack = loplayer_action_pack_menu_state_; + + action->entity->movement = vec2(0, 0); + + action->state.menu = (typeof(action->state.menu)) { + .invincible = invincible, + }; + + action->camera->state = LOPLAYER_CAMERA_STATE_MENU; +} + +loplayer_action_t* loplayer_action_new( + loresource_set_t* res, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_event_t* event, + loplayer_status_t* status, + loplayer_entity_t* entity, + loplayer_combat_t* combat, + const loplayer_controller_t* controller, + loplayer_camera_t* camera, + loplayer_hud_t* hud, + loplayer_menu_t* menu) { + assert(res != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(event != NULL); + assert(status != NULL); + assert(entity != NULL); + assert(combat != NULL); + assert(controller != NULL); + assert(camera != NULL); + assert(hud != NULL); + assert(menu != NULL); + + loplayer_action_t* action = memory_new(sizeof(*action)); + *action = (typeof(*action)) { + .res = res, + .ticker = ticker, + .bullets = bullets, + .entities = entities, + .event = event, + .status = status, + .entity = entity, + .combat = combat, + .controller = controller, + .camera = camera, + .hud = hud, + .menu = menu, + }; + loplayer_action_start_stand_state_(action); + return action; +} + +void loplayer_action_delete(loplayer_action_t* action) { + if (action == NULL) return; + + memory_delete(action); +} + +void loplayer_action_start_menu_popup_state(loplayer_action_t* action) { + assert(action != NULL); + + loplayer_action_start_menu_state_(action, true /* invincible */); +} + +void loplayer_action_execute(loplayer_action_t* action) { + assert(action != NULL); + + assert(action->execute != NULL); + action->execute(action); +} + +void loplayer_action_pack(const loplayer_action_t* action, msgpack_packer* packer) { + assert(action != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "since"); + msgpack_pack_uint64(packer, action->since); + + assert(action->pack != NULL); + + mpkutil_pack_str(packer, "state"); + action->pack(action, packer); +} + +bool loplayer_action_unpack( + loplayer_action_t* action, const msgpack_object* obj) { + assert(action != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + + const msgpack_object* since = mpkutil_get_map_item_by_str(root, "since"); + if (!mpkutil_get_uint64(since, &action->since)) return false; + + const msgpack_object_map* state = mpkutil_get_map( + mpkutil_get_map_item_by_str(root, "state")); + + bool state_loaded = false; + + const msgpack_object* name = mpkutil_get_map_item_by_str(state, "name"); + const char* v; + size_t len; + if (!mpkutil_get_str(name, &v, &len)) { + loplayer_action_start_stand_state_(action); + state_loaded = true; + } + +# define unpack_state_(name_) do { \ + if (!state_loaded && strncmp(v, #name_, len) == 0 && #name_[len] == 0) { \ + action->execute = loplayer_action_execute_##name_##_state_; \ + action->pack = loplayer_action_pack_##name_##_state_; \ + if (!loplayer_action_unpack_##name_##_state_(action, state)) { \ + loplayer_action_start_stand_state_(action); \ + } \ + state_loaded = true; \ + } \ + } while (0) + + LOPLAYER_ACTION_STATE_EACH_(unpack_state_); + +# undef unpack_state_ + + return true; +} diff --git a/core/loplayer/action.h b/core/loplayer/action.h new file mode 100644 index 0000000..1eb4817 --- /dev/null +++ b/core/loplayer/action.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include + +#include "core/lobullet/pool.h" +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" + +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +struct loplayer_action_t; +typedef struct loplayer_action_t loplayer_action_t; + +loplayer_action_t* /* OWNERSHIP */ +loplayer_action_new( + loresource_set_t* res, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + loplayer_event_t* event, + loplayer_status_t* status, + loplayer_entity_t* entity, + loplayer_combat_t* combat, + const loplayer_controller_t* controller, + loplayer_camera_t* camera, + loplayer_hud_t* hud, + loplayer_menu_t* menu +); + +void +loplayer_action_delete( + loplayer_action_t* action /* OWNERSHIP */ +); + +void +loplayer_action_start_menu_popup_state( + loplayer_action_t* action +); + +void +loplayer_action_execute( + loplayer_action_t* action +); + +void +loplayer_action_pack( + const loplayer_action_t* action, + msgpack_packer* packer +); + +bool +loplayer_action_unpack( + loplayer_action_t* action, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loplayer/camera.c b/core/loplayer/camera.c new file mode 100644 index 0000000..f442bff --- /dev/null +++ b/core/loplayer/camera.c @@ -0,0 +1,273 @@ +#include "./camera.h" + +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/math/matrix.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +#define LOPLAYER_CAMERA_STATE_EACH_(PROC) do { \ + PROC(DEFAULT, default); \ + PROC(COMBAT, combat); \ + PROC(DEAD, dead); \ + PROC(MENU, menu); \ +} while (0) + +static void loplayer_camera_bind_position_in_area_( + const loplayer_camera_t* camera, + locommon_position_t* pos, + const locommon_position_t* areapos, + const vec2_t* areasize) { + assert(camera != NULL); + assert(locommon_position_valid(pos)); + assert(locommon_position_valid(areapos)); + assert(vec2_valid(areasize)); + + vec2_t szoffset = camera->display_chunksz; + vec2_diveq(&szoffset, camera->scale); + + vec2_t sz; + vec2_sub(&sz, areasize, &szoffset); + + vec2_t v; + locommon_position_sub(&v, pos, areapos); + +# define fix_coordinate_(axis) do { \ + if (sz.axis > 0) { \ + if (MATH_ABS(v.axis) > sz.axis) v.axis = MATH_SIGN(v.axis)*sz.axis; \ + } else { \ + v.axis = 0; \ + } \ + } while (0) + + fix_coordinate_(x); + fix_coordinate_(y); + +# undef fix_coordinate_ + + *pos = *areapos; + vec2_addeq(&pos->fract, &v); + locommon_position_reduce(pos); +} + +const char* loplayer_camera_state_stringify(loplayer_camera_state_t state) { +# define each_(NAME, name) do { \ + if (state == LOPLAYER_CAMERA_STATE_##NAME) return #name; \ + } while (0) + + LOPLAYER_CAMERA_STATE_EACH_(each_); + + assert(false); + return NULL; + +# undef each_ +} + +bool loplayer_camera_state_unstringify( + loplayer_camera_state_t* state, const char* str, size_t len) { + assert(state != NULL); + +# define each_(NAME, name) do { \ + if (strncmp(str, #name, len) == 0 && #name[len] == 0) { \ + *state = LOPLAYER_CAMERA_STATE_##NAME; \ + return true; \ + } \ + } while (0) + + LOPLAYER_CAMERA_STATE_EACH_(each_); + return false; + +# undef each_ +} + +void loplayer_camera_initialize( + loplayer_camera_t* camera, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity, + const mat4_t* proj) { + assert(camera != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(event != NULL); + assert(status != NULL); + assert(entity != NULL); + assert(mat4_valid(proj)); + + mat4_t inv_proj; + mat4_inv(&inv_proj, proj); + + static const vec4_t chunk = vec4(1, 1, 0, 0); + vec4_t chunksz; + mat4_mul_vec4(&chunksz, &inv_proj, &chunk); + + *camera = (typeof(*camera)) { + .shaders = shaders, + .ticker = ticker, + .event = event, + .status = status, + .entity = entity, + + .display_chunksz = chunksz.xy, + + .matrix = mat4_scale(1, 1, 1), + .scale = 1.0f, + + .cinesco = { + .color = vec4(0, 0, 0, 1), + }, + + .state = LOPLAYER_CAMERA_STATE_DEFAULT, + .brightness = 1, + }; +} + +void loplayer_camera_deinitialize(loplayer_camera_t* camera) { + assert(camera != NULL); + +} + +void loplayer_camera_update(loplayer_camera_t* camera) { + assert(camera != NULL); + + const float d = camera->ticker->delta_f; + const loplayer_status_t* stat = camera->status; + + locommon_position_t target = camera->entity->super.super.pos; + + /* ---- movement ---- */ + const loplayer_event_param_t* e = loplayer_event_get_param(camera->event); + if (e != NULL && e->area_size.x > 0 && e->area_size.y > 0) { + loplayer_camera_bind_position_in_area_( + camera, &target, &e->area_pos, &e->area_size); + } + + vec2_t dist; + locommon_position_sub(&dist, &target, &camera->pos); + if (vec2_pow_length(&dist) > 2) camera->pos = target; + + locommon_easing_smooth_position(&camera->pos, &target, d*10); + +# define ease_float_(name, ed, speed) \ + locommon_easing_smooth_float(&camera->name, ed, d*(speed)) + + /* ---- cinema scope ---- */ + ease_float_(cinesco.size, !!(e != NULL && e->cinescope)*.3f, 2); + + /* ---- damage effect ---- */ + const bool damaged = + stat->last_damage_time > 0 && + stat->last_damage_time+500 > camera->ticker->time; + ease_float_(pe.raster, !!damaged*.5f, damaged? 5: 3); + + /* ---- amnesia effect ---- */ + const uint64_t amnesia_st = stat->recipient.effects.amnesia.begin; + const uint64_t amnesia_dur = stat->recipient.effects.amnesia.duration; + ease_float_( + pe.amnesia_displacement, + !!(amnesia_st+amnesia_dur > camera->ticker->time), 5); + + /* ---- dying effect ---- */ + const float dying = stat->dying_effect; + camera->pixsort = dying < .1f? dying/.1f: powf(1-(dying-.1f), 1.5f); + if (camera->pixsort < 0) camera->pixsort = 0; + + /* ---- switch by current state ---- */ + switch (camera->state) { + case LOPLAYER_CAMERA_STATE_DEFAULT: + ease_float_(scale, 1.0f, 10); + ease_float_(pe.whole_blur, 0.0f, 1); + ease_float_(pe.radial_displacement, 0.0f, 5); + ease_float_(pe.radial_fade, 0.5f, 3); + break; + case LOPLAYER_CAMERA_STATE_COMBAT: + ease_float_(scale, 1.5f, 8); + ease_float_(pe.whole_blur, 0.0f, 1); + ease_float_(pe.radial_displacement, 0.6f, 3); + ease_float_(pe.radial_fade, 0.8f, 1); + break; + case LOPLAYER_CAMERA_STATE_DEAD: + ease_float_(scale, 2.0f, 1); + ease_float_(pe.whole_blur, 1.0f, 1); + ease_float_(pe.radial_displacement, 0.3f, .7f); + ease_float_(pe.radial_fade, 1.5f, .7f); + break; + case LOPLAYER_CAMERA_STATE_MENU: + ease_float_(scale, 1.0f, 10); + ease_float_(pe.whole_blur, 0.9f, 1); + ease_float_(pe.radial_displacement, 0.0f, 10); + ease_float_(pe.radial_fade, 0.6f, 1); + break; + } + +# undef ease_float_ + + /* ---- fixed params ---- */ + camera->pe.brightness = camera->brightness; + + /* ---- matrix ---- */ + camera->matrix = mat4_scale(camera->scale, camera->scale, 1); +} + +void loplayer_camera_draw(const loplayer_camera_t* camera) { + assert(camera != NULL); + + loshader_pixsort_drawer_set_intensity( + camera->shaders->drawer.pixsort, camera->pixsort); + + loshader_posteffect_drawer_set_param( + camera->shaders->drawer.posteffect, &camera->pe); + + loshader_cinescope_drawer_set_param( + camera->shaders->drawer.cinescope, &camera->cinesco); +} + +void loplayer_camera_pack( + const loplayer_camera_t* camera, msgpack_packer* packer) { + assert(camera != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 2); + + mpkutil_pack_str(packer, "state"); + mpkutil_pack_str(packer, loplayer_camera_state_stringify(camera->state)); + + mpkutil_pack_str(packer, "pos"); + locommon_position_pack(&camera->pos, packer); +} + +bool loplayer_camera_unpack( + loplayer_camera_t* camera, const msgpack_object* obj) { + assert(camera != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + const char* v; + size_t len; + if (!mpkutil_get_str(item_("state"), &v, &len) || + !loplayer_camera_state_unstringify(&camera->state, v, len)) { + return false; + } + if (!locommon_position_unpack(&camera->pos, item_("pos"))) { + return false; + } +# undef item_ + return true; +} diff --git a/core/loplayer/camera.h b/core/loplayer/camera.h new file mode 100644 index 0000000..b5c58a2 --- /dev/null +++ b/core/loplayer/camera.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include + +#include "util/math/matrix.h" +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loshader/cinescope.h" +#include "core/loshader/posteffect.h" +#include "core/loshader/set.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +typedef enum { + LOPLAYER_CAMERA_STATE_DEFAULT, + LOPLAYER_CAMERA_STATE_COMBAT, + LOPLAYER_CAMERA_STATE_DEAD, + LOPLAYER_CAMERA_STATE_MENU, +} loplayer_camera_state_t; + +typedef struct { + /* injected deps */ + loshader_set_t* shaders; + const locommon_ticker_t* ticker; + const loplayer_event_t* event; + const loplayer_status_t* status; + const loplayer_entity_t* entity; + + /* immutable params */ + vec2_t display_chunksz; + + /* read-only mutable params */ + locommon_position_t pos; + mat4_t matrix; + + float scale; + float pixsort; + loshader_posteffect_drawer_param_t pe; + loshader_cinescope_drawer_param_t cinesco; + + /* public params */ + loplayer_camera_state_t state; + float brightness; + +} loplayer_camera_t; + +const char* +loplayer_camera_state_stringify( + loplayer_camera_state_t state +); + +bool +loplayer_camera_state_unstringify( + loplayer_camera_state_t* state, + const char* str, + size_t len +); + +void +loplayer_camera_initialize( + loplayer_camera_t* camera, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity, + const mat4_t* proj +); + +void +loplayer_camera_deinitialize( + loplayer_camera_t* camera +); + +void +loplayer_camera_update( + loplayer_camera_t* camera +); + +void +loplayer_camera_draw( + const loplayer_camera_t* camera +); + +void +loplayer_camera_pack( + const loplayer_camera_t* camera, + msgpack_packer* packer +); + +bool +loplayer_camera_unpack( + loplayer_camera_t* camera, + const msgpack_object* packer +); diff --git a/core/loplayer/combat.c b/core/loplayer/combat.c new file mode 100644 index 0000000..d1dece5 --- /dev/null +++ b/core/loplayer/combat.c @@ -0,0 +1,503 @@ +#include "./combat.h" + +#include +#include +#include +#include + +#include "util/math/algorithm.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/effect.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/combat_ring.h" + +#include "./entity.h" +#include "./status.h" + +struct loplayer_combat_t { + /* injected deps */ + loresource_sound_t* sound; + const locommon_ticker_t* ticker; + loshader_combat_ring_drawer_t* drawer; + loentity_store_t* entities; + + loplayer_status_t* status; + loplayer_entity_t* entity; + + /* temporary cache for drawing */ + uint64_t ring_start; + uint64_t ring_end; + + uint64_t guard_start; + uint64_t guard_end; + + uint64_t attack_end; + float alpha; + + /* params */ + bool accepted; + + size_t length; + loplayer_combat_attack_t attacks[1]; +}; + +#define LOPLAYER_COMBAT_GUARD_ERROR_THRESHOLD 0.55f + +#define LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("attacker", attacker); \ + PROC("start", start); \ + PROC("duration", duration); \ + PROC("knockback", knockback); \ + PROC("effect", effect); \ +} while (0) +#define LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_COUNT 5 + +static bool loplayer_combat_find_attack_index_in_period_( + loplayer_combat_t* combat, size_t* index, uint64_t st, uint64_t ed) { + assert(combat != NULL); + assert(st <= ed); + + static size_t dummy_; + if (index == NULL) index = &dummy_; + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t ist = combat->attacks[i].start; + const uint64_t ied = combat->attacks[i].duration + ist; + + if (ist < ed && ied > st) { + *index = i; + return true; + } + } + return false; +} + +static bool loplayer_combat_find_unused_attack_index_( + loplayer_combat_t* combat, size_t *index) { + assert(combat != NULL); + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t ed = combat->attacks[i].start + combat->attacks[i].duration; + if (ed <= combat->ticker->time) { + *index = i; + return true; + } + } + return false; +} + +static void loplayer_combat_execute_reflective_attack_( + loplayer_combat_t* combat, + loplayer_combat_attack_t* attack, + loentity_character_t* chara) { + assert(combat != NULL); + assert(attack != NULL); + + if (chara == NULL) return; + + const loeffect_t dmg = + loeffect_immediate_damage(combat->status->recipient.status.attack); + loentity_character_apply_effect(chara, &dmg); + + vec2_t knockback = attack->knockback; + vec2_muleq(&knockback, -1); + loentity_character_knockback(chara, &knockback); + + loresource_sound_play(combat->sound, "reflection"); +} + +static void loplayer_combat_execute_enemy_attack_( + loplayer_combat_t* combat, + loplayer_combat_attack_t* attack, + loentity_character_t* chara) { + assert(combat != NULL); + assert(attack != NULL); + + /* chara can be NULL */ + (void) chara; + + loplayer_status_apply_effect(combat->status, &attack->effect); + loentity_character_knockback(&combat->entity->super, &attack->knockback); +} + +static void loplayer_combat_handle_attack_( + loplayer_combat_t* combat, + loplayer_combat_attack_t* attack, + loentity_character_t* chara) { + assert(combat != NULL); + assert(attack != NULL); + + if (combat->guard_start < combat->guard_end) { + const uint64_t atked = attack->start + attack->duration; + + const uint64_t stdiff = + MATH_MAX(attack->start, combat->guard_start) - + MATH_MIN(attack->start, combat->guard_start); + const uint64_t eddiff = + MATH_MAX(atked, combat->guard_end) - + MATH_MIN(atked, combat->guard_end); + + const float guard_error = (stdiff + eddiff)*1.0f / attack->duration; + + const bool back_attack = + attack->knockback.x * combat->entity->direction > 0; + + float guard_error_thresh = LOPLAYER_COMBAT_GUARD_ERROR_THRESHOLD; + if (back_attack) guard_error_thresh /= 10; + + if (guard_error < guard_error_thresh) { + loplayer_combat_execute_reflective_attack_(combat, attack, chara); + return; + } + } + loplayer_combat_execute_enemy_attack_(combat, attack, chara); +} + +static void loplayer_combat_draw_ring_base_( + const loplayer_combat_t* combat) { + assert(combat != NULL); + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = -1, /* = draw ring base */ + .color = vec4(0, 0, 0, .8f*combat->alpha), + }); +} + +static void loplayer_combat_draw_attacks_(const loplayer_combat_t* combat) { + assert(combat != NULL); + + const uint64_t ring_st = combat->ring_start; + const uint64_t ring_ed = combat->ring_end; + assert(ring_st <= ring_ed); + + const uint64_t ring_dur = ring_ed - ring_st; + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + + if (st < ring_ed && ring_st < ed) { + const uint64_t rst = st - MATH_MIN(ring_st, st); + const uint64_t red = ed - ring_st; + assert(rst <= red); + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = .8f, + .start = rst*1.f/ring_dur, + .end = MATH_MIN(red, ring_dur)*1.f/ring_dur, + .color = vec4(.7f, .1f, .1f, combat->alpha), + }); + } + } +} + +static void loplayer_combat_draw_guard_(const loplayer_combat_t* combat) { + assert(combat != NULL); + + const bool now_guarding = (combat->guard_start > combat->guard_end); + if (!now_guarding && combat->guard_end <= combat->ring_start) { + return; + } + + const float ring_dur = combat->ring_end - combat->ring_start; + assert(ring_dur > 0); + + const uint64_t st = combat->guard_start - + MATH_MIN(combat->ring_start, combat->guard_start); + const uint64_t ed = + (now_guarding? combat->ticker->time: combat->guard_end) - + combat->ring_start; + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = .7f, + .start = st/ring_dur, + .end = ed/ring_dur, + .color = vec4(.1f, .1f, .7f, combat->alpha), + }); +} + +static void loplayer_combat_draw_clockhand_( + const loplayer_combat_t* combat) { + assert(combat != NULL); + + const uint64_t ring_dur = combat->ring_end - combat->ring_start; + assert(ring_dur > 0); + + assert(combat->ticker->time >= combat->ring_start); + const uint64_t cur = combat->ticker->time - combat->ring_start; + const float curf = cur*1.f/ring_dur; + + loshader_combat_ring_drawer_add_instance( + combat->drawer, &(loshader_combat_ring_drawer_instance_t) { + .range = 0, /* = draw clockhand */ + .start = curf, + .color = vec4(1, 1, 1, combat->alpha), + }); +} + +loplayer_combat_t* loplayer_combat_new( + loresource_sound_t* sound, + loshader_combat_ring_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + loplayer_status_t* status, + loplayer_entity_t* entity, + size_t length) { + assert(sound != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(entities != NULL); + assert(status != NULL); + assert(entity != NULL); + assert(length > 0); + + loplayer_combat_t* combat = memory_new( + sizeof(*combat) + (length-1)*sizeof(combat->attacks[0])); + *combat = (typeof(*combat)) { + .sound = sound, + .drawer = drawer, + .ticker = ticker, + .entities = entities, + .status = status, + .entity = entity, + .length = length, + }; + + for (size_t i = 0; i < combat->length; ++i) { + combat->attacks[i] = (typeof(combat->attacks[0])) {0}; + } + return combat; +} + +void loplayer_combat_delete(loplayer_combat_t* combat) { + if (combat == NULL) return; + + memory_delete(combat); +} + +bool loplayer_combat_add_attack( + loplayer_combat_t* combat, const loplayer_combat_attack_t* attack) { + assert(combat != NULL); + assert(attack != NULL); + + if (loplayer_combat_find_attack_index_in_period_( + combat, NULL, attack->start, attack->start + attack->duration)) { + return false; + } + + size_t index; + if (!loplayer_combat_find_unused_attack_index_(combat, &index)) { + return false; + } + + combat->attacks[index] = *attack; + return true; +} + +bool loplayer_combat_accept_all_attacks(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (combat->accepted) return true; + + if (!loplayer_combat_find_attack_index_in_period_( + combat, NULL, combat->ticker->time, SIZE_MAX)) { + return false; + } + + combat->accepted = true; + return true; +} + +void loplayer_combat_drop_all_attacks(loplayer_combat_t* combat) { + assert(combat != NULL); + + for (size_t i = 0; i < combat->length; ++i) { + combat->attacks[i] = (typeof(combat->attacks[0])) {0}; + } + combat->accepted = false; + combat->ring_start = 0; + combat->ring_end = 0; + combat->guard_start = 0; + combat->guard_end = 0; + combat->attack_end = 0; +} + +void loplayer_combat_guard(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (combat->ring_end <= combat->ticker->time || + combat->guard_start > combat->guard_end) { + return; + } + combat->guard_start = combat->ticker->time; + + loresource_sound_play(combat->sound, "guard"); +} + +void loplayer_combat_unguard(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (combat->guard_start <= combat->guard_end) return; + + combat->guard_end = combat->ticker->time; +} + +void loplayer_combat_update(loplayer_combat_t* combat) { + assert(combat != NULL); + + if (!combat->accepted) return; + + const uint64_t cur = combat->ticker->time; + const uint64_t pre = cur - combat->ticker->delta; + + combat->ring_end = 0; + for (size_t i = 0; i < combat->length; ++i) { + loentity_store_iterator_t itr = (typeof(itr)) {0}; + + if (!loentity_store_find_item_by_id( + combat->entities, &itr, combat->attacks[i].attacker)) { + combat->attacks[i].start = 0; + combat->attacks[i].duration = 0; + continue; + } + + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + + const bool pre_active = st <= pre && pre < ed; + const bool cur_active = st <= cur && cur < ed; + if (!pre_active && cur_active) { + combat->attack_end = ed; + } else if (pre_active && !cur_active) { + loplayer_combat_handle_attack_( + combat, &combat->attacks[i], itr.character); + } + combat->ring_end = MATH_MAX(combat->ring_end, ed); + } + if (combat->ring_end > cur) { + if (combat->ring_start == 0) { + combat->ring_start = cur; + } + locommon_easing_smooth_float( + &combat->alpha, 1, combat->ticker->delta_f*10); + } else { + combat->alpha = 0; + loplayer_combat_drop_all_attacks(combat); + } +} + +void loplayer_combat_draw_ui(const loplayer_combat_t* combat) { + assert(combat != NULL); + + if (!combat->accepted || combat->ring_end <= combat->ticker->time) { + return; + } + loplayer_combat_draw_ring_base_(combat); + loplayer_combat_draw_attacks_(combat); + loplayer_combat_draw_guard_(combat); + loplayer_combat_draw_clockhand_(combat); +} + +void loplayer_combat_pack( + const loplayer_combat_t* combat, msgpack_packer* packer) { + assert(combat != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 1); + + mpkutil_pack_str(packer, "attacks"); + + size_t len = 0; + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + const uint64_t cur = combat->ticker->time; + + if (st <= cur && cur < ed) ++len; + } + msgpack_pack_array(packer, len); + + for (size_t i = 0; i < combat->length; ++i) { + const uint64_t st = combat->attacks[i].start; + const uint64_t ed = combat->attacks[i].duration + st; + const uint64_t cur = combat->ticker->time; + + if (st <= cur && cur < ed) { + loplayer_combat_attack_pack(&combat->attacks[i], packer); + } + } +} + +bool loplayer_combat_unpack( + loplayer_combat_t* combat, const msgpack_object* obj) { + assert(combat != NULL); + + loplayer_combat_drop_all_attacks(combat); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + + const msgpack_object_array* array = + mpkutil_get_array(mpkutil_get_map_item_by_str(root, "attacks")); + + size_t src = 0, dst = 0; + while (src < array->size && dst < combat->length) { + if (loplayer_combat_attack_unpack( + &combat->attacks[dst], &array->ptr[src++])) { + ++dst; + } + } + return true; +} + +void loplayer_combat_attack_pack( + const loplayer_combat_attack_t* attack, + msgpack_packer* packer) { + assert(attack != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &attack->var); \ + } while (0) + + LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool loplayer_combat_attack_unpack( + loplayer_combat_attack_t* attack, + const msgpack_object* obj) { + assert(attack != NULL); + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &attack->var)) { \ + return false; \ + } \ + } while (0) + + LOPLAYER_COMBAT_ATTACK_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ +# undef item_ +} diff --git a/core/loplayer/combat.h b/core/loplayer/combat.h new file mode 100644 index 0000000..5d83f35 --- /dev/null +++ b/core/loplayer/combat.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/ticker.h" +#include "core/loeffect/effect.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/combat_ring.h" + +#include "./entity.h" +#include "./status.h" + +struct loplayer_combat_t; +typedef struct loplayer_combat_t loplayer_combat_t; + +typedef struct { + loentity_id_t attacker; + + uint64_t start; + uint64_t duration; + + vec2_t knockback; + + loeffect_t effect; +} loplayer_combat_attack_t; + +loplayer_combat_t* /* OWNERSHIP */ +loplayer_combat_new( + loresource_sound_t* sound, + loshader_combat_ring_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + loplayer_status_t* status, + loplayer_entity_t* entity, + size_t length +); + +void +loplayer_combat_delete( + loplayer_combat_t* combat /* OWNERSHIP */ +); + +bool +loplayer_combat_add_attack( + loplayer_combat_t* combat, + const loplayer_combat_attack_t* attack +); + +bool +loplayer_combat_accept_all_attacks( + loplayer_combat_t* combat +); + +void +loplayer_combat_drop_all_attacks( + loplayer_combat_t* combat +); + +void +loplayer_combat_guard( + loplayer_combat_t* combat +); + +void +loplayer_combat_unguard( + loplayer_combat_t* combat +); + +void +loplayer_combat_update( + loplayer_combat_t* combat +); + +void +loplayer_combat_draw_ui( + const loplayer_combat_t* combat +); + +void +loplayer_combat_pack( + const loplayer_combat_t* combat, + msgpack_packer* packer +); + +bool +loplayer_combat_unpack( + loplayer_combat_t* combat, + const msgpack_object* obj /* NULLABLE */ +); + +void +loplayer_combat_attack_pack( + const loplayer_combat_attack_t* attack, + msgpack_packer* packer +); + +bool +loplayer_combat_attack_unpack( + loplayer_combat_attack_t* attack, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loplayer/controller.c b/core/loplayer/controller.c new file mode 100644 index 0000000..b065143 --- /dev/null +++ b/core/loplayer/controller.c @@ -0,0 +1,79 @@ +#include "./controller.h" + +#include +#include +#include + +#include "core/locommon/input.h" +#include "core/locommon/position.h" + +void loplayer_controller_initialize(loplayer_controller_t* controller) { + assert(controller != NULL); + + *controller = (typeof(*controller)) {0}; +} + +void loplayer_controller_deinitialize(loplayer_controller_t* controller) { + assert(controller != NULL); + +} + +void loplayer_controller_update( + loplayer_controller_t* controller, + const locommon_input_t* input, + const locommon_position_t* cursor) { + assert(controller != NULL); + assert(input != NULL); + assert(locommon_position_valid(cursor)); + + controller->looking = *cursor; + controller->cursor = input->cursor; + + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_NONE; + controller->action = LOPLAYER_CONTROLLER_ACTION_NONE; + + const bool prev_jump = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_JUMP; + const bool prev_guarding = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_GUARD; + const bool prev_dash = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_DASH; + const bool prev_menu = + controller->prev.buttons & LOCOMMON_INPUT_BUTTON_MENU; + + if (input->buttons & LOCOMMON_INPUT_BUTTON_JUMP && !prev_jump) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_JUMP; + + } else if (input->buttons & LOCOMMON_INPUT_BUTTON_LEFT) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT; + if (input->buttons & LOCOMMON_INPUT_BUTTON_DASH) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT; + } + + } else if (input->buttons & LOCOMMON_INPUT_BUTTON_RIGHT) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT; + if (input->buttons & LOCOMMON_INPUT_BUTTON_DASH) { + controller->movement = LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT; + } + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_ATTACK) { + controller->action = LOPLAYER_CONTROLLER_ACTION_ATTACK; + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_GUARD) { + if (!prev_guarding) controller->action = LOPLAYER_CONTROLLER_ACTION_GUARD; + } else { + if (prev_guarding) controller->action = LOPLAYER_CONTROLLER_ACTION_UNGUARD; + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_DASH && !prev_dash) { + controller->action = LOPLAYER_CONTROLLER_ACTION_DODGE; + } + + if (input->buttons & LOCOMMON_INPUT_BUTTON_MENU && !prev_menu) { + controller->action = LOPLAYER_CONTROLLER_ACTION_MENU; + } + + controller->prev = *input; +} diff --git a/core/loplayer/controller.h b/core/loplayer/controller.h new file mode 100644 index 0000000..7ff7bc8 --- /dev/null +++ b/core/loplayer/controller.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "core/locommon/input.h" +#include "core/locommon/position.h" + +typedef enum { + LOPLAYER_CONTROLLER_MOVEMENT_NONE, + LOPLAYER_CONTROLLER_MOVEMENT_JUMP, + LOPLAYER_CONTROLLER_MOVEMENT_WALK_LEFT, + LOPLAYER_CONTROLLER_MOVEMENT_WALK_RIGHT, + LOPLAYER_CONTROLLER_MOVEMENT_DASH_LEFT, + LOPLAYER_CONTROLLER_MOVEMENT_DASH_RIGHT, +} loplayer_controller_movement_t; + +typedef enum { + LOPLAYER_CONTROLLER_ACTION_NONE, + LOPLAYER_CONTROLLER_ACTION_ATTACK, + LOPLAYER_CONTROLLER_ACTION_GUARD, + LOPLAYER_CONTROLLER_ACTION_UNGUARD, + LOPLAYER_CONTROLLER_ACTION_DODGE, + LOPLAYER_CONTROLLER_ACTION_MENU, +} loplayer_controller_action_t; + +typedef struct { + locommon_position_t looking; + vec2_t cursor; /* display coordinate (-1~1) */ + + loplayer_controller_movement_t movement; + loplayer_controller_action_t action; + + locommon_input_t prev; +} loplayer_controller_t; + +void +loplayer_controller_initialize( + loplayer_controller_t* controller +); + +void +loplayer_controller_deinitialize( + loplayer_controller_t* controller +); + +void +loplayer_controller_update( + loplayer_controller_t* controller, + const locommon_input_t* input, + const locommon_position_t* cursor +); diff --git a/core/loplayer/entity.c b/core/loplayer/entity.c new file mode 100644 index 0000000..f01e00e --- /dev/null +++ b/core/loplayer/entity.c @@ -0,0 +1,311 @@ +#include "./entity.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/physics.h" +#include "core/locommon/ticker.h" +#include "core/loentity/character.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./event.h" +#include "./status.h" + +#define LOPLAYER_ENTITY_WIDTH .02f +#define LOPLAYER_ENTITY_HEIGHT .05f +#define LOPLAYER_ENTITY_DRAW_SIZE LOPLAYER_ENTITY_HEIGHT +#define LOPLAYER_ENTITY_SHIFT_Y .03f + +#define LOPLAYER_ENTITY_GRAVITY_ACCELARATION 2.2f +#define LOPLAYER_ENTITY_RECOVERY_ACCELARATION 1.f + +#define LOPLAYER_ENTITY_MAX_GRAVITY 2.f + +#define LOPLAYER_ENTITY_DIRECTION_EPSILON .05f + +#define LOPLAYER_ENTITY_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("pos", super.super.pos); \ + PROC("movement", movement); \ + PROC("knockback", knockback); \ + PROC("gravity", gravity); \ +} while (0) +#define LOPLAYER_ENTITY_PARAM_TO_PACK_COUNT 4 + +static void loplayer_entity_update_position_( + loplayer_entity_t* p, vec2_t* velocity) { + assert(p != NULL); + assert(vec2_valid(velocity)); + + vec2_t disp = *velocity; + vec2_muleq(&disp, p->ticker->delta_f); + + vec2_addeq(&p->super.super.pos.fract, &disp); + p->super.super.pos.fract.y -= LOPLAYER_ENTITY_SHIFT_Y; + locommon_position_reduce(&p->super.super.pos); + + locommon_physics_entity_t e = { + .size = vec2(LOPLAYER_ENTITY_WIDTH, LOPLAYER_ENTITY_HEIGHT), + .pos = p->super.super.pos, + .velocity = *velocity, + }; + + loentity_store_solve_collision_between_ground( + p->entities, &e, p->ticker->delta_f); + + p->super.super.pos = e.pos; + + p->super.super.pos.fract.y += LOPLAYER_ENTITY_SHIFT_Y; + locommon_position_reduce(&p->super.super.pos); + + p->on_ground = false; + if (e.velocity.y == 0) { + if (velocity->y <= 0) { + p->on_ground = true; + } + if (p->gravity*velocity->y >= 0) p->gravity = 0; + if (p->knockback.y*velocity->y >= 0) p->knockback.y = 0; + } + if (e.velocity.x == 0 && velocity->x != 0) { + if (p->knockback.x*velocity->x >= 0) p->knockback.x = 0; + } + p->last_velocity = *velocity = e.velocity; +} + +static void loplayer_entity_bind_in_event_area_(loplayer_entity_t* p) { + assert(p != NULL); + + const loplayer_event_param_t* e = loplayer_event_get_param(p->event); + if (e == NULL || e->area_size.x <= 0 || e->area_size.y <= 0) return; + + vec2_t v; + locommon_position_sub(&v, &p->super.super.pos, &e->area_pos); + + if (MATH_ABS(v.x) > e->area_size.x) { + v.x = MATH_SIGN(v.x) * e->area_size.x; + } + if (MATH_ABS(v.y) > e->area_size.y) { + v.y = MATH_SIGN(v.y) * e->area_size.y; + } + + p->super.super.pos = e->area_pos; + vec2_addeq(&p->super.super.pos.fract, &v); + locommon_position_reduce(&p->super.super.pos); +} + +static void loplayer_entity_delete_(loentity_t* entity) { + assert(entity != NULL); + + /* does not anything */ +} + +static bool loplayer_entity_update_(loentity_t* entity) { + assert(entity != NULL); + + loplayer_entity_t* p = (typeof(p)) entity; + + /* ---- position ---- */ + vec2_t velocity = p->movement; + vec2_addeq(&velocity, &p->knockback); + velocity.y += p->gravity; + loplayer_entity_update_position_(p, &velocity); + loplayer_entity_bind_in_event_area_(p); + + /* ---- gravity ---- */ + const float dt = p->ticker->delta_f; + p->gravity -= LOPLAYER_ENTITY_GRAVITY_ACCELARATION*dt; + p->gravity = MATH_MAX(p->gravity, -LOPLAYER_ENTITY_MAX_GRAVITY); + + /* ---- recovery from knockback ---- */ + locommon_easing_linear_float( + &p->knockback.x, 0, LOPLAYER_ENTITY_RECOVERY_ACCELARATION*dt); + locommon_easing_linear_float( + &p->knockback.y, 0, LOPLAYER_ENTITY_RECOVERY_ACCELARATION*dt); + return true; +} + +static void loplayer_entity_draw_( + loentity_t* entity, const locommon_position_t* basepos) { + assert(entity != NULL); + assert(basepos != NULL); + + loplayer_entity_t* p = (typeof(p)) entity; + + locommon_position_t center = p->super.super.pos; + center.fract.y -= LOPLAYER_ENTITY_SHIFT_Y; + locommon_position_reduce(¢er); + + loshader_character_drawer_instance_t instance = { + .character_id = LOSHADER_CHARACTER_ID_PLAYER, + .from_motion_id = p->motion.from, + .to_motion_id = p->motion.to, + .motion_time = p->motion.time, + .marker = p->status->bullet_immune_until < p->ticker->time, + .marker_offset = vec2(0, LOPLAYER_ENTITY_SHIFT_Y), + + .size = vec2( + LOPLAYER_ENTITY_DRAW_SIZE*p->direction, LOPLAYER_ENTITY_DRAW_SIZE), + .color = vec4(0, 0, 0, 1), + }; + locommon_position_sub(&instance.pos, ¢er, basepos); + + loshader_character_drawer_add_instance(p->drawer, &instance); +} + +static void loplayer_entity_apply_effect_( + loentity_character_t* chara, const loeffect_t* effect) { + assert(chara != NULL); + assert(effect != NULL); + + loplayer_entity_t* p = (typeof(p)) chara; + loplayer_status_apply_effect(p->status, effect); +} + +static void loplayer_entity_knockback_( + loentity_character_t* chara, const vec2_t* v) { + assert(chara != NULL); + assert(vec2_valid(v)); + + loplayer_entity_t* p = (typeof(p)) chara; + vec2_addeq(&p->knockback, v); +} + +void loplayer_entity_initialize( + loplayer_entity_t* player, + loentity_id_t id, + loresource_sound_t* sound, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + const loplayer_event_t* event, + loplayer_status_t* status) { + assert(player != NULL); + assert(sound != NULL); + assert(drawer != NULL); + assert(ticker != NULL); + assert(entities != NULL); + assert(event != NULL); + assert(status != NULL); + + *player = (typeof(*player)) { + .super = { + .super = { + .vtable = { + .delete = loplayer_entity_delete_, + .update = loplayer_entity_update_, + .draw = loplayer_entity_draw_, + }, + .subclass = LOENTITY_SUBCLASS_CHARACTER, + .id = id, + .pos = locommon_position(0, 0, vec2(0.5, 0.5)), + .dont_save = true, + }, + .vtable = { + .apply_effect = loplayer_entity_apply_effect_, + .knockback = loplayer_entity_knockback_, + }, + }, + .sound = sound, + .drawer = drawer, + .ticker = ticker, + .entities = entities, + .event = event, + .status = status, + + .direction = 1, + }; +} + +void loplayer_entity_deinitialize(loplayer_entity_t* player) { + assert(player != NULL); + +} + +void loplayer_entity_move( + loplayer_entity_t* player, const locommon_position_t* pos) { + assert(player != NULL); + assert(locommon_position_valid(pos)); + + player->super.super.pos = *pos; + player->on_ground = false; + player->movement = vec2(0, 0); + player->knockback = vec2(0, 0); + player->gravity = 0; +} + +void loplayer_entity_aim( + loplayer_entity_t* player, const locommon_position_t* pos) { + assert(player != NULL); + assert(locommon_position_valid(pos)); + + vec2_t dir; + locommon_position_sub(&dir, pos, &player->super.super.pos); + + if (MATH_ABS(dir.x) > LOPLAYER_ENTITY_DIRECTION_EPSILON) { + player->direction = MATH_SIGN(dir.x); + } +} + +bool loplayer_entity_affect_bullet(loplayer_entity_t* player) { + assert(player != NULL); + + return loentity_store_affect_bullets_shot_by_others( + player->entities, + &player->super, + &player->last_velocity, + player->ticker->delta_f); +} + +void loplayer_entity_pack( + const loplayer_entity_t* player, msgpack_packer* packer) { + assert(player != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOPLAYER_ENTITY_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &player->var); \ + } while (0) + + LOPLAYER_ENTITY_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool loplayer_entity_unpack( + loplayer_entity_t* player, const msgpack_object* obj) { + assert(player != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &player->var)) { \ + return false; \ + } \ + } while (0) + + LOPLAYER_ENTITY_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ +# undef item_ +} diff --git a/core/loplayer/entity.h b/core/loplayer/entity.h new file mode 100644 index 0000000..37c1d6a --- /dev/null +++ b/core/loplayer/entity.h @@ -0,0 +1,93 @@ +#pragma once + +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/ticker.h" +#include "core/loentity/character.h" +#include "core/loentity/store.h" +#include "core/loresource/sound.h" +#include "core/loshader/character.h" + +#include "./event.h" +#include "./status.h" + +typedef struct { + loentity_character_t super; + + /* injected deps */ + const locommon_ticker_t* ticker; + loresource_sound_t* sound; + loshader_character_drawer_t* drawer; + loentity_store_t* entities; + const loplayer_event_t* event; + loplayer_status_t* status; + + /* read-only mutable params */ + float direction; + + /* public params */ + vec2_t movement; + vec2_t knockback; + float gravity; + + /* temporary cache for update */ + bool on_ground; + vec2_t last_velocity; + + /* temporary cache for draw */ + struct { + loshader_character_motion_id_t from; + loshader_character_motion_id_t to; + float time; + } motion; +} loplayer_entity_t; + +void +loplayer_entity_initialize( + loplayer_entity_t* player, + loentity_id_t id, + loresource_sound_t* sound, + loshader_character_drawer_t* drawer, + const locommon_ticker_t* ticker, + loentity_store_t* entities, + const loplayer_event_t* event, + loplayer_status_t* status +); + +void +loplayer_entity_deinitialize( + loplayer_entity_t* player +); + +void +loplayer_entity_move( + loplayer_entity_t* player, + const locommon_position_t* pos +); + +void +loplayer_entity_aim( + loplayer_entity_t* player, + const locommon_position_t* pos +); + +bool +loplayer_entity_affect_bullet( + loplayer_entity_t* player +); + +void +loplayer_entity_pack( + const loplayer_entity_t* player, + msgpack_packer* packer +); + +bool +loplayer_entity_unpack( + loplayer_entity_t* player, + const msgpack_object* obj +); diff --git a/core/loplayer/event.c b/core/loplayer/event.c new file mode 100644 index 0000000..2039473 --- /dev/null +++ b/core/loplayer/event.c @@ -0,0 +1,146 @@ +#include "./event.h" + +#include +#include +#include + +#include "util/glyphas/block.h" +#include "util/glyphas/cache.h" +#include "util/math/vector.h" +#include "util/memory/memory.h" + +#include "core/locommon/position.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +struct loplayer_event_t { + loplayer_event_param_t param; + /* convertible between loplayer_event_t* and loplayer_event_param_t* */ + + /* injected deps */ + loresource_set_t* res; + loshader_set_t* shaders; + + /* owned objects */ + glyphas_cache_t* font; + glyphas_block_t* text; + + /* immutable params */ + struct { + vec2_t fontsz; + } geometry; +}; + +static void loplayer_event_calculate_geometry_(loplayer_event_t* event) { + assert(event != NULL); + + typeof(event->geometry)* geo = &event->geometry; + + geo->fontsz = event->shaders->dpi; + vec2_muleq(&geo->fontsz, .15f); +} + +loplayer_event_t* loplayer_event_new( + loresource_set_t* res, loshader_set_t* shaders) { + assert(res != NULL); + assert(shaders != NULL); + + loplayer_event_t* event = memory_new(sizeof(*event)); + *event = (typeof(*event)) { + .res = res, + .shaders = shaders, + }; + loplayer_event_calculate_geometry_(event); + + event->font = glyphas_cache_new( + shaders->tex.event_line, + &res->font.serif, + event->geometry.fontsz.x, + event->geometry.fontsz.y); + + event->text = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -event->geometry.fontsz.y, + INT32_MAX, + 256); + + return event; +} + +void loplayer_event_delete(loplayer_event_t* event) { + if (event == NULL) return; + + glyphas_cache_delete(event->font); + glyphas_block_delete(event->text); + + memory_delete(event); +} + +loplayer_event_param_t* loplayer_event_take_control( + loplayer_event_t* event, loentity_id_t id) { + assert(event != NULL); + + if (event->param.controlled) return NULL; + + event->param = (typeof(event->param)) { + .controlled = true, + .controlled_by = id, + }; + return &event->param; +} + +void loplayer_event_abort(loplayer_event_t* event) { + assert(event != NULL); + + if (!event->param.controlled) return; + + loplayer_event_param_release_control(&event->param); +} + +void loplayer_event_draw(const loplayer_event_t* event) { + assert(event != NULL); + + if (!event->param.controlled) return; + + loshader_event_line_drawer_add_block( + &event->shaders->drawer.event_line, event->text); +} + +const loplayer_event_param_t* loplayer_event_get_param( + const loplayer_event_t* event) { + assert(event != NULL); + + if (!event->param.controlled) return NULL; + + return &event->param; +} + +void loplayer_event_param_set_line( + loplayer_event_param_t* param, const char* str, size_t len) { + assert(param != NULL); + + loplayer_event_t* event = (typeof(event)) param; + + glyphas_block_clear(event->text); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters(event->text, event->font, &white, str, len); + + static const vec2_t center = vec2(.5f, -.5f); + glyphas_block_set_origin(event->text, ¢er); + + const vec2_t scale = vec2( + 2/event->shaders->resolution.x, 2/event->shaders->resolution.y); + glyphas_block_scale(event->text, &scale); + + static const vec2_t trans = vec2(0, -.85f); + glyphas_block_translate(event->text, &trans); +} + +void loplayer_event_param_release_control(loplayer_event_param_t* param) { + assert(param != NULL); + assert(param->controlled); + + loplayer_event_param_set_line(param, "", 0); + param->controlled = false; +} diff --git a/core/loplayer/event.h b/core/loplayer/event.h new file mode 100644 index 0000000..01057f4 --- /dev/null +++ b/core/loplayer/event.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/loentity/entity.h" +#include "core/loresource/music.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +struct loplayer_event_t; +typedef struct loplayer_event_t loplayer_event_t; + +typedef struct { + bool controlled; + /* You can return the control by assigning false. */ + + loentity_id_t controlled_by; + + locommon_position_t area_pos; + vec2_t area_size; + + bool cinescope; + bool hide_hud; + + loresource_music_player_t* music; +} loplayer_event_param_t; + +loplayer_event_t* /* OWNERSHIP */ +loplayer_event_new( + loresource_set_t* res, + loshader_set_t* shaders +); + +void +loplayer_event_delete( + loplayer_event_t* event /* OWNERSHIP*/ +); + +loplayer_event_param_t* /* NULLABLE */ +loplayer_event_take_control( + loplayer_event_t* event, + loentity_id_t id +); + +void +loplayer_event_abort( + loplayer_event_t* event +); + +void +loplayer_event_draw( + const loplayer_event_t* event +); + +const loplayer_event_param_t* /* NULLABLE */ +loplayer_event_get_param( + const loplayer_event_t* event +); + +void +loplayer_event_param_set_line( + loplayer_event_param_t* param, + const char* str, + size_t len +); + +void +loplayer_event_param_release_control( + loplayer_event_param_t* param +); diff --git a/core/loplayer/hud.c b/core/loplayer/hud.c new file mode 100644 index 0000000..7ccaff7 --- /dev/null +++ b/core/loplayer/hud.c @@ -0,0 +1,409 @@ +#include "./hud.h" + +#include +#include +#include +#include + +#include "util/conv/charcode.h" +#include "util/glyphas/block.h" +#include "util/glyphas/cache.h" +#include "util/math/algorithm.h" +#include "util/memory/memory.h" + +#include "core/locommon/easing.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/hud_bar.h" +#include "core/loshader/set.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +typedef struct { + float alpha; + float remain; + + glyphas_block_t* text; +} loplayer_hud_effect_t; + +struct loplayer_hud_t { + /* injected deps */ + loshader_set_t* shaders; + const locommon_ticker_t* ticker; + const loplayer_event_t* event; + const loplayer_status_t* status; + const loplayer_entity_t* entity; + + /* owned objects */ + struct { + glyphas_cache_t* sans; + glyphas_cache_t* serif; + } font; + + /* immutable params */ + struct { + vec2_t fontsz_normal_px; + vec2_t fontsz_normal; + vec2_t fontsz_small_px; + vec2_t fontsz_small; + + vec2_t padding; + + vec2_t madness_bar_pos; + vec2_t madness_bar_size; + + vec2_t faith_bar_pos; + vec2_t faith_bar_size; + + vec2_t effect_bar_pos; + vec2_t effect_bar_size; + } geometry; + + /* mutable params */ + float alpha; + + float prev_madness; + float prev_faith; + + bool shown; + + glyphas_block_t* biome_text; + + union { + struct { + loplayer_hud_effect_t curse; + loplayer_hud_effect_t amnesia; + loplayer_hud_effect_t lost; + }; + loplayer_hud_effect_t array[3]; + } effects; +}; + +static void loplayer_hud_calculate_geometry_(loplayer_hud_t* hud) { + assert(hud != NULL); + + const vec2_t* dpi = &hud->shaders->dpi; + const vec2_t* reso = &hud->shaders->resolution; + + typeof(hud->geometry)* geo = &hud->geometry; + +# define px_to_disp_(v) vec2((v).x/reso->x*2, (v).y/reso->y*2) + + geo->fontsz_normal_px = *dpi; + vec2_muleq(&geo->fontsz_normal_px, .4f); + + geo->fontsz_normal = px_to_disp_(geo->fontsz_normal_px); + + geo->fontsz_small_px = *dpi; + vec2_muleq(&geo->fontsz_small_px, .3f); + + geo->fontsz_small = px_to_disp_(geo->fontsz_small_px); + + geo->padding = *dpi; + vec2_muleq(&geo->padding, .4f); + geo->padding = px_to_disp_(geo->padding); + + geo->madness_bar_size = vec2(.5f, .1f*dpi->y); + geo->madness_bar_size.y /= reso->y/2; + + geo->madness_bar_pos = vec2( + -1 + geo->padding.x + geo->madness_bar_size.x, + 1 - geo->padding.y - geo->madness_bar_size.y); + + geo->faith_bar_size = vec2(.4f, .05f*dpi->y); + geo->faith_bar_size.y /= reso->y/2; + + geo->faith_bar_pos = vec2( + -1 + geo->padding.x + geo->faith_bar_size.x, + 1 - geo->padding.y - geo->madness_bar_size.y*2 - geo->faith_bar_size.y); + + geo->effect_bar_size = vec2(3*dpi->x/reso->x, 4/reso->y); + + geo->effect_bar_pos = vec2(1-geo->padding.x, -1+geo->padding.y); + vec2_subeq(&geo->effect_bar_pos, &geo->effect_bar_size); + +# undef px_to_disp_ +} + +static glyphas_block_t* loplayer_hud_create_effect_text_block_( + const loplayer_hud_t* hud, const char* text) { + assert(hud != NULL); + + glyphas_block_t* block = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -hud->geometry.fontsz_normal_px.y, + INT32_MAX, + 32); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + block, hud->font.sans, &white, text, strlen(text)); + + const vec2_t scale = vec2( + 2/hud->shaders->resolution.x, 2/hud->shaders->resolution.y); + glyphas_block_scale(block, &scale); + + return block; +} + +static void loplayer_hud_initialize_effect_text_( + loplayer_hud_t* hud, loresource_set_t* res) { + assert(hud != NULL); + +# define init_effect_text_(name) \ + hud->effects.name.text = loplayer_hud_create_effect_text_block_( \ + hud, loresource_text_get(res->lang, "effect_"#name)) + + init_effect_text_(curse); + init_effect_text_(amnesia); + init_effect_text_(lost); + +# undef init_effect_ +} + +static void loplayer_hud_update_bars_(loplayer_hud_t* hud) { + assert(hud != NULL); + + const float d = hud->ticker->delta_f; + + locommon_easing_smooth_float( + &hud->prev_madness, hud->status->recipient.madness, d*2); + locommon_easing_smooth_float( + &hud->prev_faith, hud->status->recipient.faith, d*2); +} + +static void loplayer_hud_update_lasting_effect_( + loplayer_hud_t* hud, + loplayer_hud_effect_t* e, + const loeffect_generic_lasting_param_t* p) { + assert(hud != NULL); + assert(e != NULL); + assert(p != NULL); + + const uint64_t end = p->begin + p->duration; + const uint64_t t = hud->ticker->time; + if (p->duration == 0 || end <= t || p->begin > t) { + e->remain = 0; + return; + } + e->remain = 1 - (t - p->begin)*1.f/p->duration; +} + +static void loplayer_hud_update_effects_(loplayer_hud_t* hud) { + assert(hud != NULL); + + const float d = hud->ticker->delta_f; + + loplayer_hud_update_lasting_effect_( + hud, &hud->effects.curse, &hud->status->recipient.effects.curse); + loplayer_hud_update_lasting_effect_( + hud, &hud->effects.amnesia, &hud->status->recipient.effects.amnesia); + hud->effects.lost.remain = + hud->status->recipient.faith > 0? 0: (1-hud->status->recipient.madness); + + static const size_t len = + sizeof(hud->effects.array)/sizeof(hud->effects.array[0]); + for (size_t i = 0; i < len; ++i) { + loplayer_hud_effect_t* e = &hud->effects.array[i]; + locommon_easing_linear_float(&e->alpha, !!(e->remain > 0), d*2); + } +} + +static void loplayer_hud_draw_bars_(const loplayer_hud_t* hud) { + assert(hud != NULL); + + const typeof(hud->geometry)* geo = &hud->geometry; + + const loshader_hud_bar_drawer_instance_t madness = { + .pos = geo->madness_bar_pos, + .size = geo->madness_bar_size, + .bgcolor = vec4(0, 0, 0, 1*hud->alpha), + .fgcolor = vec4(1, 1, 1, .9f*hud->alpha), + .value = MATH_CLAMP(hud->status->recipient.madness, 0, 1), + .prev_value = MATH_CLAMP(hud->prev_madness, 0, 1), + }; + loshader_hud_bar_drawer_add_instance(hud->shaders->drawer.hud_bar, &madness); + + const loshader_hud_bar_drawer_instance_t faith = { + .pos = geo->faith_bar_pos, + .size = geo->faith_bar_size, + .bgcolor = vec4(0, 0, 0, 1*hud->alpha), + .fgcolor = vec4(.9f, .9f, .9f, .9f*hud->alpha), + .value = MATH_CLAMP(hud->status->recipient.faith, 0, 1), + .prev_value = MATH_CLAMP(hud->prev_faith, 0, 1), + }; + loshader_hud_bar_drawer_add_instance(hud->shaders->drawer.hud_bar, &faith); +} + +static void loplayer_hud_draw_effects_(const loplayer_hud_t* hud) { + assert(hud != NULL); + + const typeof(hud->geometry)* geo = &hud->geometry; + + const float lineheight = hud->geometry.fontsz_normal.y; + float h = 0; + + static const size_t len = + sizeof(hud->effects.array)/sizeof(hud->effects.array[0]); + for (size_t i = 0; i < len; ++i) { + const loplayer_hud_effect_t* e = &hud->effects.array[i]; + if (e->alpha == 0) continue; + + const float y = h*lineheight + geo->effect_bar_pos.y; + + static const vec2_t origin = vec2(1, -1); + glyphas_block_set_origin(e->text, &origin); + + const vec2_t trans = vec2(1-geo->padding.x, y); + glyphas_block_translate(e->text, &trans); + glyphas_block_set_alpha(e->text, e->alpha); + + loshader_hud_text_drawer_add_block( + &hud->shaders->drawer.hud_text, e->text); + + const loshader_hud_bar_drawer_instance_t instance = { + .pos = vec2(geo->effect_bar_pos.x, y), + .size = vec2(-geo->effect_bar_size.x, geo->effect_bar_size.y), + .bgcolor = vec4(1, 1, 1, .2f*hud->alpha), + .fgcolor = vec4(1, 1, 1, .8f*hud->alpha), + .value = MATH_CLAMP(e->remain, 0, 1), + .prev_value = MATH_CLAMP(e->remain, 0, 1), + }; + loshader_hud_bar_drawer_add_instance( + hud->shaders->drawer.hud_bar, &instance); + + h += e->alpha * e->alpha * (3-2*e->alpha); + } +} + +loplayer_hud_t* loplayer_hud_new( + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity) { + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(event != NULL); + assert(status != NULL); + assert(entity != NULL); + + loplayer_hud_t* hud = memory_new(sizeof(*hud)); + *hud = (typeof(*hud)) { + .shaders = shaders, + .ticker = ticker, + .event = event, + .status = status, + .entity = entity, + }; + loplayer_hud_calculate_geometry_(hud); + + hud->font = (typeof(hud->font)) { + .sans = glyphas_cache_new( + shaders->tex.hud_text, + &res->font.sans, + hud->geometry.fontsz_normal_px.x, + hud->geometry.fontsz_normal_px.y), + .serif = glyphas_cache_new( + shaders->tex.hud_text, + &res->font.serif, + hud->geometry.fontsz_small_px.x, + hud->geometry.fontsz_small_px.y), + }; + + hud->biome_text = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -hud->geometry.fontsz_normal_px.y, + INT32_MAX, + 32); + + loplayer_hud_initialize_effect_text_(hud, res); + + return hud; +} + +void loplayer_hud_delete(loplayer_hud_t* hud) { + if (hud == NULL) return; + + static const size_t len = + sizeof(hud->effects.array) / sizeof(hud->effects.array[0]); + for (size_t i = 0; i < len; ++i) { + glyphas_block_delete(hud->effects.array[i].text); + } + + glyphas_block_delete(hud->biome_text); + + glyphas_cache_delete(hud->font.sans); + glyphas_cache_delete(hud->font.serif); + + memory_delete(hud); +} + +void loplayer_hud_show(loplayer_hud_t* hud) { + assert(hud != NULL); + + hud->shown = true; +} + +void loplayer_hud_hide(loplayer_hud_t* hud) { + assert(hud != NULL); + + hud->shown = false; +} + +void loplayer_hud_set_biome_text(loplayer_hud_t* hud, const char* text) { + assert(hud != NULL); + + glyphas_block_clear(hud->biome_text); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + hud->biome_text, hud->font.serif, &white, text, strlen(text)); + + glyphas_block_set_origin(hud->biome_text, &vec2(0, -1)); + + const vec2_t scale = vec2( + 2/hud->shaders->resolution.x, 2/hud->shaders->resolution.y); + glyphas_block_scale(hud->biome_text, &scale); + + const typeof(hud->geometry)* geo = &hud->geometry; + glyphas_block_translate( + hud->biome_text, &vec2(-1+geo->padding.x, -1+geo->padding.y)); + + glyphas_block_make_glitched(hud->biome_text, hud->ticker->time); +} + +void loplayer_hud_update(loplayer_hud_t* hud) { + assert(hud != NULL); + + bool shown = hud->shown; + + const loplayer_event_param_t* e = loplayer_event_get_param(hud->event); + if (e != NULL) { + shown = shown && !e->hide_hud; + } + + const float dt = hud->ticker->delta_f; + locommon_easing_smooth_float(&hud->alpha, !!shown, dt*2); + + loplayer_hud_update_bars_(hud); + loplayer_hud_update_effects_(hud); +} + +void loplayer_hud_draw_ui(const loplayer_hud_t* hud) { + assert(hud != NULL); + + hud->shaders->drawer.hud_text.alpha = hud->alpha; + + loplayer_hud_draw_bars_(hud); + loplayer_hud_draw_effects_(hud); + + loshader_hud_text_drawer_add_block( + &hud->shaders->drawer.hud_text, hud->biome_text); +} diff --git a/core/loplayer/hud.h b/core/loplayer/hud.h new file mode 100644 index 0000000..1c788d3 --- /dev/null +++ b/core/loplayer/hud.h @@ -0,0 +1,53 @@ +#pragma once + +#include "core/locommon/ticker.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./entity.h" +#include "./event.h" +#include "./status.h" + +struct loplayer_hud_t; +typedef struct loplayer_hud_t loplayer_hud_t; + +loplayer_hud_t* +loplayer_hud_new( + loresource_set_t* resource, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_event_t* event, + const loplayer_status_t* status, + const loplayer_entity_t* entity +); + +void +loplayer_hud_delete( + loplayer_hud_t* hud +); + +void +loplayer_hud_show( + loplayer_hud_t* hud +); + +void +loplayer_hud_hide( + loplayer_hud_t* hud +); + +void +loplayer_hud_set_biome_text( + loplayer_hud_t* hud, + const char* text +); + +void +loplayer_hud_update( + loplayer_hud_t* hud +); + +void +loplayer_hud_draw_ui( + const loplayer_hud_t* hud +); diff --git a/core/loplayer/menu.c b/core/loplayer/menu.c new file mode 100644 index 0000000..3e58593 --- /dev/null +++ b/core/loplayer/menu.c @@ -0,0 +1,565 @@ +#include "./menu.h" + +#include +#include +#include +#include +#include + +#include "util/glyphas/block.h" +#include "util/glyphas/cache.h" +#include "util/math/algorithm.h" +#include "util/math/constant.h" +#include "util/memory/memory.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" +#include "core/loresource/text.h" +#include "core/loshader/set.h" + +#include "./controller.h" +#include "./entity.h" +#include "./status.h" + +typedef struct { + glyphas_block_t* name; + glyphas_block_t* desc; + glyphas_block_t* note; +} loplayer_menu_stance_text_set_t; + +typedef enum { + LOPLAYER_MENU_STATE_HIDDEN, + LOPLAYER_MENU_STATE_STATUS, + LOPLAYER_MENU_STATE_POPUP, +} loplayer_menu_state_t; + +struct loplayer_menu_t { + /* injected deps */ + loresource_set_t* res; + loshader_set_t* shaders; + + const locommon_ticker_t* ticker; + const loplayer_status_t* status; + const loplayer_controller_t* controller; + + /* owned objects */ + struct { + glyphas_cache_t* large; + glyphas_cache_t* normal; + glyphas_cache_t* small; + } font; + + struct { + loplayer_menu_stance_text_set_t stance[LOEFFECT_STANCE_ID_LENGTH_+1]; + /* [0] is for unknown stance */ + + glyphas_block_t* popup_title; + glyphas_block_t* popup_body; + + glyphas_block_t* exit; + } text; + + /* immutable params */ + struct { + vec2_t fontsz_large; + vec2_t fontsz_normal; + vec2_t fontsz_small; + + vec2_t fontsz_large_px; + vec2_t fontsz_normal_px; + vec2_t fontsz_small_px; + + vec2_t range; + vec2_t icon; + vec2_t padding; + } geometry; + + /* mutable params */ + loplayer_menu_state_t state; + float alpha; + + bool stance_hovering; + size_t display_stance_text_index; + + float stance_highlight; + size_t highlighted_stance; + + bool request_exit; +}; + +static void loplayer_menu_calculate_geometry_(loplayer_menu_t* menu) { + assert(menu != NULL); + + const vec2_t* dpi = &menu->shaders->dpi; + const vec2_t* reso = &menu->shaders->resolution; + + typeof(menu->geometry)* geo = &menu->geometry; + + geo->fontsz_large_px = *dpi; + vec2_muleq(&geo->fontsz_large_px, .2f); + + geo->fontsz_normal_px = *dpi; + vec2_muleq(&geo->fontsz_normal_px, .15f); + + geo->fontsz_small_px = *dpi; + vec2_muleq(&geo->fontsz_small_px, .12f); + +# define px_to_disp_(v) vec2((v).x/reso->x*2, (v).y/reso->y*2) + + geo->fontsz_large = px_to_disp_(geo->fontsz_large_px); + geo->fontsz_normal = px_to_disp_(geo->fontsz_normal_px); + geo->fontsz_small = px_to_disp_(geo->fontsz_small_px); + + geo->range = *dpi; + vec2_muleq(&geo->range, 2); + geo->range = px_to_disp_(geo->range); + vec2_diveq(&geo->range, MATH_MAX(geo->range.x, 1)); + vec2_diveq(&geo->range, MATH_MAX(geo->range.y, 1)); + + geo->icon = *dpi; + vec2_muleq(&geo->icon, .4f); + geo->icon = px_to_disp_(geo->icon); + + geo->padding = *dpi; + vec2_muleq(&geo->padding, .2f); + geo->padding = px_to_disp_(geo->padding); + +# undef px_to_disp_ +} + +static void loplayer_menu_initialize_stance_text_set_( + const loplayer_menu_t* menu, + loplayer_menu_stance_text_set_t* set, + const char* name, + const char* desc, + const char* note) { + assert(menu != NULL); + assert(set != NULL); + + const typeof(menu->geometry)* geo = &menu->geometry; + + *set = (typeof(*set)) { + .name = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -geo->fontsz_large_px.y, + INT32_MAX, + 32), + .desc = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -geo->fontsz_normal_px.y*1.3f, + geo->fontsz_normal_px.x*18, + 512), + .note = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -geo->fontsz_small_px.y*1.1f, + geo->fontsz_normal_px.x*18, + 512), + }; + + static const vec4_t white = vec4(1, 1, 1, 1); + static const vec4_t yellow = vec4(1, .7f, 0, 1); + + glyphas_block_add_characters( + set->name, menu->font.large, &white, name, strlen(name)); + glyphas_block_add_characters( + set->desc, menu->font.normal, &white, desc, strlen(desc)); + glyphas_block_add_characters( + set->note, menu->font.small, &yellow, note, strlen(note)); + + glyphas_block_set_origin(set->name, &vec2(.5f, -1)); + glyphas_block_set_origin(set->desc, &vec2(.5f, -.5f)); + glyphas_block_set_origin(set->note, &vec2(.5f, 0)); + + const vec2_t scale = + vec2(2/menu->shaders->resolution.x, 2/menu->shaders->resolution.y); + glyphas_block_scale(set->name, &scale); + glyphas_block_scale(set->desc, &scale); + glyphas_block_scale(set->note, &scale); + + vec2_t offset, size; + glyphas_block_calculate_geometry(set->desc, &offset, &size); + + glyphas_block_translate(set->name, &vec2(0, size.y/2 * 1.1f)); + glyphas_block_translate(set->note, &vec2(0, -size.y/2 * 1.2f)); +} + +static void loplayer_menu_deinitialize_stance_text_set_( + loplayer_menu_stance_text_set_t* set) { + assert(set != NULL); + + glyphas_block_delete(set->name); + glyphas_block_delete(set->desc); + glyphas_block_delete(set->note); +} + +static void loplayer_menu_create_stance_text_(loplayer_menu_t* menu) { + assert(menu != NULL); + +# define text_(name) loresource_text_get(menu->res->lang, name) + + loplayer_menu_initialize_stance_text_set_( + menu, &menu->text.stance[0], "???", "???", ""); + +# define init_stance_set_(NAME, name) \ + loplayer_menu_initialize_stance_text_set_( \ + menu, \ + &menu->text.stance[LOEFFECT_STANCE_ID_##NAME+1], \ + text_("stance_"#name"_name"), \ + text_("stance_"#name"_desc"), \ + text_("stance_"#name"_note")) + + LOEFFECT_STANCE_EACH(init_stance_set_); + +# undef init_stance_set_ +# undef text_ +} + +static void loplayer_menu_create_exit_text_(loplayer_menu_t* menu) { + assert(menu != NULL); + + menu->text.exit = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -menu->geometry.fontsz_normal_px.y, + INT32_MAX, + 32); + + const char* text = loresource_text_get(menu->res->lang, "menu_exit"); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + menu->text.exit, menu->font.normal, &white, text, strlen(text)); + + glyphas_block_set_origin(menu->text.exit, &vec2(.5f, -1)); + + const vec2_t scale = + vec2(2/menu->shaders->resolution.x, 2/menu->shaders->resolution.y); + glyphas_block_scale(menu->text.exit, &scale); + + glyphas_block_translate(menu->text.exit, &vec2(0, -1+menu->geometry.padding.y)); +} + +static void loplayer_menu_build_popup_text_( + loplayer_menu_t* menu, const char* title, const char* text) { + assert(menu != NULL); + + glyphas_block_clear(menu->text.popup_title); + glyphas_block_clear(menu->text.popup_body); + + static const vec4_t white = vec4(1, 1, 1, 1); + glyphas_block_add_characters( + menu->text.popup_title, menu->font.large, &white, title, strlen(title)); + glyphas_block_add_characters( + menu->text.popup_body, menu->font.normal, &white, text, strlen(text)); + + static const vec2_t bottom = vec2(.5f, -1); + static const vec2_t center = vec2(.5f, -.5f); + glyphas_block_set_origin(menu->text.popup_title, &bottom); + glyphas_block_set_origin(menu->text.popup_body, ¢er); + + const vec2_t scale = + vec2(2/menu->shaders->resolution.x, 2/menu->shaders->resolution.y); + glyphas_block_scale(menu->text.popup_title, &scale); + glyphas_block_scale(menu->text.popup_body, &scale); + + vec2_t offset, size; + glyphas_block_calculate_geometry(menu->text.popup_body, &offset, &size); + + const vec2_t upper = vec2(0, size.y/2*1.1f); + glyphas_block_translate(menu->text.popup_title, &upper); +} + +static bool loplayer_menu_find_stance_icon_at_( + const loplayer_menu_t* menu, size_t* index, const vec2_t* pos) { + assert(menu != NULL); + assert(index != NULL); + assert(vec2_valid(pos)); + + const typeof(menu->geometry)* geo = &menu->geometry; + + for (size_t i = 0; i < 5; ++i) { + const float theta = MATH_PI*2/5 * i + MATH_PI/2; + const vec2_t p = + vec2(geo->range.x*cos(theta), geo->range.y*sin(theta)); + + vec2_t diff = *pos; + vec2_subeq(&diff, &p); + + diff.x /= geo->icon.x; + diff.y /= geo->icon.y; + + const float manhattan = MATH_ABS(diff.x) + MATH_ABS(diff.y); + if (manhattan < 1) { + *index = i; + return true; + } + } + return false; +} + +static void loplayer_menu_update_status_(loplayer_menu_t* menu) { + assert(menu != NULL); + + if (menu->alpha != 1) return; + + locommon_easing_linear_float( + &menu->stance_highlight, 0, menu->ticker->delta_f); + + size_t hovered; + menu->stance_hovering = loplayer_menu_find_stance_icon_at_( + menu, &hovered, &menu->controller->cursor); + if (menu->stance_hovering) { + const bool taken = + loeffect_stance_set_has(&menu->status->stances, hovered); + menu->display_stance_text_index = taken? hovered+1: 0; + } + + const typeof(menu->geometry)* geo = &menu->geometry; + if (menu->controller->cursor.y < -1+geo->fontsz_normal.y+geo->padding.y) { + if (menu->controller->prev.buttons & LOCOMMON_INPUT_BUTTON_ATTACK) { + menu->request_exit = true; + } + } +} + +static void loplayer_menu_update_popup_(loplayer_menu_t* menu) { + assert(menu != NULL); + +} + +static void loplayer_menu_draw_stance_icons_(const loplayer_menu_t* menu) { + assert(menu != NULL); + + const typeof(menu->geometry)* geo = &menu->geometry; + + for (size_t i = 0; i < 5; ++i) { + const bool taken = + loeffect_stance_set_has(&menu->status->stances, i); + + const float theta = MATH_PI*2/5 * i + MATH_PI/2; + + const loshader_menu_stance_drawer_instance_t instance = { + .id = taken? + loeffect_stance_get_id_for_menu_shader(i): + LOSHADER_MENU_STANCE_ID_EMPTY, + .pos = vec2(geo->range.x*cos(theta), geo->range.y*sin(theta)), + .size = geo->icon, + .alpha = menu->alpha, + .highlight = menu->stance_highlight * !!(menu->highlighted_stance == i), + }; + loshader_menu_stance_drawer_add_instance( + menu->shaders->drawer.menu_stance, &instance); + } +} + +static void loplayer_menu_draw_status_(const loplayer_menu_t* menu) { + assert(menu != NULL); + + loplayer_menu_draw_stance_icons_(menu); + + if (menu->stance_hovering) { + const loplayer_menu_stance_text_set_t* set = + &menu->text.stance[menu->display_stance_text_index]; + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, set->name); + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, set->desc); + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, set->note); + } + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, menu->text.exit); +} + +static void loplayer_menu_draw_popup_(const loplayer_menu_t* menu) { + assert(menu != NULL); + + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, menu->text.popup_title); + loshader_menu_text_drawer_add_block( + &menu->shaders->drawer.menu_text, menu->text.popup_body); +} + +loplayer_menu_t* loplayer_menu_new( + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_status_t* status, + const loplayer_controller_t* controller) { + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(status != NULL); + assert(controller != NULL); + + loplayer_menu_t* menu = memory_new(sizeof(*menu)); + *menu = (typeof(*menu)) { + .res = res, + .shaders = shaders, + .ticker = ticker, + .status = status, + .controller = controller, + }; + + loplayer_menu_calculate_geometry_(menu); + + menu->font = (typeof(menu->font)) { + .large = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.sans, + menu->geometry.fontsz_large_px.x, + menu->geometry.fontsz_large_px.y), + .normal = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.sans, + menu->geometry.fontsz_normal_px.x, + menu->geometry.fontsz_normal_px.y), + .small = glyphas_cache_new( + shaders->tex.menu_text, + &res->font.serif, + menu->geometry.fontsz_small_px.x, + menu->geometry.fontsz_small_px.y), + }; + + menu->text = (typeof(menu->text)) { + .popup_title = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -menu->geometry.fontsz_large_px.y, + INT32_MAX, + 32), + .popup_body = glyphas_block_new( + GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, + -menu->geometry.fontsz_normal_px.y*1.3f, + menu->geometry.fontsz_normal_px.x*18, + 512), + }; + loplayer_menu_create_stance_text_(menu); + loplayer_menu_create_exit_text_(menu); + + return menu; +} + +void loplayer_menu_delete(loplayer_menu_t* menu) { + if (menu == NULL) return; + + static const size_t len = + sizeof(menu->text.stance)/sizeof(menu->text.stance[0]); + for (size_t i = 0; i < len; ++i) { + loplayer_menu_deinitialize_stance_text_set_(&menu->text.stance[i]); + } + + glyphas_block_delete(menu->text.popup_title); + glyphas_block_delete(menu->text.popup_body); + glyphas_block_delete(menu->text.exit); + + glyphas_cache_delete(menu->font.large); + glyphas_cache_delete(menu->font.normal); + glyphas_cache_delete(menu->font.small); + + memory_delete(menu); +} + +void loplayer_menu_show_status(loplayer_menu_t* menu) { + assert(menu != NULL); + + menu->state = LOPLAYER_MENU_STATE_STATUS; + menu->stance_hovering = false; + menu->stance_highlight = 0; +} + +void loplayer_menu_show_status_with_stance_highlighted( + loplayer_menu_t* menu, loeffect_stance_id_t id) { + assert(menu != NULL); + + loplayer_menu_show_status(menu); + menu->stance_highlight = 1; + menu->highlighted_stance = id; +} + +void loplayer_menu_popup( + loplayer_menu_t* menu, const char* title, const char* text) { + assert(menu != NULL); + + menu->state = LOPLAYER_MENU_STATE_POPUP; + loplayer_menu_build_popup_text_(menu, title, text); +} + +void loplayer_menu_hide(loplayer_menu_t* menu) { + assert(menu != NULL); + + menu->state = LOPLAYER_MENU_STATE_HIDDEN; +} + +void loplayer_menu_update(loplayer_menu_t* menu) { + assert(menu != NULL); + + if (menu->state == LOPLAYER_MENU_STATE_HIDDEN) { + locommon_easing_linear_float(&menu->alpha, 0, menu->ticker->delta_f*2); + return; + } + locommon_easing_linear_float(&menu->alpha, 1, menu->ticker->delta_f*2); + + switch (menu->state) { + case LOPLAYER_MENU_STATE_STATUS: + loplayer_menu_update_status_(menu); + return; + case LOPLAYER_MENU_STATE_POPUP: + loplayer_menu_update_popup_(menu); + return; + default: + assert(false); + } +} + +void loplayer_menu_draw_ui(const loplayer_menu_t* menu) { + assert(menu != NULL); + + loshader_menu_background_drawer_set_alpha( + menu->shaders->drawer.menu_background, menu->alpha); + menu->shaders->drawer.menu_text.alpha = menu->alpha; + + switch (menu->state) { + case LOPLAYER_MENU_STATE_HIDDEN: + return; + case LOPLAYER_MENU_STATE_STATUS: + loplayer_menu_draw_status_(menu); + return; + case LOPLAYER_MENU_STATE_POPUP: + loplayer_menu_draw_popup_(menu); + return; + default: + assert(false); + } +} + +bool loplayer_menu_is_shown(const loplayer_menu_t* menu) { + assert(menu != NULL); + + return menu->state != LOPLAYER_MENU_STATE_HIDDEN; +} + +bool loplayer_menu_is_exit_requested(const loplayer_menu_t* menu) { + assert(menu != NULL); + + return menu->request_exit; +} + +void loplayer_menu_pack(const loplayer_menu_t* menu, msgpack_packer* packer) { + assert(menu != NULL); + assert(packer != NULL); + + mpkutil_pack_bool(packer, menu->state != LOPLAYER_MENU_STATE_HIDDEN); +} + +bool loplayer_menu_unpack(loplayer_menu_t* menu, const msgpack_object* obj) { + assert(menu != NULL); + + bool shown = false; + if (!mpkutil_get_bool(obj, &shown)) return false; + + if (shown) loplayer_menu_show_status(menu); + return true; +} diff --git a/core/loplayer/menu.h b/core/loplayer/menu.h new file mode 100644 index 0000000..5312551 --- /dev/null +++ b/core/loplayer/menu.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include + +#include "core/locommon/ticker.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./controller.h" +#include "./entity.h" +#include "./status.h" + +struct loplayer_menu_t; +typedef struct loplayer_menu_t loplayer_menu_t; + +loplayer_menu_t* /* OWNERSHIP */ +loplayer_menu_new( + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + const loplayer_status_t* status, + const loplayer_controller_t* controller +); + +void +loplayer_menu_delete( + loplayer_menu_t* menu /* OWNERSHIP */ +); + +void +loplayer_menu_show_status( + loplayer_menu_t* menu +); + +void +loplayer_menu_show_status_with_stance_highlighted( + loplayer_menu_t* menu, + loeffect_stance_id_t id +); + +void +loplayer_menu_popup( + loplayer_menu_t* menu, + const char* title, + const char* text +); + +void +loplayer_menu_hide( + loplayer_menu_t* menu +); + +void +loplayer_menu_update( + loplayer_menu_t* menu +); + +void +loplayer_menu_draw_ui( + const loplayer_menu_t* menu +); + +bool +loplayer_menu_is_shown( + const loplayer_menu_t* menu +); + +bool +loplayer_menu_is_exit_requested( + const loplayer_menu_t* menu +); + +void +loplayer_menu_pack( + const loplayer_menu_t* menu, + msgpack_packer* packer +); + +bool +loplayer_menu_unpack( + loplayer_menu_t* menu, + const msgpack_object* obj /* NULLABLE */ +); diff --git a/core/loplayer/player.c b/core/loplayer/player.c new file mode 100644 index 0000000..83d2199 --- /dev/null +++ b/core/loplayer/player.c @@ -0,0 +1,288 @@ +#include "./player.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/math/matrix.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/input.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loshader/hud_bar.h" +#include "core/loshader/posteffect.h" +#include "core/loshader/set.h" + +#include "./action.h" +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +#define LOPLAYER_COMBAT_ATTACK_RESERVE 32 + +void loplayer_initialize( + loplayer_t* player, + loentity_id_t id, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + const mat4_t* proj) { + assert(player != NULL); + assert(res != NULL); + assert(shaders != NULL); + assert(ticker != NULL); + assert(bullets != NULL); + assert(entities != NULL); + assert(mat4_valid(proj)); + + *player = (typeof(*player)) { + .shaders = shaders, + }; + + player->event = loplayer_event_new( + res, + shaders); + + loplayer_status_initialize(&player->status, res, ticker); + + loplayer_entity_initialize( + &player->entity, + id, + res->sound, + shaders->drawer.character, + ticker, + entities, + player->event, + &player->status); + loentity_store_add(entities, &player->entity.super.super); + + player->combat = loplayer_combat_new( + res->sound, + shaders->drawer.combat_ring, + ticker, + entities, + &player->status, + &player->entity, + LOPLAYER_COMBAT_ATTACK_RESERVE); + + loplayer_controller_initialize(&player->controller); + + loplayer_camera_initialize( + &player->camera, + shaders, + ticker, + player->event, + &player->status, + &player->entity, + proj); + player->hud = loplayer_hud_new( + res, + shaders, + ticker, + player->event, + &player->status, + &player->entity); + player->menu = loplayer_menu_new( + res, + shaders, + ticker, + &player->status, + &player->controller); + + player->action = loplayer_action_new( + res, + ticker, + bullets, + entities, + player->event, + &player->status, + &player->entity, + player->combat, + &player->controller, + &player->camera, + player->hud, + player->menu); +} + +void loplayer_deinitialize(loplayer_t* player) { + assert(player != NULL); + + loplayer_action_delete(player->action); + + loplayer_menu_delete(player->menu); + loplayer_hud_delete(player->hud); + loplayer_camera_deinitialize(&player->camera); + + loplayer_controller_deinitialize(&player->controller); + + loplayer_combat_delete(player->combat); + + loplayer_entity_deinitialize(&player->entity); + loplayer_status_deinitialize(&player->status); + + loplayer_event_delete(player->event); +} + +void loplayer_popup(loplayer_t* player, const char* title, const char* text) { + assert(player != NULL); + + loplayer_action_start_menu_popup_state(player->action); + loplayer_menu_popup(player->menu, title, text); +} + +bool loplayer_attack( + loplayer_t* player, const loplayer_combat_attack_t* attack) { + assert(player != NULL); + assert(attack != NULL); + + return loplayer_combat_add_attack(player->combat, attack); +} + +void loplayer_touch_encephalon(loplayer_t* player) { + assert(player != NULL); + + loplayer_status_set_respawn_position( + &player->status, &player->entity.super.super.pos); + loplayer_status_reset(&player->status); +} + +void loplayer_gain_stance(loplayer_t* player, loeffect_stance_id_t id) { + assert(player != NULL); + + if (!loplayer_status_add_stance(&player->status, id)) return; + + loplayer_action_start_menu_popup_state(player->action); + loplayer_menu_show_status_with_stance_highlighted(player->menu, id); +} + +void loplayer_gain_faith(loplayer_t* player, float amount) { + assert(player != NULL); + + player->status.recipient.faith += amount; +} + +void loplayer_update( + loplayer_t* player, + const locommon_input_t* input, + const locommon_position_t* cursor) { + assert(player != NULL); + assert(input != NULL); + assert(locommon_position_valid(cursor)); + + loplayer_status_update(&player->status); + /* entity is updated through entity store. */ + + loplayer_combat_update(player->combat); + + loplayer_camera_update(&player->camera); + loplayer_hud_update(player->hud); + loplayer_menu_update(player->menu); + + loplayer_controller_update(&player->controller, input, cursor); + loplayer_action_execute(player->action); +} + +void loplayer_draw(const loplayer_t* player) { + assert(player != NULL); + + loplayer_camera_draw(&player->camera); + loplayer_event_draw(player->event); + + loplayer_combat_draw_ui(player->combat); + loplayer_hud_draw_ui(player->hud); + loplayer_menu_draw_ui(player->menu); +} + +void loplayer_pack(const loplayer_t* player, msgpack_packer* packer) { + assert(player != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, 6); + + mpkutil_pack_str(packer, "status"); + loplayer_status_pack(&player->status, packer); + + mpkutil_pack_str(packer, "entity"); + loplayer_entity_pack(&player->entity, packer); + + mpkutil_pack_str(packer, "combat"); + loplayer_combat_pack(player->combat, packer); + + mpkutil_pack_str(packer, "camera"); + loplayer_camera_pack(&player->camera, packer); + + mpkutil_pack_str(packer, "menu"); + loplayer_menu_pack(player->menu, packer); + + mpkutil_pack_str(packer, "action"); + loplayer_action_pack(player->action, packer); +} + +bool loplayer_unpack(loplayer_t* player, const msgpack_object* obj) { + assert(player != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + if (!loplayer_status_unpack(&player->status, item_("status")) || + !loplayer_entity_unpack(&player->entity, item_("entity")) || + !loplayer_combat_unpack( player->combat, item_("combat")) || + !loplayer_camera_unpack(&player->camera, item_("camera")) || + !loplayer_menu_unpack ( player->menu, item_("menu" )) || + !loplayer_action_unpack( player->action, item_("action"))) { + return false; + } +# undef item_ + return true; +} + +void loplayer_test_packing(const loplayer_t* player) { + assert(player != NULL); + + msgpack_sbuffer buf; + msgpack_sbuffer_init(&buf); + + msgpack_packer pk; + msgpack_packer_init(&pk, &buf, msgpack_sbuffer_write); + + loplayer_pack(player, &pk); + + msgpack_unpacked upk; + msgpack_unpacked_init(&upk); + + size_t off = 0; + const msgpack_unpack_return r = + msgpack_unpack_next(&upk, buf.data, buf.size, &off); + if (r != MSGPACK_UNPACK_SUCCESS) { + fprintf(stderr, "player: invalid msgpack format\n"); + abort(); + } + if (!loplayer_unpack((loplayer_t*) player, &upk.data)) { + fprintf(stderr, "player: failed to unpack\n"); + abort(); + } + printf("player: packing test passed\n"); + msgpack_unpacked_destroy(&upk); + msgpack_sbuffer_destroy(&buf); +} diff --git a/core/loplayer/player.h b/core/loplayer/player.h new file mode 100644 index 0000000..499b3f2 --- /dev/null +++ b/core/loplayer/player.h @@ -0,0 +1,122 @@ +#pragma once + +#include + +#include + +#include "util/math/matrix.h" + +#include "core/lobullet/pool.h" +#include "core/locommon/input.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/stance.h" +#include "core/loentity/entity.h" +#include "core/loentity/store.h" +#include "core/loresource/set.h" +#include "core/loshader/set.h" + +#include "./action.h" +#include "./camera.h" +#include "./combat.h" +#include "./controller.h" +#include "./entity.h" +#include "./event.h" +#include "./hud.h" +#include "./menu.h" +#include "./status.h" + +typedef struct { + loshader_set_t* shaders; + + loplayer_event_t* event; + + loplayer_status_t status; + loplayer_entity_t entity; + + loplayer_combat_t* combat; + + loplayer_controller_t controller; + + loplayer_camera_t camera; + loplayer_hud_t* hud; + loplayer_menu_t* menu; + + loplayer_action_t* action; +} loplayer_t; + +void +loplayer_initialize( + loplayer_t* player, + loentity_id_t id, + loresource_set_t* res, + loshader_set_t* shaders, + const locommon_ticker_t* ticker, + lobullet_pool_t* bullets, + loentity_store_t* entities, + const mat4_t* proj +); + +void +loplayer_deinitialize( + loplayer_t* player +); + +void +loplayer_popup( + loplayer_t* player, + const char* title, + const char* text +); + +bool +loplayer_attack( + loplayer_t* player, + const loplayer_combat_attack_t* attack +); + +void +loplayer_touch_encephalon( + loplayer_t* player +); + +void +loplayer_gain_stance( + loplayer_t* player, + loeffect_stance_id_t id +); + +void +loplayer_gain_faith( + loplayer_t* player, + float amount +); + +void +loplayer_update( + loplayer_t* player, + const locommon_input_t* input, + const locommon_position_t* cursor +); + +void +loplayer_draw( + const loplayer_t* player +); + +void +loplayer_pack( + const loplayer_t* player, + msgpack_packer* packer +); + +bool +loplayer_unpack( + loplayer_t* player, + const msgpack_object* obj /* NULLABLE */ +); + +void +loplayer_test_packing( + const loplayer_t* player +); diff --git a/core/loplayer/status.c b/core/loplayer/status.c new file mode 100644 index 0000000..34e1376 --- /dev/null +++ b/core/loplayer/status.c @@ -0,0 +1,193 @@ +#include "./status.h" + +#include +#include +#include +#include + +#include + +#include "util/math/algorithm.h" +#include "util/math/vector.h" +#include "util/mpkutil/get.h" +#include "util/mpkutil/pack.h" + +#include "core/locommon/easing.h" +#include "core/locommon/msgpack.h" +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" +#include "core/loresource/sound.h" + +static const loeffect_recipient_status_t loplayer_status_base_ = { + .attack = .3f, + .defence = .2f, + .speed = .3f, + .jump = 1.f, +}; + +#define LOPLAYER_STATUS_PARAM_TO_PACK_EACH_(PROC) do { \ + PROC("stances", stances); \ + PROC("madness", recipient.madness); \ + PROC("faith", recipient.faith); \ + PROC("effects", recipient.effects); \ + PROC("respawn-pos", respawn_pos); \ + PROC("last-damage-time", last_damage_time); \ + PROC("bullet-immune-until", bullet_immune_until); \ +} while (0) +#define LOPLAYER_STATUS_PARAM_TO_PACK_COUNT 7 + +#define LOPLAYER_STATUS_MADNESS_DYING_THRESHOLD .3f + +void loplayer_status_initialize( + loplayer_status_t* status, + loresource_set_t* res, + const locommon_ticker_t* ticker) { + assert(status != NULL); + assert(ticker != NULL); + + *status = (typeof(*status)) { + .res = res, + .ticker = ticker, + + .respawn_pos = locommon_position(0, 0, vec2(.5f, .5f)), + }; + + loeffect_stance_set_initialize(&status->stances); + + loeffect_recipient_initialize(&status->recipient, ticker); +} + +void loplayer_status_deinitialize(loplayer_status_t* status) { + assert(status != NULL); + + loeffect_stance_set_deinitialize(&status->stances); + loeffect_recipient_deinitialize(&status->recipient); +} + +void loplayer_status_reset(loplayer_status_t* status) { + assert(status != NULL); + + loeffect_recipient_reset(&status->recipient); +} + +bool loplayer_status_add_stance( + loplayer_status_t* status, loeffect_stance_id_t id) { + assert(status != NULL); + + if (loeffect_stance_set_has(&status->stances, id)) return false; + loeffect_stance_set_add(&status->stances, id); + return true; +} + +void loplayer_status_remove_stance( + loplayer_status_t* status, loeffect_stance_id_t id) { + assert(status != NULL); + + loeffect_stance_set_remove(&status->stances, id); +} + +void loplayer_status_apply_effect( + loplayer_status_t* status, const loeffect_t* effect) { + assert(status != NULL); + assert(effect != NULL); + + if (effect->id == LOEFFECT_ID_IMMEDIATE_DAMAGE) { + status->last_damage_time = status->ticker->time; + loresource_sound_play(status->res->sound, "damage"); + } + + loeffect_recipient_apply_effect(&status->recipient, effect); +} + +void loplayer_status_update(loplayer_status_t* status) { + assert(status != NULL); + + static const uint64_t dying_sound_period = 4000; + + loeffect_recipient_status_t base = loplayer_status_base_; + if (loeffect_stance_set_has(&status->stances, LOEFFECT_STANCE_ID_PHILOSOPHER)) { + base.defence += .3f; + } + + loeffect_recipient_update(&status->recipient, &base); + + /* Makes decreasing madness faster if the player has a stance, UNFINISHER. */ + if (loeffect_stance_set_has( + &status->stances, LOEFFECT_STANCE_ID_UNFINISHER)) { + if (status->recipient.madness > 0) { + if (status->recipient.faith <= 0) { + status->recipient.madness -= status->ticker->delta_f/30; + status->recipient.last_damage = LOEFFECT_ID_LOST; + } else if (status->recipient.faith >= .5f) { + status->recipient.madness += status->ticker->delta_f/120; + } + } + } + + if (status->recipient.madness > 0 && + status->recipient.madness <= LOPLAYER_STATUS_MADNESS_DYING_THRESHOLD) { + uint64_t t = status->ticker->time; + uint64_t pt = t - status->ticker->delta; + + status->dying_effect = t%dying_sound_period*1.f / dying_sound_period; + + t /= dying_sound_period; + pt /= dying_sound_period; + + if (t != pt) loresource_sound_play(status->res->sound, "dying"); + } else { + locommon_easing_linear_float( + &status->dying_effect, 0, status->ticker->delta_f*5); + } +} + +void loplayer_status_set_respawn_position( + loplayer_status_t* status, const locommon_position_t* pos) { + assert(status != NULL); + assert(locommon_position_valid(pos)); + + status->respawn_pos = *pos; +} + +void loplayer_status_pack( + const loplayer_status_t* status, msgpack_packer* packer) { + assert(status != NULL); + assert(packer != NULL); + + msgpack_pack_map(packer, LOPLAYER_STATUS_PARAM_TO_PACK_COUNT); + +# define pack_(name, var) do { \ + mpkutil_pack_str(packer, name); \ + LOCOMMON_MSGPACK_PACK_ANY(packer, &status->var); \ + } while (0) + + LOPLAYER_STATUS_PARAM_TO_PACK_EACH_(pack_); + +# undef pack_ +} + +bool loplayer_status_unpack( + loplayer_status_t* status, const msgpack_object* obj) { + assert(status != NULL); + + if (obj == NULL) return false; + + const msgpack_object_map* root = mpkutil_get_map(obj); + +# define item_(v) mpkutil_get_map_item_by_str(root, v) + +# define unpack_(name, var) do { \ + if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &status->var)) { \ + return false; \ + } \ + } while (0) + + LOPLAYER_STATUS_PARAM_TO_PACK_EACH_(unpack_); + return true; + +# undef unpack_ +# undef item_ +} diff --git a/core/loplayer/status.h b/core/loplayer/status.h new file mode 100644 index 0000000..8c5d261 --- /dev/null +++ b/core/loplayer/status.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include + +#include + +#include "util/math/vector.h" + +#include "core/locommon/position.h" +#include "core/locommon/ticker.h" +#include "core/loeffect/recipient.h" +#include "core/loeffect/stance.h" +#include "core/loresource/set.h" + +typedef struct { + loresource_set_t* res; + const locommon_ticker_t* ticker; + + loeffect_stance_set_t stances; + loeffect_recipient_t recipient; + + locommon_position_t respawn_pos; + + uint64_t last_damage_time; + uint64_t bullet_immune_until; + + float dying_effect; /* 0~1 */ +} loplayer_status_t; + +void +loplayer_status_initialize( + loplayer_status_t* status, + loresource_set_t* res, + const locommon_ticker_t* ticker +); + +void +loplayer_status_deinitialize( + loplayer_status_t* status +); + +void +loplayer_status_reset( + loplayer_status_t* status +); + +bool +loplayer_status_add_stance( + loplayer_status_t* status, + loeffect_stance_id_t id +); + +void +loplayer_status_remove_stance( + loplayer_status_t* status, + loeffect_stance_id_t id +); + +void +loplayer_status_apply_effect( + loplayer_status_t* status, + const loeffect_t* effect +); + +void +loplayer_status_update( + loplayer_status_t* status +); + +void +loplayer_status_set_respawn_position( + loplayer_status_t* status, + const locommon_position_t* pos +); + +void +loplayer_status_pack( + const loplayer_status_t* status, + msgpack_packer* packer +); + +bool +loplayer_status_unpack( + loplayer_status_t* status, + const msgpack_object* obj +); diff --git a/core/loresource/CMakeLists.txt b/core/loresource/CMakeLists.txt new file mode 100644 index 0000000..5bcd7b8 --- /dev/null +++ b/core/loresource/CMakeLists.txt @@ -0,0 +1,40 @@ +add_library(loresource + font.c + language.c + music.c + set.c + sound.c + text.c +) +target_any_sources(loresource + font/sans.woff + font/serif.woff + + music/biome_boss.mp3 + music/biome_cavias_camp.mp3 + music/biome_laboratory.mp3 + music/biome_metaphysical_gate.mp3 + music/boss_big_warder.mp3 + music/boss_greedy_scientist.mp3 + music/boss_theists_child.mp3 + music/title.mp3 + + sound/bomb.mp3 + sound/enemy_shoot.mp3 + sound/enemy_trigger.mp3 + sound/damage.mp3 + sound/dodge.mp3 + sound/dying.mp3 + sound/guard.mp3 + sound/player_shoot.mp3 + sound/player_trigger.mp3 + sound/reflection.mp3 + sound/touch_gate.mp3 +) +target_link_libraries(loresource + dictres + glyphas + jukebox + math + memory +) diff --git a/core/loresource/font.c b/core/loresource/font.c new file mode 100644 index 0000000..81fe60d --- /dev/null +++ b/core/loresource/font.c @@ -0,0 +1,41 @@ +#include "./font.h" + +#include +#include + +#include "util/glyphas/context.h" +#include "util/glyphas/face.h" + +/* resources */ +#include "anysrc/font/sans.woff.h" +#include "anysrc/font/serif.woff.h" + +void loresource_font_initialize(loresource_font_t* font) { + assert(font != NULL); + + *font = (typeof(*font)) {0}; + + glyphas_context_initialize(&font->glyphas); + +# define load_face_(name) \ + glyphas_face_initialize_from_buffer( \ + &font->name, \ + &font->glyphas, \ + loresource_font_##name##_woff_, \ + sizeof(loresource_font_##name##_woff_), \ + 0) + + load_face_(sans); + load_face_(serif); + +# undef load_face_ +} + +void loresource_font_deinitialize(loresource_font_t* font) { + assert(font != NULL); + + glyphas_face_deinitialize(&font->sans); + glyphas_face_deinitialize(&font->serif); + + glyphas_context_deinitialize(&font->glyphas); +} diff --git a/core/loresource/font.h b/core/loresource/font.h new file mode 100644 index 0000000..5df58c1 --- /dev/null +++ b/core/loresource/font.h @@ -0,0 +1,21 @@ +#pragma once + +#include "util/glyphas/context.h" +#include "util/glyphas/face.h" + +typedef struct { + glyphas_context_t glyphas; + + glyphas_face_t sans; + glyphas_face_t serif; +} loresource_font_t; + +void +loresource_font_initialize( + loresource_font_t* font +); + +void +loresource_font_deinitialize( + loresource_font_t* font +); diff --git a/core/loresource/font/LICENSE-SIL.txt b/core/loresource/font/LICENSE-SIL.txt new file mode 100644 index 0000000..c3045a2 --- /dev/null +++ b/core/loresource/font/LICENSE-SIL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2011, Cyreal (www.cyreal.org), +with Reserved Font Name "Junge". +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/core/loresource/font/README.md b/core/loresource/font/README.md new file mode 100644 index 0000000..5c55bef --- /dev/null +++ b/core/loresource/font/README.md @@ -0,0 +1,13 @@ +font files +==== + +All fonts are under **SIL OPEN FONT LICENSE**. +Please see LICENSE.txt in the same directory. + +## sans.woff + +[Noto Sans CJK Jp Regular (minimized version)](https://github.com/hirofumii/Noto-Sans-CJK-JP.min) + +## serif.woff + +[Noto Serif CJK Jp Regular (minimized version)](https://github.com/hirofumii/Noto-Serif-CJK-JP.min) diff --git a/core/loresource/font/sans.woff b/core/loresource/font/sans.woff new file mode 100755 index 0000000000000000000000000000000000000000..da490500e100c040542e11c214c3596884002d00 GIT binary patch literal 664288 zcmV)ZK&!uZPew*hR8&s@3L@YD2><{94An>g0RR91000000000000000000000000( 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μ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