[RELEASE] u22-v04

This version is submitted for U22 final presentation. (squashed 158 commits)
This commit is contained in:
falsycat 2020-10-09 00:00:00 +00:00
parent 84c3a02b9a
commit 80b3b82332
277 changed files with 12154 additions and 13836 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/.build
/.rbuild
*.swp

5
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "thirdparty/miniaudio"]
path = thirdparty/miniaudio/repo
url = https://github.com/dr-soft/miniaudio
url = https://github.com/mackron/miniaudio
[submodule "thirdparty/tinycthread"]
path = thirdparty/tinycthread
url = https://github.com/tinycthread/tinycthread

View File

@ -2,35 +2,37 @@ cmake_minimum_required(VERSION 3.16)
project(leftone C)
if (BUILD_TESTING)
enable_testing()
endif()
include(CTest)
set(LEFTONE_C_FLAGS
-Werror -Wall -Wextra -Wno-missing-field-initializers
$<$<CONFIG:Release>:-Wno-unused-parameter>
$<$<PLATFORM_ID:Windows>:-mconsole>
)
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)
set(SDL2_TARGET SDL2::SDL2)
if (NOT TARGET SDL2::SDL2)
set(SDL2_TARGET ${SDL2_LIBRARIES})
endif()
include_directories(SYSTEM
${FREETYPE_INCLUDE_DIRS}
${GLEW_INCLUDE_DIRS}
${OPENGL_INCLUDE_DIR}
${SDL2_INCLUDE_DIRS}
)
include_directories(.)
include_directories(. ${CMAKE_CURRENT_BINARY_DIR})
include(cmake/anysrc.cmake)
include(cmake/benum.cmake)
include(cmake/crial.cmake)
include(cmake/sos.cmake)
add_subdirectory(app)

361
LICENSE Normal file
View File

@ -0,0 +1,361 @@
Creative Commons Legal Code
Attribution-NonCommercial-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(g) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined above) for the purposes of this
License.
c. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
d. "License Elements" means the following high-level license attributes
as selected by Licensor and indicated in the title of this License:
Attribution, Noncommercial, ShareAlike.
e. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
f. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
g. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
h. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
i. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
j. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved, including but not limited to the
rights described in Section 4(e).
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(d), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(d), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under: (i)
the terms of this License; (ii) a later version of this License with
the same License Elements as this License; (iii) a Creative Commons
jurisdiction license (either this or a later license version) that
contains the same License Elements as this License (e.g.,
Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License").
You must include a copy of, or the URI, for Applicable License with
every copy of each Adaptation You Distribute or Publicly Perform. You
may not offer or impose any terms on the Adaptation that restrict the
terms of the Applicable License or the ability of the recipient of the
Adaptation to exercise the rights granted to that recipient under the
terms of the Applicable License. You must keep intact all notices that
refer to the Applicable License and to the disclaimer of warranties
with every copy of the Work as included in the Adaptation You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Adaptation, You may not impose any effective technological
measures on the Adaptation that restrict the ability of a recipient of
the Adaptation from You to exercise the rights granted to that
recipient under the terms of the Applicable License. This Section 4(b)
applies to the Adaptation as incorporated in a Collection, but this
does not require the Collection apart from the Adaptation itself to be
made subject to the terms of the Applicable License.
c. You may not exercise any of the rights granted to You in Section 3
above in any manner that is primarily intended for or directed toward
commercial advantage or private monetary compensation. The exchange of
the Work for other copyrighted works by means of digital file-sharing
or otherwise shall not be considered to be intended for or directed
toward commercial advantage or private monetary compensation, provided
there is no payment of any monetary compensation in con-nection with
the exchange of copyrighted works.
d. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and, (iv) consistent with Section 3(b), in the case of an Adaptation,
a credit identifying the use of the Work in the Adaptation (e.g.,
"French translation of the Work by Original Author," or "Screenplay
based on original Work by Original Author"). The credit required by
this Section 4(d) may be implemented in any reasonable manner;
provided, however, that in the case of a Adaptation or Collection, at
a minimum such credit will appear, if a credit for all contributing
authors of the Adaptation or Collection appears, then as part of these
credits and in a manner at least as prominent as the credits for the
other contributing authors. For the avoidance of doubt, You may only
use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this
License, You may not implicitly or explicitly assert or imply any
connection with, sponsorship or endorsement by the Original Author,
Licensor and/or Attribution Parties, as appropriate, of You or Your
use of the Work, without the separate, express prior written
permission of the Original Author, Licensor and/or Attribution
Parties.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor reserves
the exclusive right to collect such royalties for any exercise by
You of the rights granted under this License if Your exercise of
such rights is for a purpose or use which is otherwise than
noncommercial as permitted under Section 4(c) and otherwise waives
the right to collect royalties through any statutory or compulsory
licensing scheme; and,
iii. Voluntary License Schemes. The Licensor reserves the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License that is for a
purpose or use which is otherwise than noncommercial as permitted
under Section 4(c).
f. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE
FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS
AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE
WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT
DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED
WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at https://creativecommons.org/.

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# LEFTONE
LEFTONE is 2d action game got a prize on [2020 U22 Programming Contest](https://u22procon.com)
I'm noob at composing so don't expect the BGM quality. :(
## AUTHOR
falsycat
[portfolio](https://falsy.cat/) | [twitter](https://twitter.com/falsycat)
## LICENSE
***CC-BY-NC-SA-3.0***
### Libraries
- [freetype2](https://www.freetype.org/index.html) - FreeType License
- [glew](http://glew.sourceforge.net) - Modified BSD License
- [miniaudio](https://github.com/mackron/miniaudio) - public domain
- [msgpack-c](https://github.com/msgpack/msgpack-c) - Boost Software License
- [SDL2](https://www.libsdl.org/index.php) - zlib License
- [tinycthread](https://github.com/tinycthread/tinycthread) - zlib License
### Resources
- [Otologic](https://otologic.jp) - CC-BY-4.0
- [99sounds](https://99sounds.org) - [license](https://99sounds.org/license/)
- They replied me it's no problem to put their works on GitHub.
- [minimized Noto Font](https://github.com/hirofumii/Noto-Sans-CJK-JP.min) - SIL OPEN FONT LICENSE
### Why CC?
Because some of my shaders are inspired by the followings on ShaderToy, whose default license is CC-BY-NC-SA-3.0.
***YOU HAD BETTER NOT OPEN THESE LINKS BY POOR MACHINES.***
- [fractal cage - gaz](https://www.shadertoy.com/view/3l2yDd)
- [Ray Marching: Part 2 - jlfwong](https://www.shadertoy.com/view/lt33z7)
- [Raymarching - Primitives - iq](https://www.shadertoy.com/view/Xds3zN)

View File

@ -1 +1,3 @@
add_compile_options(${LEFTONE_C_FLAGS})
add_subdirectory(sdl)

View File

@ -5,7 +5,7 @@ add_executable(app-sdl
)
target_link_libraries(app-sdl
GLEW::GLEW
SDL2::SDL2
${SDL2_TARGET}
conv
parsarg

View File

@ -75,9 +75,6 @@ void app_args_parse(app_args_t* args, int argc, char** argv) {
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);

View File

@ -10,15 +10,13 @@
#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_LSHIFT? LOCOMMON_INPUT_BUTTON_DODGE: \
(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_LEFT? LOCOMMON_INPUT_BUTTON_SHOOT: \
(m) == SDL_BUTTON_RIGHT? LOCOMMON_INPUT_BUTTON_GUARD: \
0)

View File

@ -3,7 +3,9 @@
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <SDL2/SDL.h>
@ -128,7 +130,8 @@ int main(int argc, char** argv) {
app_get_dpi_(&libs, &args);
loscene_context_t* ctx = loscene_context_new(&args.scene);
loscene_context_t ctx;
loscene_context_initialize(&ctx, &args.scene);
locommon_input_t input = {
.resolution = vec2(args.scene.width, args.scene.height),
@ -147,10 +150,10 @@ int main(int argc, char** argv) {
if (!app_event_handle(&input, &e)) goto EXIT;
}
if (!loscene_context_update(ctx, &input, base_time)) goto EXIT;
if (!loscene_context_update(&ctx, &input, base_time)) goto EXIT;
glClear(GL_COLOR_BUFFER_BIT);
loscene_context_draw(ctx);
loscene_context_draw(&ctx);
SDL_GL_SwapWindow(libs.win);
# ifndef NDEBUG
@ -166,7 +169,7 @@ int main(int argc, char** argv) {
}
EXIT:
loscene_context_delete(ctx);
loscene_context_deinitialize(&ctx);
app_deinitialize_libraries_(&libs);
return 0;
return EXIT_SUCCESS;
}

View File

@ -1,8 +1,6 @@
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})
@ -12,9 +10,10 @@ function(target_any_sources target)
set(out ${CMAKE_CURRENT_BINARY_DIR}/anysrc/${path})
add_custom_command(
OUTPUT ${out}.c ${out}.h
COMMAND cat ${in} | ${bin2c} ${name} ${out}
COMMAND cat ${in} | bash ${bin2c} ${name} ${out}
DEPENDS ${path} ${bin2c}
COMMENT "converting ${path} to C header")
COMMENT "converting ${path} to C header"
VERBATIM)
target_sources(${target} PRIVATE ${out}.c ${out}.h)
endforeach()
endfunction()

29
cmake/benum.cmake Normal file
View File

@ -0,0 +1,29 @@
add_custom_target(benum-generated)
function(target_benum_sources target)
set(benum ${LEFTONE_TOOL_DIR}/benum.sh)
set(output_files "")
foreach (path ${ARGN})
get_filename_component(dirname ${path} DIRECTORY)
get_filename_component(basename ${path} NAME_WE)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/benum/${dirname})
set(in ${CMAKE_CURRENT_SOURCE_DIR}/${path})
set(out ${CMAKE_CURRENT_BINARY_DIR}/benum/${dirname}/${basename})
add_custom_command(
OUTPUT ${out}.c ${out}.h
COMMAND bash ${benum} ${out} ${in} < ${in}
DEPENDS ${path} ${benum}
COMMENT "generating benum utilities for ${path}"
VERBATIM)
target_sources(${target} PRIVATE ${out}.c)
list(APPEND output_files ${out}.c ${out}.h)
endforeach()
add_custom_target(${target}-benum-generated DEPENDS ${output_files})
add_dependencies(${target} ${target}-benum-generated)
add_dependencies(benum-generated ${target}-benum-generated)
endfunction()

19
cmake/crial.cmake Normal file
View File

@ -0,0 +1,19 @@
function(target_crial_sources target)
set(crial ${LEFTONE_TOOL_DIR}/crial.sh)
foreach (path ${ARGN})
get_filename_component(dirname ${path} DIRECTORY)
get_filename_component(basename ${path} NAME_WE)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/crial/${dirname})
set(in ${CMAKE_CURRENT_SOURCE_DIR}/${path})
set(out ${CMAKE_CURRENT_BINARY_DIR}/crial/${dirname}/${basename}.h)
add_custom_command(
OUTPUT ${out}
COMMAND bash ${crial} < ${in} > ${out}
DEPENDS ${path} ${crial}
COMMENT "generating serializer from ${path}"
VERBATIM)
target_sources(${target} PRIVATE ${out})
endforeach()
endfunction()

View File

@ -1,20 +1,22 @@
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)
foreach (path ${ARGN})
get_filename_component(dirname ${path} DIRECTORY)
get_filename_component(basename ${path} NAME_WE)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sos/${dirname})
set(sos_target sos-${target}-${name})
set(in ${CMAKE_CURRENT_SOURCE_DIR}/${file})
set(out ${CMAKE_CURRENT_BINARY_DIR}/sos/${file})
set(sos_target ${target}-${basename}-sos-generator)
set(in ${CMAKE_CURRENT_SOURCE_DIR}/${path})
set(out ${CMAKE_CURRENT_BINARY_DIR}/sos/${path})
add_executable(${sos_target} ${in})
add_custom_command(
OUTPUT ${out}
COMMAND ${sos_target} > ${out}
DEPENDS ${sos_target}
COMMENT "generating ${file}")
COMMENT "generating ${path}"
VERBATIM)
target_sources(${target} PRIVATE ${out})
endforeach()
endfunction()

View File

@ -1,13 +1,15 @@
include_directories(.)
add_compile_options(${LEFTONE_C_FLAGS})
add_subdirectory(lobullet)
add_subdirectory(locharacter)
add_subdirectory(lochara)
add_subdirectory(locommon)
add_subdirectory(loeffect)
add_subdirectory(loentity)
add_subdirectory(loground)
add_subdirectory(loparticle)
add_subdirectory(loplayer)
add_subdirectory(loresource)
add_subdirectory(loscene)
add_subdirectory(loshader)
add_subdirectory(loui)
add_subdirectory(loworld)

View File

@ -1,10 +1,14 @@
add_library(lobullet
base.c
bomb.c
linear.c
misc.c
pool.c
)
target_benum_sources(lobullet
type.h
)
target_crial_sources(lobullet
base.crial
)
target_link_libraries(lobullet
msgpackc

View File

@ -12,6 +12,7 @@
#include "util/mpkutil/get.h"
#include "util/mpkutil/pack.h"
#include "core/locommon/msgpack.h"
#include "core/locommon/position.h"
#include "core/locommon/ticker.h"
#include "core/loentity/bullet.h"
@ -20,10 +21,20 @@
#include "core/loentity/store.h"
#include "core/loresource/set.h"
#include "core/loshader/bullet.h"
#include "core/loshader/set.h"
#include "./bomb.h"
#include "./linear.h"
#include "./misc.h"
#include "./type.h"
/* generated serializer */
#include "core/lobullet/crial/base.h"
static bool
(*const update_function_vtable_[LOBULLET_TYPE_COUNT])(lobullet_base_t* base) = {
[LOBULLET_TYPE_LINEAR_CIRCLE] = lobullet_linear_circle_update,
[LOBULLET_TYPE_LINEAR_TRIANGLE] = lobullet_linear_triangle_update,
[LOBULLET_TYPE_LINEAR_SQUARE] = lobullet_linear_square_update,
};
static void lobullet_base_delete_(loentity_t* entity) {
assert(entity != NULL);
@ -32,18 +43,6 @@ static void lobullet_base_delete_(loentity_t* 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) {
@ -55,18 +54,22 @@ static bool lobullet_base_update_(loentity_t* entity) {
assert(entity != NULL);
lobullet_base_t* base = (typeof(base)) entity;
base->cache = (typeof(base->cache)) {0};
base->super.owner = base->param.owner;
base->super.velocity = vec2(0, 0);
# define each_(NAME, name) do { \
if (base->type == LOBULLET_TYPE_##NAME) { \
return lobullet_##name##_update(base); \
} \
} while (0)
const locommon_position_t oldpos = base->super.super.pos;
LOBULLET_TYPE_EACH_(each_);
return false;
# undef each_
assert(update_function_vtable_[base->param.type] != NULL);
if (!update_function_vtable_[base->param.type](base)) {
return false;
}
if (base->cache.velocity_calc) {
locommon_position_sub(
&base->super.velocity, &base->super.super.pos, &oldpos);
}
return true;
}
static void lobullet_base_draw_(
@ -80,7 +83,8 @@ static void lobullet_base_draw_(
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);
loshader_bullet_drawer_add_instance(
&base->shaders->drawer.bullet, &base->cache.instance);
}
static void lobullet_base_pack_(
@ -90,29 +94,8 @@ static void lobullet_base_pack_(
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_
msgpack_pack_map(packer, CRIAL_PROPERTY_COUNT_);
CRIAL_SERIALIZER_;
}
static bool lobullet_base_affect_(
@ -122,57 +105,31 @@ static bool lobullet_base_affect_(
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);
}
locommon_position_sub(&v, &chara->super.pos, &base->super.super.pos);
vec2_muleq(&v, base->cache.knockback);
loentity_character_knockback(chara, &v);
if (base->cache.toxic) {
loentity_character_apply_effect(chara, &base->cache.effect);
loentity_character_apply_effect(chara, &base->param.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) {
lobullet_base_t* base,
loresource_set_t* res,
loshader_set_t* shaders,
const locommon_ticker_t* ticker,
loentity_store_t* entities) {
assert(base != NULL);
assert(res != NULL);
assert(drawer != NULL);
assert(shaders != 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,
.shaders = shaders,
.ticker = ticker,
.entities = entities,
};
@ -180,12 +137,31 @@ void lobullet_base_initialize(
void lobullet_base_reinitialize(lobullet_base_t* base, loentity_id_t id) {
assert(base != NULL);
assert(!base->used);
base->super.super.id = id;
base->super = (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_,
},
.id = id,
.subclass = LOENTITY_SUBCLASS_BULLET,
},
.vtable = {
.affect = lobullet_base_affect_,
},
};
base->param = (typeof(base->param)) {0};
}
void lobullet_base_deinitialize(lobullet_base_t* base) {
assert(base != NULL);
assert(!base->used);
lobullet_base_delete_(&base->super.super);
}
@ -195,41 +171,13 @@ bool lobullet_base_unpack(lobullet_base_t* base, const msgpack_object* obj) {
lobullet_base_reinitialize(base, 0);
const char* v;
size_t vlen;
const msgpack_object_map* root = mpkutil_get_map(obj);
if (root == NULL) goto FAIL;
# 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_
CRIAL_DESERIALIZER_;
return true;
FAIL:
lobullet_base_delete_(&base->super.super);
return false;
}

44
core/lobullet/base.crial Normal file
View File

@ -0,0 +1,44 @@
/* CRIAL
SERIALIZER_BEGIN
mpkutil_pack_str(packer, "$name");
mpkutil_pack_str(packer, $code);
END
DESERIALIZER_BEGIN
const char* v;
size_t vlen;
if (!mpkutil_get_str(
mpkutil_get_map_item_by_str(root, "$name"), &v, &vlen) ||
strncmp(v, $code, vlen) != 0 || $code[vlen] != 0) {
goto FAIL;
}
END
PROPERTY subclass = "bullet"
SERIALIZER_BEGIN
mpkutil_pack_str(packer, "$name");
LOCOMMON_MSGPACK_PACK_ANY(packer, &base->$code);
END
DESERIALIZER_BEGIN
if (!LOCOMMON_MSGPACK_UNPACK_ANY(
mpkutil_get_map_item_by_str(root, "$name"), &base->$code)) {
goto FAIL;
}
END
PROPERTY id = super.super.id
PROPERTY pos = super.super.pos
PROPERTY owner = param.owner
PROPERTY type = param.type
PROPERTY target = param.target
PROPERTY basepos = param.basepos
PROPERTY size = param.size
PROPERTY color = param.color
PROPERTY velocity = param.velocity
PROPERTY acceleration = param.acceleration
PROPERTY angle = param.angle
PROPERTY angular_velocity = param.angular_velocity
PROPERTY quiet = param.quiet
PROPERTY knockback = param.knockback
PROPERTY effect = param.effect
PROPERTY since = param.since
PROPERTY duration = param.duration
*/

View File

@ -13,54 +13,61 @@
#include "core/loentity/store.h"
#include "core/loresource/set.h"
#include "core/loshader/bullet.h"
#include "core/loshader/set.h"
#include "./misc.h"
#include "./type.h"
typedef enum {
LOBULLET_BASE_KNOCKBACK_ALGORITHM_VELOCITY,
LOBULLET_BASE_KNOCKBACK_ALGORITHM_POSITION,
} lobullet_base_knockback_algorithm_t;
typedef struct {
lobullet_type_t type;
loentity_id_t owner;
loentity_id_t target;
locommon_position_t basepos;
vec2_t size;
vec4_t color;
vec2_t velocity;
vec2_t acceleration;
float angle;
float angular_velocity;
bool quiet;
float knockback;
loeffect_t effect;
uint64_t since;
uint64_t duration;
} lobullet_base_param_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;
loresource_set_t* res;
loshader_set_t* shaders;
const locommon_ticker_t* ticker;
loentity_store_t* entities;
lobullet_base_param_t param;
/* 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;
bool toxic;
float knockback;
bool velocity_calc;
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
lobullet_base_t* base,
loresource_set_t* res,
loshader_set_t* shaders,
const locommon_ticker_t* ticker,
loentity_store_t* entities
);
void

View File

@ -1,205 +0,0 @@
#include "./bomb.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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(&param->pos) &&
vec2_valid(&param->size) &&
MATH_FLOAT_VALID(param->angle) &&
vec4_valid(&param->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;
}

View File

@ -1,82 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <msgpack.h>
#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)

View File

@ -1,197 +1,100 @@
#include "./linear.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <msgpack.h>
#include "util/coly2d/shape.h"
#include "util/math/constant.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/locommon/position.h"
#include "core/loeffect/effect.h"
#include "core/loentity/entity.h"
#include "core/loshader/bullet.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);
#define FADE_DURATION_ 200
static bool lobullet_linear_update_(lobullet_base_t* base) {
assert(base != NULL);
const lobullet_linear_param_t* p = (typeof(p)) base->data;
const uint64_t t = base->ticker->time - base->param.since;
const float tf = t/1000.f;
const float t = (base->ticker->time - p->since)/1000.f;
base->super.owner = p->owner;
if (t >= base->param.duration) {
return false;
}
const uint64_t rt = base->param.duration - t;
const float alpha = rt < FADE_DURATION_? rt*1.f/FADE_DURATION_: 1;
/* ---- movement ---- */
vec2_t v1;
vec2_mul(&v1, &p->velocity, t);
vec2_mul(&v1, &base->param.velocity, tf);
vec2_t v2;
vec2_mul(&v2, &p->acceleration, t*t/2);
vec2_mul(&v2, &base->param.acceleration, tf*tf/2);
base->super.super.pos = p->pos;
base->super.super.pos = base->param.basepos;
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);
vec2_mul(&base->super.velocity, &base->param.acceleration, tf);
vec2_addeq(&base->super.velocity, &base->param.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;
/* ---- parameters ---- */
const float angle = base->param.angle + base->param.angular_velocity*tf;
base->cache = (typeof(base->cache)) {
.toxic = true,
.knockback = base->param.knockback,
/* ---- 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,
.instance = {
.size = base->param.size,
.theta = angle,
.color = base->param.color,
.time = alpha,
},
};
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(&param->pos) &&
vec2_valid(&param->size) &&
vec2_valid(&param->velocity) &&
vec2_valid(&param->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->super.shape = (coly2d_shape_t) {
.size = base->param.size,
.angle = angle,
};
base->cache.instance.color.w = alpha;
return true;
}
bool lobullet_linear_circle_update(lobullet_base_t* base) {
assert(base != NULL);
if (!lobullet_linear_update_(base)) return false;
base->cache.instance.bullet_id = LOSHADER_BULLET_ID_LIGHT;
base->super.shape.type = COLY2D_SHAPE_TYPE_ELLIPSE;
return true;
}
bool lobullet_linear_triangle_update(lobullet_base_t* base) {
assert(base != NULL);
if (!lobullet_linear_update_(base)) return false;
base->cache.instance.bullet_id = LOSHADER_BULLET_ID_TRIANGLE;
base->super.shape.type = COLY2D_SHAPE_TYPE_TRIANGLE;
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;
}
bool lobullet_linear_square_update(lobullet_base_t* base) {
assert(base != NULL);
if (!lobullet_linear_update_(base)) return false;
base->cache.instance.bullet_id = LOSHADER_BULLET_ID_SQUARE;
base->super.shape.type = COLY2D_SHAPE_TYPE_RECT;
return true;
}
void lobullet_linear_build_(
lobullet_base_t* base,
const lobullet_base_param_t* param) {
assert(base != NULL);
assert(param != NULL);
base->super.super.pos = param->basepos;
base->param = *param;
base->param.since = base->ticker->time;
}

View File

@ -1,81 +1,51 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <msgpack.h>
#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_linear_circle_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)
bool
lobullet_linear_square_update(
lobullet_base_t* base
);
void
lobullet_linear_build_(
lobullet_base_t* base,
const lobullet_base_param_t* param
);
#define lobullet_linear_circle_build(base, ...) \
lobullet_linear_build_( \
base, \
&(lobullet_base_param_t) { \
.type = LOBULLET_TYPE_LINEAR_CIRCLE, \
__VA_ARGS__ \
})
#define lobullet_linear_triangle_build(base, ...) \
lobullet_linear_build_( \
base, \
&(lobullet_base_param_t) { \
.type = LOBULLET_TYPE_LINEAR_TRIANGLE, \
__VA_ARGS__ \
})
#define lobullet_linear_square_build(base, ...) \
lobullet_linear_build_( \
base, \
&(lobullet_base_param_t) { \
.type = LOBULLET_TYPE_LINEAR_SQUARE, \
__VA_ARGS__ \
})

View File

@ -1,37 +0,0 @@
#include "./misc.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
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_
}

View File

@ -1,31 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
/* 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
);

View File

@ -8,43 +8,24 @@
#include "core/locommon/counter.h"
#include "core/locommon/ticker.h"
#include "core/loentity/pool.h"
#include "core/loentity/store.h"
#include "core/loresource/set.h"
#include "core/loshader/bullet.h"
#include "core/loshader/set.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();
}
LOENTITY_POOL_SOURCE_TEMPLATE(lobullet)
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) {
loresource_set_t* res,
loshader_set_t* shaders,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
size_t length) {
assert(res != NULL);
assert(drawer != NULL);
assert(shaders != NULL);
assert(idgen != NULL);
assert(ticker != NULL);
assert(entities != NULL);
@ -53,52 +34,16 @@ lobullet_pool_t* lobullet_pool_new(
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,
.idgen = idgen,
.length = length,
};
for (size_t i = 0; i < pool->length; ++i) {
lobullet_base_initialize(
&pool->items[i],
res,
drawer,
shaders,
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];
}

View File

@ -8,7 +8,7 @@
#include "core/locommon/ticker.h"
#include "core/loentity/store.h"
#include "core/loresource/set.h"
#include "core/loshader/bullet.h"
#include "core/loshader/set.h"
#include "./base.h"
@ -17,12 +17,12 @@ 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
loresource_set_t* res,
loshader_set_t* shaders,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
size_t length
);
void

11
core/lobullet/type.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
typedef enum {
/* BENUM BEGIN lobullet_type */
LOBULLET_TYPE_LINEAR_CIRCLE,
LOBULLET_TYPE_LINEAR_TRIANGLE,
LOBULLET_TYPE_LINEAR_SQUARE,
/* BENUM END */
} lobullet_type_t;
#include "core/lobullet/benum/type.h"

View File

@ -0,0 +1,37 @@
add_library(lochara
base.c
big_warder.c
cavia.c
combat.c
encephalon.c
player.c
pool.c
state.c
strategy.c
theists_child.c
warder.c
)
target_benum_sources(lochara
state.h
strategy.h
type.h
)
target_crial_sources(lochara
base.crial
)
target_link_libraries(lochara
msgpackc
chaos
math
statman
lobullet
locommon
loeffect
loentity
loplayer
loresource
loshader
)

333
core/lochara/base.c Normal file
View File

@ -0,0 +1,333 @@
#include "./base.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <msgpack.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/mpkutil/get.h"
#include "util/mpkutil/pack.h"
#include "util/statman/statman.h"
#include "core/lobullet/pool.h"
#include "core/locommon/easing.h"
#include "core/locommon/msgpack.h"
#include "core/locommon/physics.h"
#include "core/locommon/ticker.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/character.h"
#include "core/loentity/ground.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./big_warder.h"
#include "./cavia.h"
#include "./encephalon.h"
#include "./player.h"
#include "./state.h"
#include "./theists_child.h"
#include "./type.h"
#include "./warder.h"
/* generated serializer */
#include "core/lochara/crial/base.h"
#define GRAVITY_ACCELERATION_ 2.2f
#define KNOCKBACK_RECOVERY_ACCELERATION_ 4.4f
#define BULLET_INVINCIBLE_DURATION_ 500
static bool
(*const update_function_vtable_[LOCHARA_TYPE_COUNT])(lochara_base_t* base) = {
[LOCHARA_TYPE_PLAYER] = lochara_player_update,
[LOCHARA_TYPE_ENCEPHALON] = lochara_encephalon_update,
[LOCHARA_TYPE_CAVIA] = lochara_cavia_update,
[LOCHARA_TYPE_WARDER] = lochara_warder_update,
[LOCHARA_TYPE_BIG_WARDER] = lochara_big_warder_update,
[LOCHARA_TYPE_THEISTS_CHILD] = lochara_theists_child_update,
};
static loentity_ground_t* lochara_base_find_ground_(
lochara_base_t* base, loentity_id_t id, vec2_t* pos) {
assert(base != NULL);
assert(pos != NULL);
loentity_store_iterator_t itr;
if (!loentity_store_find_item_by_id(base->entities, &itr, id) ||
itr.ground == NULL) {
return NULL;
}
locommon_position_sub(pos, &base->super.super.pos, &itr.ground->super.pos);
pos->x /= itr.ground->size.x;
pos->y -= itr.ground->size.y;
return itr.ground;
}
static void lochara_base_delete_(loentity_t* entity) {
assert(entity != NULL);
lochara_base_t* base = (typeof(base)) entity;
loeffect_recipient_deinitialize(&base->param.recipient);
base->used = false;
}
static void lochara_base_die_(loentity_t* entity) {
assert(entity != NULL);
}
static bool lochara_base_update_(loentity_t* entity) {
assert(entity != NULL);
lochara_base_t* base = (typeof(base)) entity;
base->cache = (typeof(base->cache)) {0};
base->super.velocity = vec2(0, 0);
base->cache.ground = lochara_base_find_ground_(
base, base->param.ground, &base->cache.ground_pos);
assert(update_function_vtable_[base->param.type] != NULL);
return update_function_vtable_[base->param.type](base);
}
static void lochara_base_draw_(
loentity_t* entity, const locommon_position_t* basepos) {
assert(entity != NULL);
assert(locommon_position_valid(basepos));
lochara_base_t* base = (typeof(base)) entity;
vec2_t pos;
locommon_position_sub(&pos, &base->super.super.pos, basepos);
vec2_addeq(&base->cache.instance.pos, &pos);
loshader_character_drawer_add_instance(
&base->shaders->drawer.character, &base->cache.instance);
}
static void lochara_base_pack_(
const loentity_t* entity, msgpack_packer* packer) {
assert(entity != NULL);
assert(packer != NULL);
lochara_base_t* base = (typeof(base)) entity;
msgpack_pack_map(packer, CRIAL_PROPERTY_COUNT_);
CRIAL_SERIALIZER_;
}
static void lochara_base_apply_effect_(
loentity_character_t* chara, const loeffect_t* effect) {
assert(chara != NULL);
assert(effect != NULL);
lochara_base_t* base = (typeof(base)) chara;
const bool player = base->param.type == LOCHARA_TYPE_PLAYER;
loeffect_recipient_apply_effect(&base->param.recipient, effect);
switch (effect->id) {
case LOEFFECT_ID_DAMAGE:
if (player) {
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_DAMAGE);
}
break;
default:
break;
}
}
static void lochara_base_knockback_(
loentity_character_t* chara, const vec2_t* v) {
assert(chara != NULL);
assert(vec2_valid(v));
lochara_base_t* base = (typeof(base)) chara;
base->param.gravity += v->y;
base->param.knockback += v->x;
if (vec2_pow_length(v) > 0) {
base->param.last_knockback = base->ticker->time;
}
}
void lochara_base_initialize(
lochara_base_t* base,
loresource_set_t* res,
loshader_set_t* shaders,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet) {
assert(base != NULL);
assert(res != NULL);
assert(shaders != NULL);
assert(ticker != NULL);
assert(entities != NULL);
assert(player != NULL);
assert(bullet != NULL);
*base = (typeof(*base)) {
.res = res,
.shaders = shaders,
.ticker = ticker,
.entities = entities,
.player = player,
.bullet = bullet,
};
}
void lochara_base_reinitialize(lochara_base_t* base, loentity_id_t id) {
assert(base != NULL);
assert(!base->used);
base->super = (typeof(base->super)) {
.super = {
.vtable = {
.delete = lochara_base_delete_,
.die = lochara_base_die_,
.update = lochara_base_update_,
.draw = lochara_base_draw_,
.pack = lochara_base_pack_,
},
.id = id,
.subclass = LOENTITY_SUBCLASS_CHARACTER,
},
.vtable = {
.apply_effect = lochara_base_apply_effect_,
.knockback = lochara_base_knockback_,
},
};
base->param = (typeof(base->param)) {0};
}
void lochara_base_deinitialize(lochara_base_t* base) {
assert(base != NULL);
assert(!base->used);
}
void lochara_base_calculate_physics(
lochara_base_t* base, const vec2_t* size, const vec2_t* offset) {
assert(base != NULL);
assert(vec2_valid(size));
assert(vec2_valid(offset));
const float dt = base->ticker->delta_f;
vec2_t velocity = base->param.movement;
velocity.y += base->param.gravity;
velocity.x += base->param.knockback;
base->param.gravity -= GRAVITY_ACCELERATION_*dt;
locommon_easing_linear_float(
&base->param.knockback, 0, KNOCKBACK_RECOVERY_ACCELERATION_*dt);
vec2_t disp;
vec2_mul(&disp, &velocity, base->ticker->delta_f);
vec2_addeq(&base->super.super.pos.fract, &disp);
vec2_subeq(&base->super.super.pos.fract, offset);
locommon_position_reduce(&base->super.super.pos);
locommon_physics_entity_t e = {
.size = *size,
.pos = base->super.super.pos,
.velocity = velocity,
};
loentity_store_solve_collision_between_ground(
base->entities, &e, base->ticker->delta_f);
base->super.super.pos = e.pos;
vec2_addeq(&base->super.super.pos.fract, offset);
locommon_position_reduce(&base->super.super.pos);
base->param.on_ground = false;
if (e.velocity.y == 0) {
if (velocity.y <= 0) {
base->param.on_ground = true;
}
if (base->param.gravity*velocity.y > 0) {
base->param.gravity = 0;
}
}
if (e.velocity.x == 0 && velocity.x != 0) {
if (base->param.knockback*velocity.x >= 0) {
base->param.knockback = 0;
}
}
base->super.velocity = velocity = e.velocity;
}
void lochara_base_bind_on_ground(lochara_base_t* base, const vec2_t* offset) {
assert(base != NULL);
assert(vec2_valid(offset));
if (base->cache.ground == NULL) return;
vec2_t p;
locommon_position_sub(
&p, &base->super.super.pos, &base->cache.ground->super.pos);
const vec2_t sz = base->cache.ground->size;
p.x = MATH_CLAMP(p.x, -sz.x+offset->x, sz.x-offset->x);
p.y = MATH_CLAMP(p.y, sz.y+offset->y, 1);
base->super.super.pos = base->cache.ground->super.pos;
vec2_addeq(&base->super.super.pos.fract, &p);
locommon_position_reduce(&base->super.super.pos);
}
bool lochara_base_affect_bullets(lochara_base_t* base) {
assert(base != NULL);
const uint64_t t = base->ticker->time;
if (base->param.last_bullet_hit + BULLET_INVINCIBLE_DURATION_ > t) {
return false;
}
const bool hit = loentity_store_affect_bullets_shot_by_others(
base->entities,
&base->super,
&base->super.velocity,
base->ticker->delta_f);
if (hit) {
base->param.last_bullet_hit = base->ticker->time;
}
return true;
}
bool lochara_base_unpack(lochara_base_t* base, const msgpack_object* obj) {
assert(base != NULL);
assert(obj != NULL);
assert(!base->used);
lochara_base_reinitialize(base, 0); /* id will be overwritten */
loeffect_recipient_initialize(&base->param.recipient, base->ticker, NULL);
const msgpack_object_map* root = mpkutil_get_map(obj);
if (root == NULL) goto FAIL;
CRIAL_DESERIALIZER_;
return true;
FAIL:
lochara_base_delete_(&base->super.super);
return false;
}

60
core/lochara/base.crial Normal file
View File

@ -0,0 +1,60 @@
/* CRIAL
SERIALIZER_BEGIN
mpkutil_pack_str(packer, "$name");
mpkutil_pack_str(packer, $code);
END
DESERIALIZER_BEGIN
const char* v;
size_t vlen;
if (!mpkutil_get_str(
mpkutil_get_map_item_by_str(root, "$name"), &v, &vlen) ||
strncmp(v, $code, vlen) != 0 || $code[vlen] != 0) {
goto FAIL;
}
END
PROPERTY subclass = "character"
SERIALIZER_BEGIN
mpkutil_pack_str(packer, "$name");
LOCOMMON_MSGPACK_PACK_ANY(packer, &base->$code);
END
DESERIALIZER_BEGIN
if (!LOCOMMON_MSGPACK_UNPACK_ANY(
mpkutil_get_map_item_by_str(root, "$name"), &base->$code)) {
goto FAIL;
}
END
PROPERTY id = super.super.id
PROPERTY pos = super.super.pos
PROPERTY velocity = super.velocity
PROPERTY type = param.type
PROPERTY recipient = param.recipient
PROPERTY last_state_changed = param.last_state_changed
PROPERTY last_strategy_changed = param.last_strategy_changed
PROPERTY ground = param.ground
PROPERTY on_ground = param.on_ground
PROPERTY direction = param.direction
PROPERTY movement = param.movement
PROPERTY gravity = param.gravity
PROPERTY knockback = param.knockback
PROPERTY last_knockback = param.last_knockback
PROPERTY last_bullet_hit = param.last_bullet_hit
PROPERTY anchor_pos = param.anchor.pos
PROPERTY anchor_vec = param.anchor.vec
SERIALIZER_BEGIN
const $code v = base->param.$name;
mpkutil_pack_str(packer, "$name");
LOCOMMON_MSGPACK_PACK_ANY(packer, &v);
END
DESERIALIZER_BEGIN
$code v;
if (!LOCOMMON_MSGPACK_UNPACK_ANY(
mpkutil_get_map_item_by_str(root, "$name"), &v)) {
goto FAIL;
}
base->param.$name = v;
END
PROPERTY state = lochara_state_t
PROPERTY strategy = lochara_strategy_t
*/

116
core/lochara/base.h Normal file
View File

@ -0,0 +1,116 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <msgpack.h>
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/pool.h"
#include "core/locommon/ticker.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/character.h"
#include "core/loentity/entity.h"
#include "core/loentity/ground.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/set.h"
#include "./state.h"
#include "./strategy.h"
#include "./type.h"
typedef struct lochara_base_t {
loentity_character_t super;
bool used;
loresource_set_t* res;
loshader_set_t* shaders;
const locommon_ticker_t* ticker;
loentity_store_t* entities;
loplayer_t* player;
lobullet_pool_t* bullet;
struct {
lochara_type_t type;
loeffect_recipient_t recipient;
statman_state_t state; /* actual type is lochara_state_t */
uint64_t last_state_changed;
statman_state_t strategy; /* actual type is lochara_strategy_t */
uint64_t last_strategy_changed;
loentity_id_t ground;
bool on_ground;
vec2_t direction;
vec2_t movement;
float gravity;
float knockback;
uint64_t last_knockback;
uint64_t last_bullet_hit;
/* some character uses these params for some strategy */
struct {
locommon_position_t pos;
vec2_t vec;
} anchor;
} param;
struct {
loentity_ground_t* ground;
vec2_t ground_pos;
loshader_character_drawer_instance_t instance;
} cache;
} lochara_base_t;
void
lochara_base_initialize(
lochara_base_t* base,
loresource_set_t* res,
loshader_set_t* shaders,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet
);
void
lochara_base_reinitialize(
lochara_base_t* base,
loentity_id_t id
);
void
lochara_base_deinitialize(
lochara_base_t* base
);
void
lochara_base_calculate_physics(
lochara_base_t* base,
const vec2_t* size,
const vec2_t* offset
);
void
lochara_base_bind_on_ground(
lochara_base_t* base,
const vec2_t* offset
);
bool
lochara_base_affect_bullets(
lochara_base_t* base
);
bool
lochara_base_unpack(
lochara_base_t* base,
const msgpack_object* obj
);

728
core/lochara/big_warder.c Normal file
View File

@ -0,0 +1,728 @@
#include "./big_warder.h"
#include <stdbool.h>
#include <stdint.h>
#include "util/chaos/xorshift.h"
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/position.h"
#include "core/loentity/ground.h"
#include "core/loplayer/event.h"
#include "core/loplayer/stance.h"
#include "core/loresource/music.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./state.h"
#include "./strategy.h"
#include "./type.h"
#define WIDTH_ .025f
#define HEIGHT_ .06f
#define MARKER_ .03f
#define COLOR_ vec4(0, 0, 0, 1)
#define BPM_ 80
#define BEAT_ (60.f/BPM_)
#define BEAT_MS_ (BEAT_*1000)
#define MUSIC_DURATION_ (BEAT_MS_*144)
#define WAKE_UP_RANGE_ (WIDTH_*8)
#define REWARD_STANCE_ LOPLAYER_STANCE_UNFINISHER
static const loeffect_recipient_status_t base_status_ = {
.attack = .2f,
.defence = .92f,
.speed = .31f,
.jump = 1.1f,
};
static void lochara_big_warder_initialize_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
vec2_t dir;
locommon_position_sub(
&dir, &base->player->entity->super.super.pos, &base->super.super.pos);
if (dir.x == 0 && dir.y == 0) dir = vec2(1, 0);
vec2_diveq(&dir, vec2_length(&dir));
const vec2_t invdir = vec2(dir.y, -dir.x);
base->param.direction = vec2(MATH_SIGN_NOZERO(dir.x), 0);
for (int32_t i = -4; i <= 4; ++i) {
vec2_t a;
vec2_mul(&a, &dir, 1.5f-MATH_ABS(i)/4.f*.5f);
vec2_t v;
vec2_mul(&v, &dir, .5f);
vec2_t p;
vec2_mul(&p, &invdir, i*.02f);
locommon_position_t pos = base->super.super.pos;
vec2_addeq(&pos.fract, &p);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = pos,
.size = vec2(.02f, .02f),
.color = vec4(1, 1, 1, 1),
.acceleration = a,
.velocity = v,
.knockback = 1,
.effect = loeffect_damage(
base->param.recipient.status.attack*.6f),
.duration = 2000,
);
loentity_store_add(base->entities, &b->super.super);
}
}
static void lochara_big_warder_update_thrust_in_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_big_warder_update_thrust_out_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = 1-powf(1-tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_big_warder_update_down_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const bool fast =
meta->state == LOCHARA_STATE_DOWN ||
meta->state == LOCHARA_STATE_REVIVE;
const bool reverse =
meta->state == LOCHARA_STATE_REVIVE ||
meta->state == LOCHARA_STATE_RESUSCITATE;
const uint64_t dur = fast? reverse? BEAT_MS_*3: BEAT_MS_: BEAT_MS_*4;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
if (reverse) t = dur - t;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(t*1.f/dur, 6);
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = BEAT_MS_*2,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK1,
),
lochara_state_walk(
.period = BEAT_MS_,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_dodge(
.duration = BEAT_MS_/2,
.speed = 1,
.acceleration = {{3, 3}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_WALK,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_teleport(
.duration = BEAT_MS_,
.offset = {{WIDTH_*2, 0}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK2,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_thrust_out_state_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_big_warder_initialize_shoot_state_,
.update = lochara_big_warder_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_DOWN,
.name = "DOWN",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{
.state = LOCHARA_STATE_REVIVE,
.name = "REVIVE",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{0},
};
static void lochara_big_warder_update_wait_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
static const float range2 = WAKE_UP_RANGE_*WAKE_UP_RANGE_;
lochara_base_t* base = instance;
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
if (fabsf(disp.y) < HEIGHT_/2 && vec2_pow_length(&disp) < range2) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
if (loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
*next = LOCHARA_STRATEGY_WAKE_UP;
} else {
*next = LOCHARA_STRATEGY_WAKE_UP_EVENT;
}
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_DEAD);
}
static void lochara_big_warder_initialize_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_strategy_initialize_any_(meta, instance, next);
if (meta->state != LOCHARA_STRATEGY_WAKE_UP_EVENT) return;
lochara_base_t* base = instance;
loentity_character_apply_effect(
&base->super, &loeffect_fanatic(MUSIC_DURATION_));
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse(MUSIC_DURATION_));
}
static void lochara_big_warder_update_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const bool ev = meta->state == LOCHARA_STRATEGY_WAKE_UP_EVENT;
const uint64_t dur = (ev? BEAT_MS_*16: BEAT_MS_*4);
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_strategy_changed;
if (t >= dur) {
*next = LOCHARA_STRATEGY_APPROACH;
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_RESUSCITATE);
}
static void lochara_big_warder_update_approach_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
*next = LOCHARA_STRATEGY_DEAD;
return;
}
const uint64_t since = base->param.last_strategy_changed;
uint64_t until = since + BEAT_MS_;
if (base->player->event.executor == base->super.super.id) {
const uint64_t msince = base->player->event.ctx.music.since;
if (msince < since) {
const uint64_t beats = (since - msince)/BEAT_MS_ + 1;
until = msince + beats*BEAT_MS_;
}
}
/* ---- strategy transition ---- */
const locommon_position_t* player = &base->player->entity->super.super.pos;
vec2_t disp;
locommon_position_sub(&disp, player, &base->super.super.pos);
if (player->chunk.x != base->super.super.pos.chunk.x ||
player->chunk.y != base->super.super.pos.chunk.y ||
disp.y < -HEIGHT_) {
*next = LOCHARA_STRATEGY_WAIT;
return;
}
const float dist = MATH_ABS(disp.x);
if (base->ticker->time >= until) {
if (MATH_ABS(disp.y) > HEIGHT_) {
*next = LOCHARA_STRATEGY_SHOOT1;
} else if (dist < WIDTH_*5) {
*next = LOCHARA_STRATEGY_COMBO1;
if (chaos_xorshift(base->ticker->time)%3 == 0) {
*next = LOCHARA_STRATEGY_COMBO2;
}
} else if (dist < WIDTH_*10) {
*next = LOCHARA_STRATEGY_COMBO2;
} else {
*next = LOCHARA_STRATEGY_SHOOT1;
}
return;
}
if (dist > WIDTH_*6) {
base->param.direction = vec2(MATH_SIGN_NOZERO(disp.x), 0);
}
}
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_LEFT,
),
{0},
};
static const lochara_combat_action_t combo2_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_TELEPORT_FRONT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_TELEPORT_BEHIND,
),
{0},
};
static const lochara_combat_action_t combo3_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
{0},
};
static const lochara_combat_action_t shoot1_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_LEFT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2*3,
.state = LOCHARA_STATE_STAND,
),
{0},
};
static const lochara_combat_action_t down_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_DOWN,
),
lochara_combat_action_rest(
.duration = BEAT_MS_*3,
.state = LOCHARA_STATE_REVIVE,
),
{0},
};
static const lochara_combat_action_t kill_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_*4,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
{
.state = LOCHARA_STRATEGY_WAIT,
.name = "WAIT",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_big_warder_update_wait_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP,
.name = "WAKE_UP",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_big_warder_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP_EVENT,
.name = "WAKE_UP_EVENT",
.initialize = lochara_big_warder_initialize_wake_up_strategy_,
.update = lochara_big_warder_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_APPROACH,
.name = "APPROACH",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_big_warder_update_approach_strategy_,
},
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_COMBO3,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(COMBO2,
.state_table = state_table_,
.actions = combo2_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_COMBO3,
),
lochara_strategy_combat(COMBO3,
.state_table = state_table_,
.actions = combo3_,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(SHOOT1,
.state_table = state_table_,
.actions = shoot1_,
.parry_window = 200,
.parried_next = LOCHARA_STRATEGY_DOWN,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(DOWN,
.state_table = state_table_,
.actions = down_,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(KILL,
.state_table = state_table_,
.actions = kill_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
{0},
};
static void lochara_big_warder_update_event_(lochara_base_t* base) {
assert(base != NULL);
static const loplayer_event_command_t wake_up[] = {
loplayer_event_command_play_music(LORESOURCE_MUSIC_ID_BOSS_BIG_WARDER),
loplayer_event_command_set_area(.49f, .45f),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_set_line("boss_big_warder_line0"),
loplayer_event_command_wait(BEAT_MS_*8),
loplayer_event_command_set_line("boss_big_warder_line1"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_set_line(NULL),
loplayer_event_command_set_cinescope(0),
{0},
};
static const loplayer_event_command_t kill[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_set_line("boss_big_warder_kill_line"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_finalize(),
{0},
};
static const loplayer_event_command_t dead[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_finalize(),
{0},
};
const uint64_t t = base->ticker->time;
loplayer_event_t* event = &base->player->event;
const loentity_id_t id = base->super.super.id;
locommon_position_t basepos = base->super.super.pos;
basepos.fract = vec2(.5f, .5f);
if (base->param.strategy == LOCHARA_STRATEGY_WAKE_UP_EVENT) {
loplayer_event_execute_commands(event, id, &basepos, wake_up);
return;
}
if (event->executor != id) return;
if (base->player->entity->param.state == LOCHARA_STATE_DEAD) {
statman_transition_to(
strategy_table_, base, &base->param.strategy, LOCHARA_STRATEGY_KILL);
loplayer_event_execute_commands(event, id, &basepos, kill);
return;
}
if (base->param.strategy == LOCHARA_STRATEGY_DEAD) {
loplayer_event_execute_commands(event, id, &basepos, dead);
if (!loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
loplayer_stance_set_add(&base->player->stances, REWARD_STANCE_);
loplayer_popup_queue_new_stance(&base->player->popup, REWARD_STANCE_);
}
return;
}
if (base->player->event.basetime+MUSIC_DURATION_ <= t &&
loeffect_recipient_is_alive(&base->param.recipient)) {
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse_trigger());
return;
}
loplayer_event_execute_commands(event, id, &basepos, wake_up);
}
bool lochara_big_warder_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
lochara_big_warder_update_event_(base);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_WARDER,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.strategy != LOCHARA_STRATEGY_WAIT &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP_EVENT &&
base->param.strategy != LOCHARA_STRATEGY_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_big_warder_build(
lochara_base_t* base, loentity_ground_t* gnd) {
assert(base != NULL);
assert(gnd != NULL);
base->super.super.pos = gnd->super.pos;
vec2_addeq(&base->super.super.pos.fract, &vec2(0, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_BIG_WARDER,
.state = LOCHARA_STATE_DEAD,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_WAIT,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

18
core/lochara/big_warder.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_big_warder_update(
lochara_base_t* base
);
void
lochara_big_warder_build(
lochara_base_t* base,
loentity_ground_t* gnd
);

305
core/lochara/cavia.c Normal file
View File

@ -0,0 +1,305 @@
#include "./cavia.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/base.h"
#include "core/lobullet/linear.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/ground.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./combat.h"
#define WIDTH_ .02f
#define HEIGHT_ .05f
#define MARKER_ .02f
#define COLOR_ vec4(0, 0, 0, 1)
static const loeffect_recipient_status_t base_status_ = {
.attack = .1f,
.defence = .3f,
.speed = .05f,
.jump = 1.f,
};
static void lochara_cavia_update_thrust_in_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_cavia_update_thrust_out_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_cavia_initialize_dead_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
for (size_t i = 0; i < 12; ++i) {
const float t = MATH_PI/6*i;
vec2_t v, a, p;
p = vec2(cos(t), sin(t));
vec2_mul(&v, &p, .7f);
vec2_mul(&a, &p, -1.4f);
vec2_muleq(&p, .1f);
locommon_position_t pos = base->super.super.pos;
vec2_addeq(&pos.fract, &p);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_triangle_build(b,
.owner = b->super.super.id,
.basepos = pos,
.velocity = v,
.acceleration = a,
.size = vec2(.01f, .045f),
.color = vec4(0, 0, 0, .8f),
.angle = t-MATH_PI,
.knockback = 1,
.effect = loeffect_damage(.1f),
.duration = 1200,
);
loentity_store_add(base->entities, &b->super.super);
}
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_ENEMY_TRIGGER);
}
static void lochara_cavia_update_dead_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > 2000) {
t = 2000;
}
if (meta->state == LOCHARA_STATE_RESUSCITATE) {
if (t == 0) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
}
t = 2000 - t;
}
if (t < 1000) {
const float tf = t/1000.f;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.time = powf(tf, 6);
} else {
const float tf = (t-1000)/1000.f;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(tf, 4);
}
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = 4000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND2,
),
lochara_state_walk(
.period = 1000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_cavia_update_thrust_in_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_cavia_update_thrust_out_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_cavia_initialize_dead_,
.update = lochara_cavia_update_dead_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_state_initialize_any_,
.update = lochara_cavia_update_dead_,
},
{0},
};
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = 300,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = 100,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 150,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = 150,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 150,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = 1000,
.state = LOCHARA_STATE_THRUST_OUT,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
lochara_combat_action_rest(
.duration = 2000,
.state = LOCHARA_STATE_RESUSCITATE,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
lochara_strategy_scouting(
.state_table = state_table_,
.period = 2000,
.stagger = .5f,
.range_close = WIDTH_*2,
.found_close = LOCHARA_STRATEGY_COMBO1,
.range_back = 1,
),
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
{0},
};
bool lochara_cavia_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_CAVIA,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.state != LOCHARA_STATE_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_cavia_build(
lochara_base_t* base, const loentity_ground_t* gnd, float pos) {
assert(base != NULL);
assert(gnd != NULL);
assert(MATH_FLOAT_VALID(pos));
base->super.super.pos = gnd->super.pos;
vec2_addeq(
&base->super.super.pos.fract,
&vec2(gnd->size.x*pos, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_CAVIA,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_SCOUTING,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

19
core/lochara/cavia.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_cavia_update(
lochara_base_t* base
);
void
lochara_cavia_build(
lochara_base_t* base,
const loentity_ground_t* gnd,
float pos
);

79
core/lochara/combat.c Normal file
View File

@ -0,0 +1,79 @@
#include "./combat.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "core/loeffect/effect.h"
#include "core/loentity/character.h"
#include "core/loplayer/combat.h"
#include "./base.h"
#include "./state.h"
static void lochara_combat_action_handle_attack_(
const loplayer_combat_attack_t* attack,
loplayer_combat_attack_result_t result) {
assert(attack != NULL);
lochara_base_t* attacker = attack->data1;
/* TODO(catfoot): store the result in the attacker */
if (result != LOPLAYER_COMBAT_ATTACK_RESULT_EXECUTED) return;
const float v = attacker->param.recipient.status.attack*
MATH_SIGN_NOZERO(attacker->param.direction.x);
loentity_character_knockback(&attacker->player->entity->super, &vec2(v, 0));
const lochara_combat_action_t* action = attack->data2;
switch (action->type) {
case LOCHARA_COMBAT_ACTION_TYPE_REST:
break;
case LOCHARA_COMBAT_ACTION_TYPE_ATTACK:
loentity_character_apply_effect(
&attacker->player->entity->super,
&loeffect_damage(
action->damage*attacker->param.recipient.status.attack));
break;
case LOCHARA_COMBAT_ACTION_TYPE_ATTACK_EFFECT:
loentity_character_apply_effect(
&attacker->player->entity->super, &action->effect);
break;
}
}
void lochara_combat_action_execute_all_attacks(
const lochara_combat_action_t* actions, lochara_base_t* attacker) {
assert(actions != NULL);
assert(attacker != NULL);
loplayer_combat_t* combat = &attacker->player->combat;
uint64_t t = attacker->ticker->time;
for (; actions->duration != 0; t += actions->duration, ++actions) {
if (actions->type == LOCHARA_COMBAT_ACTION_TYPE_REST) continue;
loplayer_combat_add_attack(
combat,
&(loplayer_combat_attack_t) {
.attacker = attacker->super.super.id,
.start = t,
.duration = actions->duration,
.data1 = attacker,
.data2 = (void*) actions,
.handle = lochara_combat_action_handle_attack_,
});
}
}
const lochara_combat_action_t* lochara_combat_action_find_by_time(
const lochara_combat_action_t* actions, uint64_t time) {
assert(actions != NULL);
while (actions->duration != 0 && actions->duration <= time) {
time -= actions->duration;
++actions;
}
return actions->duration > 0? actions: NULL;
}

51
core/lochara/combat.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include <stdint.h>
#include "core/loeffect/effect.h"
#include "./state.h"
typedef struct lochara_base_t lochara_base_t;
typedef enum {
LOCHARA_COMBAT_ACTION_TYPE_REST,
LOCHARA_COMBAT_ACTION_TYPE_ATTACK,
LOCHARA_COMBAT_ACTION_TYPE_ATTACK_EFFECT,
} lochara_combat_action_type_t;
typedef struct {
lochara_combat_action_type_t type;
lochara_state_t state;
uint64_t duration;
union {
float damage;
loeffect_t effect;
};
} lochara_combat_action_t;
#define lochara_combat_action_rest(...) { \
.type = LOCHARA_COMBAT_ACTION_TYPE_REST, \
__VA_ARGS__ \
}
#define lochara_combat_action_attack(...) { \
.type = LOCHARA_COMBAT_ACTION_TYPE_ATTACK, \
__VA_ARGS__ \
}
#define lochara_combat_action_attack_effect(...) { \
.type = LOCHARA_COMBAT_ACTION_TYPE_ATTACK_EFFECT, \
__VA_ARGS__ \
}
void
lochara_combat_action_execute_all_attacks(
const lochara_combat_action_t* actions,
lochara_base_t* attacker
);
const lochara_combat_action_t* /* NULLABLE */
lochara_combat_action_find_by_time(
const lochara_combat_action_t* actions,
uint64_t t
);

85
core/lochara/encephalon.c Normal file
View File

@ -0,0 +1,85 @@
#include "./encephalon.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/chaos/xorshift.h"
#include "util/math/vector.h"
#include "core/locommon/position.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/ground.h"
#include "core/loshader/character.h"
#include "./base.h"
#define WIDTH_ .1f
#define HEIGHT_ .1f
#define RANGE_ .15f
#define COLOR_ vec4(0, 0, 0, .8f)
bool lochara_encephalon_update(lochara_base_t* base) {
assert(base != NULL);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_ENCEPHALON,
.size = vec2(WIDTH_, HEIGHT_),
.color = COLOR_,
};
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
const bool active = fabsf(disp.x) < RANGE_ && fabsf(disp.y) < HEIGHT_;
if (active) {
if (base->param.state != LOCHARA_STATE_SPELL) {
loresource_sound_set_play(
&base->res->sound, LORESOURCE_SOUND_ID_TOUCH_GATE);
base->param.last_state_changed = base->ticker->time;
}
base->param.state = LOCHARA_STATE_SPELL;
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_resuscitate());
base->player->entity->param.anchor.pos =
base->player->entity->super.super.pos;
if (base->param.last_state_changed+500 > base->ticker->time) {
base->cache.instance.color.w = chaos_xorshift(base->ticker->time)%3 >= 1;
}
} else {
base->param.state = LOCHARA_STATE_STAND;
}
if (base->cache.ground == NULL) return false;
base->super.super.pos = base->cache.ground->super.pos;
base->super.super.pos.fract.y += base->cache.ground->size.y + HEIGHT_;
locommon_position_reduce(&base->super.super.pos);
return true;
}
void lochara_encephalon_build(
lochara_base_t* base, const loentity_ground_t* gnd) {
assert(base != NULL);
assert(gnd != NULL);
base->super.super.pos = gnd->super.pos;
base->super.super.pos.fract.y += gnd->size.y + HEIGHT_;
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_ENCEPHALON,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient,
base->ticker,
&(loeffect_recipient_status_t) {0});
}

18
core/lochara/encephalon.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_encephalon_update(
lochara_base_t* base
);
void
lochara_encephalon_build(
lochara_base_t* base,
const loentity_ground_t* gnd
);

492
core/lochara/player.c Normal file
View File

@ -0,0 +1,492 @@
#include "./player.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/easing.h"
#include "core/locommon/position.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/store.h"
#include "core/loplayer/stance.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./state.h"
#include "./type.h"
#define INITIAL_POS_ locommon_position(0, 0, vec2(.5f, .8f))
#define WIDTH_ .02f
#define HEIGHT_ .05f
#define MARKER_ .03f
#define COLOR_ vec4(0, 0, 0, 1)
#define RESPAWN_DURATION_ 4000
#define BULLET_SIZE_ vec2(.02f, .02f)
#define BULLET_COLOR_ vec4(1, .9f, .9f, 1)
#define BULLET_KNOCKBACK_ 4
#define BULLET_DAMAGE_ .2f
#define BULLET_DURATION_ 1000
#define BULLET_COST_ .06f
#define CAMERA_SPEED_ 10
#define CAMERA_SHIFT_Y_ .1f
#define CAMERA_FADE_RADIAL_ .6f
#define CAMERA_DEAD_FADE_ 2
#define CAMERA_DEAD_SPEED_ .3f
#define CAMERA_DEAD_RECOVERY_SPEED_ 10
#define CAMERA_COMBAT_SPEED_ 10
#define CAMERA_COMBAT_RECOVERY_SPEED_ 5
#define CAMERA_ENEMY_ATTACK_INTENSITY_ .2f
#define CAMERA_ENEMY_ATTACK_SPEED_ 4
#define CAMERA_DAMAGE_INTENSITY_ .5f
#define CAMERA_DAMAGE_DURATION_ 1000
#define CAMERA_ABERRATION_ 1
#define CAMERA_CORRUPTION_THRESH_ .2f
#define CAMERA_CORRUPTION_PERIOD_ 4000
#define DIRECTION_X_EPSILON_ .005f
#define GUARD_THRESHOLD_ .8f
#define BACKGUARD_THRESHOLD_ .95f
static const loeffect_recipient_status_t base_status_ = {
.attack = .2f,
.defence = .2f,
.speed = .3f,
.jump = 1.f,
};
static void lochara_player_update_dead_state_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
const uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t >= RESPAWN_DURATION_ && base->player->event.executor == 0) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
base->super.super.pos = base->param.anchor.pos;
*next = LOCHARA_STATE_STAND;
return;
}
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.time = 0;
}
static void lochara_player_update_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
static const uint64_t dur = 200;
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_state_changed;
const float dt = base->ticker->delta_f;
locommon_easing_linear_float(&base->param.movement.x, 0, dt);
locommon_easing_linear_float(&base->param.movement.y, 0, dt);
const bool has_stance = loplayer_stance_set_has(
&base->player->stances, LOPLAYER_STANCE_REVOLUTIONER);
if (has_stance && t >= dur && base->param.recipient.faith > 0) {
locommon_position_t pos = base->super.super.pos;
pos.fract.x += WIDTH_*MATH_SIGN_NOZERO(base->param.direction.x);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = pos,
.size = BULLET_SIZE_,
.color = BULLET_COLOR_,
.velocity = base->param.direction,
.knockback = BULLET_KNOCKBACK_,
.effect = loeffect_damage(
base->param.recipient.status.attack*BULLET_DAMAGE_),
.duration = BULLET_DURATION_,);
loentity_store_add(base->entities, &b->super.super);
loentity_character_apply_effect(
&base->super, &loeffect_lost(BULLET_COST_));
base->param.last_state_changed = base->ticker->time;
}
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND2;
base->cache.instance.motion.time = t > dur? 1: t*1.f / dur;
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = 2000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND2,
),
lochara_state_walk(
.period = 350,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_sprint(
.period = 300,
.acceleration = {{5, 5}},
.speed = 1.4f,
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_dodge(
.duration = 100,
.acceleration = {{3, 3}},
.speed = 1,
.motion1 = LOSHADER_CHARACTER_MOTION_ID_WALK,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_jump(),
{
.state = LOCHARA_STATE_GUARD,
.name = "GUARD",
.data = &(lochara_state_move_param_t) {
.speed = 0,
.period = 1000,
.acceleration = {{10, 10}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
},
.initialize = lochara_state_initialize_any_,
.update = lochara_state_update_move_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_state_initialize_any_,
.update = lochara_player_update_shoot_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_player_update_dead_state_,
.finalize = lochara_state_cancel_transition_,
},
{0},
};
static void lochara_player_handle_controller_(lochara_base_t* base) {
assert(base != NULL);
lochara_state_t next = base->param.state;
switch (base->player->controller.state) {
case LOPLAYER_CONTROLLER_STATE_NONE:
next = LOCHARA_STATE_STAND;
break;
case LOPLAYER_CONTROLLER_STATE_WALK_LEFT:
next = LOCHARA_STATE_WALK_LEFT;
break;
case LOPLAYER_CONTROLLER_STATE_WALK_RIGHT:
next = LOCHARA_STATE_WALK_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_SPRINT_LEFT:
next = LOCHARA_STATE_SPRINT_LEFT;
break;
case LOPLAYER_CONTROLLER_STATE_SPRINT_RIGHT:
next = LOCHARA_STATE_SPRINT_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_DODGE_FORWARD:
next =
base->param.direction.x < 0?
LOCHARA_STATE_DODGE_LEFT:
LOCHARA_STATE_DODGE_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_DODGE_LEFT:
next = LOCHARA_STATE_DODGE_LEFT;
break;
case LOPLAYER_CONTROLLER_STATE_DODGE_RIGHT:
next = LOCHARA_STATE_DODGE_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_JUMP:
if (base->param.on_ground) next = LOCHARA_STATE_JUMP;
break;
case LOPLAYER_CONTROLLER_STATE_GUARD:
next = LOCHARA_STATE_GUARD;
break;
case LOPLAYER_CONTROLLER_STATE_SHOOT:
next = LOCHARA_STATE_SHOOT;
break;
}
if (loplayer_combat_is_attack_pending(&base->player->combat)) {
if (next != LOCHARA_STATE_STAND &&
next != LOCHARA_STATE_DODGE_LEFT &&
next != LOCHARA_STATE_DODGE_RIGHT &&
next != LOCHARA_STATE_GUARD) {
next = LOCHARA_STATE_STAND;
}
} else {
vec2_t dir;
locommon_position_sub(
&dir, &base->player->controller.cursor, &base->super.super.pos);
if (fabsf(dir.x) > DIRECTION_X_EPSILON_) {
vec2_div(&base->param.direction, &dir, vec2_length(&dir));
}
}
statman_transition_to(state_table_, base, &base->param.state, next);
}
static void lochara_player_update_combat_(lochara_base_t* base) {
assert(base != NULL);
const lochara_state_t state = base->param.state;
if (state == LOCHARA_STATE_DEAD) {
loplayer_combat_set_accepting(
&base->player->combat,
false,
LOPLAYER_COMBAT_ATTACK_RESULT_ABORTED);
} else {
loplayer_combat_set_accepting(
&base->player->combat,
state != LOCHARA_STATE_DODGE_LEFT &&
state != LOCHARA_STATE_DODGE_RIGHT,
LOPLAYER_COMBAT_ATTACK_RESULT_DODGED);
}
loplayer_combat_set_guarding(
&base->player->combat,
state == LOCHARA_STATE_GUARD);
if (loplayer_combat_is_attack_pending(&base->player->combat)) {
base->param.movement = vec2(0, 0);
base->param.gravity = 0;
}
float r;
loplayer_combat_attack_t a;
if (!loplayer_combat_pop_attack(&base->player->combat, &a, &r)) return;
loentity_store_iterator_t itr;
if (!loentity_store_find_item_by_id(base->entities, &itr, a.attacker) ||
itr.character == NULL) {
return;
}
vec2_t disp;
locommon_position_sub(
&disp, &itr.character->super.pos, &base->super.super.pos);
const float dir = disp.x * base->param.direction.x;
const bool reflected =
r > (dir < 0? BACKGUARD_THRESHOLD_: GUARD_THRESHOLD_);
loplayer_combat_attack_handle(
&a,
reflected?
LOPLAYER_COMBAT_ATTACK_RESULT_REFLECTED:
LOPLAYER_COMBAT_ATTACK_RESULT_EXECUTED);
if (!reflected) return;
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_REFLECTION);
loentity_character_apply_effect(
itr.character,
&loeffect_damage(base->param.recipient.status.attack));
loentity_character_knockback(
itr.character,
&vec2(MATH_SIGN(disp.x)*base->param.recipient.status.attack, 0));
}
static void lochara_player_update_camera_(lochara_base_t* base) {
assert(base != NULL);
const loeffect_recipient_t* r = &base->param.recipient;
const uint64_t t = base->ticker->time;
const uint64_t pt = base->ticker->prev_time;
const float dt = base->ticker->delta_f;
const bool combat = loplayer_combat_is_attack_pending(&base->player->combat);
loplayer_camera_t* camera = &base->player->camera;
vec2_t sz = base->player->camera.chunk_winsz;
vec2_diveq(&sz,
base->player->camera.scale*
(camera->posteffect.distortion_radial+1));
locommon_position_t pos = base->super.super.pos;
if (!combat) {
pos.fract.y += CAMERA_SHIFT_Y_;
locommon_position_reduce(&pos);
}
loplayer_event_bind_rect_in_area(&base->player->event, &pos, &sz);
vec2_t disp;
locommon_position_sub(&disp, &pos, &camera->pos);
if (vec2_pow_length(&disp) < 1) {
locommon_easing_smooth_position(&camera->pos, &pos, dt*CAMERA_SPEED_);
} else {
camera->pos = pos;
}
locommon_easing_smooth_float(&camera->scale, 1, dt);
/* ---- dead ---- */
if (base->param.state == LOCHARA_STATE_DEAD) {
locommon_easing_smooth_float(
&camera->posteffect.fade_radial,
CAMERA_DEAD_FADE_,
dt*CAMERA_DEAD_SPEED_);
} else {
locommon_easing_smooth_float(
&camera->posteffect.fade_radial,
CAMERA_FADE_RADIAL_,
dt*CAMERA_DEAD_RECOVERY_SPEED_);
}
/* ---- combat ---- */
if (combat) {
locommon_easing_smooth_float(
&camera->posteffect.distortion_radial,
1,
dt*CAMERA_COMBAT_SPEED_);
} else {
locommon_easing_smooth_float(
&camera->posteffect.distortion_radial,
0,
dt*CAMERA_COMBAT_RECOVERY_SPEED_);
}
/* ---- enemy attack ---- */
const loplayer_combat_attack_t* attack = base->player->combat.first_attack;
if (attack != NULL && pt < attack->start && attack->start <= t) {
camera->posteffect.distortion_urgent = CAMERA_ENEMY_ATTACK_INTENSITY_;
} else {
locommon_easing_smooth_float(
&camera->posteffect.distortion_urgent,
0,
dt*CAMERA_ENEMY_ATTACK_SPEED_);
}
/* ---- damage ----- */
if (0 < r->last_damage &&
r->last_damage < t && t < r->last_damage+CAMERA_DAMAGE_DURATION_) {
camera->posteffect.raster_whole =
(1 - (t - r->last_damage)*1.f/CAMERA_DAMAGE_DURATION_)*
CAMERA_DAMAGE_INTENSITY_;
} else {
camera->posteffect.raster_whole = 0;
}
/* ---- amnesia ---- */
locommon_easing_smooth_float(
&camera->posteffect.distortion_amnesia,
!!(r->effects.amnesia.start < t &&
t < r->effects.amnesia.start+r->effects.amnesia.duration),
dt);
/* ---- corruption ---- */
if (r->madness <= CAMERA_CORRUPTION_THRESH_) {
if (camera->corruption_since == 0) camera->corruption_since = t;
const uint64_t p =
(t - camera->corruption_since)%CAMERA_CORRUPTION_PERIOD_;
camera->pixsort = 1-powf(p*1.f/CAMERA_CORRUPTION_PERIOD_, 2);
} else {
camera->corruption_since = 0;
locommon_easing_smooth_float(&camera->pixsort, 0, dt);
}
/* ---- passive ---- */
locommon_easing_smooth_float(
&camera->posteffect.aberration_radial, CAMERA_ABERRATION_, dt);
}
bool lochara_player_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
statman_transition_to(
state_table_, base, &base->param.state, LOCHARA_STATE_DEAD);
}
if (loplayer_stance_set_has(
&base->player->stances, LOPLAYER_STANCE_UNFINISHER)) {
if (base->param.recipient.faith > .5f) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_heal(base->ticker->delta_f*.01f));
}
}
lochara_player_handle_controller_(base);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_PLAYER,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
lochara_player_update_combat_(base);
if (base->param.state != LOCHARA_STATE_DODGE_LEFT &&
base->param.state != LOCHARA_STATE_DODGE_RIGHT &&
base->param.state != LOCHARA_STATE_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
loplayer_event_bind_rect_in_area(
&base->player->event,
&base->super.super.pos,
&vec2(WIDTH_*.8f, HEIGHT_));
lochara_player_update_camera_(base);
return true;
}
void lochara_player_build(lochara_base_t* base) {
assert(base != NULL);
base->super.super.pos = base->param.anchor.pos = INITIAL_POS_;
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_PLAYER,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

15
core/lochara/player.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
#include "./base.h"
bool
lochara_player_update(
lochara_base_t* base
);
void
lochara_player_build(
lochara_base_t* base
);

58
core/lochara/pool.c Normal file
View File

@ -0,0 +1,58 @@
#include "./pool.h"
#include <assert.h>
#include <stddef.h>
#include <msgpack.h>
#include "util/memory/memory.h"
#include "core/lobullet/pool.h"
#include "core/locommon/counter.h"
#include "core/locommon/ticker.h"
#include "core/loentity/pool.h"
#include "core/loentity/entity.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/set.h"
#include "./base.h"
LOENTITY_POOL_SOURCE_TEMPLATE(lochara)
lochara_pool_t* lochara_pool_new(
loresource_set_t* res,
loshader_set_t* shaders,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet,
size_t length) {
assert(res != NULL);
assert(shaders != NULL);
assert(idgen != NULL);
assert(ticker != NULL);
assert(entities != NULL);
assert(player != NULL);
assert(bullet != NULL);
assert(length > 0);
lochara_pool_t* pool =
memory_new(sizeof(*pool) + (length-1)*sizeof(pool->items[0]));
*pool = (typeof(*pool)) {
.idgen = idgen,
.length = length,
};
for (size_t i = 0; i < pool->length; ++i) {
lochara_base_initialize(
&pool->items[i],
res,
shaders,
ticker,
entities,
player,
bullet);
}
return pool;
}

46
core/lochara/pool.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include <stddef.h>
#include <msgpack.h>
#include "core/lobullet/pool.h"
#include "core/locommon/counter.h"
#include "core/locommon/ticker.h"
#include "core/loentity/entity.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/set.h"
#include "./base.h"
struct lochara_pool_t;
typedef struct lochara_pool_t lochara_pool_t;
lochara_pool_t* /* OWNERSHIP */
lochara_pool_new(
loresource_set_t* res,
loshader_set_t* shaders,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet,
size_t length
);
void
lochara_pool_delete(
lochara_pool_t* pool /* OWNERSHIP */
);
lochara_base_t*
lochara_pool_create(
lochara_pool_t* pool
);
lochara_base_t*
lochara_pool_unpack_item(
lochara_pool_t* pool,
const msgpack_object* obj
);

179
core/lochara/state.c Normal file
View File

@ -0,0 +1,179 @@
#include "./state.h"
#include <assert.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/locommon/easing.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#define MIDAIR_SPEED_ .8f
#define BACKWALK_SPEED_ .6f
void lochara_state_initialize_any_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
base->param.last_state_changed = base->ticker->time;
}
void lochara_state_cancel_transition_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
*state = meta->state;
}
void lochara_state_update_move_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const lochara_state_move_param_t* p = meta->data;
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_state_changed;
const float dt = base->ticker->delta_f;
assert(vec2_valid(&p->acceleration));
assert(MATH_FLOAT_VALID(p->speed));
assert(vec2_valid(&p->velocity));
assert(p->period > 0);
/* ---- acceleration ---- */
float speed = p->speed*base->param.recipient.status.speed;
if (!base->param.on_ground) {
speed *= MIDAIR_SPEED_;
}
if (vec2_dot(&p->velocity, &base->param.direction) < 0) {
speed *= BACKWALK_SPEED_;
}
vec2_t velocity = p->velocity;
vec2_muleq(&velocity, speed);
locommon_easing_linear_float(
&base->param.movement.x, velocity.x, p->acceleration.x*dt);
locommon_easing_linear_float(
&base->param.movement.y, velocity.y, p->acceleration.y*dt);
/* ---- periodic motion ---- */
base->cache.instance.motion.time =
(!!base->param.on_ground)*(1-fabs(t%p->period*1.f/p->period*2 - 1));
base->cache.instance.motion.from = p->motion1;
base->cache.instance.motion.to = p->motion2;
}
void lochara_state_initialize_dodge_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_DODGE);
}
void lochara_state_update_dodge_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const lochara_state_dodge_param_t* p = meta->data;
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_state_changed;
assert(p->duration > 0);
assert(vec2_valid(&p->acceleration));
assert(vec2_valid(&p->velocity));
if (t > p->duration) {
*state = LOCHARA_STATE_STAND;
return;
}
/* ---- acceleration ---- */
base->param.movement = p->acceleration;
vec2_muleq(&base->param.movement, t/1000.f);
base->param.movement.x *= -MATH_SIGN(p->velocity.x);
base->param.movement.y *= -MATH_SIGN(p->velocity.y);
vec2_addeq(&base->param.movement, &p->velocity);
/* ---- motion ---- */
base->cache.instance.motion.time = powf(t*1.f/p->duration, 1.2f);
base->cache.instance.motion.from = p->motion1;
base->cache.instance.motion.to = p->motion2;
const float a = powf(t*2.f/p->duration-1, 2);
base->cache.instance.size.x *= (1-a)*.8f+1;
base->cache.instance.color.w *= MATH_MIN(a+.2f, 1);
}
void lochara_state_initialize_jump_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_state_initialize_any_(meta, instance, next);
lochara_base_t* base = instance;
base->param.gravity += base->param.recipient.status.jump;
*next = LOCHARA_STATE_STAND;
}
void lochara_state_update_teleport_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const lochara_state_teleport_param_t* p = meta->data;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
const uint64_t t = base->ticker->time - base->param.last_state_changed;
const uint64_t pt =
t == 0? 0: base->ticker->prev_time - base->param.last_state_changed;
if (pt < p->duration/2 && p->duration/2 <= t) {
const lochara_base_t* player = base->player->entity;
const float pdir =
MATH_SIGN_NOZERO(player->param.direction.x)*p->direction;
base->param.direction = vec2(-pdir, 0);
vec2_t offset = p->offset;
offset.x *= pdir;
base->super.super.pos = player->super.super.pos;
vec2_addeq(&base->super.super.pos.fract, &offset);
locommon_position_reduce(&base->super.super.pos);
}
const float tf = fabsf(t*1.f/p->duration*2 - 1);
base->cache.instance.motion.from = p->motion2;
base->cache.instance.motion.to = p->motion1;
base->cache.instance.motion.time = tf;
base->cache.instance.color.w = tf;
}

233
core/lochara/state.h Normal file
View File

@ -0,0 +1,233 @@
#pragma once
#include <stddef.h>
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/loshader/character.h"
typedef enum {
/* BENUM BEGIN lochara_state */
LOCHARA_STATE_STAND,
LOCHARA_STATE_WALK_LEFT,
LOCHARA_STATE_WALK_RIGHT,
LOCHARA_STATE_SPRINT_LEFT,
LOCHARA_STATE_SPRINT_RIGHT,
LOCHARA_STATE_DODGE_LEFT,
LOCHARA_STATE_DODGE_RIGHT,
LOCHARA_STATE_JUMP,
LOCHARA_STATE_TELEPORT_BEHIND,
LOCHARA_STATE_TELEPORT_FRONT,
LOCHARA_STATE_THRUST_IN,
LOCHARA_STATE_THRUST_OUT,
LOCHARA_STATE_SLASH,
LOCHARA_STATE_SPELL,
LOCHARA_STATE_SHOOT,
LOCHARA_STATE_DOWN,
LOCHARA_STATE_DEAD,
LOCHARA_STATE_REVIVE,
LOCHARA_STATE_RESUSCITATE,
LOCHARA_STATE_GUARD,
/* BENUM END */
} lochara_state_t;
/* generated benum utils */
#include "core/lochara/benum/state.h"
/* ---- default state handlers ---- */
void
lochara_state_initialize_any_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_cancel_transition_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_update_move_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_initialize_dodge_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_update_dodge_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_initialize_jump_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_update_teleport_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
/* ---- state meta constructor ---- */
typedef struct {
vec2_t acceleration;
float speed;
vec2_t velocity;
uint64_t period;
loshader_character_motion_id_t motion1;
loshader_character_motion_id_t motion2;
} lochara_state_move_param_t;
#define lochara_state_stand(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_STAND, \
.name = "STAND", \
.data = &(lochara_state_move_param_t) { \
.speed = 0, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}
#define lochara_state_walk(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_WALK_LEFT, \
.name = "WALK_LEFT", \
.data = &(lochara_state_move_param_t) { \
.speed = 1, \
.velocity = {{-1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_WALK_RIGHT, \
.name = "WALK_RIGHT", \
.data = &(lochara_state_move_param_t) { \
.speed = 1, \
.velocity = {{1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}
#define lochara_state_sprint(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_SPRINT_LEFT, \
.name = "SPRINT_LEFT", \
.data = &(lochara_state_move_param_t) { \
.velocity = {{-1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_SPRINT_RIGHT, \
.name = "SPRINT_RIGHT", \
.data = &(lochara_state_move_param_t) { \
.velocity = {{1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}
typedef struct {
uint64_t duration;
float speed;
vec2_t acceleration;
vec2_t velocity;
loshader_character_motion_id_t motion1;
loshader_character_motion_id_t motion2;
} lochara_state_dodge_param_t;
#define lochara_state_dodge(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_DODGE_LEFT, \
.name = "DODGE_LEFT", \
.data = &(lochara_state_dodge_param_t) { \
.velocity = {{-1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_dodge_, \
.update = lochara_state_update_dodge_, \
.finalize = lochara_state_cancel_transition_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_DODGE_RIGHT, \
.name = "DODGE_RIGHT", \
.data = &(lochara_state_dodge_param_t) { \
.velocity = {{1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_dodge_, \
.update = lochara_state_update_dodge_, \
.finalize = lochara_state_cancel_transition_, \
}
#define lochara_state_jump() \
(statman_meta_t) { \
.state = LOCHARA_STATE_JUMP, \
.name = "JUMP", \
.initialize = lochara_state_initialize_jump_, \
}
typedef struct {
uint64_t duration;
float direction;
vec2_t offset;
loshader_character_motion_id_t motion1;
loshader_character_motion_id_t motion2;
} lochara_state_teleport_param_t;
#define lochara_state_teleport(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_TELEPORT_FRONT, \
.name = "TELEPORT_FRONT", \
.data = &(lochara_state_teleport_param_t) { \
.direction = 1, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_teleport_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_TELEPORT_BEHIND, \
.name = "TELEPORT_BEHIND", \
.data = &(lochara_state_teleport_param_t) { \
.direction = -1, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_teleport_, \
}

184
core/lochara/strategy.c Normal file
View File

@ -0,0 +1,184 @@
#include "./strategy.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/chaos/xorshift.h"
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "./base.h"
#include "./combat.h"
#include "./state.h"
#define SEED_ 3467
#define PARRY_DAMAGE_ 1
void lochara_strategy_initialize_any_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
base->param.last_strategy_changed = base->ticker->time;
}
void lochara_strategy_cancel_transition_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
*next = meta->state;
}
void lochara_strategy_initialize_scouting_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_strategy_initialize_any_(meta, instance, next);
const lochara_strategy_scouting_param_t* p = meta->data;
lochara_base_t* base = instance;
statman_transition_to(
p->state_table, base, &base->param.state, LOCHARA_STATE_STAND);
}
void lochara_strategy_update_scouting_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const lochara_strategy_scouting_param_t* p = meta->data;
assert(p->state_table != NULL);
assert(p->period > 0);
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time;
const bool knockback = base->param.last_knockback+500 > t;
const bool event = base->player->event.executor != 0;
/* ---- die ---- */
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
*next = LOCHARA_STRATEGY_DEAD;
return;
}
/* ---- strategy transition ---- */
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
if (!knockback && MATH_ABS(disp.y) < .1f && !event) {
disp.x *= MATH_SIGN_NOZERO(base->param.direction.x);
const float b = p->range_back;
if (-p->range_close*b < disp.x && disp.x < p->range_close) {
*next = p->found_close;
} else if (-p->range_mid*b < disp.x && disp.x < p->range_mid) {
*next = p->found_mid;
} else if (-p->range_long*b < disp.x && disp.x < p->range_long) {
*next = p->found_long;
}
if (*next != LOCHARA_STRATEGY_SCOUTING) return;
}
/* ---- state transition ---- */
const uint64_t since = base->param.last_state_changed;
uint64_t seed = 1+since*SEED_;
# define rand_() (seed = chaos_xorshift(seed))
const uint64_t dur = rand_()%p->period + p->period/2;
lochara_state_t state = base->param.state;
if (knockback) {
state = LOCHARA_STATE_STAND;
} else if (since+dur < t) {
seed = 1+t*SEED_;
if (state == LOCHARA_STATE_STAND && rand_()%100/100.f < p->stagger) {
const float r = .5f + base->cache.ground_pos.x*.5f;
if (rand_()%100/100.f < r) {
base->param.direction = vec2(-1, 0);
state = LOCHARA_STATE_WALK_LEFT;
} else {
base->param.direction = vec2(1, 0);
state = LOCHARA_STATE_WALK_RIGHT;
}
} else {
state = LOCHARA_STATE_STAND;
}
}
statman_transition_to(p->state_table, base, &base->param.state, state);
# undef rand_
}
void lochara_strategy_initialize_combat_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
const lochara_strategy_combat_param_t* p = meta->data;
assert(p->actions != NULL);
if (base->param.last_knockback+p->parry_window > base->ticker->time) {
loentity_character_apply_effect(
&base->super, &loeffect_damage(PARRY_DAMAGE_));
*next = p->parried_next;
return;
}
lochara_strategy_initialize_any_(meta, instance, next);
lochara_combat_action_execute_all_attacks(p->actions, base);
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
base->param.direction = vec2(MATH_SIGN_NOZERO(disp.x), 0);
}
void lochara_strategy_update_combat_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const lochara_strategy_combat_param_t* p = meta->data;
assert(p->actions != NULL);
assert(p->next != meta->state);
lochara_base_t* base = instance;
if (!p->gravity) base->param.gravity = 0;
const uint64_t t = base->ticker->time - base->param.last_strategy_changed;
const lochara_combat_action_t* action =
lochara_combat_action_find_by_time(p->actions, t);
if (action == NULL) {
*next = p->next;
return;
}
statman_transition_to(
p->state_table, base, &base->param.state, action->state);
}

131
core/lochara/strategy.h Normal file
View File

@ -0,0 +1,131 @@
#pragma once
#include "util/statman/statman.h"
#include "./combat.h"
typedef enum {
/* BENUM BEGIN lochara_strategy */
LOCHARA_STRATEGY_WAIT,
LOCHARA_STRATEGY_WAKE_UP,
LOCHARA_STRATEGY_WAKE_UP_EVENT,
LOCHARA_STRATEGY_SCOUTING,
LOCHARA_STRATEGY_APPROACH,
LOCHARA_STRATEGY_COMBO1,
LOCHARA_STRATEGY_COMBO2,
LOCHARA_STRATEGY_COMBO3,
LOCHARA_STRATEGY_SHOOT1,
LOCHARA_STRATEGY_SHOOT2,
LOCHARA_STRATEGY_SHOOT3,
LOCHARA_STRATEGY_SPELL1,
LOCHARA_STRATEGY_SPELL2,
LOCHARA_STRATEGY_SPELL3,
LOCHARA_STRATEGY_DOWN,
LOCHARA_STRATEGY_DEAD,
LOCHARA_STRATEGY_KILL,
/* BENUM END */
} lochara_strategy_t;
/* generated benum header */
#include "core/lochara/benum/strategy.h"
/* ---- default strategy handlers ---- */
void
lochara_strategy_initialize_any_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_cancel_transition_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_initialize_scouting_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_update_scouting_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_initialize_combat_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_update_combat_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
/* ---- default strategy constructors ---- */
typedef struct {
const statman_meta_t* state_table;
uint64_t period;
float stagger; /* 0~1 */
float range_back;
float range_close;
float range_mid;
float range_long;
lochara_strategy_t found_close;
lochara_strategy_t found_mid;
lochara_strategy_t found_long;
} lochara_strategy_scouting_param_t;
#define lochara_strategy_scouting(...) \
(statman_meta_t) { \
.state = LOCHARA_STRATEGY_SCOUTING, \
.name = "SCOUTING", \
.data = &(lochara_strategy_scouting_param_t) { \
__VA_ARGS__ \
}, \
.initialize = lochara_strategy_initialize_scouting_, \
.update = lochara_strategy_update_scouting_, \
}
typedef struct {
const statman_meta_t* state_table;
const lochara_combat_action_t* actions;
uint64_t parry_window;
lochara_strategy_t parried_next;
bool gravity;
lochara_strategy_t next;
} lochara_strategy_combat_param_t;
#define lochara_strategy_combat(NAME, ...) \
(statman_meta_t) { \
.state = LOCHARA_STRATEGY_##NAME, \
.name = #NAME, \
.data = &(lochara_strategy_combat_param_t) { \
__VA_ARGS__ \
}, \
.initialize = lochara_strategy_initialize_combat_, \
.update = lochara_strategy_update_combat_, \
.finalize = lochara_strategy_cancel_transition_, \
}

View File

@ -0,0 +1,718 @@
#include "./theists_child.h"
#include <stdbool.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/position.h"
#include "core/loentity/ground.h"
#include "core/loplayer/event.h"
#include "core/loplayer/stance.h"
#include "core/loresource/music.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./state.h"
#include "./strategy.h"
#include "./type.h"
#define WIDTH_ .025f
#define HEIGHT_ .06f
#define MARKER_ .03f
#define COLOR_ vec4(0, 0, 0, 1)
#define BPM_ 140
#define BEAT_ (60.f/BPM_)
#define BEAT_MS_ (BEAT_*1000)
#define MUSIC_DURATION_ (BEAT_MS_*232)
#define WAKE_UP_RANGE_ (WIDTH_*6)
#define REWARD_STANCE_ LOPLAYER_STANCE_REVOLUTIONER
static const loeffect_recipient_status_t base_status_ = {
.attack = .3f,
.defence = .92f,
.speed = .31f,
.jump = 1.1f,
};
static void lochara_theists_child_initialize_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
vec2_t dir;
locommon_position_sub(
&dir, &base->player->entity->super.super.pos, &base->super.super.pos);
if (dir.x == 0 && dir.y == 0) dir = vec2(1, 0);
vec2_diveq(&dir, vec2_length(&dir));
const vec2_t invdir = vec2(dir.y, -dir.x);
base->param.direction = vec2(MATH_SIGN_NOZERO(dir.x), 0);
for (int32_t i = -4; i <= 4; ++i) {
vec2_t v;
vec2_mul(&v, &dir, 1);
vec2_t p;
vec2_mul(&p, &invdir, i*.02f);
locommon_position_t pos = base->super.super.pos;
vec2_addeq(&pos.fract, &p);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = pos,
.size = vec2(.02f, .02f),
.color = vec4(1, 1, 1, 1),
.velocity = v,
.knockback = 1,
.effect = loeffect_damage(
base->param.recipient.status.attack*.6f),
.duration = 1000,
);
loentity_store_add(base->entities, &b->super.super);
}
}
static void lochara_theists_child_update_thrust_in_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_theists_child_update_thrust_out_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_theists_child_update_down_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const bool fast =
meta->state == LOCHARA_STATE_DOWN ||
meta->state == LOCHARA_STATE_REVIVE;
const bool reverse =
meta->state == LOCHARA_STATE_REVIVE ||
meta->state == LOCHARA_STATE_RESUSCITATE;
const uint64_t dur = fast? reverse? BEAT_MS_*3: BEAT_MS_: BEAT_MS_*16;
const uint64_t key = fast? dur: dur/2;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
if (reverse) t = dur - t;
if (t < key) {
const float tf = t*1.f/key;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.time = powf(tf, 6);
} else {
const float tf = (t-key)*1.f/(dur-key);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(tf, 4);
}
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = BEAT_MS_*2,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK1,
),
lochara_state_walk(
.period = BEAT_MS_,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_dodge(
.duration = BEAT_MS_/2,
.speed = 1,
.acceleration = {{3, 3}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_WALK,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_jump(),
lochara_state_teleport(
.duration = BEAT_MS_*2,
.offset = {{WIDTH_*2, 0}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK2,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_thrust_out_state_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_theists_child_initialize_shoot_state_,
.update = lochara_theists_child_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_DOWN,
.name = "DOWN",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{
.state = LOCHARA_STATE_REVIVE,
.name = "REVIVE",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{0},
};
static void lochara_theists_child_update_wait_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
static const float range2 = WAKE_UP_RANGE_*WAKE_UP_RANGE_;
lochara_base_t* base = instance;
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
if (fabsf(disp.y) < HEIGHT_/2 && vec2_pow_length(&disp) < range2) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
if (loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
*next = LOCHARA_STRATEGY_WAKE_UP;
} else {
*next = LOCHARA_STRATEGY_WAKE_UP_EVENT;
}
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_DEAD);
}
static void lochara_theists_child_initialize_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_strategy_initialize_any_(meta, instance, next);
if (meta->state != LOCHARA_STRATEGY_WAKE_UP_EVENT) return;
lochara_base_t* base = instance;
loentity_character_apply_effect(
&base->super, &loeffect_fanatic(MUSIC_DURATION_));
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse(MUSIC_DURATION_));
}
static void lochara_theists_child_update_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const bool ev = meta->state == LOCHARA_STRATEGY_WAKE_UP_EVENT;
const uint64_t dur = (ev? BEAT_MS_*64: BEAT_MS_*16);
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_strategy_changed;
if (t >= dur) {
*next = LOCHARA_STRATEGY_APPROACH;
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_RESUSCITATE);
}
static void lochara_theists_child_update_approach_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
*next = LOCHARA_STRATEGY_DEAD;
return;
}
const uint64_t since = base->param.last_strategy_changed;
uint64_t until = since + BEAT_MS_;
if (base->player->event.executor == base->super.super.id) {
const uint64_t msince = base->player->event.ctx.music.since;
if (msince < since) {
const uint64_t beats = (since - msince)/BEAT_MS_ + 1;
until = msince + beats*BEAT_MS_;
}
}
/* ---- strategy transition ---- */
const locommon_position_t* player = &base->player->entity->super.super.pos;
vec2_t disp;
locommon_position_sub(&disp, player, &base->super.super.pos);
if (player->chunk.x != base->super.super.pos.chunk.x ||
player->chunk.y != base->super.super.pos.chunk.y ||
disp.y < -HEIGHT_) {
*next = LOCHARA_STRATEGY_WAIT;
return;
}
const float dist = MATH_ABS(disp.x);
if (base->ticker->time >= until) {
if (MATH_ABS(disp.y) > HEIGHT_) {
*next = LOCHARA_STRATEGY_SHOOT1;
} else if (dist < WIDTH_*4) {
*next = LOCHARA_STRATEGY_COMBO1;
} else if (dist < WIDTH_*8) {
*next = LOCHARA_STRATEGY_SHOOT1;
} else {
*next = LOCHARA_STRATEGY_COMBO2;
}
return;
}
/* ---- approaching ---- */
if (dist > WIDTH_*6) {
base->param.direction = vec2(MATH_SIGN_NOZERO(disp.x), 0);
const lochara_state_t state =
disp.x < 0? LOCHARA_STATE_WALK_LEFT: LOCHARA_STATE_WALK_RIGHT;
statman_transition_to(state_table_, base, &base->param.state, state);
}
}
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_/4*3,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4*3,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_DODGE_LEFT,
),
{0},
};
static const lochara_combat_action_t combo2_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_*2,
.state = LOCHARA_STATE_TELEPORT_FRONT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .6f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
{0},
};
static const lochara_combat_action_t shoot1_[] = {
lochara_combat_action_rest(
.duration = 1,
.state = LOCHARA_STATE_JUMP,
),
lochara_combat_action_rest(
.duration = BEAT_MS_-1,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_*2,
.state = LOCHARA_STATE_TELEPORT_BEHIND,
),
{0},
};
static const lochara_combat_action_t down_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_DOWN,
),
lochara_combat_action_rest(
.duration = BEAT_MS_*3,
.state = LOCHARA_STATE_REVIVE,
),
{0},
};
static const lochara_combat_action_t kill_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_*12,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
{
.state = LOCHARA_STRATEGY_WAIT,
.name = "WAIT",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_theists_child_update_wait_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP,
.name = "WAKE_UP",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_theists_child_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP_EVENT,
.name = "WAKE_UP_EVENT",
.initialize = lochara_theists_child_initialize_wake_up_strategy_,
.update = lochara_theists_child_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_APPROACH,
.name = "APPROACH",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_theists_child_update_approach_strategy_,
},
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(COMBO2,
.state_table = state_table_,
.actions = combo2_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(SHOOT1,
.state_table = state_table_,
.actions = shoot1_,
.parry_window = 200,
.parried_next = LOCHARA_STRATEGY_DOWN,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(DOWN,
.state_table = state_table_,
.actions = down_,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(KILL,
.state_table = state_table_,
.actions = kill_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
{0},
};
static void lochara_theists_child_update_event_(lochara_base_t* base) {
assert(base != NULL);
static const loplayer_event_command_t wake_up[] = {
loplayer_event_command_play_music(LORESOURCE_MUSIC_ID_BOSS_THEISTS_CHILD),
loplayer_event_command_set_area(.49f, .45f),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_*16),
loplayer_event_command_set_line("boss_theists_child_line0"),
loplayer_event_command_wait(BEAT_MS_*16),
loplayer_event_command_set_line("boss_theists_child_line1"),
loplayer_event_command_wait(BEAT_MS_*16),
loplayer_event_command_set_line("boss_theists_child_line2"),
loplayer_event_command_wait(BEAT_MS_*15),
loplayer_event_command_set_line(NULL),
loplayer_event_command_set_cinescope(0),
{0},
};
static const loplayer_event_command_t kill[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_set_line("boss_theists_child_kill_line0"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_set_line("boss_theists_child_kill_line1"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_finalize(),
{0},
};
static const loplayer_event_command_t dead[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_set_line("boss_theists_child_dead_line"),
loplayer_event_command_wait(BEAT_MS_*8),
loplayer_event_command_finalize(),
{0},
};
const uint64_t t = base->ticker->time;
loplayer_event_t* event = &base->player->event;
const loentity_id_t id = base->super.super.id;
locommon_position_t basepos = base->super.super.pos;
basepos.fract = vec2(.5f, .5f);
if (base->param.strategy == LOCHARA_STRATEGY_WAKE_UP_EVENT) {
loplayer_event_execute_commands(event, id, &basepos, wake_up);
return;
}
if (event->executor != id) return;
if (base->player->entity->param.state == LOCHARA_STATE_DEAD) {
statman_transition_to(
strategy_table_, base, &base->param.strategy, LOCHARA_STRATEGY_KILL);
loplayer_event_execute_commands(event, id, &basepos, kill);
return;
}
if (base->param.strategy == LOCHARA_STRATEGY_DEAD) {
loplayer_event_execute_commands(event, id, &basepos, dead);
if (!loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
loplayer_stance_set_add(&base->player->stances, REWARD_STANCE_);
loplayer_popup_queue_new_stance(&base->player->popup, REWARD_STANCE_);
}
return;
}
if (base->player->event.basetime+MUSIC_DURATION_ <= t &&
loeffect_recipient_is_alive(&base->param.recipient)) {
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse_trigger());
return;
}
loplayer_event_execute_commands(event, id, &basepos, wake_up);
}
bool lochara_theists_child_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
lochara_theists_child_update_event_(base);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_CAVIA,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.strategy != LOCHARA_STRATEGY_WAIT &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP_EVENT &&
base->param.strategy != LOCHARA_STRATEGY_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_theists_child_build(
lochara_base_t* base, loentity_ground_t* gnd) {
assert(base != NULL);
assert(gnd != NULL);
base->super.super.pos = gnd->super.pos;
vec2_addeq(&base->super.super.pos.fract, &vec2(0, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_THEISTS_CHILD,
.state = LOCHARA_STATE_DEAD,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_WAIT,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_theists_child_update(
lochara_base_t* base
);
void
lochara_theists_child_build(
lochara_base_t* base,
loentity_ground_t* gnd
);

18
core/lochara/type.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
typedef enum {
/* BENUM BEGIN lochara_type */
LOCHARA_TYPE_PLAYER,
LOCHARA_TYPE_ENCEPHALON,
LOCHARA_TYPE_CAVIA,
LOCHARA_TYPE_WARDER,
LOCHARA_TYPE_BIG_WARDER,
LOCHARA_TYPE_THEISTS_CHILD,
/* BENUM END*/
} lochara_type_t;
/* generated benum utils */
#include "core/lochara/benum/type.h"

370
core/lochara/warder.c Normal file
View File

@ -0,0 +1,370 @@
#include "./warder.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/base.h"
#include "core/lobullet/linear.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/ground.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./combat.h"
#define WIDTH_ .02f
#define HEIGHT_ .055f
#define MARKER_ .022f
#define COLOR_ vec4(0, 0, 0, 1)
#define BULLET_DAMAGE_ .9f
#define BULLET_KNOCKBACK_ 6
#define BULLET_DURATION_ 2000
#define BULLET_SPEED_ .6f
#define BULLET_COLOR_ vec4(0, 0, 0, 1)
#define BULLET_SIZE_ vec2(.03f, .03f)
#define SHOOT_DURATION_ 2000
static const loeffect_recipient_status_t base_status_ = {
.attack = .2f,
.defence = .05f,
.speed = .05f,
.jump = 1.f,
};
static void lochara_warder_update_thrust_in_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_warder_update_thrust_out_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_warder_update_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_base_t* base = instance;
const uint64_t since = base->param.last_state_changed;
uint64_t t = base->ticker->time - since;
if (t > SHOOT_DURATION_) t = SHOOT_DURATION_;
const uint64_t pt = t == 0? 0: base->ticker->prev_time - since;
if (pt < SHOOT_DURATION_/2 && SHOOT_DURATION_/2 <= t) {
vec2_t v = base->param.direction;
vec2_muleq(&v, BULLET_SPEED_);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = base->super.super.pos,
.size = BULLET_SIZE_,
.color = BULLET_COLOR_,
.velocity = v,
.knockback = BULLET_KNOCKBACK_,
.effect = loeffect_damage(
base->param.recipient.status.attack*BULLET_DAMAGE_),
.duration = BULLET_DURATION_,);
loentity_store_add(base->entities, &b->super.super);
loresource_sound_set_play(
&base->res->sound, LORESOURCE_SOUND_ID_ENEMY_SHOOT);
}
float tf = t*1.f/SHOOT_DURATION_*2;
if (tf < 1) {
tf *= 2;
if (tf < 1) {
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
} else {
tf -= 1;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
base->cache.instance.motion.time = powf(tf, 4);
} else {
tf -= 1;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.time = tf;
}
}
static void lochara_warder_initialize_resuscitate_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
}
static void lochara_warder_update_dead_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static uint64_t dur = 1000;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
if (base->param.state == LOCHARA_STATE_RESUSCITATE) t = dur - t;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(t*1.f/dur, 6);
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = 4000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_walk(
.period = 1000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_thrust_out_state_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_shoot_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_dead_state_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_warder_initialize_resuscitate_state_,
.update = lochara_warder_update_dead_state_,
},
{0},
};
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = 400,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_attack(
.duration = 200,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = 200,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = 200,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = 200,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = 400,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = 600,
.state = LOCHARA_STATE_STAND,
),
{0},
};
static const lochara_combat_action_t shoot1_[] = {
lochara_combat_action_rest(
.duration = 100,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_rest(
.duration = SHOOT_DURATION_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = 500,
.state = LOCHARA_STATE_STAND,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
lochara_combat_action_rest(
.duration = 2000,
.state = LOCHARA_STATE_RESUSCITATE,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
lochara_strategy_scouting(
.state_table = state_table_,
.period = 2000,
.stagger = .8f,
.range_back = .5f,
.range_close = WIDTH_*4,
.found_close = LOCHARA_STRATEGY_COMBO1,
.range_mid = .4f,
.found_mid = LOCHARA_STRATEGY_SHOOT1,
),
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
lochara_strategy_combat(SHOOT1,
.state_table = state_table_,
.actions = shoot1_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
{0},
};
bool lochara_warder_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_WARDER,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.state != LOCHARA_STATE_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_warder_build(
lochara_base_t* base, const loentity_ground_t* gnd, float pos) {
assert(base != NULL);
assert(gnd != NULL);
assert(MATH_FLOAT_VALID(pos));
base->super.super.pos = gnd->super.pos;
vec2_addeq(
&base->super.super.pos.fract,
&vec2(gnd->size.x*pos, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_WARDER,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_SCOUTING,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

19
core/lochara/warder.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_warder_update(
lochara_base_t* base
);
void
lochara_warder_build(
lochara_base_t* base,
const loentity_ground_t* gnd,
float pos
);

View File

@ -1,27 +0,0 @@
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
)

View File

@ -1,434 +0,0 @@
#include "./base.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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_
}

View File

@ -1,100 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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
);

View File

@ -1,787 +0,0 @@
#include "./big_warder.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#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-