Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 62e14cf075 | |||
| 4249a0238b | |||
| cfd9ed8708 | |||
| 1977a12d8b | |||
| 190318e5c4 | |||
| 1aa002d8ba | |||
| 1bc0f298fc | |||
| e6463fc264 | |||
| 7621607152 | |||
| 82ac145e87 | |||
| ff780c34b3 | |||
| 5c03f0cd2c |
@@ -4,3 +4,4 @@
|
||||
*.vroid filter=lfs diff=lfs merge=lfs -text
|
||||
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
+455
-364
File diff suppressed because it is too large
Load Diff
+51
-16
@@ -71,6 +71,8 @@ add_subdirectory(src/gamedata)
|
||||
add_subdirectory(src/miniaudio)
|
||||
add_subdirectory(src/sound)
|
||||
add_subdirectory(audio/gui)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(lua-scripts)
|
||||
|
||||
# add the source files as usual
|
||||
add_executable(0_Bootstrap Bootstrap.cpp)
|
||||
@@ -117,6 +119,19 @@ if(OGRE_STATIC)
|
||||
target_link_options(Procedural PRIVATE -static-libstdc++ -static-libgcc)
|
||||
endif()
|
||||
add_dependencies(Procedural stage_files import_buildings)
|
||||
file(GLOB FONTS_SRC ${CMAKE_SOURCE_DIR}/assets/fonts/*.ttf)
|
||||
set(FONT_OUTPUT_FILES)
|
||||
foreach(FONT_FILE ${FONTS_SRC})
|
||||
get_filename_component(FILE_NAME ${FONT_FILE} NAME_WE)
|
||||
set(FONT_OUTPUT_FILE ${CMAKE_BINARY_DIR}/resources/fonts/${FILE_NAME}.ttf)
|
||||
add_custom_command(
|
||||
OUTPUT ${FONT_OUTPUT_FILE}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/resources/fonts
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${FONT_FILE} ${FONT_OUTPUT_FILE}
|
||||
DEPENDS ${FONT_FILE})
|
||||
list(APPEND FONT_OUTPUT_FILES ${FONT_OUTPUT_FILE})
|
||||
endforeach()
|
||||
add_custom_target(fonts ALL DEPENDS ${FONT_OUTPUT_FILES})
|
||||
file(GLOB BUILDINGS_SRC ${CMAKE_SOURCE_DIR}/assets/blender/buildings/*.blend)
|
||||
set(BUILDING_OUTPUT_FILES)
|
||||
foreach(BUILDING_FILE ${BUILDINGS_SRC})
|
||||
@@ -178,19 +193,14 @@ if(OGRE_STATIC)
|
||||
target_link_libraries(Editor fix::assimp pugixml)
|
||||
endif()
|
||||
|
||||
file(GLOB LUA_SCRIPTS_SRC ${CMAKE_SOURCE_DIR}/lua-scripts/*.lua)
|
||||
set(LUA_SCRIPTS_OUTPUT)
|
||||
foreach(LUA_SCRIPT_FILE ${LUA_SCRIPTS_SRC})
|
||||
get_filename_component(FILE_NAME ${LUA_SCRIPT_FILE} NAME_WE)
|
||||
set(LUA_SCRIPT_OUTPUT_FILE ${CMAKE_BINARY_DIR}/lua-scripts/${FILE_NAME}.lua)
|
||||
add_custom_command(OUTPUT ${LUA_SCRIPT_OUTPUT_FILE}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${LUA_SCRIPT_FILE} ${LUA_SCRIPT_OUTPUT_FILE}
|
||||
DEPENDS ${LUA_SCRIPT_FILE})
|
||||
list(APPEND LUA_SCRIPTS_OUTPUT ${LUA_SCRIPT_OUTPUT_FILE})
|
||||
endforeach()
|
||||
add_custom_target(stage_lua_scripts ALL DEPENDS ${LUA_SCRIPTS_OUTPUT})
|
||||
add_dependencies(TerrainTest stage_lua_scripts stage_files)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/stories
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/lua-scripts/stories ${CMAKE_BINARY_DIR}/stories
|
||||
DEPENDS ${CMAKE_BINARY_DIR}/lua-scripts/stories
|
||||
)
|
||||
add_custom_target(stage_stories ALL DEPENDS stage_lua_scripts ${CMAKE_BINARY_DIR}/stories)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/resources.cfg
|
||||
COMMAND cp ${CMAKE_SOURCE_DIR}/resources.cfg ${CMAKE_BINARY_DIR}/resources.cfg
|
||||
@@ -272,9 +282,8 @@ endforeach()
|
||||
|
||||
file(GLOB VRM_FILES RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/assets/vroid/*.vrm)
|
||||
file(GLOB MIXAMO_FILES RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/assets/blender/mixamo/**/*.fbx)
|
||||
file(GLOB EDITED_BLENDS RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/assets/blender/edited-*.blend)
|
||||
set(VRM_SOURCE)
|
||||
foreach(VRM_FILE ${VRM_FILES} ${MIXAMO_FILES} ${EDITED_BLENDS})
|
||||
foreach(VRM_FILE ${VRM_FILES} ${MIXAMO_FILES})
|
||||
set(OUTPUT_FILE "${CMAKE_BINARY_DIR}/${VRM_FILE}")
|
||||
set(INPUT_FILE "${CMAKE_SOURCE_DIR}/${VRM_FILE}")
|
||||
add_custom_command(OUTPUT "${OUTPUT_FILE}"
|
||||
@@ -297,13 +306,38 @@ add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS}
|
||||
${VRM_SOURCE}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
set(CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/male/normal-male.glb)
|
||||
set(EDITED_BLENDS_LIST
|
||||
male
|
||||
female
|
||||
)
|
||||
set(EDITED_BLEND_TARGETS)
|
||||
set(CHARACTER_GLBS)
|
||||
foreach(EDITED_BLEND ${EDITED_BLENDS_LIST})
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
|
||||
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E copy ${CMAKE_SOURCE_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
|
||||
${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
|
||||
COMMAND ${BLENDER} -b -Y
|
||||
${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
|
||||
-P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/copy_animations.py --
|
||||
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend ${EDITED_BLEND}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend)
|
||||
list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/${EDITED_BLEND}/normal-${EDITED_BLEND}.glb)
|
||||
endforeach()
|
||||
|
||||
add_custom_target(edited-blends ALL DEPENDS ${EDITED_BLEND_TARGETS})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CHARACTER_GLBS}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
|
||||
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${CHARACTER_GLBS}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLENDS}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
#add_custom_command(
|
||||
# OUTPUT ${CMAKE_SOURCE_DIR}/characters/female/vroid-normal-female.scene
|
||||
@@ -326,7 +360,8 @@ add_custom_command(
|
||||
|
||||
add_custom_target(stage_files ALL DEPENDS ${CMAKE_BINARY_DIR}/resources.cfg ${MATERIALS_OUTPUT}
|
||||
${CMAKE_BINARY_DIR}/resources/terrain/world_map.png
|
||||
${CMAKE_BINARY_DIR}/resources/terrain/brushes.png)
|
||||
${CMAKE_BINARY_DIR}/resources/terrain/brushes.png
|
||||
edited-blends)
|
||||
|
||||
add_custom_target(remove_scenes COMMAND rm -f ${VRM_SOURCE} ${VRM_IMPORTED_BLENDS} ${CHARACTER_GLBS})
|
||||
|
||||
|
||||
@@ -167,6 +167,123 @@ public:
|
||||
mSkyBoxGenParameters.skyBoxDistance = distance;
|
||||
}
|
||||
};
|
||||
class App;
|
||||
class KeyboardListener : public OgreBites::InputListener {
|
||||
App *mApp;
|
||||
|
||||
uint32_t control;
|
||||
ECS::Vector2 mouse;
|
||||
float wheel_y;
|
||||
bool mouse_moved, wheel_moved;
|
||||
float mInitDelay;
|
||||
|
||||
public:
|
||||
Ogre::Timer fps_timer;
|
||||
bool fast;
|
||||
KeyboardListener(App *app)
|
||||
: OgreBites::InputListener()
|
||||
, mApp(app)
|
||||
, fast(false)
|
||||
, control(0)
|
||||
, mouse({ 0, 0 })
|
||||
, wheel_y(0.0f)
|
||||
, mouse_moved(false)
|
||||
, wheel_moved(false)
|
||||
, mInitDelay(1.0)
|
||||
{
|
||||
}
|
||||
bool isGuiEnabled()
|
||||
{
|
||||
if (!ECS::get().has<ECS::GUI>())
|
||||
return false;
|
||||
return (ECS::get().get<ECS::GUI>().enabled);
|
||||
}
|
||||
void setGuiEnabled(bool value)
|
||||
{
|
||||
if (!ECS::get().has<ECS::GUI>())
|
||||
return;
|
||||
ECS::get().get_mut<ECS::GUI>().enabled = value;
|
||||
ECS::get().modified<ECS::GUI>();
|
||||
}
|
||||
bool keyPressed(const OgreBites::KeyboardEvent &evt) override
|
||||
{
|
||||
bool updated = false;
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) {
|
||||
OgreAssert(ECS::get().has<ECS::GUI>(), "");
|
||||
setGuiEnabled(true);
|
||||
if (ECS::get().has<ECS::GUI>())
|
||||
ECS::get<ECS::GUI>().setWindowGrab(false);
|
||||
return true;
|
||||
}
|
||||
OgreBites::Keycode key = evt.keysym.sym;
|
||||
if (key == 'w')
|
||||
control |= 1;
|
||||
else if (key == 'a')
|
||||
control |= 2;
|
||||
else if (key == 's')
|
||||
control |= 4;
|
||||
else if (key == 'd')
|
||||
control |= 8;
|
||||
else if (key == OgreBites::SDLK_LSHIFT)
|
||||
control |= 16;
|
||||
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
|
||||
key == OgreBites::SDLK_LSHIFT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool keyReleased(const OgreBites::KeyboardEvent &evt) override
|
||||
{
|
||||
OgreBites::Keycode key = evt.keysym.sym;
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
if (key == 'w')
|
||||
control &= ~1;
|
||||
else if (key == 'a')
|
||||
control &= ~2;
|
||||
else if (key == 's')
|
||||
control &= ~4;
|
||||
else if (key == 'd')
|
||||
control &= ~8;
|
||||
else if (key == OgreBites::SDLK_LSHIFT)
|
||||
control &= ~16;
|
||||
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
|
||||
key == OgreBites::SDLK_LSHIFT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool mouseMoved(const OgreBites::MouseMotionEvent &evt) override
|
||||
{
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
mouse.x = evt.xrel;
|
||||
mouse.y = evt.yrel;
|
||||
mouse_moved = true;
|
||||
/* no special mouse handling */
|
||||
return true;
|
||||
}
|
||||
bool mouseWheelRolled(const OgreBites::MouseWheelEvent &evt) override
|
||||
{
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
/* no special mouse wheel handling */
|
||||
wheel_y = evt.y;
|
||||
wheel_moved = true;
|
||||
return true;
|
||||
}
|
||||
bool mousePressed(const OgreBites::MouseButtonEvent &evt) override
|
||||
{
|
||||
std::cout << "Mouse press " << (int)evt.button << " "
|
||||
<< (int)evt.clicks << "\n";
|
||||
if ((int)evt.button == 1)
|
||||
control |= 256;
|
||||
else
|
||||
control &= ~256;
|
||||
return false;
|
||||
}
|
||||
void frameRendered(const Ogre::FrameEvent &evt) override;
|
||||
};
|
||||
class App : public OgreBites::ApplicationContext {
|
||||
std::unique_ptr<Ogre::Bullet::DynamicsWorld> mDynWorld;
|
||||
std::unique_ptr<Ogre::Bullet::DebugDrawer> mDbgDraw;
|
||||
@@ -177,171 +294,8 @@ class App : public OgreBites::ApplicationContext {
|
||||
Ogre::Viewport *mViewport;
|
||||
SkyBoxRenderer *sky;
|
||||
bool mGrab;
|
||||
class KeyboardListener : public OgreBites::InputListener,
|
||||
public Ogre::FrameListener {
|
||||
App *mApp;
|
||||
|
||||
uint32_t control;
|
||||
ECS::Vector2 mouse{ 0, 0 };
|
||||
float wheel_y;
|
||||
bool mouse_moved = false, wheel_moved = false;
|
||||
float mInitDelay;
|
||||
|
||||
public:
|
||||
Ogre::Timer fps_timer;
|
||||
bool fast;
|
||||
KeyboardListener(App *app)
|
||||
: OgreBites::InputListener()
|
||||
, Ogre::FrameListener()
|
||||
, mApp(app)
|
||||
, fast(false)
|
||||
, control(0)
|
||||
, mInitDelay(1.0)
|
||||
{
|
||||
}
|
||||
bool isGuiEnabled()
|
||||
{
|
||||
if (!ECS::get().has<ECS::GUI>())
|
||||
return false;
|
||||
return (ECS::get().get<ECS::GUI>().enabled);
|
||||
}
|
||||
void setGuiEnabled(bool value)
|
||||
{
|
||||
if (!ECS::get().has<ECS::GUI>())
|
||||
return;
|
||||
ECS::get().get_mut<ECS::GUI>().enabled = value;
|
||||
ECS::get().modified<ECS::GUI>();
|
||||
}
|
||||
bool keyPressed(const OgreBites::KeyboardEvent &evt) override
|
||||
{
|
||||
bool updated = false;
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) {
|
||||
OgreAssert(ECS::get().has<ECS::GUI>(), "");
|
||||
setGuiEnabled(true);
|
||||
if (ECS::get().has<ECS::GUI>())
|
||||
ECS::get<ECS::GUI>().setWindowGrab(
|
||||
false);
|
||||
return true;
|
||||
}
|
||||
OgreBites::Keycode key = evt.keysym.sym;
|
||||
if (key == 'w')
|
||||
control |= 1;
|
||||
else if (key == 'a')
|
||||
control |= 2;
|
||||
else if (key == 's')
|
||||
control |= 4;
|
||||
else if (key == 'd')
|
||||
control |= 8;
|
||||
else if (key == OgreBites::SDLK_LSHIFT)
|
||||
control |= 16;
|
||||
if (key == 'w' || key == 'a' || key == 's' ||
|
||||
key == 'd' || key == OgreBites::SDLK_LSHIFT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool keyReleased(const OgreBites::KeyboardEvent &evt) override
|
||||
{
|
||||
OgreBites::Keycode key = evt.keysym.sym;
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
if (key == 'w')
|
||||
control &= ~1;
|
||||
else if (key == 'a')
|
||||
control &= ~2;
|
||||
else if (key == 's')
|
||||
control &= ~4;
|
||||
else if (key == 'd')
|
||||
control &= ~8;
|
||||
else if (key == OgreBites::SDLK_LSHIFT)
|
||||
control &= ~16;
|
||||
if (key == 'w' || key == 'a' || key == 's' ||
|
||||
key == 'd' || key == OgreBites::SDLK_LSHIFT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool mouseMoved(const OgreBites::MouseMotionEvent &evt) override
|
||||
{
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
mouse.x = evt.xrel;
|
||||
mouse.y = evt.yrel;
|
||||
mouse_moved = true;
|
||||
/* no special mouse handling */
|
||||
return true;
|
||||
}
|
||||
bool
|
||||
mouseWheelRolled(const OgreBites::MouseWheelEvent &evt) override
|
||||
{
|
||||
if (isGuiEnabled())
|
||||
return false;
|
||||
/* no special mouse wheel handling */
|
||||
wheel_y = evt.y;
|
||||
wheel_moved = true;
|
||||
return true;
|
||||
}
|
||||
bool
|
||||
mousePressed(const OgreBites::MouseButtonEvent &evt) override
|
||||
{
|
||||
std::cout << "Mouse press " << (int)evt.button << " "
|
||||
<< (int)evt.clicks << "\n";
|
||||
if ((int)evt.button == 1)
|
||||
control |= 256;
|
||||
else
|
||||
control &= ~256;
|
||||
return false;
|
||||
}
|
||||
void update(float delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
void frameRendered(const Ogre::FrameEvent &evt) override
|
||||
{
|
||||
if (fps_timer.getMilliseconds() > 1000.0f) {
|
||||
std::cout << "FPS: "
|
||||
<< mApp->getRenderWindow()
|
||||
->getStatistics()
|
||||
.lastFPS
|
||||
<< " ";
|
||||
std::cout << "Draw calls: "
|
||||
<< mApp->getRenderWindow()
|
||||
->getStatistics()
|
||||
.batchCount
|
||||
<< " ";
|
||||
fps_timer.reset();
|
||||
std::cout << "Drops: "
|
||||
<< mApp->getRenderWindow()
|
||||
->getStatistics()
|
||||
.vBlankMissCount
|
||||
<< "\n";
|
||||
fps_timer.reset();
|
||||
}
|
||||
update(evt.timeSinceLastFrame);
|
||||
if (!isGuiEnabled() && mApp->isTerrainReady()) {
|
||||
OgreAssert(mApp->isTerrainReady(),
|
||||
"terrain is not ready");
|
||||
}
|
||||
if (!isGuiEnabled()) {
|
||||
mApp->updateWorld(evt.timeSinceLastFrame);
|
||||
if (mInitDelay >= 0.0f)
|
||||
mInitDelay -= evt.timeSinceLastFrame;
|
||||
}
|
||||
if (!isGuiEnabled() && ECS::get().has<ECS::Input>()) {
|
||||
ECS::Input &input =
|
||||
ECS::get().get_mut<ECS::Input>();
|
||||
input.control = control;
|
||||
input.mouse = mouse;
|
||||
mouse.x = 0;
|
||||
mouse.y = 0;
|
||||
input.wheel_y = wheel_y;
|
||||
wheel_y = 0;
|
||||
input.mouse_moved = mouse_moved;
|
||||
input.wheel_moved = wheel_moved;
|
||||
}
|
||||
}
|
||||
};
|
||||
KeyboardListener mKbd;
|
||||
bool enabldDbgDraw;
|
||||
|
||||
public:
|
||||
App()
|
||||
@@ -350,6 +304,7 @@ public:
|
||||
, mDynWorld(new Ogre::Bullet::DynamicsWorld(
|
||||
Ogre::Vector3(0, -9.8, 0)))
|
||||
, mGrab(false)
|
||||
, enabldDbgDraw(false)
|
||||
{
|
||||
}
|
||||
virtual ~App()
|
||||
@@ -376,6 +331,11 @@ public:
|
||||
{
|
||||
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
|
||||
"Water", true);
|
||||
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
|
||||
"LuaScripts", false);
|
||||
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
|
||||
"./lua-scripts", "FileSystem", "LuaScripts", true,
|
||||
true);
|
||||
OgreBites::ApplicationContext::locateResources();
|
||||
}
|
||||
void loadResources() override
|
||||
@@ -439,11 +399,13 @@ public:
|
||||
.initialiseAllResourceGroups();
|
||||
// OgreBites::ApplicationContext::loadResources();
|
||||
// setupCursor();
|
||||
std::cout << "Create content" << "\n";
|
||||
createContent();
|
||||
std::cout << "Setup input" << "\n";
|
||||
setupInput();
|
||||
std::cout << "Create content" << "\n";
|
||||
createContent();
|
||||
std::cout << "Setup done" << "\n";
|
||||
mDbgDraw->setDebugMode(mDbgDraw->getDebugMode() |
|
||||
btIDebugDraw::DBG_DrawContactPoints);
|
||||
}
|
||||
Ogre::SceneManager *getSceneManager()
|
||||
{
|
||||
@@ -466,17 +428,22 @@ public:
|
||||
}
|
||||
void updateWorld(float delta)
|
||||
{
|
||||
mDynWorld->getBtWorld()->stepSimulation(delta, 4);
|
||||
mDynWorld->getBtWorld()->stepSimulation(delta, 3);
|
||||
if (!ECS::get().has<ECS::GUI>())
|
||||
return;
|
||||
/* Update window grab */
|
||||
if (ECS::get().has<ECS::GUI>() &&
|
||||
ECS::get().get<ECS::GUI>().grabChanged) {
|
||||
setWindowGrab(ECS::get().get<ECS::GUI>().grab);
|
||||
ECS::get().get_mut<ECS::GUI>().grabChanged = false;
|
||||
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
|
||||
if (gui.grabChanged) {
|
||||
setWindowGrab(gui.grab);
|
||||
gui.grabChanged = false;
|
||||
ECS::get().modified<ECS::GUI>();
|
||||
}
|
||||
|
||||
ECS::update(delta);
|
||||
// mDbgDraw->update();
|
||||
|
||||
/*
|
||||
if (enabldDbgDraw)
|
||||
mDbgDraw->update();
|
||||
*/
|
||||
}
|
||||
class InputListenerChainFlexible : public OgreBites::InputListener {
|
||||
protected:
|
||||
@@ -609,7 +576,30 @@ public:
|
||||
flecs::entity find_wait_gui;
|
||||
void setupInput()
|
||||
{
|
||||
ECS::App &p = ECS::get().get_mut<ECS::App>();
|
||||
}
|
||||
void createContent()
|
||||
{
|
||||
int i;
|
||||
sky = new SkyBoxRenderer(getSceneManager());
|
||||
bool drawFirst = true;
|
||||
uint8_t renderQueue = drawFirst ?
|
||||
Ogre::RENDER_QUEUE_SKIES_EARLY :
|
||||
Ogre::RENDER_QUEUE_SKIES_LATE;
|
||||
sky->create("Skybox/Dynamic", 450, renderQueue,
|
||||
Ogre::Quaternion::IDENTITY,
|
||||
Ogre::ResourceGroupManager::
|
||||
AUTODETECT_RESOURCE_GROUP_NAME);
|
||||
sky->setEnabled(true);
|
||||
|
||||
Ogre::MaterialPtr m =
|
||||
Ogre::MaterialManager::getSingleton().getByName(
|
||||
"Skybox/Dynamic", "General");
|
||||
OgreAssert(m, "Sky box material not found.");
|
||||
m->load();
|
||||
ECS::setup(mScnMgr, mDynWorld.get(), mCameraNode, mCamera,
|
||||
getRenderWindow());
|
||||
ECS::get().set<ECS::RenderWindow>(
|
||||
{ getRenderWindow(), getDisplayDPI() });
|
||||
ECS::get()
|
||||
.observer<ECS::GUI>("UpdateGrab")
|
||||
.event(flecs::OnSet)
|
||||
@@ -619,11 +609,6 @@ public:
|
||||
std::cout << "grab: " << gui.grab << "\n";
|
||||
std::cout << "GUI enabled: " << gui.enabled
|
||||
<< "\n";
|
||||
std::cout << "grabbed: " << isWindowGrab()
|
||||
<< "\n";
|
||||
std::cout
|
||||
<< "UI active: " << mKbd.isGuiEnabled()
|
||||
<< "\n";
|
||||
});
|
||||
ECS::get()
|
||||
.observer<ECS::App>("UpdateInputListener")
|
||||
@@ -632,10 +617,10 @@ public:
|
||||
if (app.mInput)
|
||||
removeInputListener(app.mInput);
|
||||
delete app.mInput;
|
||||
app.mInput = new OgreBites::InputListenerChain(
|
||||
app.listeners);
|
||||
app.mInput =
|
||||
OGRE_NEW OgreBites::InputListenerChain(
|
||||
app.listeners);
|
||||
addInputListener(app.mInput);
|
||||
std::cout << "Update listeners\n";
|
||||
});
|
||||
#if 0
|
||||
ECS::get()
|
||||
@@ -707,52 +692,10 @@ public:
|
||||
<< "\n";
|
||||
});
|
||||
#endif
|
||||
|
||||
p.listeners.clear();
|
||||
p.listeners.push_back(&mKbd);
|
||||
ECS::get().modified<ECS::App>();
|
||||
}
|
||||
void createContent()
|
||||
{
|
||||
int i;
|
||||
std::cout << "addFrameListener\n";
|
||||
getRoot()->addFrameListener(&mKbd);
|
||||
sky = new SkyBoxRenderer(getSceneManager());
|
||||
bool drawFirst = true;
|
||||
uint8_t renderQueue = drawFirst ?
|
||||
Ogre::RENDER_QUEUE_SKIES_EARLY :
|
||||
Ogre::RENDER_QUEUE_SKIES_LATE;
|
||||
sky->create("Skybox/Dynamic", 450, renderQueue,
|
||||
Ogre::Quaternion::IDENTITY,
|
||||
Ogre::ResourceGroupManager::
|
||||
AUTODETECT_RESOURCE_GROUP_NAME);
|
||||
sky->setEnabled(true);
|
||||
|
||||
Ogre::MaterialPtr m =
|
||||
Ogre::MaterialManager::getSingleton().getByName(
|
||||
"Skybox/Dynamic", "General");
|
||||
OgreAssert(m, "Sky box material not found.");
|
||||
m->load();
|
||||
ECS::setup(mScnMgr, mDynWorld.get(), mCameraNode, mCamera);
|
||||
ECS::get()
|
||||
.system<const ECS::GUI, ECS::App>("SetInputListener")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](const ECS::GUI &gui, ECS::App &app) {
|
||||
if (gui.mGuiInpitListener &&
|
||||
app.listeners.size() == 1) {
|
||||
app.listeners.clear();
|
||||
app.listeners.push_back(
|
||||
gui.mGuiInpitListener);
|
||||
app.listeners.push_back(&mKbd);
|
||||
ECS::modified<ECS::App>();
|
||||
}
|
||||
});
|
||||
ECS::get().set<ECS::RenderWindow>(
|
||||
{ getRenderWindow(), getDisplayDPI() });
|
||||
ECS::get().set<ECS::App>(
|
||||
{ static_cast<OgreBites::ApplicationContext *>(this),
|
||||
{ initialiseImGui(),
|
||||
nullptr,
|
||||
{} });
|
||||
{ getImGuiInputListener(), &mKbd } });
|
||||
Sound::setup();
|
||||
Sound::ding();
|
||||
}
|
||||
@@ -792,7 +735,51 @@ public:
|
||||
ECS::get().lookup("ECS::CharacterModule::player");
|
||||
return player;
|
||||
}
|
||||
void enableDbgDraw(bool enable)
|
||||
{
|
||||
enabldDbgDraw = enable;
|
||||
}
|
||||
bool isEnabledDbgDraw() const
|
||||
{
|
||||
return enabldDbgDraw;
|
||||
}
|
||||
};
|
||||
void KeyboardListener::frameRendered(const Ogre::FrameEvent &evt)
|
||||
{
|
||||
if (fps_timer.getMilliseconds() > 1000.0f) {
|
||||
std::cout << "FPS: "
|
||||
<< mApp->getRenderWindow()->getStatistics().lastFPS
|
||||
<< " ";
|
||||
std::cout << "Draw calls: "
|
||||
<< mApp->getRenderWindow()->getStatistics().batchCount
|
||||
<< " ";
|
||||
fps_timer.reset();
|
||||
std::cout << "Drops: "
|
||||
<< mApp->getRenderWindow()
|
||||
->getStatistics()
|
||||
.vBlankMissCount
|
||||
<< "\n";
|
||||
fps_timer.reset();
|
||||
}
|
||||
if (!isGuiEnabled() ||
|
||||
(isGuiEnabled() && ECS::get<ECS::GUI>().narrationBox)) {
|
||||
mApp->updateWorld(evt.timeSinceLastFrame);
|
||||
if (mInitDelay >= 0.0f)
|
||||
mInitDelay -= evt.timeSinceLastFrame;
|
||||
}
|
||||
|
||||
if (!isGuiEnabled() && ECS::get().has<ECS::Input>()) {
|
||||
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
|
||||
input.control = control;
|
||||
input.mouse = mouse;
|
||||
mouse.x = 0;
|
||||
mouse.y = 0;
|
||||
input.wheel_y = wheel_y;
|
||||
wheel_y = 0;
|
||||
input.mouse_moved = mouse_moved;
|
||||
input.wheel_moved = wheel_moved;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
@@ -803,7 +790,7 @@ int main()
|
||||
// register for input events
|
||||
// KeyHandler keyHandler;
|
||||
// ctx.addInputListener(&keyHandler);
|
||||
|
||||
ctx.enableDbgDraw(false);
|
||||
ctx.getRoot()->startRendering();
|
||||
ctx.setWindowGrab(false);
|
||||
ctx.closeApp();
|
||||
|
||||
+2
-2
@@ -141,7 +141,7 @@ public:
|
||||
cs->calculateLocalInertia(mass, inertia);
|
||||
} break;
|
||||
case Ogre::Bullet::CT_CAPSULE: {
|
||||
cs = new btCompoundShape();
|
||||
cs = new btCompoundShape(false);
|
||||
btScalar height = 1.0f;
|
||||
btScalar radius = 0.3f;
|
||||
shape = new btCapsuleShape(radius,
|
||||
@@ -377,7 +377,7 @@ void CharacterController::setupBody()
|
||||
// mRigidBody = world->addCharacter(mBodyEnt, 0);
|
||||
// mCollisionShape = static_cast<btCompoundShape *>(mRigidBody->getCollisionShape());
|
||||
mGhostObject = new btPairCachingGhostObject();
|
||||
mCollisionShape = new btCompoundShape;
|
||||
mCollisionShape = new btCompoundShape(false);
|
||||
mGhostObject->setCollisionShape(mCollisionShape);
|
||||
|
||||
{
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:]
|
||||
|
||||
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
|
||||
|
||||
source_filepath = argv[0]
|
||||
armature_name = argv[1]
|
||||
|
||||
print("starting...")
|
||||
|
||||
def copy_actions_and_create_nla_tracks(source_filepath, target_object_name):
|
||||
"""
|
||||
Copies actions from a source Blender file that do not exist in the current
|
||||
file, creates NLA tracks for them on a target object, and saves the file.
|
||||
|
||||
Args:
|
||||
source_filepath (str): The full path to the source Blender file.
|
||||
target_object_name (str): The name of the object in the current file
|
||||
to which the actions and NLA tracks will be applied.
|
||||
"""
|
||||
|
||||
# 1. Link or Append Actions from the Source File
|
||||
with bpy.data.libraries.load(source_filepath, link=False) as (data_from, data_to):
|
||||
source_action_names = data_from.actions
|
||||
current_action_names = {action.name for action in bpy.data.actions}
|
||||
actions_to_append = [name for name in source_action_names if name not in current_action_names]
|
||||
data_to.actions = actions_to_append
|
||||
print(actions_to_append)
|
||||
|
||||
# Get the target object
|
||||
target_object = bpy.data.objects.get(target_object_name)
|
||||
if not target_object:
|
||||
print(f"Error: Object '{target_object_name}' not found in the current file.")
|
||||
return
|
||||
|
||||
# Ensure the object has an NLA editor
|
||||
if not target_object.animation_data:
|
||||
target_object.animation_data_create()
|
||||
|
||||
# 2. Iterate through newly imported actions and create NLA tracks
|
||||
for action in data_to.actions:
|
||||
# Check if an action with the same name already exists in the current file
|
||||
# Add the action to the NLA editor as a strip
|
||||
nla_track = target_object.animation_data.nla_tracks.new()
|
||||
nla_track.name = f"NLA_Track_{action.name}"
|
||||
nla_track.strips.new(name=action.name, start=1, action=action)
|
||||
print(f"Created NLA track for action: {action.name}")
|
||||
|
||||
# 3. Save the current Blender file
|
||||
bpy.ops.wm.save_as_mainfile(filepath=bpy.context.blend_data.filepath)
|
||||
print(f"File saved: {bpy.context.blend_data.filepath}")
|
||||
|
||||
# --- Usage Example ---
|
||||
if __name__ == "__main__":
|
||||
# Replace with your actual source file path and target object name
|
||||
source_file = source_filepath
|
||||
target_obj = armature_name
|
||||
|
||||
copy_actions_and_create_nla_tracks(source_file, target_obj)
|
||||
|
||||
@@ -9,7 +9,7 @@ from mathutils import Vector, Matrix
|
||||
from math import radians, pi
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited
|
||||
from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited, ExportMappingFemaleEdited
|
||||
|
||||
basepath = os.getcwd()
|
||||
def check_bone(bname):
|
||||
@@ -179,7 +179,7 @@ def extra_linear(angle, offset):
|
||||
ret += offt
|
||||
return ret
|
||||
|
||||
for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape(), ExportMappingMaleEdited()]:
|
||||
for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape(), ExportMappingMaleEdited(), ExportMappingFemaleEdited()]:
|
||||
if not os.path.exists(mapping.blend_path):
|
||||
print("Skipping mapping: " + mapping.blend_path)
|
||||
continue
|
||||
|
||||
@@ -6,7 +6,7 @@ class VRMDataFemale:
|
||||
editfile = "modelling-vroid-normal-female.blend"
|
||||
armature_name = "female"
|
||||
mixamo_animation_path = basepath + "/assets/blender/mixamo/female/"
|
||||
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn"]
|
||||
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn", "swimming", "treading_water"]
|
||||
fbx_scale = 0.89
|
||||
class VRMDataMale:
|
||||
path = "buch1.vrm"
|
||||
@@ -14,7 +14,7 @@ class VRMDataMale:
|
||||
editfile = "modelling-vroid-normal-male.blend"
|
||||
armature_name = "male"
|
||||
mixamo_animation_path = basepath + "/assets/blender/mixamo/male/"
|
||||
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn"]
|
||||
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn", "swimming", "treading_water"]
|
||||
fbx_scale = 1.0
|
||||
class VRMDataMaleBabyShape:
|
||||
path = "buch1-chibi.vrm"
|
||||
@@ -54,6 +54,20 @@ class ExportMappingMale:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
class ExportMappingMaleEdited:
|
||||
blend_path = "assets/blender/" + "edited-normal-male.blend"
|
||||
gltf_path = "characters/male/normal-male.gltf"
|
||||
ogre_scene = "characters/male/vroid-normal-male.scene"
|
||||
inner_path = "Object"
|
||||
objs = ["male", "Body", "Hair", "Face", "BackHair"]
|
||||
armature_name = "male"
|
||||
outfile = "tmp-male.blend"
|
||||
default_action = 'default'
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
for fobj in self.objs:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
class ExportMappingMaleClothedEdited:
|
||||
blend_path = "assets/blender/" + "edited-normal-male.blend"
|
||||
gltf_path = "characters/male/normal-male.gltf"
|
||||
ogre_scene = "characters/male/vroid-normal-male.scene"
|
||||
@@ -67,6 +81,21 @@ class ExportMappingMaleEdited:
|
||||
for fobj in self.objs:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
class ExportMappingFemaleEdited:
|
||||
blend_path = "assets/blender/" + "edited-normal-female.blend"
|
||||
gltf_path = "characters/female/normal-female.gltf"
|
||||
ogre_scene = "characters/female/vroid-normal-female.scene"
|
||||
inner_path = "Object"
|
||||
# objs = ["male", "Body", "Hair", "Face", "BackHair", "Tops", "Bottoms", "Shoes", "Accessory"]
|
||||
objs = ["female", "Body", "Hair", "Face", "BackHair", "Tops", "Bottoms", "Shoes", "Accessory"]
|
||||
armature_name = "female"
|
||||
outfile = "tmp-female.blend"
|
||||
default_action = 'default'
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
for fobj in self.objs:
|
||||
self.files.append({"name": fobj})
|
||||
|
||||
class ExportMappingMaleBabyShape:
|
||||
blend_path = "assets/blender/shapes/male/" + "vrm-vroid-normal-male-chibi.blend"
|
||||
gltf_path = "assets/blender/" + "characters/shapes/male/chibi/vroid-normal-male-chibi.gltf"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
project(lua-scripts)
|
||||
set(LUA_SCRIPTS_SRC
|
||||
data.lua
|
||||
)
|
||||
set(LUA_SCRIPTS_OUTPUT)
|
||||
set(LUA_PACKAGES narrator stories)
|
||||
foreach(LUA_SCRIPT_FILE ${LUA_SCRIPTS_SRC})
|
||||
get_filename_component(FILE_NAME ${LUA_SCRIPT_FILE} NAME_WE)
|
||||
set(LUA_SCRIPT_OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.lua)
|
||||
add_custom_command(OUTPUT ${LUA_SCRIPT_OUTPUT_FILE}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${LUA_SCRIPT_FILE} ${LUA_SCRIPT_OUTPUT_FILE}
|
||||
DEPENDS ${LUA_SCRIPT_FILE})
|
||||
list(APPEND LUA_SCRIPTS_OUTPUT ${LUA_SCRIPT_OUTPUT_FILE})
|
||||
endforeach()
|
||||
set(LUA_PACKAGES_OUTPUT)
|
||||
foreach(LUA_PACKAGE ${LUA_PACKAGES})
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LUA_PACKAGE}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/${LUA_PACKAGE}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${LUA_PACKAGE}
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${LUA_PACKAGE})
|
||||
list(APPEND LUA_PACKAGES_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LUA_PACKAGE})
|
||||
endforeach()
|
||||
add_custom_target(stage_lua_scripts ALL DEPENDS ${LUA_SCRIPTS_OUTPUT} ${LUA_PACKAGES_OUTPUT})
|
||||
+234
-1
@@ -36,4 +36,237 @@ function foo()
|
||||
end
|
||||
v = Vector3(0, 1, 2)
|
||||
end
|
||||
foo()
|
||||
--[[
|
||||
narration = {
|
||||
position = 1,
|
||||
narration_start = {
|
||||
"The party was hot, girls were sexy the wine, beer and whisky were in \
|
||||
enormous numbers. It was anniversary since you set sail with your friends. \
|
||||
The sea was calm and everything promised you another great night.",
|
||||
"The whole year with your friends on your ship over vast seas of the world in decay was almost over.\
|
||||
It was so good year full of adventure, romance, indecency and luxury.",
|
||||
"Your trusted friends decided that you have too much, they have too much of you and you owe them a lot.",
|
||||
"They found you drunk in your room and moved to the deck and used ropes to \
|
||||
restrain you and attach a bucket of stones or something else heavy to your body.",
|
||||
"After a few hours passed two strong people pulled you to the side and \
|
||||
dropped you into the sea. Last thing you heard before you hit the water was happy laugher..."
|
||||
},
|
||||
progress = function(this)
|
||||
if #this.narration_start < this.position then
|
||||
return ""
|
||||
end
|
||||
local ret = this.narration_start[this.position]
|
||||
this.position = this.position + 1
|
||||
return ret
|
||||
end,
|
||||
}
|
||||
]]--
|
||||
local narrator = require('narrator.narrator')
|
||||
|
||||
function dump(o)
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
function _narration()
|
||||
local ret = ""
|
||||
local choices = {}
|
||||
if story:can_continue() then
|
||||
print("can continue")
|
||||
local paragraph = story:continue(1)
|
||||
print(dump(paragraph))
|
||||
local text = paragraph.text
|
||||
if paragraph.tags then
|
||||
text = text .. ' #' .. table.concat(paragraph.tags, ' #')
|
||||
for i, tag in ipairs(paragraph.tags) do
|
||||
if tag == "mc_is_free" then
|
||||
ecs_character_set("player", "gravity", true)
|
||||
ecs_character_set("player", "buoyancy", true)
|
||||
end
|
||||
end
|
||||
end
|
||||
ret = text
|
||||
if story:can_choose() then
|
||||
local ch = story:get_choices()
|
||||
for i, choice in ipairs(ch) do
|
||||
table.insert(choices, choice.text)
|
||||
print(i, dump(choice))
|
||||
end
|
||||
if #choices == 1 and choices[1] == "Ascend" then
|
||||
story:choose(1)
|
||||
choices = {}
|
||||
end
|
||||
if #choices == 1 and choices[1] == "Continue" then
|
||||
story:choose(1)
|
||||
choices = {}
|
||||
end
|
||||
end
|
||||
else
|
||||
print("can NOT continue")
|
||||
end
|
||||
print(ret)
|
||||
if (#choices > 0) then
|
||||
print("choices!!!")
|
||||
narrate(ret, choices)
|
||||
else
|
||||
narrate(ret)
|
||||
end
|
||||
end
|
||||
]]--
|
||||
function Quest(name, book)
|
||||
local quest = {
|
||||
name = name,
|
||||
active = false,
|
||||
activate = function(this)
|
||||
this.active = true
|
||||
this.story:begin()
|
||||
this:_narration()
|
||||
end,
|
||||
event = function(this, event)
|
||||
if not this.active then
|
||||
return
|
||||
end
|
||||
if event == "narration_progress" then
|
||||
this:_narration()
|
||||
elseif event == "narration_answered" then
|
||||
local answer = narration_get_answer()
|
||||
this.story:choose(answer)
|
||||
print("answered:", answer)
|
||||
this:_narration()
|
||||
end
|
||||
end,
|
||||
_handle_tag = function(this, tag)
|
||||
if tag == "mc_is_free" then
|
||||
ecs_character_set("player", "gravity", true)
|
||||
ecs_character_set("player", "buoyancy", true)
|
||||
end
|
||||
end,
|
||||
_narration = function(this)
|
||||
local ret = ""
|
||||
local choices = {}
|
||||
local have_choice = false
|
||||
local have_paragraph = false
|
||||
if not this.active then
|
||||
return
|
||||
end
|
||||
if this.story:can_continue() then
|
||||
print("can continue")
|
||||
have_paragraph = true
|
||||
local paragraph = this.story:continue(1)
|
||||
print(dump(paragraph))
|
||||
local text = paragraph.text
|
||||
if paragraph.tags then
|
||||
text = text .. ' #' .. table.concat(paragraph.tags, ' #')
|
||||
for i, tag in ipairs(paragraph.tags) do
|
||||
this:_handle_tag(tag)
|
||||
end
|
||||
end
|
||||
ret = text
|
||||
if this.story:can_choose() then
|
||||
have_choice = true
|
||||
local ch = this.story:get_choices()
|
||||
for i, choice in ipairs(ch) do
|
||||
table.insert(choices, choice.text)
|
||||
print(i, dump(choice))
|
||||
end
|
||||
if #choices == 1 and choices[1] == "Ascend" then
|
||||
this.story:choose(1)
|
||||
choices = {}
|
||||
end
|
||||
if #choices == 1 and choices[1] == "Continue" then
|
||||
this.story:choose(1)
|
||||
choices = {}
|
||||
end
|
||||
end
|
||||
else
|
||||
print("can NOT continue")
|
||||
end
|
||||
if not have_choice and not have_paragraph then
|
||||
this:complete()
|
||||
this.active = false
|
||||
end
|
||||
print(ret)
|
||||
if (#choices > 0) then
|
||||
print("choices!!!")
|
||||
narrate(ret, choices)
|
||||
else
|
||||
narrate(ret)
|
||||
end
|
||||
end,
|
||||
complete = function(this)
|
||||
print(this.name .. 'complete')
|
||||
end,
|
||||
story = narrator.init_story(book)
|
||||
}
|
||||
|
||||
-- Begin the story
|
||||
|
||||
return quest
|
||||
end
|
||||
function StartGameQuest()
|
||||
-- Parse a book from the Ink file.
|
||||
local book = narrator.parse_file('stories.initiation')
|
||||
local quest = Quest('start game', book)
|
||||
quest.base = {}
|
||||
quest.base.activate = quest.activate
|
||||
quest.base.complete = quest.complete
|
||||
quest.boat = false
|
||||
quest.activate = function(this)
|
||||
print('activate...')
|
||||
local mc_is_free = function()
|
||||
ecs_character_params_set("player", "gravity", true)
|
||||
ecs_character_params_set("player", "buoyancy", true)
|
||||
end
|
||||
this.story:bind('mc_is_free', mc_is_free)
|
||||
this.base.activate(this)
|
||||
end
|
||||
quest.complete = function(this)
|
||||
this.base.complete(this)
|
||||
this.active = false
|
||||
if not this.boat then
|
||||
local boat = ecs_vehicle_set("boat", 0, 0, -20, 1.75)
|
||||
local trigger = ecs_child_character_trigger(boat, "entered_boat", 0, 0, 0, 3, 3)
|
||||
local npc = ecs_npc_set("normal-female.glb", 0, 2, -20, 1.75)
|
||||
this.boat = true
|
||||
end
|
||||
end
|
||||
return quest
|
||||
end
|
||||
quests = {}
|
||||
setup_handler(function(event)
|
||||
print(event)
|
||||
for k, v in pairs(quests) do
|
||||
if v.active then
|
||||
v:event(event)
|
||||
end
|
||||
end
|
||||
if event == "startup" then
|
||||
main_menu()
|
||||
elseif event == "narration_progress" then
|
||||
_narration()
|
||||
elseif event == "narration_answered" then
|
||||
local answer = narration_get_answer()
|
||||
story:choose(answer)
|
||||
print("answered:", answer)
|
||||
_narration()
|
||||
elseif event == "new_game" then
|
||||
ecs_character_params_set("player", "gravity", true)
|
||||
ecs_character_params_set("player", "buoyancy", false)
|
||||
local quest = StartGameQuest()
|
||||
quests[quest.name] = quest
|
||||
for k, v in pairs(quests) do
|
||||
print(k, v.active)
|
||||
end
|
||||
quest:activate()
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
---@class Narrator.Book.Version
|
||||
---@field engine number
|
||||
---@field tree number
|
||||
|
||||
---@class Narrator.Book
|
||||
---@field version Narrator.Book.Version
|
||||
---@field inclusions string[]
|
||||
---@field lists table
|
||||
---@field constants table
|
||||
---@field variables table
|
||||
---@field params table
|
||||
---@field tree table
|
||||
|
||||
---@class Narrator.ParsingParams
|
||||
---@field save boolean Save a parsed book to the lua file
|
||||
|
||||
---@class Narrator.Paragraph
|
||||
---@field text string
|
||||
---@field tags string[]|nil
|
||||
|
||||
---@class Narrator.Choice
|
||||
---@field text string
|
||||
---@field tags string[]|nil
|
||||
|
||||
---@class Narrator.State
|
||||
---@field version number
|
||||
---@field temp table
|
||||
---@field seeds table
|
||||
---@field variables table
|
||||
---@field params table|nil
|
||||
---@field visits table
|
||||
---@field current_path table
|
||||
---@field paragraphs table
|
||||
---@field choices table
|
||||
---@field output table
|
||||
---@field tunnels table|nil
|
||||
---@field path table
|
||||
@@ -0,0 +1,32 @@
|
||||
local enums = {
|
||||
|
||||
---Bump it when the state structure is changed
|
||||
engine_version = 2,
|
||||
|
||||
---@enum Narrator.ItemType
|
||||
item = {
|
||||
text = 1,
|
||||
alts = 2,
|
||||
choice = 3,
|
||||
condition = 4,
|
||||
variable = 5
|
||||
},
|
||||
|
||||
---@enum Narrator.Sequence
|
||||
sequence = {
|
||||
cycle = 1,
|
||||
stopping = 2,
|
||||
once = 3
|
||||
},
|
||||
|
||||
---@enum Narrator.ReadMode
|
||||
read_mode = {
|
||||
text = 1,
|
||||
choices = 2,
|
||||
gathers = 3,
|
||||
quit = 4
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return enums
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
--
|
||||
-- classic
|
||||
--
|
||||
-- Copyright (c) 2014, rxi
|
||||
--
|
||||
-- This module is free software; you can redistribute it and/or modify it under
|
||||
-- the terms of the MIT license. See LICENSE for details.
|
||||
--
|
||||
|
||||
|
||||
local Object = {}
|
||||
Object.__index = Object
|
||||
|
||||
|
||||
function Object:new()
|
||||
end
|
||||
|
||||
|
||||
function Object:extend()
|
||||
local cls = {}
|
||||
for k, v in pairs(self) do
|
||||
if k:find("__") == 1 then
|
||||
cls[k] = v
|
||||
end
|
||||
end
|
||||
cls.__index = cls
|
||||
cls.super = self
|
||||
setmetatable(cls, self)
|
||||
return cls
|
||||
end
|
||||
|
||||
|
||||
function Object:implement(...)
|
||||
for _, cls in pairs({...}) do
|
||||
for k, v in pairs(cls) do
|
||||
if self[k] == nil and type(v) == "function" then
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Object:is(T)
|
||||
local mt = getmetatable(self)
|
||||
while mt do
|
||||
if mt == T then
|
||||
return true
|
||||
end
|
||||
mt = getmetatable(mt)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function Object:__tostring()
|
||||
return "Object"
|
||||
end
|
||||
|
||||
|
||||
function Object:__call(...)
|
||||
local obj = setmetatable({}, self)
|
||||
obj:new(...)
|
||||
return obj
|
||||
end
|
||||
|
||||
|
||||
return Object
|
||||
Executable
+780
@@ -0,0 +1,780 @@
|
||||
--
|
||||
-- lume
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local lume = { _version = "2.3.0" }
|
||||
|
||||
local pairs, ipairs = pairs, ipairs
|
||||
local type, assert, unpack = type, assert, unpack or table.unpack
|
||||
local tostring, tonumber = tostring, tonumber
|
||||
local math_floor = math.floor
|
||||
local math_ceil = math.ceil
|
||||
local math_atan2 = math.atan2 or math.atan
|
||||
local math_sqrt = math.sqrt
|
||||
local math_abs = math.abs
|
||||
|
||||
local noop = function()
|
||||
end
|
||||
|
||||
local identity = function(x)
|
||||
return x
|
||||
end
|
||||
|
||||
local patternescape = function(str)
|
||||
return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
|
||||
end
|
||||
|
||||
local absindex = function(len, i)
|
||||
return i < 0 and (len + i + 1) or i
|
||||
end
|
||||
|
||||
local iscallable = function(x)
|
||||
if type(x) == "function" then return true end
|
||||
local mt = getmetatable(x)
|
||||
return mt and mt.__call ~= nil
|
||||
end
|
||||
|
||||
local getiter = function(x)
|
||||
if lume.isarray(x) then
|
||||
return ipairs
|
||||
elseif type(x) == "table" then
|
||||
return pairs
|
||||
end
|
||||
error("expected table", 3)
|
||||
end
|
||||
|
||||
local iteratee = function(x)
|
||||
if x == nil then return identity end
|
||||
if iscallable(x) then return x end
|
||||
if type(x) == "table" then
|
||||
return function(z)
|
||||
for k, v in pairs(x) do
|
||||
if z[k] ~= v then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
return function(z) return z[x] end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function lume.clamp(x, min, max)
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
|
||||
function lume.round(x, increment)
|
||||
if increment then return lume.round(x / increment) * increment end
|
||||
return x >= 0 and math_floor(x + .5) or math_ceil(x - .5)
|
||||
end
|
||||
|
||||
|
||||
function lume.sign(x)
|
||||
return x < 0 and -1 or 1
|
||||
end
|
||||
|
||||
|
||||
function lume.lerp(a, b, amount)
|
||||
return a + (b - a) * lume.clamp(amount, 0, 1)
|
||||
end
|
||||
|
||||
|
||||
function lume.smooth(a, b, amount)
|
||||
local t = lume.clamp(amount, 0, 1)
|
||||
local m = t * t * (3 - 2 * t)
|
||||
return a + (b - a) * m
|
||||
end
|
||||
|
||||
|
||||
function lume.pingpong(x)
|
||||
return 1 - math_abs(1 - x % 2)
|
||||
end
|
||||
|
||||
|
||||
function lume.distance(x1, y1, x2, y2, squared)
|
||||
local dx = x1 - x2
|
||||
local dy = y1 - y2
|
||||
local s = dx * dx + dy * dy
|
||||
return squared and s or math_sqrt(s)
|
||||
end
|
||||
|
||||
|
||||
function lume.angle(x1, y1, x2, y2)
|
||||
return math_atan2(y2 - y1, x2 - x1)
|
||||
end
|
||||
|
||||
|
||||
function lume.vector(angle, magnitude)
|
||||
return math.cos(angle) * magnitude, math.sin(angle) * magnitude
|
||||
end
|
||||
|
||||
|
||||
function lume.random(a, b)
|
||||
if not a then a, b = 0, 1 end
|
||||
if not b then b = 0 end
|
||||
return a + math.random() * (b - a)
|
||||
end
|
||||
|
||||
|
||||
function lume.randomchoice(t)
|
||||
return t[math.random(#t)]
|
||||
end
|
||||
|
||||
|
||||
function lume.weightedchoice(t)
|
||||
local sum = 0
|
||||
for _, v in pairs(t) do
|
||||
assert(v >= 0, "weight value less than zero")
|
||||
sum = sum + v
|
||||
end
|
||||
assert(sum ~= 0, "all weights are zero")
|
||||
local rnd = lume.random(sum)
|
||||
for k, v in pairs(t) do
|
||||
if rnd < v then return k end
|
||||
rnd = rnd - v
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.isarray(x)
|
||||
return type(x) == "table" and x[1] ~= nil
|
||||
end
|
||||
|
||||
|
||||
function lume.push(t, ...)
|
||||
local n = select("#", ...)
|
||||
for i = 1, n do
|
||||
t[#t + 1] = select(i, ...)
|
||||
end
|
||||
return ...
|
||||
end
|
||||
|
||||
|
||||
function lume.remove(t, x)
|
||||
local iter = getiter(t)
|
||||
for i, v in iter(t) do
|
||||
if v == x then
|
||||
if lume.isarray(t) then
|
||||
table.remove(t, i)
|
||||
break
|
||||
else
|
||||
t[i] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
|
||||
function lume.clear(t)
|
||||
local iter = getiter(t)
|
||||
for k in iter(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.extend(t, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
if x then
|
||||
for k, v in pairs(x) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.shuffle(t)
|
||||
local rtn = {}
|
||||
for i = 1, #t do
|
||||
local r = math.random(i)
|
||||
if r ~= i then
|
||||
rtn[i] = rtn[r]
|
||||
end
|
||||
rtn[r] = t[i]
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.sort(t, comp)
|
||||
local rtn = lume.clone(t)
|
||||
if comp then
|
||||
if type(comp) == "string" then
|
||||
table.sort(rtn, function(a, b) return a[comp] < b[comp] end)
|
||||
else
|
||||
table.sort(rtn, comp)
|
||||
end
|
||||
else
|
||||
table.sort(rtn)
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.array(...)
|
||||
local t = {}
|
||||
for x in ... do t[#t + 1] = x end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.each(t, fn, ...)
|
||||
local iter = getiter(t)
|
||||
if type(fn) == "string" then
|
||||
for _, v in iter(t) do v[fn](v, ...) end
|
||||
else
|
||||
for _, v in iter(t) do fn(v, ...) end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.map(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
local rtn = {}
|
||||
for k, v in iter(t) do rtn[k] = fn(v) end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.all(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
if not fn(v) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function lume.any(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
if fn(v) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function lume.reduce(t, fn, first)
|
||||
local started = first ~= nil
|
||||
local acc = first
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
if started then
|
||||
acc = fn(acc, v)
|
||||
else
|
||||
acc = v
|
||||
started = true
|
||||
end
|
||||
end
|
||||
assert(started, "reduce of an empty table with no first value")
|
||||
return acc
|
||||
end
|
||||
|
||||
|
||||
function lume.unique(t)
|
||||
local rtn = {}
|
||||
for k in pairs(lume.invert(t)) do
|
||||
rtn[#rtn + 1] = k
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.filter(t, fn, retainkeys)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
local rtn = {}
|
||||
if retainkeys then
|
||||
for k, v in iter(t) do
|
||||
if fn(v) then rtn[k] = v end
|
||||
end
|
||||
else
|
||||
for _, v in iter(t) do
|
||||
if fn(v) then rtn[#rtn + 1] = v end
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.reject(t, fn, retainkeys)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
local rtn = {}
|
||||
if retainkeys then
|
||||
for k, v in iter(t) do
|
||||
if not fn(v) then rtn[k] = v end
|
||||
end
|
||||
else
|
||||
for _, v in iter(t) do
|
||||
if not fn(v) then rtn[#rtn + 1] = v end
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.merge(...)
|
||||
local rtn = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local t = select(i, ...)
|
||||
local iter = getiter(t)
|
||||
for k, v in iter(t) do
|
||||
rtn[k] = v
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.concat(...)
|
||||
local rtn = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local t = select(i, ...)
|
||||
if t ~= nil then
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
rtn[#rtn + 1] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.find(t, value)
|
||||
local iter = getiter(t)
|
||||
for k, v in iter(t) do
|
||||
if v == value then return k end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function lume.match(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
for k, v in iter(t) do
|
||||
if fn(v) then return v, k end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function lume.count(t, fn)
|
||||
local count = 0
|
||||
local iter = getiter(t)
|
||||
if fn then
|
||||
fn = iteratee(fn)
|
||||
for _, v in iter(t) do
|
||||
if fn(v) then count = count + 1 end
|
||||
end
|
||||
else
|
||||
if lume.isarray(t) then
|
||||
return #t
|
||||
end
|
||||
for _ in iter(t) do count = count + 1 end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
|
||||
function lume.slice(t, i, j)
|
||||
i = i and absindex(#t, i) or 1
|
||||
j = j and absindex(#t, j) or #t
|
||||
local rtn = {}
|
||||
for x = i < 1 and 1 or i, j > #t and #t or j do
|
||||
rtn[#rtn + 1] = t[x]
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.first(t, n)
|
||||
if not n then return t[1] end
|
||||
return lume.slice(t, 1, n)
|
||||
end
|
||||
|
||||
|
||||
function lume.last(t, n)
|
||||
if not n then return t[#t] end
|
||||
return lume.slice(t, -n, -1)
|
||||
end
|
||||
|
||||
|
||||
function lume.invert(t)
|
||||
local rtn = {}
|
||||
for k, v in pairs(t) do rtn[v] = k end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.pick(t, ...)
|
||||
local rtn = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local k = select(i, ...)
|
||||
rtn[k] = t[k]
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.keys(t)
|
||||
local rtn = {}
|
||||
local iter = getiter(t)
|
||||
for k in iter(t) do rtn[#rtn + 1] = k end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.clone(t)
|
||||
local rtn = {}
|
||||
for k, v in pairs(t) do rtn[k] = v end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.fn(fn, ...)
|
||||
assert(iscallable(fn), "expected a function as the first argument")
|
||||
local args = { ... }
|
||||
return function(...)
|
||||
local a = lume.concat(args, { ... })
|
||||
return fn(unpack(a))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.once(fn, ...)
|
||||
local f = lume.fn(fn, ...)
|
||||
local done = false
|
||||
return function(...)
|
||||
if done then return end
|
||||
done = true
|
||||
return f(...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local memoize_fnkey = {}
|
||||
local memoize_nil = {}
|
||||
|
||||
function lume.memoize(fn)
|
||||
local cache = {}
|
||||
return function(...)
|
||||
local c = cache
|
||||
for i = 1, select("#", ...) do
|
||||
local a = select(i, ...) or memoize_nil
|
||||
c[a] = c[a] or {}
|
||||
c = c[a]
|
||||
end
|
||||
c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)}
|
||||
return unpack(c[memoize_fnkey])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.combine(...)
|
||||
local n = select('#', ...)
|
||||
if n == 0 then return noop end
|
||||
if n == 1 then
|
||||
local fn = select(1, ...)
|
||||
if not fn then return noop end
|
||||
assert(iscallable(fn), "expected a function or nil")
|
||||
return fn
|
||||
end
|
||||
local funcs = {}
|
||||
for i = 1, n do
|
||||
local fn = select(i, ...)
|
||||
if fn ~= nil then
|
||||
assert(iscallable(fn), "expected a function or nil")
|
||||
funcs[#funcs + 1] = fn
|
||||
end
|
||||
end
|
||||
return function(...)
|
||||
for _, f in ipairs(funcs) do f(...) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.call(fn, ...)
|
||||
if fn then
|
||||
return fn(...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.time(fn, ...)
|
||||
local start = os.clock()
|
||||
local rtn = {fn(...)}
|
||||
return (os.clock() - start), unpack(rtn)
|
||||
end
|
||||
|
||||
|
||||
local lambda_cache = {}
|
||||
|
||||
function lume.lambda(str)
|
||||
if not lambda_cache[str] then
|
||||
local args, body = str:match([[^([%w,_ ]-)%->(.-)$]])
|
||||
assert(args and body, "bad string lambda")
|
||||
local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend"
|
||||
lambda_cache[str] = lume.dostring(s)
|
||||
end
|
||||
return lambda_cache[str]
|
||||
end
|
||||
|
||||
|
||||
local serialize
|
||||
|
||||
local serialize_map = {
|
||||
[ "boolean" ] = tostring,
|
||||
[ "nil" ] = tostring,
|
||||
[ "string" ] = function(v) return string.format("%q", v) end,
|
||||
[ "number" ] = function(v)
|
||||
if v ~= v then return "0/0" -- nan
|
||||
elseif v == 1 / 0 then return "1/0" -- inf
|
||||
elseif v == -1 / 0 then return "-1/0" end -- -inf
|
||||
return tostring(v)
|
||||
end,
|
||||
[ "table" ] = function(t, stk)
|
||||
stk = stk or {}
|
||||
if stk[t] then error("circular reference") end
|
||||
local rtn = {}
|
||||
stk[t] = true
|
||||
for k, v in pairs(t) do
|
||||
rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk)
|
||||
end
|
||||
stk[t] = nil
|
||||
return "{" .. table.concat(rtn, ",") .. "}"
|
||||
end
|
||||
}
|
||||
|
||||
setmetatable(serialize_map, {
|
||||
__index = function(_, k) error("unsupported serialize type: " .. k) end
|
||||
})
|
||||
|
||||
serialize = function(x, stk)
|
||||
return serialize_map[type(x)](x, stk)
|
||||
end
|
||||
|
||||
function lume.serialize(x)
|
||||
return serialize(x)
|
||||
end
|
||||
|
||||
|
||||
function lume.deserialize(str)
|
||||
return lume.dostring("return " .. str)
|
||||
end
|
||||
|
||||
|
||||
function lume.split(str, sep)
|
||||
if not sep then
|
||||
return lume.array(str:gmatch("([%S]+)"))
|
||||
else
|
||||
assert(sep ~= "", "empty separator")
|
||||
local psep = patternescape(sep)
|
||||
return lume.array((str..sep):gmatch("(.-)("..psep..")"))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.trim(str, chars)
|
||||
if not chars then return str:match("^[%s]*(.-)[%s]*$") end
|
||||
chars = patternescape(chars)
|
||||
return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$")
|
||||
end
|
||||
|
||||
|
||||
function lume.wordwrap(str, limit)
|
||||
limit = limit or 72
|
||||
local check
|
||||
if type(limit) == "number" then
|
||||
check = function(s) return #s >= limit end
|
||||
else
|
||||
check = limit
|
||||
end
|
||||
local rtn = {}
|
||||
local line = ""
|
||||
for word, spaces in str:gmatch("(%S+)(%s*)") do
|
||||
local s = line .. word
|
||||
if check(s) then
|
||||
table.insert(rtn, line .. "\n")
|
||||
line = word
|
||||
else
|
||||
line = s
|
||||
end
|
||||
for c in spaces:gmatch(".") do
|
||||
if c == "\n" then
|
||||
table.insert(rtn, line .. "\n")
|
||||
line = ""
|
||||
else
|
||||
line = line .. c
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(rtn, line)
|
||||
return table.concat(rtn)
|
||||
end
|
||||
|
||||
|
||||
function lume.format(str, vars)
|
||||
if not vars then return str end
|
||||
local f = function(x)
|
||||
return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}")
|
||||
end
|
||||
return (str:gsub("{(.-)}", f))
|
||||
end
|
||||
|
||||
|
||||
function lume.trace(...)
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
local t = { info.short_src .. ":" .. info.currentline .. ":" }
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
if type(x) == "number" then
|
||||
x = string.format("%g", lume.round(x, .01))
|
||||
end
|
||||
t[#t + 1] = tostring(x)
|
||||
end
|
||||
print(table.concat(t, " "))
|
||||
end
|
||||
|
||||
|
||||
function lume.dostring(str)
|
||||
return assert((loadstring or load)(str))()
|
||||
end
|
||||
|
||||
|
||||
function lume.uuid()
|
||||
local fn = function(x)
|
||||
local r = math.random(16) - 1
|
||||
r = (x == "x") and (r + 1) or (r % 4) + 9
|
||||
return ("0123456789abcdef"):sub(r, r)
|
||||
end
|
||||
return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
|
||||
end
|
||||
|
||||
|
||||
function lume.hotswap(modname)
|
||||
local oldglobal = lume.clone(_G)
|
||||
local updated = {}
|
||||
local function update(old, new)
|
||||
if updated[old] then return end
|
||||
updated[old] = true
|
||||
local oldmt, newmt = getmetatable(old), getmetatable(new)
|
||||
if oldmt and newmt then update(oldmt, newmt) end
|
||||
for k, v in pairs(new) do
|
||||
if type(v) == "table" then update(old[k], v) else old[k] = v end
|
||||
end
|
||||
end
|
||||
local err = nil
|
||||
local function onerror(e)
|
||||
for k in pairs(_G) do _G[k] = oldglobal[k] end
|
||||
err = lume.trim(e)
|
||||
end
|
||||
local ok, oldmod = pcall(require, modname)
|
||||
oldmod = ok and oldmod or nil
|
||||
xpcall(function()
|
||||
package.loaded[modname] = nil
|
||||
local newmod = require(modname)
|
||||
if type(oldmod) == "table" then update(oldmod, newmod) end
|
||||
for k, v in pairs(oldglobal) do
|
||||
if v ~= _G[k] and type(v) == "table" then
|
||||
update(v, _G[k])
|
||||
_G[k] = v
|
||||
end
|
||||
end
|
||||
end, onerror)
|
||||
package.loaded[modname] = oldmod
|
||||
if err then return nil, err end
|
||||
return oldmod
|
||||
end
|
||||
|
||||
|
||||
local ripairs_iter = function(t, i)
|
||||
i = i - 1
|
||||
local v = t[i]
|
||||
if v ~= nil then
|
||||
return i, v
|
||||
end
|
||||
end
|
||||
|
||||
function lume.ripairs(t)
|
||||
return ripairs_iter, t, (#t + 1)
|
||||
end
|
||||
|
||||
|
||||
function lume.color(str, mul)
|
||||
mul = mul or 1
|
||||
local r, g, b, a
|
||||
r, g, b = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||
if r then
|
||||
r = tonumber(r, 16) / 0xff
|
||||
g = tonumber(g, 16) / 0xff
|
||||
b = tonumber(b, 16) / 0xff
|
||||
a = 1
|
||||
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
|
||||
local f = str:gmatch("[%d.]+")
|
||||
r = (f() or 0) / 0xff
|
||||
g = (f() or 0) / 0xff
|
||||
b = (f() or 0) / 0xff
|
||||
a = f() or 1
|
||||
else
|
||||
error(("bad color string '%s'"):format(str))
|
||||
end
|
||||
return r * mul, g * mul, b * mul, a * mul
|
||||
end
|
||||
|
||||
|
||||
local chain_mt = {}
|
||||
chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
|
||||
function(fn)
|
||||
return function(self, ...)
|
||||
self._value = fn(self._value, ...)
|
||||
return self
|
||||
end
|
||||
end)
|
||||
chain_mt.__index.result = function(x) return x._value end
|
||||
|
||||
function lume.chain(value)
|
||||
return setmetatable({ _value = value }, chain_mt)
|
||||
end
|
||||
|
||||
setmetatable(lume, {
|
||||
__call = function(_, ...)
|
||||
return lume.chain(...)
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
return lume
|
||||
@@ -0,0 +1,401 @@
|
||||
--
|
||||
-- Dependencies
|
||||
|
||||
local lume = require('narrator.libs.lume')
|
||||
|
||||
--
|
||||
-- Metatable
|
||||
|
||||
local mt = { lists = { } }
|
||||
|
||||
function mt.__tostring(self)
|
||||
local pool = { }
|
||||
|
||||
local list_keys = { }
|
||||
for key, _ in pairs(self) do
|
||||
table.insert(list_keys, key)
|
||||
end
|
||||
table.sort(list_keys)
|
||||
|
||||
for i = 1, #list_keys do
|
||||
local list_name = list_keys[i]
|
||||
local list_items = self[list_name]
|
||||
for index = 1, #mt.lists[list_name] do
|
||||
pool[index] = pool[index] or { }
|
||||
local item_name = mt.lists[list_name][index]
|
||||
if list_items[item_name] == true then
|
||||
table.insert(pool[index], 1, item_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local items = { }
|
||||
|
||||
for _, titles in ipairs(pool) do
|
||||
for _, title in ipairs(titles) do
|
||||
table.insert(items, title)
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(items, ', ')
|
||||
end
|
||||
|
||||
--
|
||||
-- Operators
|
||||
|
||||
function mt.__add(lhs, rhs) -- +
|
||||
if type(rhs) == 'table' then
|
||||
return mt.__add_list(lhs, rhs)
|
||||
elseif type(rhs) == 'number' then
|
||||
return mt.__shift_by_number(lhs, rhs)
|
||||
else
|
||||
error('Attempt to sum the list with ' .. type(rhs))
|
||||
end
|
||||
end
|
||||
|
||||
function mt.__sub(lhs, rhs) -- -
|
||||
if type(rhs) == 'table' then
|
||||
return mt.__subList(lhs, rhs)
|
||||
elseif type(rhs) == 'number' then
|
||||
return mt.__shift_by_number(lhs, -rhs)
|
||||
else
|
||||
error('Attempt to sub the list with ' .. type(rhs))
|
||||
end
|
||||
end
|
||||
|
||||
function mt.__mod(lhs, rhs) -- % (contain)
|
||||
if type(rhs) ~= 'table' then
|
||||
error('Attempt to check content of the list for ' .. type(rhs))
|
||||
end
|
||||
|
||||
for list_name, list_items in pairs(rhs) do
|
||||
if lhs[list_name] == nil then return false end
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
if (lhs[list_name][item_name] or false) ~= item_value then return false end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function mt.__pow(lhs, rhs) -- ^ (intersection)
|
||||
if type(rhs) ~= 'table' then
|
||||
error('Attempt to interselect the list with ' .. type(rhs))
|
||||
end
|
||||
|
||||
local intersection = { }
|
||||
|
||||
for list_name, list_items in pairs(lhs) do
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
local left = lhs[list_name][item_name]
|
||||
local right = (rhs[list_name] or { })[item_name]
|
||||
if left == true and right == true then
|
||||
intersection[list_name] = intersection[list_name] or { }
|
||||
intersection[list_name][item_name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(intersection, mt)
|
||||
return intersection
|
||||
end
|
||||
|
||||
function mt.__len(self) -- #
|
||||
local len = 0
|
||||
|
||||
for list_name, list_items in pairs(self) do
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
if item_value == true then len = len + 1 end
|
||||
end
|
||||
end
|
||||
|
||||
return len
|
||||
end
|
||||
|
||||
function mt.__eq(lhs, rhs) -- ==
|
||||
if type(rhs) ~= 'table' then
|
||||
error('Attempt to compare the list with ' .. type(rhs))
|
||||
end
|
||||
|
||||
local function keys_count(object)
|
||||
local count = 0
|
||||
for _, _ in pairs(object) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
local left_lists_count = keys_count(lhs)
|
||||
local right_lists_count = keys_count(rhs)
|
||||
if left_lists_count ~= right_lists_count then
|
||||
return false
|
||||
end
|
||||
|
||||
for list_name, left_items in pairs(lhs) do
|
||||
local right_items = rhs[list_name]
|
||||
if right_items == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
local left_items_count = keys_count(left_items)
|
||||
local right_items_count = keys_count(right_items)
|
||||
|
||||
if left_items_count ~= right_items_count then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return mt.__mod(lhs, rhs)
|
||||
end
|
||||
|
||||
function mt.__lt(lhs, rhs) -- <
|
||||
if type(rhs) ~= 'table' then
|
||||
error('Attempt to compare the list with ' .. type(rhs))
|
||||
end
|
||||
|
||||
-- LEFT < RIGHT means "the smallest value in RIGHT is bigger than the largest values in LEFT"
|
||||
|
||||
local minLeft = mt.min_value_of(lhs, true)
|
||||
local maxRight = mt.max_value_of(rhs, true)
|
||||
|
||||
return minLeft < maxRight
|
||||
end
|
||||
|
||||
function mt.__le(lhs, rhs) -- <=
|
||||
if type(rhs) ~= 'table' then
|
||||
error('Attempt to compare the list with ' .. type(rhs))
|
||||
end
|
||||
|
||||
-- LEFT => RIGHT means "the smallest value in RIGHT is at least the smallest value in LEFT,
|
||||
-- and the largest value in RIGHT is at least the largest value in LEFT".
|
||||
|
||||
local minRight = mt.min_value_of(rhs, true)
|
||||
local minLeft = mt.min_value_of(lhs, true)
|
||||
local maxRight = mt.max_value_of(rhs, true)
|
||||
local maxLeft = mt.max_value_of(lhs, true)
|
||||
|
||||
return minRight >= minLeft and maxRight >= maxLeft
|
||||
end
|
||||
|
||||
--
|
||||
-- Custom operators
|
||||
|
||||
function mt.__add_list(lhs, rhs)
|
||||
local result = lume.clone(lhs)
|
||||
|
||||
for list_name, list_items in pairs(rhs) do
|
||||
result[list_name] = result[list_name] or { }
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
result[list_name][item_name] = item_value
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function mt.__subList(lhs, rhs)
|
||||
local result = lume.clone(lhs)
|
||||
|
||||
for list_name, list_items in pairs(rhs) do
|
||||
if lhs[list_name] ~= nil then
|
||||
for item_name, _ in pairs(list_items) do
|
||||
lhs[list_name][item_name] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return mt.remove_empties_in_list(result)
|
||||
end
|
||||
|
||||
function mt.__shift_by_number(list, number)
|
||||
local result = { }
|
||||
|
||||
for list_name, list_items in pairs(list) do
|
||||
result[list_name] = { }
|
||||
for index, item_name in ipairs(mt.lists[list_name]) do
|
||||
if list_items[item_name] == true then
|
||||
local nextItem = mt.lists[list_name][index + number]
|
||||
if nextItem ~= nil then
|
||||
result[list_name][nextItem] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return mt.remove_empties_in_list(result)
|
||||
end
|
||||
|
||||
--
|
||||
-- Helpers
|
||||
|
||||
function mt.remove_empties_in_list(list)
|
||||
local result = lume.clone(list)
|
||||
|
||||
for list_name, list_items in pairs(list) do
|
||||
if next(list_items) == nil then
|
||||
result[list_name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function mt.min_value_of(list, raw)
|
||||
local min_index = 0
|
||||
local min_value = { }
|
||||
|
||||
local list_keys = { }
|
||||
for key, _ in pairs(list) do
|
||||
table.insert(list_keys, key)
|
||||
end
|
||||
table.sort(list_keys)
|
||||
|
||||
for i = 1, #list_keys do
|
||||
local list_name = list_keys[i]
|
||||
local list_items = list[list_name]
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
if item_value == true then
|
||||
local index = lume.find(mt.lists[list_name], item_name)
|
||||
if index and index < min_index or min_index == 0 then
|
||||
min_index = index
|
||||
min_value = { [list_name] = { [item_name] = true } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return raw and min_index or min_value
|
||||
end
|
||||
|
||||
function mt.max_value_of(list, raw)
|
||||
local max_index = 0
|
||||
local max_value = { }
|
||||
|
||||
local list_keys = { }
|
||||
for key, _ in pairs(list) do
|
||||
table.insert(list_keys, key)
|
||||
end
|
||||
table.sort(list_keys)
|
||||
|
||||
for i = 1, #list_keys do
|
||||
local list_name = list_keys[i]
|
||||
local list_items = list[list_name]
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
if item_value == true then
|
||||
local index = lume.find(mt.lists[list_name], item_name)
|
||||
if index and index > max_index or max_index == 0 then
|
||||
max_index = index
|
||||
max_value = { [list_name] = { [item_name] = true } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return raw and max_index or max_value
|
||||
end
|
||||
|
||||
function mt.random_value_of(list)
|
||||
local items = { }
|
||||
|
||||
local list_keys = { }
|
||||
for key, _ in pairs(list) do
|
||||
table.insert(list_keys, key)
|
||||
end
|
||||
table.sort(list_keys)
|
||||
|
||||
for i = 1, #list_keys do
|
||||
local list_name = list_keys[i]
|
||||
local list_items = list[list_name]
|
||||
local items_keys = { }
|
||||
for key, _ in pairs(list_items) do
|
||||
table.insert(items_keys, key)
|
||||
end
|
||||
table.sort(items_keys)
|
||||
|
||||
for i = 1, #items_keys do
|
||||
local item_name = items_keys[i]
|
||||
local item_value = list_items[item_name]
|
||||
if item_value == true then
|
||||
local result = { [list_name] = { [item_name] = true } }
|
||||
table.insert(items, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local random_index = math.random(1, #items)
|
||||
return items[random_index]
|
||||
end
|
||||
|
||||
function mt.first_raw_value_of(list)
|
||||
local result = 0
|
||||
|
||||
for list_name, list_items in pairs(list) do
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
if item_value == true then
|
||||
local index = lume.find(mt.lists[list_name], item_name)
|
||||
if index then
|
||||
result = index
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function mt.posible_values_of(list)
|
||||
local result = { }
|
||||
|
||||
for list_name, list_items in pairs(list) do
|
||||
local subList = { }
|
||||
for _, item_name in ipairs(mt.lists[list_name]) do
|
||||
subList[item_name] = true
|
||||
end
|
||||
result[list_name] = subList
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function mt.range_of(list, min, max)
|
||||
if type(min) ~= 'table' and type(min) ~= 'number' then
|
||||
error('Attempt to get a range with incorrect min value of type ' .. type(min))
|
||||
end
|
||||
if type(max) ~= 'table' and type(max) ~= 'number' then
|
||||
error('Attempt to get a range with incorrect max value of type ' .. type(max))
|
||||
end
|
||||
|
||||
local result = { }
|
||||
local allList = mt.posible_values_of(list)
|
||||
local min_index = type(min) == 'number' and min or mt.first_raw_value_of(min)
|
||||
local max_index = type(max) == 'number' and max or mt.first_raw_value_of(max)
|
||||
|
||||
for list_name, list_items in pairs(allList) do
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
local index = lume.find(mt.lists[list_name], item_name)
|
||||
if index and index >= min_index and index <= max_index and list[list_name][item_name] == true then
|
||||
result[list_name] = result[list_name] or { }
|
||||
result[list_name][item_name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function mt.invert(list)
|
||||
local result = mt.posible_values_of(list)
|
||||
|
||||
for list_name, list_items in pairs(list) do
|
||||
for item_name, item_value in pairs(list_items) do
|
||||
if item_value == true then
|
||||
result[list_name][item_name] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return mt
|
||||
@@ -0,0 +1,150 @@
|
||||
local lume = require('narrator.libs.lume')
|
||||
local enums = require('narrator.enums')
|
||||
local parser = require('narrator.parser')
|
||||
local Story = require('narrator.story')
|
||||
|
||||
--
|
||||
-- Local
|
||||
|
||||
local folder_separator = package.config:sub(1, 1)
|
||||
|
||||
---Clear path from '.lua' and '.ink' extensions and replace '.' to '/' or '\'
|
||||
---@param path string
|
||||
---@return string normalized_path
|
||||
local function normalize_path(path)
|
||||
local path = path:gsub('.lua$', '')
|
||||
local path = path:gsub('.ink$', '')
|
||||
|
||||
if path:match('%.') and not path:match(folder_separator) then
|
||||
path = path:gsub('%.', folder_separator)
|
||||
end
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
---Parse an .ink file to the content string.
|
||||
---@param path string
|
||||
---@return string content
|
||||
local function read_ink_file(path)
|
||||
local path = normalize_path(path) .. '.ink'
|
||||
|
||||
local file = io.open(path, 'r')
|
||||
assert(file, 'File doesn\'t exist: ' .. path)
|
||||
|
||||
local content = file:read('*all')
|
||||
file:close()
|
||||
|
||||
return content
|
||||
end
|
||||
|
||||
---Save a book to the lua module
|
||||
---@param book Narrator.Book
|
||||
---@param path string
|
||||
---@return boolean success
|
||||
local function save_book(book, path)
|
||||
local path = normalize_path(path) .. '.lua'
|
||||
|
||||
local data = lume.serialize(book)
|
||||
data = data:gsub('%[%d+%]=', '')
|
||||
data = data:gsub('[\'[%w_]+\']', function(match) return
|
||||
match:sub(3, #match - 2)
|
||||
end)
|
||||
|
||||
local file = io.open(path, 'w')
|
||||
if file == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
file:write('return ' .. data)
|
||||
file:close()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---Merge a chapter to the book
|
||||
---@param book Narrator.Book
|
||||
---@param chapter Narrator.Book
|
||||
---@return Narrator.Book
|
||||
local function merge_chapter_to_book(book, chapter)
|
||||
-- Check a engine version compatibility
|
||||
if chapter.version.engine and chapter.version.engine ~= enums.engine_version then
|
||||
assert('Version ' .. chapter.version.engine .. ' of book isn\'t equal to the version ' .. enums.engine_version .. ' of Narrator.')
|
||||
end
|
||||
|
||||
--Merge the root knot and it's stitch
|
||||
book.tree._._ = lume.concat(chapter.tree._._, book.tree._._)
|
||||
chapter.tree._._ = nil
|
||||
book.tree._ = lume.merge(chapter.tree._, book.tree._)
|
||||
chapter.tree._ = nil
|
||||
|
||||
--Merge a chapter to the book
|
||||
book.tree = lume.merge(book.tree or { }, chapter.tree or { })
|
||||
book.constants = lume.merge(book.constants or { }, chapter.constants or { })
|
||||
book.lists = lume.merge(book.lists or { }, chapter.lists or { })
|
||||
book.variables = lume.merge(book.variables or { }, chapter.variables or { })
|
||||
book.params = lume.merge(book.params or { }, chapter.params or { })
|
||||
|
||||
return book
|
||||
end
|
||||
|
||||
--
|
||||
-- Public
|
||||
|
||||
local narrator = { }
|
||||
|
||||
---Parse a book from an Ink file
|
||||
---Use it during development, but prefer already parsed and stored books in production
|
||||
---Requires `lpeg` and `io`.
|
||||
---@param path string
|
||||
---@param params Narrator.ParsingParams|nil
|
||||
---@return Narrator.Book
|
||||
function narrator.parse_file(path, params)
|
||||
local params = params or { save = false }
|
||||
assert(parser, 'Can\'t parse anything without lpeg, sorry.')
|
||||
|
||||
local content = read_ink_file(path)
|
||||
local book = parser.parse(content)
|
||||
|
||||
for _, inclusion in ipairs(book.inclusions) do
|
||||
local folder_path = normalize_path(path):match('(.*' .. folder_separator .. ')')
|
||||
local inclusion_path = folder_path .. normalize_path(inclusion) .. '.ink'
|
||||
local chapter = narrator.parse_file(inclusion_path)
|
||||
|
||||
merge_chapter_to_book(book, chapter)
|
||||
end
|
||||
|
||||
if params.save then
|
||||
save_book(book, path)
|
||||
end
|
||||
|
||||
return book
|
||||
end
|
||||
|
||||
---Parse a book from the ink content string
|
||||
---Use it during development, but prefer already parsed and stored books in production
|
||||
---Requires `lpeg`
|
||||
---@param content string
|
||||
---@param inclusions string[]
|
||||
---@return Narrator.Book
|
||||
function narrator.parse_content(content, inclusions)
|
||||
local inclusions = inclusions or { }
|
||||
assert(parser, 'Can\'t parse anything without a parser.')
|
||||
|
||||
local book = parser.parse(content)
|
||||
|
||||
for _, inclusion in ipairs(inclusions) do
|
||||
local chapter = parser.parse(inclusion)
|
||||
merge_chapter_to_book(book, chapter)
|
||||
end
|
||||
|
||||
return book
|
||||
end
|
||||
|
||||
---Init a story based on the book
|
||||
---@param book Narrator.Book
|
||||
---@return Narrator.Story
|
||||
function narrator.init_story(book)
|
||||
return Story(book)
|
||||
end
|
||||
|
||||
return narrator
|
||||
@@ -0,0 +1,789 @@
|
||||
local lume = require('narrator.libs.lume')
|
||||
local enums = require('narrator.enums')
|
||||
|
||||
--
|
||||
-- LPeg
|
||||
|
||||
-- To allow to build in Defold
|
||||
local lpeg_name = 'lpeg'
|
||||
|
||||
if not pcall(require, lpeg_name) then
|
||||
return false
|
||||
end
|
||||
|
||||
local lpeg = require(lpeg_name)
|
||||
|
||||
local S, C, P, V = lpeg.S, lpeg.C, lpeg.P, lpeg.V
|
||||
local Cb, Ct, Cc, Cg = lpeg.Cb, lpeg.Ct, lpeg.Cc, lpeg.Cg
|
||||
local Cmt = lpeg.Cmt
|
||||
|
||||
lpeg.locale(lpeg)
|
||||
|
||||
--
|
||||
-- Parser
|
||||
|
||||
local parser = { }
|
||||
local constructor = { }
|
||||
|
||||
---Parse ink content string
|
||||
---@param content string
|
||||
---@return Narrator.Book
|
||||
function parser.parse(content)
|
||||
|
||||
--
|
||||
-- Basic patterns
|
||||
|
||||
local function get_length(array) return
|
||||
#array
|
||||
end
|
||||
|
||||
local eof = -1
|
||||
local sp = S(' \t') ^ 0
|
||||
local ws = S(' \t\r\n') ^ 0
|
||||
local nl = S('\r\n') ^ 1
|
||||
local none = Cc(nil)
|
||||
|
||||
local divert_sign = P'->'
|
||||
local gather_mark = sp * C('-' - divert_sign)
|
||||
local gather_level = Cg(Ct(gather_mark ^ 1) / get_length + none, 'level')
|
||||
|
||||
local sticky_marks = Cg(Ct((sp * C('+')) ^ 1) / get_length, 'level') * Cg(Cc(true), 'sticky')
|
||||
local choice_marks = Cg(Ct((sp * C('*')) ^ 1) / get_length, 'level') * Cg(Cc(false), 'sticky')
|
||||
local choice_level = sticky_marks + choice_marks
|
||||
|
||||
local id = (lpeg.alpha + '_') * (lpeg.alnum + '_') ^ 0
|
||||
local label = Cg('(' * sp * C(id) * sp * ')', 'label')
|
||||
local address = id * ('.' * id) ^ -2
|
||||
|
||||
---Something for tunnels
|
||||
local function check_tunnel(s, i, a)
|
||||
local r = lpeg.match (sp * divert_sign, s, i)
|
||||
return i, r ~= nil
|
||||
end
|
||||
|
||||
-- TODO: Clean divert expression to divert and tunnel
|
||||
local divert = divert_sign * sp * Cg(address, 'path') -- base search for divert symbol and path to follow
|
||||
local check_tunnel = Cg(Cmt(Cb('path'), check_tunnel), 'tunnel') -- a weird way to to check tunnel
|
||||
local opt_tunnel_sign = (sp * divert_sign * sp * (#nl + #S'#') ) ^ -1 -- tunnel sign in end of string, keep newline not consumed
|
||||
divert = Cg(Ct(divert * sp * check_tunnel * opt_tunnel_sign), 'divert')
|
||||
|
||||
local divert_to_nothing = divert_sign * none
|
||||
local exit_tunnel = Cg(divert_sign * divert_sign, 'exit')
|
||||
local tag = '#' * sp * V'text'
|
||||
local tags = Cg(Ct(tag * (sp * tag) ^ 0), 'tags')
|
||||
|
||||
local todo = sp * 'TODO:' * (1 - nl) ^ 0
|
||||
local comment_line = sp * '//' * sp * (1 - nl) ^ 0
|
||||
local comment_multi = sp * '/*' * ((P(1) - '*/') ^ 0) * '*/'
|
||||
local comment = comment_line + comment_multi
|
||||
|
||||
local multiline_end = ws * '}'
|
||||
|
||||
--
|
||||
-- Dynamic patterns and evaluation helpers
|
||||
|
||||
local function item_type(type)
|
||||
return Cg(Cc(type), 'type')
|
||||
end
|
||||
|
||||
local function balanced_multiline_item(is_restricted)
|
||||
local is_restricted = is_restricted ~= nil and is_restricted or false
|
||||
local paragraph = is_restricted and V'restricted_paragraph' or V'paragraph'
|
||||
return sp * paragraph ^ -1 * sp * V'multiline_item' * sp * paragraph ^ -1 * ws
|
||||
end
|
||||
|
||||
local function sentence_before(excluded, tailed)
|
||||
local tailed = tailed or false
|
||||
local character = P(1 - S(' \t')) - excluded
|
||||
local pattern = (sp * character ^ 1) ^ 1
|
||||
local with_tail = C(pattern * sp)
|
||||
local without_tail = C(pattern) * sp
|
||||
local without_tail_always = C(pattern) * sp * #(tags + nl)
|
||||
return without_tail_always + (tailed and with_tail or without_tail)
|
||||
end
|
||||
|
||||
local function unwrap_assignment(assignment)
|
||||
local unwrapped = assignment
|
||||
unwrapped = unwrapped:gsub('([%w_]*)%s*([%+%-])[%+%-]', '%1 = %1 %2 1')
|
||||
unwrapped = unwrapped:gsub('([%w_]*)%s*([%+%-])=%s*(.*)', '%1 = %1 %2 %3')
|
||||
local name, value = unwrapped:match('([%w_]*)%s*=%s*(.*)')
|
||||
return name or '', value or assignment
|
||||
end
|
||||
|
||||
local function check_special_escape(s, i, a)
|
||||
if string.sub(s, i - 2, i - 2) == '\\' then
|
||||
return
|
||||
end
|
||||
|
||||
return i
|
||||
end
|
||||
|
||||
--
|
||||
-- Grammar rules
|
||||
|
||||
local ink_grammar = P({ 'root',
|
||||
|
||||
-- Root
|
||||
|
||||
root = ws * V'items' + eof,
|
||||
items = Ct(V'item' ^ 0),
|
||||
|
||||
item = balanced_multiline_item() + V'singleline_item',
|
||||
singleline_item = sp * (V'global' + V'statement' + V'paragraph' + V'gatherPoint') * ws,
|
||||
multiline_item = ('{' * sp * (V'sequence' + V'switch') * sp * multiline_end) - V'inline_condition',
|
||||
|
||||
-- Gather points
|
||||
gatherPoint = Ct(gather_level * sp * nl * item_type('gather')),
|
||||
|
||||
-- Global declarations
|
||||
|
||||
global =
|
||||
Ct(V'inclusion' * item_type('inclusion')) +
|
||||
Ct(V'list' * item_type('list')) +
|
||||
Ct(V'constant' * item_type('constant')) +
|
||||
Ct(V'variable' * item_type('variable'))
|
||||
,
|
||||
|
||||
inclusion = 'INCLUDE ' * sp * Cg(sentence_before(nl + comment), 'filename'),
|
||||
list = 'LIST ' * sp * V'assignment_pair',
|
||||
constant = 'CONST ' * sp * V'assignment_pair',
|
||||
variable = 'VAR ' * sp * V'assignment_pair',
|
||||
|
||||
-- Statements
|
||||
|
||||
statement =
|
||||
Ct(V'return_from_func' * item_type('return')) +
|
||||
Ct(V'assignment' * item_type('assignment')) +
|
||||
Ct(V'func' * item_type('func')) +
|
||||
Ct(V'knot' * item_type('knot')) +
|
||||
Ct(V'stitch' * item_type('stitch')) +
|
||||
Ct(V'choice' * item_type('choice')) +
|
||||
comment + todo
|
||||
,
|
||||
|
||||
section_name = C(id) * sp * P'=' ^ 0,
|
||||
knot = P'==' * (P'=' ^ 0) * sp * Cg(V'section_name', 'knot'),
|
||||
stitch = '=' * sp * Cg(V'section_name', 'stitch'),
|
||||
|
||||
func_param = sp * C(id) * sp * S','^0,
|
||||
func_params = P'(' * Cg(Ct(V'func_param'^0), 'params') * P')',
|
||||
function_name = P'function' * sp * Cg(id, 'name') * sp * V'func_params' * sp * P'=' ^ 0,
|
||||
func = P'==' * (P'=' ^ 0) * sp * Cg(Ct(V'function_name'), 'func'),
|
||||
|
||||
return_from_func = sp * '~' * sp * P('return') * sp * Cg((P(1) - nl)^0, 'value') * nl ^ 0,
|
||||
|
||||
assignment = gather_level * sp * '~' * sp * V'assignment_temp' * sp * V'assignment_pair',
|
||||
assignment_temp = Cg('temp' * Cc(true) + Cc(false), 'temp'),
|
||||
assignment_pair = Cg(sentence_before(nl + comment) / unwrap_assignment, 'name') * Cg(Cb('name') / 2, 'value'),
|
||||
|
||||
choice_condition = Cg(V'expression' + none, 'condition'),
|
||||
choice_fallback = choice_level * sp * V'label_optional' * sp * V'choice_condition' * sp * (divert + divert_to_nothing) * sp * V'tags_optional',
|
||||
choice_normal = choice_level * sp * V'label_optional' * sp * V'choice_condition' * sp * Cg(V'text', 'text') * divert ^ -1 * sp * V'tags_optional',
|
||||
choice = V'choice_fallback' + V'choice_normal',
|
||||
|
||||
-- Paragraph
|
||||
|
||||
paragraph = Ct(gather_level * sp * (V'paragraph_label' + V'paragraph_text' + V'paragraph_tags') * item_type('paragraph')),
|
||||
paragraph_label = label * sp * Cg(V'text_optional', 'parts') * sp * V'tags_optional',
|
||||
paragraph_text = V'label_optional' * sp * Cg(V'text_complex', 'parts') * sp * V'tags_optional',
|
||||
paragraph_tags = V'label_optional' * sp * Cg(V'text_optional', 'parts') * sp * tags,
|
||||
|
||||
label_optional = label + none,
|
||||
text_optional = V'text_complex' + none,
|
||||
tags_optional = tags + none,
|
||||
|
||||
text_complex = Ct((Ct(
|
||||
Cg(V'inline_condition', 'condition') +
|
||||
Cg(V'inline_sequence', 'sequence') +
|
||||
Cg(V'expression', 'expression') +
|
||||
Cg(V'text' + ' ', 'text') * (exit_tunnel ^ -1) * (divert ^ -1) + exit_tunnel + divert
|
||||
) - V'multiline_item') ^ 1),
|
||||
|
||||
special_check_escape = Cmt(S("{|}"), check_special_escape),
|
||||
|
||||
text = sentence_before(nl + exit_tunnel + divert + comment + tag + V'special_check_escape', true) - V'statement',
|
||||
-- Inline expressions, conditions, sequences
|
||||
|
||||
expression = '{' * sp * sentence_before('}' + nl) * sp * '}',
|
||||
|
||||
inline_condition = '{' * sp * Ct(V'inline_if_else' + V'inline_if') * sp * '}',
|
||||
inline_if = Cg(sentence_before(S':}' + nl), 'condition') * sp * ':' * sp * Cg(V'text_complex', 'success'),
|
||||
inline_if_else = (V'inline_if') * sp * '|' * sp * Cg(V'text_complex', 'failure'),
|
||||
|
||||
inline_alt_empty = Ct(Ct(Cg(sp * Cc'', 'text') * sp * divert ^ -1)),
|
||||
inline_alt = V'text_complex' + V'inline_alt_empty',
|
||||
inline_alts = Ct(((sp * V'inline_alt' * sp * '|') ^ 1) * sp * V'inline_alt'),
|
||||
inline_sequence = '{' * sp * (
|
||||
'!' * sp * Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('once'), 'sequence')) +
|
||||
'&' * sp * Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('cycle'), 'sequence')) +
|
||||
'~' * sp * Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('stopping'), 'sequence') * Cg(Cc(true), 'shuffle')) +
|
||||
Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('stopping'), 'sequence'))
|
||||
) * sp * '}',
|
||||
|
||||
-- Multiline conditions and switches
|
||||
|
||||
switch = Ct((V'switch_comparative' + V'switch_conditional') * item_type('switch')),
|
||||
|
||||
switch_comparative = Cg(V'switch_condition', 'expression') * ws * Cg(Ct((sp * V'switch_case') ^ 1), 'cases'),
|
||||
switch_conditional = Cg(Ct(V'switch_cases_headed' + V'switch_cases_only'), 'cases'),
|
||||
|
||||
switch_cases_headed = V'switch_if' * ((sp * V'switch_case') ^ 0),
|
||||
switch_cases_only = ws * ((sp * V'switch_case') ^ 1),
|
||||
|
||||
switch_if = Ct(Cg(V'switch_condition', 'condition') * ws * Cg(Ct(V'switch_items'), 'node')),
|
||||
switch_case = ('-' - divert_sign) * sp * V'switch_if',
|
||||
switch_condition = sentence_before(':' + nl) * sp * ':' * sp * comment ^ -1,
|
||||
switch_items = (V'restricted_item' - V'switch_case') ^ 1,
|
||||
|
||||
-- Multiline sequences
|
||||
|
||||
sequence = Ct((V'sequence_params' * sp * nl * sp * V'sequence_alts') * item_type('sequence')),
|
||||
|
||||
sequence_params = (
|
||||
V'sequence_shuffle_optional' * sp * V'sequence_type' +
|
||||
V'sequence_shuffle' * sp * V'sequence_type' +
|
||||
V'sequence_shuffle' * sp * V'sequence_type_optional'
|
||||
) * sp * ':' * sp * comment ^ -1,
|
||||
|
||||
sequence_shuffle_optional = V'sequence_shuffle' + Cg(Cc(false), 'shuffle'),
|
||||
sequence_shuffle = Cg(P'shuffle' / function() return true end, 'shuffle'),
|
||||
|
||||
sequence_type_optional = V'sequence_type' + Cg(Cc'cycle', 'sequence'),
|
||||
sequence_type = Cg(P'cycle' + 'stopping' + 'once', 'sequence'),
|
||||
|
||||
sequence_alts = Cg(Ct((sp * V'sequence_alt') ^ 1), 'alts'),
|
||||
sequence_alt = ('-' - divert_sign) * ws * Ct(V'sequence_items'),
|
||||
sequence_items = (V'restricted_item' - V'sequence_alt') ^ 1,
|
||||
|
||||
-- Restricted items inside multiline items
|
||||
|
||||
restricted_item = balanced_multiline_item(true) + V'restricted_singleline_item',
|
||||
restricted_singleline_item = sp * (V'global' + V'restricted_statement' + V'restricted_paragraph' - multiline_end) * ws,
|
||||
|
||||
restricted_statement = Ct(
|
||||
V'choice' * item_type('choice') +
|
||||
V'assignment' * item_type('assignment')
|
||||
) + comment + todo,
|
||||
|
||||
restricted_paragraph = Ct((
|
||||
Cg(V'text_complex', 'parts') * sp * V'tags_optional' +
|
||||
Cg(V'text_optional', 'parts') * sp * tags
|
||||
) * item_type('paragraph'))
|
||||
|
||||
})
|
||||
|
||||
--
|
||||
-- Result
|
||||
|
||||
local parsed_items = ink_grammar:match(content)
|
||||
local book = constructor.construct_book(parsed_items)
|
||||
return book
|
||||
end
|
||||
|
||||
--
|
||||
-- A book construction
|
||||
|
||||
function constructor.unescape(text)
|
||||
local result = text
|
||||
|
||||
result = result:gsub('\\|', '|')
|
||||
result = result:gsub('\\{', '{')
|
||||
result = result:gsub('\\}', '}')
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function constructor.construct_book(items)
|
||||
|
||||
local construction = {
|
||||
current_knot = '_',
|
||||
current_stitch = '_',
|
||||
variables_to_compute = { }
|
||||
}
|
||||
|
||||
construction.book = {
|
||||
inclusions = { },
|
||||
lists = { },
|
||||
constants = { },
|
||||
variables = { },
|
||||
params = { },
|
||||
tree = { _ = { _ = { } } }
|
||||
}
|
||||
|
||||
construction.book.version = {
|
||||
engine = enums.engine_version,
|
||||
tree = 1
|
||||
}
|
||||
|
||||
construction.nodes_chain = {
|
||||
construction.book.tree[construction.current_knot][construction.current_stitch]
|
||||
}
|
||||
|
||||
constructor.add_node(construction, items)
|
||||
constructor.clear(construction.book.tree)
|
||||
constructor.compute_variables(construction)
|
||||
|
||||
return construction.book
|
||||
end
|
||||
|
||||
function constructor:add_node(items, is_restricted)
|
||||
local is_restricted = is_restricted ~= nil and is_restricted or false
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
if is_restricted then
|
||||
-- Are not allowed inside multiline blocks by Ink rules:
|
||||
-- a) nesting levels
|
||||
-- b) choices without diverts
|
||||
|
||||
item.level = nil
|
||||
if item.type == 'choice' and item.divert == nil then
|
||||
item.type = nil
|
||||
end
|
||||
end
|
||||
|
||||
if item.type == 'inclusion' then
|
||||
-- filename
|
||||
constructor.add_inclusion(self, item.filename)
|
||||
elseif item.type == 'list' then
|
||||
-- name, value
|
||||
constructor.add_list(self, item.name, item.value)
|
||||
elseif item.type == 'constant' then
|
||||
-- name, value
|
||||
constructor.add_constant(self, item.name, item.value)
|
||||
elseif item.type == 'variable' then
|
||||
-- name, value
|
||||
constructor.add_variable(self, item.name, item.value)
|
||||
elseif item.type == 'func' then
|
||||
-- function
|
||||
constructor.add_function(self, item.func.name, item.func.params)
|
||||
elseif item.type == 'knot' then
|
||||
-- knot
|
||||
constructor.add_knot(self, item.knot)
|
||||
elseif item.type == 'stitch' then
|
||||
-- stitch
|
||||
constructor.add_stitch(self, item.stitch)
|
||||
elseif item.type == 'switch' then
|
||||
-- expression, cases
|
||||
constructor.add_switch(self, item.expression, item.cases)
|
||||
elseif item.type == 'sequence' then
|
||||
-- sequence, shuffle, alts
|
||||
constructor.add_sequence(self, item.sequence, item.shuffle, item.alts)
|
||||
elseif item.type == 'assignment' then
|
||||
-- level, name, value, temp
|
||||
constructor.add_assignment(self, item.level, item.name, item.value, item.temp)
|
||||
elseif item.type == 'return' then
|
||||
constructor.add_return(self, item.value)
|
||||
elseif item.type == 'paragraph' then
|
||||
-- level, label, parts, tags
|
||||
constructor.add_paragraph(self, item.level, item.label, item.parts, item.tags)
|
||||
elseif item.type == 'gather' then
|
||||
constructor.add_paragraph(self, item.level, "", nil, item.tags)
|
||||
elseif item.type == 'choice' then
|
||||
-- level, sticky, label, condition, text, divert, tags
|
||||
constructor.add_choice(self, item.level, item.sticky, item.label, item.condition, item.text, item.divert, item.tags)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function constructor:add_inclusion(filename)
|
||||
table.insert(self.book.inclusions, filename)
|
||||
end
|
||||
|
||||
function constructor:add_list(name, value)
|
||||
local items = lume.array(value:gmatch('[%w_%.]+'))
|
||||
self.book.lists[name] = items
|
||||
|
||||
local switched = lume.array(value:gmatch('%b()'))
|
||||
switched = lume.map(switched, function(item) return item:sub(2, #item - 1) end)
|
||||
self.book.variables[name] = { [name] = { } }
|
||||
lume.each(switched, function(item) self.book.variables[name][name][item] = true end)
|
||||
end
|
||||
|
||||
function constructor:add_constant(constant, value)
|
||||
local value = lume.deserialize(value)
|
||||
self.book.constants[constant] = value
|
||||
end
|
||||
|
||||
function constructor:add_variable(variable, value)
|
||||
self.variables_to_compute[variable] = value
|
||||
end
|
||||
|
||||
function constructor:add_function(fname, params)
|
||||
local node = { }
|
||||
self.book.tree[fname] = { ['_'] = node }
|
||||
self.book.params[fname] = params
|
||||
self.nodes_chain = { node }
|
||||
end
|
||||
|
||||
function constructor:add_knot(knot)
|
||||
self.current_knot = knot
|
||||
self.current_stitch = '_'
|
||||
|
||||
local node = { }
|
||||
self.book.tree[self.current_knot] = { [self.current_stitch] = node }
|
||||
self.nodes_chain = { node }
|
||||
end
|
||||
|
||||
function constructor:add_stitch(stitch)
|
||||
-- If a root stitch is empty we need to add a divert to the first stitch in the ink file.
|
||||
if self.current_stitch == '_' then
|
||||
local root_stitch_node = self.book.tree[self.current_knot]._
|
||||
if #root_stitch_node == 0 then
|
||||
local divertItem = { divert = { path = stitch } }
|
||||
table.insert(root_stitch_node, divertItem)
|
||||
end
|
||||
end
|
||||
|
||||
self.current_stitch = stitch
|
||||
|
||||
local node = { }
|
||||
self.book.tree[self.current_knot][self.current_stitch] = node
|
||||
self.nodes_chain = { node }
|
||||
end
|
||||
|
||||
function constructor:add_switch(expression, cases)
|
||||
if expression then
|
||||
-- Convert switch cases to comparing conditions with expression
|
||||
for _, case in ipairs(cases) do
|
||||
if case.condition ~= 'else' then
|
||||
case.condition = expression .. '==' .. case.condition
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local item = {
|
||||
condition = { },
|
||||
success = { }
|
||||
}
|
||||
|
||||
for _, case in ipairs(cases) do
|
||||
if case.condition == 'else' then
|
||||
local failure_node = { }
|
||||
table.insert(self.nodes_chain, failure_node)
|
||||
constructor.add_node(self, case.node, true)
|
||||
table.remove(self.nodes_chain)
|
||||
item.failure = failure_node
|
||||
else
|
||||
local success_node = { }
|
||||
table.insert(self.nodes_chain, success_node)
|
||||
constructor.add_node(self, case.node, true)
|
||||
table.remove(self.nodes_chain)
|
||||
table.insert(item.success, success_node)
|
||||
table.insert(item.condition, case.condition)
|
||||
end
|
||||
end
|
||||
|
||||
constructor.add_item(self, nil, item)
|
||||
end
|
||||
|
||||
function constructor:add_sequence(sequence, shuffle, alts)
|
||||
local item = {
|
||||
sequence = sequence,
|
||||
shuffle = shuffle and true or nil,
|
||||
alts = { }
|
||||
}
|
||||
|
||||
for _, alt in ipairs(alts) do
|
||||
local alt_node = { }
|
||||
table.insert(self.nodes_chain, alt_node)
|
||||
constructor.add_node(self, alt, true)
|
||||
table.remove(self.nodes_chain)
|
||||
table.insert(item.alts, alt_node)
|
||||
end
|
||||
|
||||
constructor.add_item(self, nil, item)
|
||||
end
|
||||
|
||||
function constructor:add_return(value)
|
||||
local item = {
|
||||
return_value = value
|
||||
}
|
||||
|
||||
constructor.add_item(self, nil, item)
|
||||
end
|
||||
|
||||
function constructor:add_assignment(level, name, value, temp)
|
||||
local item = {
|
||||
temp = temp or nil,
|
||||
var = name,
|
||||
value = value
|
||||
}
|
||||
|
||||
constructor.add_item(self, level, item)
|
||||
end
|
||||
|
||||
function constructor:add_paragraph(level, label, parts, tags)
|
||||
local items = constructor.convert_paragraph_parts_to_items(parts, true)
|
||||
items = items or { }
|
||||
|
||||
-- If the paragraph has a label or tags we need to place them as the first text item.
|
||||
if label ~= nil or tags ~= nil then
|
||||
local first_item
|
||||
|
||||
if #items > 0 and items[1].condition == nil then
|
||||
first_item = items[1]
|
||||
else
|
||||
first_item = { }
|
||||
table.insert(items, first_item)
|
||||
end
|
||||
|
||||
first_item.label = label
|
||||
first_item.tags = tags
|
||||
end
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
constructor.add_item(self, level, item)
|
||||
end
|
||||
end
|
||||
|
||||
function constructor.convert_paragraph_parts_to_items(parts, is_root)
|
||||
if parts == nil then return nil end
|
||||
|
||||
local is_root = is_root ~= nil and is_root or false
|
||||
local items = { }
|
||||
local item
|
||||
|
||||
for index, part in ipairs(parts) do
|
||||
|
||||
if part.condition then -- Inline condition part
|
||||
|
||||
item = {
|
||||
condition = part.condition.condition,
|
||||
success = constructor.convert_paragraph_parts_to_items(part.condition.success),
|
||||
failure = constructor.convert_paragraph_parts_to_items(part.condition.failure)
|
||||
}
|
||||
|
||||
table.insert(items, item)
|
||||
item = nil
|
||||
|
||||
elseif part.sequence then -- Inline sequence part
|
||||
|
||||
item = {
|
||||
sequence = part.sequence.sequence,
|
||||
shuffle = part.sequence.shuffle and true or nil,
|
||||
alts = { }
|
||||
}
|
||||
|
||||
for _, alt in ipairs(part.sequence.alts) do
|
||||
table.insert(item.alts, constructor.convert_paragraph_parts_to_items(alt))
|
||||
end
|
||||
|
||||
table.insert(items, item)
|
||||
item = nil
|
||||
|
||||
else -- Text, expression and divert may be
|
||||
|
||||
local is_divert_only = part.divert ~= nil and part.text == nil
|
||||
|
||||
if item == nil then
|
||||
item = { text = (is_root or is_divert_only) and '' or '<>' }
|
||||
end
|
||||
|
||||
if part.text then
|
||||
item.text = item.text .. part.text:gsub('%s+', ' ')
|
||||
item.text = constructor.unescape(item.text)
|
||||
elseif part.expression then
|
||||
item.text = item.text .. '#' .. part.expression .. '#'
|
||||
end
|
||||
|
||||
if part.divert or part.exit then
|
||||
item.exit = part.exit and true or nil
|
||||
item.divert = part.divert
|
||||
item.text = #item.text > 0 and (item.text .. '<>') or nil
|
||||
table.insert(items, item)
|
||||
item = nil
|
||||
else
|
||||
local next = parts[index + 1]
|
||||
local next_is_block = next and not (next.text or next.expression)
|
||||
|
||||
if not next or next_is_block then
|
||||
if not is_root or next_is_block then
|
||||
item.text = item.text .. '<>'
|
||||
end
|
||||
table.insert(items, item)
|
||||
item = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if is_root then
|
||||
-- Add a safe prefix and suffix for correct conditions gluing
|
||||
|
||||
local first_item = items[1]
|
||||
if first_item.text == nil and first_item.divert == nil and first_item.exit == nil then
|
||||
table.insert(items, 1, { text = '' } )
|
||||
end
|
||||
|
||||
local last_item = items[#items]
|
||||
if last_item.text == nil and last_item.divert == nil and last_item.exit == nil then
|
||||
table.insert(items, { text = '' } )
|
||||
elseif last_item.text ~= nil and last_item.divert == nil then
|
||||
last_item.text = last_item.text:gsub('(.-)%s*$', '%1')
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
function constructor:add_choice(level, sticky, label, condition, sentence, divert, tags)
|
||||
local item = {
|
||||
sticky = sticky or nil,
|
||||
condition = condition,
|
||||
label = label,
|
||||
divert = divert,
|
||||
tags = tags
|
||||
}
|
||||
|
||||
if sentence == nil then
|
||||
item.choice = 0
|
||||
else
|
||||
local prefix, divider, suffix = sentence:match('(.*)%[(.*)%](.*)')
|
||||
prefix = prefix or sentence
|
||||
divider = divider or ''
|
||||
suffix = suffix or ''
|
||||
|
||||
local text = (prefix .. suffix):gsub('%s+', ' ')
|
||||
local choice = (prefix .. divider):gsub('%s+', ' '):gsub('^%s*(.-)%s*$', '%1')
|
||||
|
||||
if divert and #text > 0 and text:match('%S+') then
|
||||
text = text .. '<>'
|
||||
else
|
||||
text = text:gsub('^%s*(.-)%s*$', '%1')
|
||||
end
|
||||
|
||||
item.text = constructor.unescape(text)
|
||||
item.choice = constructor.unescape(choice)
|
||||
end
|
||||
|
||||
constructor.add_item(self, level, item)
|
||||
|
||||
if divert == nil then
|
||||
item.node = { }
|
||||
table.insert(self.nodes_chain, item.node)
|
||||
end
|
||||
end
|
||||
|
||||
function constructor:add_item(level, item)
|
||||
local level = (level ~= nil and level > 0) and level or #self.nodes_chain
|
||||
while #self.nodes_chain > level do
|
||||
table.remove(self.nodes_chain)
|
||||
end
|
||||
|
||||
local node = self.nodes_chain[#self.nodes_chain]
|
||||
table.insert(node, item)
|
||||
end
|
||||
|
||||
function constructor:compute_variable(variable, value)
|
||||
local constant = self.book.constants[value]
|
||||
if constant then
|
||||
self.book.variables[variable] = constant
|
||||
return
|
||||
end
|
||||
|
||||
local list_expression = value:match('%(([%s%w%.,_]*)%)')
|
||||
local item_expressions = list_expression and lume.array(list_expression:gmatch('[%w_%.]+')) or { value }
|
||||
local list_variable = list_expression and { } or nil
|
||||
|
||||
for _, item_expression in ipairs(item_expressions) do
|
||||
local list_part, item_part = item_expression:match('([%w_]+)%.([%w_]+)')
|
||||
item_part = item_part or item_expression
|
||||
|
||||
for list_name, list_items in pairs(self.book.lists) do
|
||||
local list_is_valid = list_part == nil or list_part == list_name
|
||||
local item_is_found = lume.find(list_items, item_part)
|
||||
|
||||
if list_is_valid and item_is_found then
|
||||
list_variable = list_variable or { }
|
||||
list_variable[list_name] = list_variable[list_name] or { }
|
||||
list_variable[list_name][item_part] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if list_variable then
|
||||
self.book.variables[variable] = list_variable
|
||||
else
|
||||
self.book.variables[variable] = lume.deserialize(value)
|
||||
end
|
||||
end
|
||||
|
||||
function constructor:compute_variables()
|
||||
for variable, value in pairs(self.variables_to_compute) do
|
||||
constructor.compute_variable(self, variable, value)
|
||||
end
|
||||
end
|
||||
|
||||
function constructor.clear(tree)
|
||||
for knot, node in pairs(tree) do
|
||||
for stitch, node in pairs(node) do
|
||||
constructor.clear_node(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function constructor.clear_node(node)
|
||||
for index, item in ipairs(node) do
|
||||
|
||||
-- Simplify text only items
|
||||
if item.text ~= nil and lume.count(item) == 1 then
|
||||
node[index] = item.text
|
||||
end
|
||||
|
||||
if item.node ~= nil then
|
||||
-- Clear choice nodes
|
||||
if #item.node == 0 then
|
||||
item.node = nil
|
||||
else
|
||||
constructor.clear_node(item.node)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if item.success ~= nil then
|
||||
-- Simplify single condition
|
||||
if type(item.condition) == 'table' and #item.condition == 1 then
|
||||
item.condition = item.condition[1]
|
||||
end
|
||||
|
||||
-- Clear success nodes
|
||||
if item.success[1] ~= nil and item.success[1][1] ~= nil then
|
||||
for index, success_node in ipairs(item.success) do
|
||||
constructor.clear_node(success_node)
|
||||
if #success_node == 1 and type(success_node[1]) == 'string' then
|
||||
item.success[index] = success_node[1]
|
||||
end
|
||||
end
|
||||
|
||||
if #item.success == 1 then
|
||||
item.success = item.success[1]
|
||||
end
|
||||
else
|
||||
constructor.clear_node(item.success)
|
||||
if #item.success == 1 and type(item.success[1]) == 'string' then
|
||||
item.success = item.success[1]
|
||||
end
|
||||
end
|
||||
|
||||
-- Clear failure nodes
|
||||
if item.failure ~= nil then
|
||||
constructor.clear_node(item.failure)
|
||||
if #item.failure == 1 and type(item.failure[1]) == 'string' then
|
||||
item.failure = item.failure[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if item.alts ~= nil then
|
||||
for index, alt_node in ipairs(item.alts) do
|
||||
constructor.clear_node(alt_node)
|
||||
if #alt_node == 1 and type(alt_node[1]) == 'string' then
|
||||
item.alts[index] = alt_node[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return parser
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
- I looked at Monsieur Fogg
|
||||
* ... and I could contain myself no longer.
|
||||
'What is the purpose of our journey, Monsieur?'
|
||||
'A wager,' he replied.
|
||||
* * 'A wager!'[] I returned.
|
||||
He nodded.
|
||||
* * * 'But surely that is foolishness!'
|
||||
* * * 'A most serious matter then!'
|
||||
- - - He nodded again.
|
||||
* * * 'But can we win?'
|
||||
'That is what we will endeavour to find out,' he answered.
|
||||
* * * 'A modest wager, I trust?'
|
||||
'Twenty thousand pounds,' he replied, quite flatly.
|
||||
* * * I asked nothing further of him then[.], and after a final, polite cough, he offered nothing more to me. <>
|
||||
* * 'Ah[.'],' I replied, uncertain what I thought.
|
||||
- - After that, <>
|
||||
* ... but I said nothing[] and <>
|
||||
- we passed the day in silence.
|
||||
- -> END
|
||||
@@ -0,0 +1 @@
|
||||
return {inclusions={},constants={},version={tree=1,engine=1},tree={_={_={"I looked at Monsieur Fogg",{text="... and I could contain myself no longer.",choice="... and I could contain myself no longer.",node={"'What is the purpose of our journey, Monsieur?'","'A wager,' he replied.",{text="'A wager!' I returned.",choice="'A wager!'",node={"He nodded.",{text="'But surely that is foolishness!'",choice="'But surely that is foolishness!'"},{text="'A most serious matter then!'",choice="'A most serious matter then!'"},"He nodded again.",{text="'But can we win?'",choice="'But can we win?'",node={"'That is what we will endeavour to find out,' he answered."}},{text="'A modest wager, I trust?'",choice="'A modest wager, I trust?'",node={"'Twenty thousand pounds,' he replied, quite flatly."}},{text="I asked nothing further of him then, and after a final, polite cough, he offered nothing more to me. <>",choice="I asked nothing further of him then."}}},{text="'Ah,' I replied, uncertain what I thought.",choice="'Ah.'"},"After that, <>"}},{text="... but I said nothing and <>",choice="... but I said nothing"},"we passed the day in silence.",{divert="END"}}}},lists={},variables={}}
|
||||
@@ -0,0 +1,35 @@
|
||||
- The party was hot, girls were sexy the wine, beer and whisky were in <>
|
||||
enormous numbers. It was anniversary since you set sail with your friends.
|
||||
The sea was calm and everything promised you another great night. <>
|
||||
The whole year with your friends on your ship over vast seas of the world in decay was almost over. <>
|
||||
It was so good year full of adventure, romance, indecency and luxury.
|
||||
You did not know what future had for you and you never thought much about it. However <>
|
||||
you had zero insights on what to follow even if you cared enough about it. You thought you could not <>
|
||||
wish for brigher future far away from troubles of this world.
|
||||
One day (a few days ago) your trusted friends decided that you have too much, they have too much of you and you owe them a lot.<>
|
||||
They waited for opportunity to get rid of you. You totally missed the point when people started conspiring against you<>
|
||||
and the following events came at total surprize.
|
||||
They found you drunk in your room and moved to the deck and used ropes to <>
|
||||
restrain you and attach a bucket of stones or something else heavy to your body. <>
|
||||
They left you lying on the deck while were checking your room, looking for papers and something of value.
|
||||
After a few hours passed two strong people pulled you to the side and <>
|
||||
dropped you into the sea. You did not see their faces. Last thing you heard before you hit the water was happy laugher...
|
||||
Suddenly next you found yourself under water out of breath in agony. You did not know nor care about time and place, you just cared for <>
|
||||
last air leaving your body. All you wished for was quick end.
|
||||
Suddenly the suffering stopped and your mind cleared. Some impactful presence <>
|
||||
subdued everything in your mind leaving you with question. The only thing you could see was a strange altar for some ancient deity nearby.
|
||||
You had very bad feeling about this.
|
||||
'You have just a few seconds, so be quick' - the voice sounded directly somewhere inside your head.
|
||||
'WILL you serve me?' Something told me my life depends on the answer.
|
||||
* Sure
|
||||
Of course you will.
|
||||
* Do I have a choice?
|
||||
You have or you have not. That depends on what you expect.
|
||||
* I will
|
||||
I know.
|
||||
- An abrupt silence.
|
||||
Then I feel my restraints removed and I started rising to the surface. 'We'll talk again soon enough' - the voice in my head told me.
|
||||
'Your service will be rewarded. Go enjoy your new life.'
|
||||
* [Ascend]
|
||||
~ mc_is_free()
|
||||
- ->END
|
||||
@@ -22,6 +22,7 @@ FileSystem=skybox
|
||||
FileSystem=resources/buildings
|
||||
FileSystem=resources/vehicles
|
||||
FileSystem=resources/debug
|
||||
FileSystem=resources/fonts
|
||||
# PBR media must come before the scripts that reference it
|
||||
#FileSystem=./Media/PBR
|
||||
#FileSystem=./Media/PBR/filament
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include <iostream>
|
||||
#include <OgreMeshManager.h>
|
||||
#include "Components.h"
|
||||
#include "GameData.h"
|
||||
#include "BoatModule.h"
|
||||
|
||||
namespace ECS
|
||||
{
|
||||
|
||||
BoatModule::BoatModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.module<BoatModule>();
|
||||
ecs.component<BoatBase>();
|
||||
ecs.component<BoatBody>();
|
||||
ecs.component<BoatType>().on_set([](flecs::entity e, BoatType &type) {
|
||||
int i;
|
||||
if (e.has<BoatBody>() || e.has<BoatBase>())
|
||||
return;
|
||||
BoatBase &boat = e.ensure<BoatBase>();
|
||||
boat.mEnt = ECS::get<EngineData>().mScnMgr->createEntity(
|
||||
type.resourceName);
|
||||
boat.mNode = ECS::get<EngineData>()
|
||||
.mScnMgr->getRootSceneNode()
|
||||
->createChildSceneNode(type.position,
|
||||
type.orientation);
|
||||
boat.mNode->attachObject(boat.mEnt);
|
||||
|
||||
BoatBody &body = e.ensure<BoatBody>();
|
||||
body.body = ECS::get<EngineData>().mWorld->addRigidBody(
|
||||
0, boat.mEnt, Ogre::Bullet::CT_HULL, nullptr, 2,
|
||||
0x7fffffff);
|
||||
e.modified<BoatBody>();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef BOAT_MODULE_H_
|
||||
#define BOAT_MODULE_H_
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
namespace Ogre
|
||||
{
|
||||
class Entity;
|
||||
class SceneNode;
|
||||
}
|
||||
namespace ECS
|
||||
{
|
||||
struct BoatType {
|
||||
Ogre::String resourceName;
|
||||
Ogre::Vector3 position;
|
||||
Ogre::Quaternion orientation;
|
||||
};
|
||||
struct BoatBase {
|
||||
Ogre::Entity *mEnt;
|
||||
Ogre::SceneNode *mNode;
|
||||
};
|
||||
struct BoatBody {
|
||||
btRigidBody *body;
|
||||
};
|
||||
struct BoatModule {
|
||||
BoatModule(flecs::world &ecs);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
project(gamedata)
|
||||
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG)
|
||||
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp GUIModule.cpp)
|
||||
target_link_libraries(GameData PUBLIC OgreMain OgreBites OgreBullet OgrePaging OgreTerrain OgreOverlay flecs::flecs_static)
|
||||
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp GUIModule.cpp LuaData.cpp WorldMapModule.cpp
|
||||
BoatModule.cpp EventTriggerModule.cpp)
|
||||
target_link_libraries(GameData PUBLIC OgreMain OgreBites OgreBullet OgrePaging OgreTerrain OgreOverlay flecs::flecs_static lua)
|
||||
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
+388
-195
@@ -4,28 +4,22 @@
|
||||
#include "GameData.h"
|
||||
#include "CharacterModule.h"
|
||||
#include "WaterModule.h"
|
||||
#include "TerrainModule.h"
|
||||
#include "Components.h"
|
||||
namespace ECS
|
||||
{
|
||||
CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.module<CharacterModule>();
|
||||
player = ecs.entity("player");
|
||||
player.set<AnimationControl>({ AnimationControl::ANIM_NONE,
|
||||
AnimationControl::ANIM_NONE, false,
|
||||
false });
|
||||
player.set<CharacterBase>(
|
||||
{ "normal-male.glb", 0.0f, nullptr, nullptr, nullptr });
|
||||
player.set<CharacterBody>(
|
||||
{ nullptr, nullptr, nullptr, { 0, 0, 0 }, false, false });
|
||||
player.add<Character>();
|
||||
player.add<Player>();
|
||||
ecs.component<Character>();
|
||||
ecs.component<Player>();
|
||||
ecs.component<CharacterBase>();
|
||||
ecs.component<CharacterVelocity>();
|
||||
ecs.component<CharacterBody>();
|
||||
ecs.system<EngineData, CharacterBase>("UpdateTimer")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](EngineData &eng, CharacterBase &ch) {
|
||||
ch.mTimer += eng.delta;
|
||||
if (eng.startupDelay >= 0.0f)
|
||||
eng.startupDelay -= eng.delta;
|
||||
});
|
||||
ecs.system<Input, Camera>("HandleInput")
|
||||
.kind(flecs::OnUpdate)
|
||||
@@ -38,6 +32,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
uint32_t active = input.control;
|
||||
float zaxis = input.motion.z;
|
||||
zaxis *= 0.9f;
|
||||
if (!camera.mCameraPivot || !camera.mCameraGoal)
|
||||
return;
|
||||
if (pressed & 1)
|
||||
std::cout << "W pressed\n";
|
||||
if (released & 1)
|
||||
@@ -92,7 +88,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
Ogre::ANIMBLEND_CUMULATIVE);
|
||||
Ogre::String
|
||||
animNames[AnimationControl::NUM_ANIMS] = {
|
||||
"idle", "walking", "running"
|
||||
"idle", "walking", "running",
|
||||
"treading_water", "swimming"
|
||||
};
|
||||
for (i = 0; i < AnimationControl::NUM_ANIMS;
|
||||
i++) {
|
||||
@@ -160,11 +157,11 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
if (anim.currentAnim != anim.nextAnim)
|
||||
setAnimation(anim);
|
||||
});
|
||||
ecs.system<EngineData, CharacterBase, AnimationControl>(
|
||||
"HandleAnimations1")
|
||||
ecs.system<const EngineData, const Input, CharacterBase,
|
||||
AnimationControl>("HandleAnimations1")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](EngineData &eng, CharacterBase &ch,
|
||||
AnimationControl &anim) {
|
||||
.each([this](const EngineData &eng, const Input &input,
|
||||
CharacterBase &ch, AnimationControl &anim) {
|
||||
float delta = eng.delta;
|
||||
Ogre::Real animSpeed = 1;
|
||||
if (anim.currentAnim != AnimationControl::ANIM_NONE) {
|
||||
@@ -172,6 +169,11 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
AnimationControl::ANIM_WALK)
|
||||
anim.mAnims[anim.currentAnim]->addTime(
|
||||
delta * 1.0f);
|
||||
else if (anim.currentAnim ==
|
||||
AnimationControl::ANIM_SWIMMING &&
|
||||
input.fast)
|
||||
anim.mAnims[anim.currentAnim]->addTime(
|
||||
delta * 20.0f);
|
||||
else
|
||||
anim.mAnims[anim.currentAnim]->addTime(
|
||||
delta * animSpeed);
|
||||
@@ -181,26 +183,38 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
return;
|
||||
ch.mBoneMotion = ch.mRootBone->getPosition();
|
||||
});
|
||||
ecs.system<const EngineData, CharacterBase, CharacterBody,
|
||||
AnimationControl>("HandleRootMotion")
|
||||
ecs.system<CharacterBase>()
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](flecs::entity e, const EngineData &eng,
|
||||
CharacterBase &ch, CharacterBody &body,
|
||||
AnimationControl &anim) {
|
||||
float delta = e.world().delta_time();
|
||||
if (!ch.mBodyNode)
|
||||
return;
|
||||
Ogre::Quaternion rot = ch.mBodyNode->getOrientation();
|
||||
.with<TerrainReady>()
|
||||
.with<WaterReady>()
|
||||
.with<InWater>()
|
||||
.each([this](flecs::entity e, CharacterBase &ch) {
|
||||
float full_subm = 2.0f;
|
||||
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
|
||||
Ogre::Vector3 boneMotion = ch.mBoneMotion;
|
||||
Ogre::Vector3 velocity = rot * boneMotion / delta;
|
||||
OgreAssert(delta > 0.0f, "Zero delta");
|
||||
int maxPen = 0;
|
||||
Ogre::Vector3 colNormal;
|
||||
bool is_on_floor = false;
|
||||
bool penetration = false;
|
||||
float current_subm = -Ogre::Math::Clamp(
|
||||
pos.y + Ogre::Math::Sin(ch.mTimer * 0.13f +
|
||||
130.0f) *
|
||||
0.07f,
|
||||
-full_subm, 0.0f);
|
||||
if (current_subm > 0.9f)
|
||||
ch.is_submerged = true;
|
||||
else if (current_subm < 0.8f)
|
||||
ch.is_submerged = false;
|
||||
});
|
||||
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
|
||||
"HandleGravityBouyanceWater")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<TerrainReady>()
|
||||
.with<WaterReady>()
|
||||
.with<InWater>()
|
||||
.each([this](flecs::entity e, const EngineData &eng,
|
||||
const CharacterBase &ch, CharacterVelocity &gr) {
|
||||
Ogre::Vector3 gravity(0, -9.8f, 0);
|
||||
if (e.has<InWater>()) {
|
||||
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
|
||||
Ogre::Vector3 v(0, 0, 0);
|
||||
if (e.has<CharacterGravity>())
|
||||
v += gravity;
|
||||
if (e.has<CharacterBuoyancy>()) {
|
||||
float volume = 2.0f * 0.5f * 0.5f;
|
||||
float density = 900.0f;
|
||||
float full_subm = 2.0f;
|
||||
@@ -216,63 +230,183 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
Ogre::Vector3 b = -gravity * density * volume *
|
||||
multiplier * current_subm /
|
||||
full_subm / mass;
|
||||
body.gvelocity += (gravity + b) * delta;
|
||||
body.gvelocity.y = Ogre::Math::Clamp(
|
||||
body.gvelocity.y, -2.5f, 2.5f);
|
||||
} else
|
||||
body.gvelocity += gravity * delta;
|
||||
if (eng.startupDelay < 0.0f) {
|
||||
body.gvelocity *= 0.99;
|
||||
velocity += body.gvelocity;
|
||||
Ogre::Vector3 rotMotion = velocity * delta;
|
||||
btVector3 currentPosition =
|
||||
body.mGhostObject->getWorldTransform()
|
||||
.getOrigin();
|
||||
is_on_floor = body.mController->isOnFloor();
|
||||
penetration = body.mController->isPenetrating();
|
||||
if (is_on_floor)
|
||||
body.gvelocity =
|
||||
Ogre::Vector3(0.0f, 0.0f, 0.0f);
|
||||
|
||||
btTransform from(
|
||||
Ogre::Bullet::convert(
|
||||
ch.mBodyNode->getOrientation()),
|
||||
Ogre::Bullet::convert(
|
||||
ch.mBodyNode->getPosition()));
|
||||
ch.mBodyNode->setPosition(
|
||||
ch.mBodyNode->getPosition() +
|
||||
rotMotion);
|
||||
ch.mBoneMotion = Ogre::Vector3(0, 0, 0);
|
||||
v += b;
|
||||
}
|
||||
gr.gvelocity += v * eng.delta;
|
||||
gr.gvelocity.y =
|
||||
Ogre::Math::Clamp(gr.gvelocity.y, -2.5f, 1.5f);
|
||||
gr.gvelocity *= (1.0 - eng.delta);
|
||||
gr.velocity.y *= (1.0 - eng.delta);
|
||||
});
|
||||
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
|
||||
"HandleGravityNoWater")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<TerrainReady>()
|
||||
.with<WaterReady>()
|
||||
.without<InWater>()
|
||||
.with<CharacterGravity>()
|
||||
.each([this](flecs::entity e, const EngineData &eng,
|
||||
const CharacterBase &ch, CharacterVelocity &gr) {
|
||||
Ogre::Vector3 gravity(0, -9.8f, 0);
|
||||
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
|
||||
gr.gvelocity += gravity * eng.delta;
|
||||
if (pos.y < -1.2) {
|
||||
gr.gvelocity.y = 0.0f;
|
||||
}
|
||||
gr.gvelocity *= (1.0 - eng.delta);
|
||||
gr.velocity.y *= (1.0 - eng.delta);
|
||||
});
|
||||
ecs.system<const EngineData, const AnimationControl,
|
||||
const CharacterBase, CharacterVelocity>("HandleSwimming")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<TerrainReady>()
|
||||
.with<WaterReady>()
|
||||
.with<InWater>()
|
||||
.with<CharacterBuoyancy>()
|
||||
.each([this](flecs::entity e, const EngineData &eng,
|
||||
const AnimationControl &anim,
|
||||
const CharacterBase &ch, CharacterVelocity &gr) {
|
||||
if (anim.currentAnim ==
|
||||
AnimationControl::ANIM_SWIMMING) {
|
||||
float h = Ogre::Math::Clamp(
|
||||
0.0f - ch.mBodyNode->getPosition().y,
|
||||
0.0f, 2000.0f);
|
||||
if (h > 0.05 && h < 2.0f)
|
||||
gr.gvelocity.y += 0.1f * (h + 1.0f) *
|
||||
h * eng.delta;
|
||||
}
|
||||
});
|
||||
ecs.system<const Input, AnimationControl>("HandlePlayerAnimations")
|
||||
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
|
||||
"HandleRootMotionVelocity")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<TerrainReady>()
|
||||
.with<WaterReady>()
|
||||
.each([this](flecs::entity e, const EngineData &eng,
|
||||
const CharacterBase &ch, CharacterVelocity &v) {
|
||||
if (eng.delta < 0.0000001f)
|
||||
return;
|
||||
if (!ch.mBodyNode)
|
||||
return;
|
||||
Ogre::Quaternion rot = ch.mBodyNode->getOrientation();
|
||||
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
|
||||
Ogre::Vector3 boneMotion = ch.mBoneMotion;
|
||||
v.velocity = rot * boneMotion / eng.delta;
|
||||
if (eng.startupDelay <= 0.0f)
|
||||
v.velocity += v.gvelocity;
|
||||
v.velocity.y = Ogre::Math::Clamp(v.velocity.y, -10.5f,
|
||||
1000000.0f);
|
||||
});
|
||||
ecs.system<const EngineData, CharacterBase, CharacterBody,
|
||||
AnimationControl, CharacterVelocity>("HandleRootMotion")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](flecs::entity e, const EngineData &eng,
|
||||
CharacterBase &ch, CharacterBody &body,
|
||||
AnimationControl &anim, CharacterVelocity &v) {
|
||||
if (!ch.mBodyNode)
|
||||
return;
|
||||
if (eng.delta < 0.0000001f)
|
||||
return;
|
||||
OgreAssert(eng.delta > 0.0f, "Zero delta");
|
||||
int maxPen = 0;
|
||||
Ogre::Vector3 colNormal;
|
||||
bool is_on_floor = false;
|
||||
bool penetration = false;
|
||||
if (eng.startupDelay < 0.0f) {
|
||||
if (body.mController) {
|
||||
Ogre::Vector3 rotMotion =
|
||||
v.velocity * eng.delta;
|
||||
btVector3 currentPosition =
|
||||
body.mGhostObject
|
||||
->getWorldTransform()
|
||||
.getOrigin();
|
||||
is_on_floor =
|
||||
body.mController->isOnFloor();
|
||||
penetration = body.mController
|
||||
->isPenetrating();
|
||||
if (is_on_floor)
|
||||
v.gvelocity = Ogre::Vector3(
|
||||
0.0f, 0.0f, 0.0f);
|
||||
|
||||
btTransform from(
|
||||
Ogre::Bullet::convert(
|
||||
ch.mBodyNode
|
||||
->getOrientation()),
|
||||
Ogre::Bullet::convert(
|
||||
ch.mBodyNode
|
||||
->getPosition()));
|
||||
ch.mBodyNode->setPosition(
|
||||
ch.mBodyNode->getPosition() +
|
||||
rotMotion);
|
||||
ch.mBoneMotion = Ogre::Vector3(0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
ecs.system<const Input, const CharacterBase, AnimationControl>(
|
||||
"HandlePlayerAnimations")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Character>()
|
||||
.with<Player>()
|
||||
.each([](const Input &input, AnimationControl &anim) {
|
||||
.each([](flecs::entity e, const Input &input,
|
||||
const CharacterBase &ch, AnimationControl &anim) {
|
||||
if (!anim.configured)
|
||||
return;
|
||||
bool controls_idle = input.motion.zeroLength();
|
||||
bool anim_is_idle = anim.currentAnim ==
|
||||
AnimationControl::ANIM_IDLE;
|
||||
bool anim_is_idle =
|
||||
anim.currentAnim ==
|
||||
AnimationControl::ANIM_IDLE ||
|
||||
anim.currentAnim ==
|
||||
AnimationControl::ANIM_TREADING_WATER;
|
||||
bool anim_is_walking = anim.currentAnim ==
|
||||
AnimationControl::ANIM_WALK;
|
||||
bool anim_is_running = anim.currentAnim ==
|
||||
AnimationControl::ANIM_RUN;
|
||||
bool anim_is_swimming = anim.currentAnim ==
|
||||
AnimationControl::ANIM_SWIMMING;
|
||||
bool anim_is_motion = anim_is_walking ||
|
||||
anim_is_running;
|
||||
anim_is_running ||
|
||||
anim_is_swimming;
|
||||
if (controls_idle) {
|
||||
if (anim.currentAnim ==
|
||||
AnimationControl::ANIM_IDLE &&
|
||||
ch.is_submerged)
|
||||
anim.nextAnim = AnimationControl::
|
||||
ANIM_TREADING_WATER;
|
||||
else if (anim.currentAnim ==
|
||||
AnimationControl::
|
||||
ANIM_TREADING_WATER &&
|
||||
!ch.is_submerged)
|
||||
anim.nextAnim =
|
||||
AnimationControl::ANIM_IDLE;
|
||||
}
|
||||
if (!controls_idle && anim_is_idle) {
|
||||
anim.reset = true;
|
||||
if (input.fast)
|
||||
anim.nextAnim =
|
||||
AnimationControl::ANIM_RUN;
|
||||
else
|
||||
anim.nextAnim =
|
||||
AnimationControl::ANIM_WALK;
|
||||
if (ch.is_submerged) {
|
||||
if (input.fast)
|
||||
anim.nextAnim =
|
||||
AnimationControl::
|
||||
ANIM_SWIMMING;
|
||||
else
|
||||
anim.nextAnim =
|
||||
AnimationControl::
|
||||
ANIM_SWIMMING;
|
||||
} else {
|
||||
if (input.fast)
|
||||
anim.nextAnim =
|
||||
AnimationControl::ANIM_RUN;
|
||||
else
|
||||
anim.nextAnim =
|
||||
AnimationControl::
|
||||
ANIM_WALK;
|
||||
}
|
||||
} else
|
||||
anim.reset = false;
|
||||
if (controls_idle && anim_is_motion)
|
||||
anim.nextAnim = AnimationControl::ANIM_IDLE;
|
||||
if (ch.is_submerged)
|
||||
anim.nextAnim = AnimationControl::
|
||||
ANIM_TREADING_WATER;
|
||||
else
|
||||
anim.nextAnim =
|
||||
AnimationControl::ANIM_IDLE;
|
||||
else if (!controls_idle && anim_is_motion) {
|
||||
if (input.fast && anim_is_walking)
|
||||
anim.nextAnim =
|
||||
@@ -280,123 +414,26 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
else if (!input.fast && anim_is_running)
|
||||
anim.nextAnim =
|
||||
AnimationControl::ANIM_WALK;
|
||||
}
|
||||
});
|
||||
ecs.system<const EngineData, CharacterBase, CharacterBody>(
|
||||
"UpdateCharacterBase")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Character>()
|
||||
.each([](const EngineData &eng, CharacterBase &ch,
|
||||
CharacterBody &body) {
|
||||
if (!ch.mBodyNode) {
|
||||
ch.mBodyEnt = eng.mScnMgr->createEntity(
|
||||
"normal-male.glb");
|
||||
ch.mBodyNode = eng.mScnMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
ch.mBodyNode->attachObject(ch.mBodyEnt);
|
||||
ch.mSkeleton = ch.mBodyEnt->getSkeleton();
|
||||
body.mGhostObject =
|
||||
new btPairCachingGhostObject();
|
||||
body.mCollisionShape = new btCompoundShape;
|
||||
body.mGhostObject->setCollisionShape(
|
||||
body.mCollisionShape);
|
||||
{
|
||||
btVector3 inertia(0, 0, 0);
|
||||
// mCollisionShape = new btCompoundShape();
|
||||
btScalar height = 1.0f;
|
||||
btScalar radius = 0.3f;
|
||||
btCapsuleShape *shape =
|
||||
new btCapsuleShape(
|
||||
radius,
|
||||
2 * height -
|
||||
2 * radius);
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
transform.setOrigin(btVector3(0, 1, 0));
|
||||
static_cast<btCompoundShape *>(
|
||||
body.mCollisionShape)
|
||||
->addChildShape(transform,
|
||||
shape);
|
||||
btScalar masses[1] = { 0 };
|
||||
btTransform principal;
|
||||
static_cast<btCompoundShape *>(
|
||||
body.mCollisionShape)
|
||||
->calculatePrincipalAxisTransform(
|
||||
masses, principal,
|
||||
inertia);
|
||||
if ((anim_is_walking || anim_is_running) &&
|
||||
ch.is_submerged) {
|
||||
if (input.fast)
|
||||
anim.nextAnim =
|
||||
AnimationControl::
|
||||
ANIM_SWIMMING;
|
||||
else
|
||||
anim.nextAnim =
|
||||
AnimationControl::
|
||||
ANIM_SWIMMING;
|
||||
} else if ((anim_is_swimming) &&
|
||||
!ch.is_submerged) {
|
||||
if (input.fast)
|
||||
anim.nextAnim =
|
||||
AnimationControl::ANIM_RUN;
|
||||
else
|
||||
anim.nextAnim =
|
||||
AnimationControl::
|
||||
ANIM_WALK;
|
||||
}
|
||||
body.mGhostObject->setCollisionFlags(
|
||||
btCollisionObject::CF_KINEMATIC_OBJECT /*|
|
||||
btCollisionObject::CF_NO_CONTACT_RESPONSE */);
|
||||
body.mGhostObject->setActivationState(
|
||||
DISABLE_DEACTIVATION);
|
||||
eng.mWorld->attachCollisionObject(
|
||||
body.mGhostObject, ch.mBodyEnt, 1,
|
||||
0x7FFFFFFF);
|
||||
body.mController =
|
||||
new Ogre::Bullet::KinematicMotionSimple(
|
||||
body.mGhostObject,
|
||||
ch.mBodyNode);
|
||||
OgreAssert(body.mGhostObject,
|
||||
"Need GhostObject");
|
||||
OgreAssert(body.mController, "Need controller");
|
||||
eng.mWorld->getBtWorld()->addAction(
|
||||
body.mController);
|
||||
|
||||
OgreAssert(body.mCollisionShape,
|
||||
"No collision shape");
|
||||
OgreAssert(ch.mSkeleton->hasBone("Root"),
|
||||
"No root bone");
|
||||
ch.mRootBone = ch.mSkeleton->getBone("Root");
|
||||
OgreAssert(ch.mRootBone, "No root bone");
|
||||
}
|
||||
});
|
||||
#define CAM_HEIGHT 1.6f // height of camera above character's center of mass
|
||||
ecs.system<const EngineData, Camera, const CharacterBase>(
|
||||
"UpdateCamera")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Player>()
|
||||
.each([](const EngineData &eng, Camera &camera,
|
||||
const CharacterBase &ch) {
|
||||
float delta = eng.delta;
|
||||
if (!camera.configured) {
|
||||
// create a pivot at roughly the character's shoulder
|
||||
camera.mCameraPivot =
|
||||
eng.mScnMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
camera.mCameraGoal =
|
||||
camera.mCameraPivot
|
||||
->createChildSceneNode(
|
||||
Ogre::Vector3(0, 2, 3));
|
||||
camera.mCameraNode->setPosition(
|
||||
camera.mCameraPivot->getPosition() +
|
||||
camera.mCameraGoal->getPosition());
|
||||
camera.mCameraPivot->setFixedYawAxis(true);
|
||||
camera.mCameraGoal->setFixedYawAxis(true);
|
||||
camera.mCameraNode->setFixedYawAxis(true);
|
||||
// our model is quite small, so reduce the clipping planes
|
||||
camera.mCamera->setNearClipDistance(0.1f);
|
||||
camera.mCamera->setFarClipDistance(700);
|
||||
|
||||
camera.mPivotPitch = 0;
|
||||
camera.configured = true;
|
||||
} else {
|
||||
// place the camera pivot roughly at the character's shoulder
|
||||
camera.mCameraPivot->setPosition(
|
||||
ch.mBodyNode->getPosition() +
|
||||
Ogre::Vector3::UNIT_Y * CAM_HEIGHT);
|
||||
// move the camera smoothly to the goal
|
||||
Ogre::Vector3 goalOffset =
|
||||
camera.mCameraGoal
|
||||
->_getDerivedPosition() -
|
||||
camera.mCameraNode->getPosition();
|
||||
camera.mCameraNode->translate(goalOffset *
|
||||
delta * 9.0f);
|
||||
// always look at the pivot
|
||||
camera.mCameraNode->lookAt(
|
||||
camera.mCameraPivot
|
||||
->_getDerivedPosition(),
|
||||
Ogre::Node::TS_PARENT);
|
||||
}
|
||||
});
|
||||
#define TURN_SPEED 500.0f // character turning in degrees per second
|
||||
@@ -450,6 +487,161 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
ch.mBodyNode->yaw(Ogre::Degree(yawToGoal));
|
||||
}
|
||||
});
|
||||
ecs.system<const EngineData, CharacterLocation, CharacterBase,
|
||||
CharacterBody>("UpdateCharacterBase")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Character>()
|
||||
.with<CharacterBody>()
|
||||
.with<CharacterBase>()
|
||||
.each([](const EngineData &eng, CharacterLocation &loc,
|
||||
CharacterBase &ch, CharacterBody &body) {
|
||||
if (!ch.mBodyNode) {
|
||||
} else {
|
||||
loc.orientation =
|
||||
ch.mBodyNode->_getDerivedOrientation();
|
||||
loc.position =
|
||||
ch.mBodyNode->_getDerivedPosition();
|
||||
}
|
||||
});
|
||||
ecs.system<const EngineData, const CharacterLocation,
|
||||
const CharacterConf>("SetupCharacter")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Character>()
|
||||
.without<CharacterBase>()
|
||||
.without<CharacterBody>()
|
||||
.each([](flecs::entity e, const EngineData &eng,
|
||||
const CharacterLocation &loc,
|
||||
const CharacterConf &conf) {
|
||||
CharacterBase &ch = e.ensure<CharacterBase>();
|
||||
CharacterBody &body = e.ensure<CharacterBody>();
|
||||
AnimationControl &anim = e.ensure<AnimationControl>();
|
||||
ch.mBodyEnt = eng.mScnMgr->createEntity(conf.type);
|
||||
ch.mBodyNode = eng.mScnMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
ch.mBodyNode->setOrientation(loc.orientation);
|
||||
ch.mBodyNode->setPosition(loc.position);
|
||||
ch.mBodyNode->attachObject(ch.mBodyEnt);
|
||||
ch.mSkeleton = ch.mBodyEnt->getSkeleton();
|
||||
OgreAssert(ch.mSkeleton->hasBone("Root"),
|
||||
"No root bone");
|
||||
ch.mRootBone = ch.mSkeleton->getBone("Root");
|
||||
OgreAssert(ch.mRootBone, "No root bone");
|
||||
body.mController = nullptr;
|
||||
e.set<CharacterVelocity>(
|
||||
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
|
||||
body.checkGround = false;
|
||||
body.checkGroundResult = false;
|
||||
body.mCollisionShape = nullptr;
|
||||
body.mGhostObject = nullptr;
|
||||
body.mController = nullptr;
|
||||
body.mGhostObject = new btPairCachingGhostObject();
|
||||
body.mCollisionShape = new btCompoundShape(false);
|
||||
body.mGhostObject->setCollisionShape(
|
||||
body.mCollisionShape);
|
||||
{
|
||||
btVector3 inertia(0, 0, 0);
|
||||
// mCollisionShape = new btCompoundShape();
|
||||
btScalar height = 1.0f;
|
||||
btScalar radius = 0.3f;
|
||||
btCapsuleShape *shape = new btCapsuleShape(
|
||||
radius, 2 * height - 2 * radius);
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
transform.setOrigin(btVector3(0, 1, 0));
|
||||
static_cast<btCompoundShape *>(
|
||||
body.mCollisionShape)
|
||||
->addChildShape(transform, shape);
|
||||
btScalar masses[1] = { 0 };
|
||||
btTransform principal;
|
||||
static_cast<btCompoundShape *>(
|
||||
body.mCollisionShape)
|
||||
->calculatePrincipalAxisTransform(
|
||||
masses, principal, inertia);
|
||||
}
|
||||
body.mGhostObject->setCollisionFlags(
|
||||
btCollisionObject::CF_KINEMATIC_OBJECT /*|
|
||||
btCollisionObject::CF_NO_CONTACT_RESPONSE */);
|
||||
body.mGhostObject->setActivationState(
|
||||
DISABLE_DEACTIVATION);
|
||||
eng.mWorld->attachCollisionObject(
|
||||
body.mGhostObject, ch.mBodyEnt, 1, 0x7FFFFFFF);
|
||||
OgreAssert(body.mGhostObject, "Need GhostObject");
|
||||
OgreAssert(body.mCollisionShape, "No collision shape");
|
||||
e.add<CharacterGravity>();
|
||||
e.add<CharacterBuoyancy>();
|
||||
anim.currentAnim = AnimationControl::ANIM_NONE;
|
||||
anim.nextAnim = AnimationControl::ANIM_NONE;
|
||||
anim.reset = false;
|
||||
anim.configured = false;
|
||||
});
|
||||
ecs.system<const EngineData, CharacterBase, CharacterBody>(
|
||||
"UpdateCharacterPhysics")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Character>()
|
||||
.with<TerrainReady>()
|
||||
.with<WaterReady>()
|
||||
.each([](const EngineData &eng, CharacterBase &ch,
|
||||
CharacterBody &body) {
|
||||
if (ch.mBodyNode && !body.mController &&
|
||||
eng.startupDelay < 0.0f) {
|
||||
body.mController =
|
||||
new Ogre::Bullet::KinematicMotionSimple(
|
||||
body.mGhostObject,
|
||||
ch.mBodyNode);
|
||||
eng.mWorld->getBtWorld()->addAction(
|
||||
body.mController);
|
||||
OgreAssert(body.mController, "Need controller");
|
||||
}
|
||||
});
|
||||
#define CAM_HEIGHT 1.6f // height of camera above character's center of mass
|
||||
ecs.system<const EngineData, Camera, const CharacterBase>(
|
||||
"UpdateCamera")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Player>()
|
||||
.with<GroundCheckReady>()
|
||||
.each([](const EngineData &eng, Camera &camera,
|
||||
const CharacterBase &ch) {
|
||||
float delta = eng.delta;
|
||||
if (!camera.configured) {
|
||||
// create a pivot at roughly the character's shoulder
|
||||
camera.mCameraPivot =
|
||||
eng.mScnMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
camera.mCameraGoal =
|
||||
camera.mCameraPivot
|
||||
->createChildSceneNode(
|
||||
Ogre::Vector3(0, 2, 3));
|
||||
camera.mCameraNode->setPosition(
|
||||
camera.mCameraPivot->getPosition() +
|
||||
camera.mCameraGoal->getPosition());
|
||||
camera.mCameraPivot->setFixedYawAxis(true);
|
||||
camera.mCameraGoal->setFixedYawAxis(true);
|
||||
camera.mCameraNode->setFixedYawAxis(true);
|
||||
// our model is quite small, so reduce the clipping planes
|
||||
camera.mCamera->setNearClipDistance(0.1f);
|
||||
camera.mCamera->setFarClipDistance(700);
|
||||
|
||||
camera.mPivotPitch = 0;
|
||||
camera.configured = true;
|
||||
} else {
|
||||
// place the camera pivot roughly at the character's shoulder
|
||||
camera.mCameraPivot->setPosition(
|
||||
ch.mBodyNode->getPosition() +
|
||||
Ogre::Vector3::UNIT_Y * CAM_HEIGHT);
|
||||
// move the camera smoothly to the goal
|
||||
Ogre::Vector3 goalOffset =
|
||||
camera.mCameraGoal
|
||||
->_getDerivedPosition() -
|
||||
camera.mCameraNode->getPosition();
|
||||
camera.mCameraNode->translate(goalOffset *
|
||||
delta * 9.0f);
|
||||
// always look at the pivot
|
||||
camera.mCameraNode->lookAt(
|
||||
camera.mCameraPivot
|
||||
->_getDerivedPosition(),
|
||||
Ogre::Node::TS_PARENT);
|
||||
}
|
||||
});
|
||||
class ClosestNotMeRayResultCallback
|
||||
: public btCollisionWorld::ClosestRayResultCallback {
|
||||
btCollisionObject *mMe;
|
||||
@@ -475,8 +667,10 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
ecs.system<const EngineData, CharacterBody>("CheckGround")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Character>()
|
||||
.with<Player>()
|
||||
.without<GroundCheckReady>()
|
||||
.each([](const EngineData &eng, CharacterBody &body) {
|
||||
if (body.checkGround) {
|
||||
if (body.mGhostObject) {
|
||||
btVector3 from =
|
||||
body.mGhostObject->getWorldTransform()
|
||||
.getOrigin() +
|
||||
@@ -488,7 +682,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
resultCallback);
|
||||
body.checkGroundResult =
|
||||
resultCallback.hasHit();
|
||||
body.checkGround = false;
|
||||
if (resultCallback.hasHit())
|
||||
ECS::get().add<GroundCheckReady>();
|
||||
}
|
||||
});
|
||||
ecs.system<const WaterBody, const CharacterBase, CharacterBody>(
|
||||
@@ -498,8 +693,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
.without<InWater>()
|
||||
.each([](flecs::entity e, const WaterBody &waterb,
|
||||
const CharacterBase &ch, CharacterBody &body) {
|
||||
if (waterb.mInWater.find(body.mGhostObject) !=
|
||||
waterb.mInWater.end() &&
|
||||
if (waterb.isInWater(body.mGhostObject) &&
|
||||
ch.mBodyNode->_getDerivedPosition().y < -0.05f) {
|
||||
e.add<InWater>();
|
||||
std::cout << "Big Splash\n";
|
||||
@@ -518,8 +712,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
.with<InWater>()
|
||||
.each([](flecs::entity e, const WaterBody &waterb,
|
||||
const CharacterBase &ch, CharacterBody &body) {
|
||||
if (waterb.mInWater.find(body.mGhostObject) ==
|
||||
waterb.mInWater.end() &&
|
||||
if (waterb.isInWater(body.mGhostObject) &&
|
||||
ch.mBodyNode->_getDerivedPosition().y > 0.05f)
|
||||
e.remove<InWater>();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ struct Camera;
|
||||
/* character */
|
||||
struct Character {}; /* tag */
|
||||
struct Player {}; /* tag */
|
||||
struct CharacterGravity {};
|
||||
struct CharacterBuoyancy {};
|
||||
struct CharacterBase {
|
||||
Ogre::String type;
|
||||
float mTimer;
|
||||
@@ -16,20 +18,33 @@ struct CharacterBase {
|
||||
Ogre::Node *mRootBone;
|
||||
Ogre::Vector3 mBoneMotion;
|
||||
Ogre::Vector3 mGoalDirection; // actual intended direction in world-space
|
||||
bool is_submerged;
|
||||
};
|
||||
struct CharacterLocation {
|
||||
Ogre::Quaternion orientation;
|
||||
Ogre::Vector3 position;
|
||||
};
|
||||
struct CharacterConf {
|
||||
Ogre::String type;
|
||||
};
|
||||
struct CharacterBody {
|
||||
btPairCachingGhostObject *mGhostObject;
|
||||
btCompoundShape *mCollisionShape;
|
||||
Ogre::Bullet::KinematicMotionSimple *mController;
|
||||
Ogre::Vector3 gvelocity;
|
||||
bool checkGround;
|
||||
bool checkGroundResult;
|
||||
};
|
||||
struct CharacterVelocity {
|
||||
Ogre::Vector3 gvelocity;
|
||||
Ogre::Vector3 velocity;
|
||||
};
|
||||
struct AnimationControl {
|
||||
enum AnimID {
|
||||
ANIM_IDLE = 0,
|
||||
ANIM_WALK,
|
||||
ANIM_RUN,
|
||||
ANIM_TREADING_WATER,
|
||||
ANIM_SWIMMING,
|
||||
NUM_ANIMS,
|
||||
ANIM_NONE = NUM_ANIMS
|
||||
};
|
||||
@@ -45,7 +60,6 @@ struct AnimationControl {
|
||||
Ogre::NodeAnimationTrack *mRootTracks[NUM_ANIMS];
|
||||
};
|
||||
struct CharacterModule {
|
||||
flecs::entity player;
|
||||
CharacterModule(flecs::world &ecs);
|
||||
void setAnimation(AnimationControl &anim);
|
||||
void fadeAnimations(AnimationControl &anim, Ogre::Real deltaTime);
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
#define COMPONENTS_H_
|
||||
#include <Ogre.h>
|
||||
#include <OgreBullet.h>
|
||||
namespace Ogre
|
||||
{
|
||||
class ImGuiOverlay;
|
||||
}
|
||||
namespace OgreBites
|
||||
{
|
||||
class ApplicationContextSDL;
|
||||
@@ -18,6 +22,8 @@ struct EngineData {
|
||||
Ogre::Bullet::DynamicsWorld *mWorld;
|
||||
float delta;
|
||||
float startupDelay;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
struct Vector3 {
|
||||
float x;
|
||||
@@ -58,10 +64,13 @@ struct RenderWindow {
|
||||
float dpi;
|
||||
};
|
||||
struct App {
|
||||
OgreBites::ApplicationContextSDL *app;
|
||||
Ogre::ImGuiOverlay *mGuiOverlay;
|
||||
OgreBites::InputListenerChain *mInput;
|
||||
std::vector<OgreBites::InputListener *> listeners;
|
||||
};
|
||||
struct InWater {};
|
||||
struct TerrainReady {};
|
||||
struct WaterReady {};
|
||||
struct GroundCheckReady {};
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,255 @@
|
||||
#include <iostream>
|
||||
#include <OgreBullet.h>
|
||||
#include <OgreMeshManager.h>
|
||||
#include "Components.h"
|
||||
#include "GameData.h"
|
||||
#include "LuaData.h"
|
||||
#include "EventTriggerModule.h"
|
||||
|
||||
struct TriggerBody {
|
||||
btPairCachingGhostObject *mBody;
|
||||
btCylinderShape *shape;
|
||||
Ogre::SceneNode *mSceneNode;
|
||||
std::set<const btCollisionObject *> contactBodies;
|
||||
};
|
||||
struct DeepPenetrationContactResultCallback : public btManifoldResult {
|
||||
DeepPenetrationContactResultCallback(
|
||||
const btCollisionObjectWrapper *body0Wrap,
|
||||
const btCollisionObjectWrapper *body1Wrap)
|
||||
: btManifoldResult(body0Wrap, body1Wrap)
|
||||
, mPenetrationDistance(0)
|
||||
, mOtherIndex(0)
|
||||
{
|
||||
}
|
||||
float mPenetrationDistance;
|
||||
int mOtherIndex;
|
||||
btVector3 mNormal, mPoint;
|
||||
void reset()
|
||||
{
|
||||
mPenetrationDistance = 0.0f;
|
||||
}
|
||||
bool hasHit()
|
||||
{
|
||||
return mPenetrationDistance < 0.0f;
|
||||
}
|
||||
virtual void addContactPoint(const btVector3 &normalOnBInWorld,
|
||||
const btVector3 &pointInWorldOnB,
|
||||
btScalar depth)
|
||||
{
|
||||
#ifdef VDEBUG
|
||||
std::cout
|
||||
<< "contact: " << Ogre::Bullet::convert(pointInWorldOnB)
|
||||
<< " " << Ogre::Bullet::convert(normalOnBInWorld)
|
||||
<< "\n";
|
||||
#endif
|
||||
if (mPenetrationDistance > depth) { // Has penetration?
|
||||
|
||||
const bool isSwapped =
|
||||
m_manifoldPtr->getBody0() !=
|
||||
m_body0Wrap->getCollisionObject();
|
||||
mPenetrationDistance = depth;
|
||||
mOtherIndex = isSwapped ? m_index0 : m_index1;
|
||||
mPoint = isSwapped ? (pointInWorldOnB +
|
||||
(normalOnBInWorld * depth)) :
|
||||
pointInWorldOnB;
|
||||
|
||||
mNormal = isSwapped ? normalOnBInWorld * -1 :
|
||||
normalOnBInWorld;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.module<EventTriggerModule>();
|
||||
ecs.component<EventTriggerExit>();
|
||||
ecs.component<TriggerBody>().on_add([](flecs::entity e,
|
||||
TriggerBody &body) {
|
||||
bool kinematic = false;
|
||||
if (e.get<EventTrigger>().parent) {
|
||||
body.mSceneNode =
|
||||
e.get<EventTrigger>()
|
||||
.parent->createChildSceneNode(
|
||||
e.get<EventTrigger>().position,
|
||||
Ogre::Quaternion(0, 0, 0, 1));
|
||||
kinematic = true;
|
||||
} else {
|
||||
body.mSceneNode =
|
||||
ECS::get<EngineData>()
|
||||
.mScnMgr->getRootSceneNode()
|
||||
->createChildSceneNode(
|
||||
e.get<EventTrigger>().position,
|
||||
Ogre::Quaternion(0, 0, 0, 1));
|
||||
}
|
||||
Ogre::MeshPtr mesh =
|
||||
Ogre::MeshManager::getSingleton().createManual(
|
||||
"trigger", "General");
|
||||
Ogre::Entity *ent =
|
||||
ECS::get<EngineData>().mScnMgr->createEntity(mesh);
|
||||
body.mSceneNode->attachObject(ent);
|
||||
body.mBody = new btPairCachingGhostObject();
|
||||
body.mBody->getWorldTransform().setOrigin(Ogre::Bullet::convert(
|
||||
body.mSceneNode->_getDerivedPosition()));
|
||||
float h = e.get<EventTrigger>().halfheight;
|
||||
float r = e.get<EventTrigger>().radius;
|
||||
Ogre::Vector3 position = e.get<EventTrigger>().position;
|
||||
body.shape = new btCylinderShape(btVector3(r, h, r));
|
||||
body.mBody->setCollisionShape(body.shape);
|
||||
int flags = body.mBody->getCollisionFlags();
|
||||
flags |= btCollisionObject::CF_NO_CONTACT_RESPONSE;
|
||||
if (kinematic)
|
||||
flags |= btCollisionObject::CF_STATIC_OBJECT;
|
||||
body.mBody->setCollisionFlags(flags);
|
||||
ECS::get<EngineData>().mWorld->attachCollisionObject(
|
||||
body.mBody, ent, 16, 0x1);
|
||||
});
|
||||
ecs.component<EventTrigger>().on_set(
|
||||
[](flecs::entity e, EventTrigger &ev) {
|
||||
e.add<TriggerBody>();
|
||||
});
|
||||
ecs.system<const EngineData, const EventTrigger, TriggerBody>(
|
||||
"CheckCollisions")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](flecs::entity e, const EngineData &eng,
|
||||
const EventTrigger &evt, TriggerBody &body) {
|
||||
btDispatcher *dispatch =
|
||||
eng.mWorld->getBtWorld()->getDispatcher();
|
||||
btHashedOverlappingPairCache *cache =
|
||||
body.mBody->getOverlappingPairCache();
|
||||
int i;
|
||||
int count = cache->getNumOverlappingPairs();
|
||||
std::set<const btCollisionObject *> currentContactBodies;
|
||||
if (count > 0) {
|
||||
btManifoldArray contacts;
|
||||
btBroadphasePairArray &collisionPairs =
|
||||
cache->getOverlappingPairArray();
|
||||
for (i = 0; i < count; i++) {
|
||||
contacts.resize(0);
|
||||
if (collisionPairs[i].m_algorithm) {
|
||||
collisionPairs[i]
|
||||
.m_algorithm
|
||||
->getAllContactManifolds(
|
||||
contacts);
|
||||
OgreAssert(false,
|
||||
"Not implemented");
|
||||
} else {
|
||||
const btBroadphasePair *collisionPairPtr =
|
||||
eng.mWorld->getBtWorld()
|
||||
->getBroadphase()
|
||||
->getOverlappingPairCache()
|
||||
->findPair(
|
||||
collisionPairs[i]
|
||||
.m_pProxy0,
|
||||
collisionPairs[i]
|
||||
.m_pProxy1);
|
||||
if (collisionPairPtr) {
|
||||
const btBroadphasePair
|
||||
&collisionPair =
|
||||
*collisionPairPtr;
|
||||
const btCollisionObject *objA = static_cast<
|
||||
btCollisionObject
|
||||
*>(
|
||||
collisionPair
|
||||
.m_pProxy0
|
||||
->m_clientObject);
|
||||
const btCollisionObject *objB = static_cast<
|
||||
btCollisionObject
|
||||
*>(
|
||||
collisionPair
|
||||
.m_pProxy1
|
||||
->m_clientObject);
|
||||
const btCollisionObject
|
||||
*me,
|
||||
*other;
|
||||
if (objA ==
|
||||
static_cast<
|
||||
btCollisionObject
|
||||
*>(
|
||||
body.mBody)) {
|
||||
me = objA;
|
||||
other = objB;
|
||||
} else {
|
||||
me = objB;
|
||||
other = objA;
|
||||
}
|
||||
const btCollisionShape *my_shape =
|
||||
me->getCollisionShape();
|
||||
const btCollisionShape *other_shape =
|
||||
other->getCollisionShape();
|
||||
btCollisionObjectWrapper obA(
|
||||
NULL, my_shape,
|
||||
body.mBody,
|
||||
body.mBody
|
||||
->getWorldTransform(),
|
||||
-1, i);
|
||||
btCollisionObjectWrapper obB(
|
||||
NULL,
|
||||
other_shape,
|
||||
other,
|
||||
other->getWorldTransform(),
|
||||
-1, 0);
|
||||
btCollisionAlgorithm *algorithm =
|
||||
dispatch->findAlgorithm(
|
||||
&obA,
|
||||
&obB,
|
||||
NULL,
|
||||
BT_CONTACT_POINT_ALGORITHMS);
|
||||
DeepPenetrationContactResultCallback
|
||||
contactPointResult(
|
||||
&obA,
|
||||
&obB);
|
||||
|
||||
algorithm->processCollision(
|
||||
&obA, &obB,
|
||||
eng.mWorld
|
||||
->getBtWorld()
|
||||
->getDispatchInfo(),
|
||||
&contactPointResult);
|
||||
algorithm
|
||||
->~btCollisionAlgorithm();
|
||||
dispatch->freeCollisionAlgorithm(
|
||||
algorithm);
|
||||
if (contactPointResult
|
||||
.hasHit()) {
|
||||
currentContactBodies
|
||||
.insert(other);
|
||||
if (body.contactBodies
|
||||
.find(other) ==
|
||||
body.contactBodies
|
||||
.end()) {
|
||||
body.contactBodies
|
||||
.insert(other);
|
||||
ECS::get<
|
||||
LuaBase>()
|
||||
.mLua
|
||||
->call_handler(
|
||||
evt.event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::set<const btCollisionObject *>::iterator it =
|
||||
body.contactBodies.begin();
|
||||
while (it != body.contactBodies.end()) {
|
||||
if (currentContactBodies.find(*it) ==
|
||||
currentContactBodies.end()) {
|
||||
if (e.has<EventTriggerExit>()) {
|
||||
const Ogre::String &exit_event =
|
||||
ECS::get<
|
||||
EventTriggerExit>()
|
||||
.event;
|
||||
ECS::get<LuaBase>()
|
||||
.mLua->call_handler(
|
||||
exit_event);
|
||||
} else {
|
||||
std::cout << "body exited"
|
||||
<< std::endl;
|
||||
}
|
||||
body.contactBodies.erase(*it);
|
||||
}
|
||||
it++;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EVENT_TRIGGER_MODULE_H_
|
||||
#define EVENT_TRIGGER_MODULE_H_
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
namespace ECS
|
||||
{
|
||||
struct EventTrigger {
|
||||
Ogre::SceneNode *parent;
|
||||
Ogre::Vector3 position;
|
||||
float halfheight;
|
||||
float radius;
|
||||
Ogre::String event;
|
||||
};
|
||||
struct EventTriggerExit {
|
||||
Ogre::String event;
|
||||
};
|
||||
struct EventTriggerModule {
|
||||
EventTriggerModule(flecs::world &ecs);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
+327
-113
@@ -7,8 +7,10 @@
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreApplicationContext.h>
|
||||
#include <OgreImGuiInputListener.h>
|
||||
#include <OgreFontManager.h>
|
||||
#include "GameData.h"
|
||||
#include "Components.h"
|
||||
#include "LuaData.h"
|
||||
#include "GUIModule.h"
|
||||
namespace ECS
|
||||
{
|
||||
@@ -20,6 +22,50 @@ struct GUIData {
|
||||
};
|
||||
struct GUIListener : public Ogre::RenderTargetListener {
|
||||
float panel_width;
|
||||
bool enableEditor;
|
||||
bool enableMapEditor;
|
||||
ImFont *smallFont, *midFont, *bigFont;
|
||||
Ogre::FontPtr _smallFont, _midFont, _bigFont;
|
||||
GUIListener()
|
||||
: Ogre::RenderTargetListener()
|
||||
{
|
||||
_midFont = createFont("midFont", "General",
|
||||
"Jupiteroid-Regular.ttf", 18.0f);
|
||||
_smallFont = createFont("smallFont", "General",
|
||||
"Jupiteroid-Regular.ttf", 13.0f);
|
||||
_bigFont = createFont("bigFont", "General", "Kenney Bold.ttf",
|
||||
32.0f);
|
||||
smallFont = ECS::get<GUIData>().mGuiOverlay->addFont(
|
||||
"smallFont", "General");
|
||||
OgreAssert(smallFont, "Could not load font");
|
||||
midFont = ECS::get<GUIData>().mGuiOverlay->addFont("midFont",
|
||||
"General");
|
||||
OgreAssert(midFont, "Could not load font");
|
||||
bigFont = ECS::get<GUIData>().mGuiOverlay->addFont("bigFont",
|
||||
"General");
|
||||
OgreAssert(bigFont, "Could not load font");
|
||||
#if 0
|
||||
Ogre::FontPtr _midFont = createFont("midFont", "General",
|
||||
"Kenney Bold.ttf", 28.0f);
|
||||
#endif
|
||||
#if 0
|
||||
ImGui::GetIO().Fonts->Build();
|
||||
#endif
|
||||
}
|
||||
Ogre::FontPtr createFont(const Ogre::String &name,
|
||||
const Ogre::String &group,
|
||||
const Ogre::String &ttfname, float fontSize)
|
||||
{
|
||||
Ogre::FontPtr ret =
|
||||
Ogre::FontManager::getSingleton().create(name, group);
|
||||
ret->setType(Ogre::FontType::FT_TRUETYPE);
|
||||
ret->setSource(ttfname);
|
||||
ret->setTrueTypeSize(fontSize);
|
||||
ret->setTrueTypeResolution(75);
|
||||
ret->addCodePointRange(Ogre::Font::CodePointRange(30, 128));
|
||||
ret->load();
|
||||
return ret;
|
||||
}
|
||||
void
|
||||
preViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override
|
||||
{
|
||||
@@ -36,14 +82,24 @@ struct GUIListener : public Ogre::RenderTargetListener {
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(window_width, window_height),
|
||||
ImGuiCond_Always);
|
||||
ImGui::Begin("Dumb and Stupid");
|
||||
ImGui::Begin("Control");
|
||||
// if (ECS::get().get<GUI>().enabled)
|
||||
// ECS::get().get<App>().app->setWindowGrab(true);
|
||||
if (ImGui::Button("Shitty Quit button"))
|
||||
if (ImGui::Button("Quit"))
|
||||
Ogre::Root::getSingleton().queueEndRendering();
|
||||
if (ImGui::Button("Chick-chick"))
|
||||
if (ImGui::Button("Return"))
|
||||
ECS::get().get<GUI>().finish();
|
||||
ImGui::Text("We do stoopid...");
|
||||
if (ImGui::Button("Enable/Disable item editor")) {
|
||||
enableEditor ^= true;
|
||||
if (enableEditor)
|
||||
enableMapEditor = false;
|
||||
}
|
||||
if (ImGui::Button("Enable/Disable map editor")) {
|
||||
enableMapEditor ^= true;
|
||||
if (enableMapEditor)
|
||||
enableEditor = false;
|
||||
}
|
||||
ImGui::Text("Text message...");
|
||||
ImGui::End();
|
||||
}
|
||||
void create_entity_node(const Ogre::String &name, int key)
|
||||
@@ -140,127 +196,286 @@ struct GUIListener : public Ogre::RenderTargetListener {
|
||||
ImGui::Text("Name: %s", pname.c_str());
|
||||
}
|
||||
}
|
||||
void map_editor()
|
||||
{
|
||||
}
|
||||
void preview(const Ogre::RenderTargetViewportEvent &evt)
|
||||
{
|
||||
int i;
|
||||
Ogre::ImGuiOverlay::NewFrame();
|
||||
if (ECS::get().get<GUI>().enabled) {
|
||||
buttons_panel();
|
||||
buildings_editor();
|
||||
if (ECS::get().get<EngineData>().startupDelay > 0.0f) {
|
||||
ImVec2 size = ImGui::GetMainViewport()->Size;
|
||||
float window_width = size.x * 0.2f;
|
||||
if (window_width > panel_width)
|
||||
window_width = panel_width;
|
||||
float window_height = size.y * 0.5f - 20;
|
||||
ImGui::SetNextWindowPos(ImVec2(size.x - window_width,
|
||||
size.y * 0.5f + 20),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(window_width,
|
||||
window_height),
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x, size.y),
|
||||
ImGuiCond_Always);
|
||||
// ImGui::Begin("Dumb and Stupid", &mKbd.gui_active);
|
||||
ImGui::Begin("Panel...");
|
||||
std::deque<Ogre::SceneNode *> tree_input_queue,
|
||||
tree_output_queue;
|
||||
std::vector<Ogre::SceneNode *> tree_list;
|
||||
tree_input_queue.push_back(
|
||||
ECS::get()
|
||||
.get<EngineData>()
|
||||
.mScnMgr->getRootSceneNode());
|
||||
tree_input_queue.push_back(nullptr);
|
||||
std::set<Ogre::SceneNode *> visited;
|
||||
while (true) {
|
||||
int new_nodes_count = 0;
|
||||
while (!tree_input_queue.empty()) {
|
||||
int child;
|
||||
Ogre::SceneNode *item =
|
||||
tree_input_queue.front();
|
||||
tree_input_queue.pop_front();
|
||||
if (item &&
|
||||
visited.find(item) ==
|
||||
visited.end()) { // new node
|
||||
new_nodes_count++;
|
||||
tree_output_queue.push_back(
|
||||
item);
|
||||
visited.insert(item);
|
||||
const Ogre::Node::ChildNodeMap
|
||||
&children =
|
||||
item->getChildren();
|
||||
for (child = 0;
|
||||
child < children.size();
|
||||
child++) {
|
||||
tree_output_queue.push_back(
|
||||
static_cast<
|
||||
Ogre::SceneNode
|
||||
*>(
|
||||
children[child]));
|
||||
ImGui::Begin(
|
||||
"StartupScreen", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoDecoration |
|
||||
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoInputs);
|
||||
// if (ECS::get().get<GUI>().enabled)
|
||||
// ECS::get().get<App>().app->setWindowGrab(true);
|
||||
ImGui::PushFont(bigFont);
|
||||
ImGui::TextWrapped(
|
||||
"%s",
|
||||
"This game does not autosave. Please use save function to keep your state");
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
} else if (ECS::get().get<GUI>().enabled) {
|
||||
if (ECS::get().get<GUI>().narrationBox) {
|
||||
ImVec2 size = ImGui::GetMainViewport()->Size;
|
||||
ImGui::SetNextWindowPos(ImVec2(0,
|
||||
size.y * 0.75f),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x,
|
||||
size.y * 0.25f),
|
||||
ImGuiCond_Always);
|
||||
ImGui::Begin("Narration...", NULL,
|
||||
ImGuiWindowFlags_NoTitleBar);
|
||||
ImGui::PushFont(midFont);
|
||||
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
ImGui::TextWrapped(
|
||||
"%s", ECS::get()
|
||||
.get<GUI>()
|
||||
.narrationText.c_str());
|
||||
if (ECS::get().get<GUI>().choices.size() == 0) {
|
||||
ImGui::SetCursorScreenPos(p);
|
||||
if (ImGui::InvisibleButton(
|
||||
"Background",
|
||||
ImGui::GetWindowSize()))
|
||||
ECS::get<LuaBase>().mLua->call_handler(
|
||||
"narration_progress");
|
||||
} else {
|
||||
int i;
|
||||
for (i = 0; i < ECS::get()
|
||||
.get<GUI>()
|
||||
.choices.size();
|
||||
i++) {
|
||||
if (ImGui::Button(
|
||||
ECS::get()
|
||||
.get<GUI>()
|
||||
.choices[i]
|
||||
.c_str())) {
|
||||
ECS::get()
|
||||
.get_mut<GUI>()
|
||||
.narration_answer =
|
||||
i + 1;
|
||||
std::cout << "answer: "
|
||||
<< i + 1
|
||||
<< std::endl;
|
||||
ECS::modified<GUI>();
|
||||
ECS::get<LuaBase>()
|
||||
.mLua
|
||||
->call_handler(
|
||||
"narration_answered");
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
} else if (ECS::get().get<GUI>().mainMenu) {
|
||||
ImVec2 size = ImGui::GetMainViewport()->Size;
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x, size.y),
|
||||
ImGuiCond_Always);
|
||||
ImGui::Begin(
|
||||
"MainMenu", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoDecoration |
|
||||
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
0);
|
||||
// if (ECS::get().get<GUI>().enabled)
|
||||
// ECS::get().get<App>().app->setWindowGrab(true);
|
||||
ImGui::PushFont(bigFont);
|
||||
ImGui::TextWrapped("%s", "Booo!!!!");
|
||||
bool pressed = false;
|
||||
bool new_game = false, cont = false,
|
||||
load_game = false, opts = false,
|
||||
quit = false;
|
||||
ImGui::SetCursorPosY(size.y / 2.0f - 300.0f);
|
||||
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
|
||||
new_game = ImGui::Button("New Game");
|
||||
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
|
||||
cont = ImGui::Button("Continue");
|
||||
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
|
||||
load_game = ImGui::Button("Load Game");
|
||||
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
|
||||
opts = ImGui::Button("Options");
|
||||
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
|
||||
quit = ImGui::Button("Quit###quit");
|
||||
pressed = new_game || cont || load_game ||
|
||||
opts || quit;
|
||||
ImGui::PopFont();
|
||||
ImGui::Spacing();
|
||||
ImGui::End();
|
||||
if (quit)
|
||||
Ogre::Root::getSingleton()
|
||||
.queueEndRendering();
|
||||
if (pressed)
|
||||
ECS::get().get<GUI>().finish();
|
||||
if (new_game) {
|
||||
ECS::get<LuaBase>().mLua->call_handler(
|
||||
"new_game");
|
||||
}
|
||||
} else {
|
||||
buttons_panel();
|
||||
if (enableEditor)
|
||||
buildings_editor();
|
||||
if (enableMapEditor)
|
||||
map_editor();
|
||||
ImVec2 size = ImGui::GetMainViewport()->Size;
|
||||
float window_width = size.x * 0.2f;
|
||||
if (window_width > panel_width)
|
||||
window_width = panel_width;
|
||||
float window_height = size.y * 0.5f - 20;
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(size.x - window_width,
|
||||
size.y * 0.5f + 20),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(window_width,
|
||||
window_height),
|
||||
ImGuiCond_Always);
|
||||
// ImGui::Begin("Dumb and Stupid", &mKbd.gui_active);
|
||||
ImGui::Begin("Panel...");
|
||||
std::deque<Ogre::SceneNode *> tree_input_queue,
|
||||
tree_output_queue;
|
||||
std::vector<Ogre::SceneNode *> tree_list;
|
||||
tree_input_queue.push_back(
|
||||
ECS::get()
|
||||
.get<EngineData>()
|
||||
.mScnMgr->getRootSceneNode());
|
||||
tree_input_queue.push_back(nullptr);
|
||||
std::set<Ogre::SceneNode *> visited;
|
||||
while (true) {
|
||||
int new_nodes_count = 0;
|
||||
while (!tree_input_queue.empty()) {
|
||||
int child;
|
||||
Ogre::SceneNode *item =
|
||||
tree_input_queue.front();
|
||||
tree_input_queue.pop_front();
|
||||
if (item &&
|
||||
visited.find(item) ==
|
||||
visited.end()) { // new node
|
||||
new_nodes_count++;
|
||||
tree_output_queue
|
||||
.push_back(
|
||||
nullptr);
|
||||
}
|
||||
} else
|
||||
tree_output_queue.push_back(
|
||||
item);
|
||||
item);
|
||||
visited.insert(item);
|
||||
const Ogre::Node::ChildNodeMap
|
||||
&children =
|
||||
item->getChildren();
|
||||
for (child = 0;
|
||||
child <
|
||||
children.size();
|
||||
child++) {
|
||||
tree_output_queue
|
||||
.push_back(static_cast<
|
||||
Ogre::SceneNode
|
||||
*>(
|
||||
children[child]));
|
||||
tree_output_queue
|
||||
.push_back(
|
||||
nullptr);
|
||||
}
|
||||
} else
|
||||
tree_output_queue
|
||||
.push_back(
|
||||
item);
|
||||
}
|
||||
if (new_nodes_count == 0)
|
||||
break;
|
||||
tree_input_queue = tree_output_queue;
|
||||
tree_output_queue.clear();
|
||||
}
|
||||
if (new_nodes_count == 0)
|
||||
break;
|
||||
tree_input_queue = tree_output_queue;
|
||||
tree_output_queue.clear();
|
||||
}
|
||||
tree_list.insert(tree_list.begin(),
|
||||
tree_output_queue.begin(),
|
||||
tree_output_queue.end());
|
||||
int count = 0;
|
||||
int depth = 0;
|
||||
std::vector<int> check_depth;
|
||||
int max_depth = 0;
|
||||
check_depth.push_back(0);
|
||||
for (count = 0; count < tree_list.size(); count++) {
|
||||
int t;
|
||||
tree_list.insert(tree_list.begin(),
|
||||
tree_output_queue.begin(),
|
||||
tree_output_queue.end());
|
||||
int count = 0;
|
||||
int depth = 0;
|
||||
std::vector<int> check_depth;
|
||||
int max_depth = 0;
|
||||
check_depth.push_back(0);
|
||||
for (count = 0; count < tree_list.size();
|
||||
count++) {
|
||||
int t;
|
||||
|
||||
Ogre::SceneNode *node = tree_list[count];
|
||||
if (node && max_depth >= depth) {
|
||||
Ogre::String name = node->getName();
|
||||
if (name.length() == 0) {
|
||||
name = "Node #" +
|
||||
Ogre::StringConverter::
|
||||
toString(count);
|
||||
Ogre::SceneNode *node =
|
||||
tree_list[count];
|
||||
if (node && max_depth >= depth) {
|
||||
Ogre::String name =
|
||||
node->getName();
|
||||
if (name.length() == 0) {
|
||||
name = "Node #" +
|
||||
Ogre::StringConverter::
|
||||
toString(
|
||||
count);
|
||||
}
|
||||
if (ImGui::TreeNode(
|
||||
name.c_str())) {
|
||||
check_depth.push_back(
|
||||
max_depth);
|
||||
max_depth++;
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
(name +
|
||||
"##caption")
|
||||
.c_str());
|
||||
position_editor(node);
|
||||
ImGui::Separator();
|
||||
orientation_editor(
|
||||
node);
|
||||
ImGui::Separator();
|
||||
ImGui::Text(
|
||||
"Attachments");
|
||||
attachments_editor(
|
||||
node);
|
||||
}
|
||||
} else if (!node &&
|
||||
max_depth >= depth) {
|
||||
max_depth = check_depth.back();
|
||||
check_depth.pop_back();
|
||||
ImGui::TreePop();
|
||||
}
|
||||
if (ImGui::TreeNode(name.c_str())) {
|
||||
check_depth.push_back(
|
||||
max_depth);
|
||||
max_depth++;
|
||||
ImGui::Text("%s",
|
||||
(name + "##caption")
|
||||
.c_str());
|
||||
position_editor(node);
|
||||
ImGui::Separator();
|
||||
orientation_editor(node);
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Attachments");
|
||||
attachments_editor(node);
|
||||
}
|
||||
} else if (!node && max_depth >= depth) {
|
||||
max_depth = check_depth.back();
|
||||
check_depth.pop_back();
|
||||
ImGui::TreePop();
|
||||
if (tree_list[count])
|
||||
depth++;
|
||||
else
|
||||
depth--;
|
||||
}
|
||||
if (tree_list[count])
|
||||
depth++;
|
||||
else
|
||||
depth--;
|
||||
ImGui::Spacing();
|
||||
ImGui::End();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
GUIModule::GUIModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.component<GUI>().add(flecs::Singleton);
|
||||
ecs.component<GUIData>().add(flecs::Singleton);
|
||||
ecs.set<GUI>({ false, true, false, nullptr });
|
||||
ecs.component<GUI>()
|
||||
.on_add([](GUI &gui) {
|
||||
gui.enabled = false;
|
||||
gui.grab = false;
|
||||
gui.grabChanged = false;
|
||||
})
|
||||
.add(flecs::Singleton);
|
||||
ecs.component<GUIData>()
|
||||
.on_add([](GUIData &priv) {
|
||||
priv.glb_names.clear();
|
||||
priv.mGUIListener = nullptr;
|
||||
priv.mGuiOverlay = nullptr;
|
||||
})
|
||||
.add(flecs::Singleton);
|
||||
ecs.set<GUI>({ false, true, false, false, false, "", {}, -1 });
|
||||
ecs.set<GUIData>({ nullptr, {}, nullptr });
|
||||
ui_wait =
|
||||
ecs.system<const RenderWindow, App, GUIData>("SetupGUI")
|
||||
@@ -275,11 +490,14 @@ GUIModule::GUIModule(flecs::world &ecs)
|
||||
Ogre::OverlayManager::getSingleton()
|
||||
.setPixelRatio(vpScale);
|
||||
std::cout << "GUI configure\n";
|
||||
gui.mGuiOverlay =
|
||||
app.app->initialiseImGui();
|
||||
OgreAssert(app.mGuiOverlay,
|
||||
"No ImGUI overlay");
|
||||
gui.mGuiOverlay = app.mGuiOverlay;
|
||||
gui.mGUIListener = new GUIListener();
|
||||
gui.mGuiOverlay->setZOrder(300);
|
||||
gui.mGuiOverlay->show();
|
||||
gui.mGUIListener = new GUIListener();
|
||||
gui.mGUIListener->panel_width = 300.0f;
|
||||
gui.mGUIListener->enableEditor = false;
|
||||
window.window->addListener(
|
||||
gui.mGUIListener);
|
||||
int i;
|
||||
@@ -299,10 +517,6 @@ GUIModule::GUIModule(flecs::world &ecs)
|
||||
names.begin(),
|
||||
names.end());
|
||||
}
|
||||
ECS::get_mut<ECS::GUI>()
|
||||
.mGuiInpitListener =
|
||||
new OgreBites::
|
||||
ImGuiInputListener();
|
||||
ECS::modified<ECS::GUI>();
|
||||
std::cout << "GUI configure finished\n";
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <flecs.h>
|
||||
namespace OgreBites
|
||||
{
|
||||
class InputListener;
|
||||
}
|
||||
namespace ECS
|
||||
{
|
||||
@@ -11,7 +10,11 @@ struct GUI {
|
||||
bool enabled;
|
||||
bool grab;
|
||||
bool grabChanged;
|
||||
OgreBites::InputListener *mGuiInpitListener;
|
||||
bool narrationBox;
|
||||
bool mainMenu;
|
||||
Ogre::String narrationText;
|
||||
std::vector<Ogre::String> choices;
|
||||
int narration_answer;
|
||||
static void setWindowGrab(bool g = true)
|
||||
{
|
||||
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
|
||||
@@ -25,6 +28,8 @@ struct GUI {
|
||||
{
|
||||
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
|
||||
gui.enabled = false;
|
||||
gui.mainMenu = false;
|
||||
gui.narrationBox = false;
|
||||
ECS::get().modified<ECS::GUI>();
|
||||
setWindowGrab(true);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,18 @@
|
||||
#include "TerrainModule.h"
|
||||
#include "SunModule.h"
|
||||
#include "GUIModule.h"
|
||||
#include "LuaData.h"
|
||||
#include "WorldMapModule.h"
|
||||
#include "BoatModule.h"
|
||||
#include "EventTriggerModule.h"
|
||||
|
||||
namespace ECS
|
||||
{
|
||||
static flecs::world ecs;
|
||||
flecs::entity player;
|
||||
void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
|
||||
Ogre::SceneNode *cameraNode, Ogre::Camera *camera)
|
||||
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
|
||||
Ogre::RenderWindow *window)
|
||||
{
|
||||
std::cout << "Setup GameData\n";
|
||||
ecs.component<EngineData>().add(flecs::Singleton);
|
||||
@@ -20,23 +26,66 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
|
||||
ecs.component<Input>().add(flecs::Singleton);
|
||||
ecs.component<Camera>().add(flecs::Singleton);
|
||||
ecs.component<InWater>();
|
||||
ecs.component<App>().add(flecs::Singleton);
|
||||
ecs.component<WaterReady>().add(flecs::Singleton);
|
||||
ecs.component<GroundCheckReady>().add(flecs::Singleton);
|
||||
ecs.component<App>()
|
||||
.on_add([](App &app) {
|
||||
app.mInput = nullptr;
|
||||
app.mGuiOverlay = nullptr;
|
||||
app.listeners.clear();
|
||||
})
|
||||
.add(flecs::Singleton);
|
||||
/* lots of things depend on it */
|
||||
ecs.component<TerrainReady>().add(flecs::Singleton);
|
||||
ecs.import <WaterModule>();
|
||||
ecs.import <CharacterModule>();
|
||||
ecs.import <SunModule>();
|
||||
ecs.import <TerrainModule>();
|
||||
ecs.import <SunModule>();
|
||||
ecs.import <GUIModule>();
|
||||
ecs.import <LuaModule>();
|
||||
ecs.import <WorldMapModule>();
|
||||
ecs.import <LuaModule>();
|
||||
ecs.import <BoatModule>();
|
||||
ecs.import <EventTriggerModule>();
|
||||
|
||||
ecs.system<EngineData>("UpdateDelta")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](EngineData &eng) {
|
||||
eng.delta = ECS::get().delta_time();
|
||||
});
|
||||
ecs.set<EngineData>({ scnMgr, world, 0.0f, 0.0f });
|
||||
ecs.system<EngineData>("UpdateDelay")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<TerrainReady>()
|
||||
.with<WaterReady>()
|
||||
.with<GroundCheckReady>()
|
||||
.each([](EngineData &eng) {
|
||||
if (eng.startupDelay >= 0.0f)
|
||||
eng.startupDelay -= eng.delta;
|
||||
#ifdef VDEBUG
|
||||
if (ECS::get().has<GroundCheckReady>())
|
||||
std::cout << "ground check ready\n";
|
||||
#endif
|
||||
});
|
||||
ecs.system<EngineData>("CheckStatus")
|
||||
.kind(flecs::OnUpdate)
|
||||
.run([](flecs::iter &it) {
|
||||
#ifdef VDEBUG
|
||||
if (ECS::get().has<WaterReady>())
|
||||
std::cout << "water ready\n";
|
||||
if (ECS::get().has<TerrainReady>())
|
||||
std::cout << "terrain ready\n";
|
||||
if (ECS::get().has<GroundCheckReady>())
|
||||
std::cout << "ground check ready\n";
|
||||
#endif
|
||||
});
|
||||
ecs.set<EngineData>({ scnMgr, world, 0.0f, 5.0f,
|
||||
(int)window->getWidth(),
|
||||
(int)window->getHeight() });
|
||||
ecs.set<Camera>({ cameraNode, camera, false });
|
||||
ecs.add<GameData>();
|
||||
ecs.add<Input>();
|
||||
ecs.set<WaterSurface>({ nullptr, nullptr, nullptr, nullptr, nullptr });
|
||||
ecs.set<WaterBody>({ nullptr });
|
||||
ecs.add<WaterSurface>();
|
||||
ecs.set<Sun>({ nullptr, nullptr, nullptr, nullptr });
|
||||
ecs.set<Terrain>({ nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
@@ -46,6 +95,14 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
|
||||
false,
|
||||
{ 0, 0, 0 } });
|
||||
std::cout << "Setup GameData done\n";
|
||||
|
||||
/* Create player */
|
||||
player = ecs.entity("player");
|
||||
Ogre::Vector3 playerPos(0, 0, 4);
|
||||
player.set<CharacterLocation>({ { 0, 0, 0, 1 }, playerPos });
|
||||
player.set<CharacterConf>({ "normal-male.glb" });
|
||||
player.add<Character>();
|
||||
player.add<Player>();
|
||||
}
|
||||
void update(float delta)
|
||||
{
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
#include <flecs.h>
|
||||
namespace ECS
|
||||
{
|
||||
extern flecs::entity player;
|
||||
void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
|
||||
Ogre::SceneNode *cameraNode, Ogre::Camera *camera);
|
||||
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
|
||||
Ogre::RenderWindow *window);
|
||||
void update(float delta);
|
||||
flecs::world get();
|
||||
template <class T> const T &get()
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
#include <OgreFileSystemLayer.h>
|
||||
#include "GameData.h"
|
||||
#include "Components.h"
|
||||
#include "GUIModule.h"
|
||||
#include "CharacterModule.h"
|
||||
#include "BoatModule.h"
|
||||
#include "EventTriggerModule.h"
|
||||
#include "LuaData.h"
|
||||
|
||||
extern "C" {
|
||||
int luaopen_lpeg(lua_State *L);
|
||||
}
|
||||
namespace ECS
|
||||
{
|
||||
struct idmap {
|
||||
std::unordered_map<int, flecs::entity> id2entity;
|
||||
int next_id;
|
||||
idmap()
|
||||
: id2entity({})
|
||||
, next_id(0)
|
||||
{
|
||||
}
|
||||
int get_next_id()
|
||||
{
|
||||
next_id++;
|
||||
return next_id;
|
||||
}
|
||||
int add_entity(flecs::entity e)
|
||||
{
|
||||
int id = get_next_id();
|
||||
id2entity[id] = e;
|
||||
return id;
|
||||
}
|
||||
flecs::entity get_entity(int id)
|
||||
{
|
||||
OgreAssert(id2entity[id].is_valid(), "Invalid entity");
|
||||
return id2entity[id];
|
||||
}
|
||||
};
|
||||
struct idmap idmap;
|
||||
|
||||
int LuaData::setup_handler()
|
||||
{
|
||||
luaL_checktype(L, 1, LUA_TFUNCTION);
|
||||
lua_pushvalue(L, 1);
|
||||
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
setup_handlers.push_back(ref);
|
||||
return 0;
|
||||
}
|
||||
int LuaData::call_handler(const Ogre::String &event)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < setup_handlers.size(); i++) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, setup_handlers[i]);
|
||||
lua_pushstring(L, event.c_str());
|
||||
lua_pcall(L, 1, 0, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int luaLibraryLoader(lua_State *L)
|
||||
{
|
||||
int i;
|
||||
if (!lua_isstring(L, 1)) {
|
||||
luaL_error(
|
||||
L,
|
||||
"luaLibraryLoader: Expected string for first parameter");
|
||||
}
|
||||
|
||||
std::string libraryFile = lua_tostring(L, 1);
|
||||
std::cout << libraryFile << std::endl;
|
||||
// In order to be compatible with the normal Lua file loader,
|
||||
// translate '.' to the file system seperator character.
|
||||
// In this case (An ogre resource) '/'
|
||||
while (libraryFile.find('.') != std::string::npos)
|
||||
libraryFile.replace(libraryFile.find('.'), 1, "/");
|
||||
|
||||
libraryFile += ".lua";
|
||||
std::cout << libraryFile << std::endl;
|
||||
Ogre::StringVectorPtr scripts =
|
||||
Ogre::ResourceGroupManager::getSingleton().listResourceNames(
|
||||
"LuaScripts", false);
|
||||
std::vector<Ogre::String> &strings = *scripts;
|
||||
for (i = 0; i < strings.size(); i++)
|
||||
std::cout << strings[i] << std::endl;
|
||||
|
||||
if (0 && !Ogre::ResourceGroupManager::getSingleton()
|
||||
.resourceExistsInAnyGroup(libraryFile)) {
|
||||
// Could not find the file.
|
||||
std::string errMessage = "\n no file '" + libraryFile +
|
||||
"' found in Ogre resource archives.";
|
||||
lua_pushstring(L, errMessage.c_str());
|
||||
} else {
|
||||
Ogre::DataStreamList streams =
|
||||
Ogre::ResourceGroupManager::getSingleton().openResources(
|
||||
"*.lua", "LuaScripts");
|
||||
Ogre::DataStreamPtr stream =
|
||||
Ogre::ResourceGroupManager::getSingleton().openResource(
|
||||
libraryFile, "LuaScripts");
|
||||
Ogre::String script = stream->getAsString();
|
||||
if (luaL_loadbuffer(L, script.c_str(), script.length(),
|
||||
libraryFile.c_str())) {
|
||||
luaL_error(
|
||||
L,
|
||||
"Error loading library '%s' from resource archive.\n%s",
|
||||
libraryFile.c_str(), lua_tostring(L, -1));
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void installLibraryLoader(lua_State *L)
|
||||
{
|
||||
// Insert the c++ func 'luaLibraryLoader' into package.loaders.
|
||||
// Inserted at the start of the table in order to take precedence.
|
||||
lua_getglobal(L, "table");
|
||||
lua_getfield(L, -1, "insert");
|
||||
lua_remove(L, -2); // table
|
||||
lua_getglobal(L, "package");
|
||||
lua_getfield(L, -1, "searchers");
|
||||
lua_remove(L, -2); // package
|
||||
lua_pushnumber(L, 1); // index where to insert into loaders table
|
||||
lua_pushcfunction(L, luaLibraryLoader);
|
||||
if (lua_pcall(L, 3, 0, 0))
|
||||
Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1);
|
||||
}
|
||||
|
||||
LuaData::LuaData()
|
||||
: L(luaL_newstate())
|
||||
{
|
||||
luaopen_base(L);
|
||||
luaopen_table(L);
|
||||
luaopen_package(L);
|
||||
luaL_requiref(L, "table", luaopen_table, 1);
|
||||
lua_pop(L, 1);
|
||||
luaL_requiref(L, "math", luaopen_math, 1);
|
||||
lua_pop(L, 1);
|
||||
luaL_requiref(L, "package", luaopen_package, 1);
|
||||
lua_pop(L, 1);
|
||||
luaL_requiref(L, "string", luaopen_string, 1);
|
||||
lua_pop(L, 1);
|
||||
luaL_requiref(L, "io", luaopen_io, 1);
|
||||
lua_pop(L, 1);
|
||||
luaL_requiref(L, "lpeg", luaopen_lpeg, 1);
|
||||
lua_pop(L, 1);
|
||||
#if 0
|
||||
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
|
||||
lua_pushcfunction(L, luaopen_lpeg);
|
||||
lua_setfield(L, -2, "lpeg");
|
||||
lua_pop(L, 1); // remove PRELOAD table
|
||||
#endif
|
||||
installLibraryLoader(L);
|
||||
lua_pop(L, 1);
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
OgreAssert(false, "Crash function called");
|
||||
return 0;
|
||||
});
|
||||
lua_setglobal(L, "crash");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
luaL_checktype(L, 1, LUA_TFUNCTION);
|
||||
ECS::get<LuaBase>().mLua->setup_handler();
|
||||
return 0;
|
||||
});
|
||||
lua_setglobal(L, "setup_handler");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
int args = lua_gettop(L);
|
||||
if (args < 1)
|
||||
return 0;
|
||||
ECS::get_mut<GUI>().choices.clear();
|
||||
luaL_checktype(L, 1, LUA_TSTRING);
|
||||
if (args > 1) {
|
||||
luaL_checktype(L, 2, LUA_TTABLE);
|
||||
lua_pushnil(L); /* first key */
|
||||
while (lua_next(L, 2) != 0) {
|
||||
Ogre::String s = lua_tostring(L, -1);
|
||||
ECS::get_mut<GUI>().choices.push_back(s);
|
||||
lua_pop(L, 1); /* remove value but keep key */
|
||||
}
|
||||
}
|
||||
size_t len;
|
||||
Ogre::String message(luaL_tolstring(L, 1, &len));
|
||||
std::cout << "narrator message: " << message
|
||||
<< " length: " << message.length() << std::endl;
|
||||
if (message.length() == 0 && ECS::get_mut<GUI>().narrationBox) {
|
||||
ECS::get_mut<GUI>().enabled = false;
|
||||
ECS::get_mut<GUI>().grab = true;
|
||||
ECS::get_mut<GUI>().grabChanged = true;
|
||||
ECS::get_mut<GUI>().narrationText = message;
|
||||
ECS::get_mut<GUI>().narrationBox = false;
|
||||
ECS::modified<GUI>();
|
||||
std::cout << "narration ended\n";
|
||||
} else if (message.length() > 0) {
|
||||
ECS::get_mut<GUI>().enabled = true;
|
||||
ECS::get_mut<GUI>().grab = false;
|
||||
ECS::get_mut<GUI>().grabChanged = true;
|
||||
ECS::get_mut<GUI>().narrationText = message;
|
||||
ECS::get_mut<GUI>().narrationBox = true;
|
||||
ECS::modified<GUI>();
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
lua_setglobal(L, "narrate");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
// ECS::get_mut<GUI>().mainMenu = true;
|
||||
lua_pushinteger(L, ECS::get<GUI>().narration_answer);
|
||||
return 1;
|
||||
});
|
||||
lua_setglobal(L, "narration_get_answer");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
// ECS::get_mut<GUI>().mainMenu = true;
|
||||
ECS::get_mut<GUI>().enabled = true;
|
||||
ECS::get_mut<GUI>().mainMenu = true;
|
||||
ECS::get_mut<GUI>().grab = false;
|
||||
ECS::get_mut<GUI>().grabChanged = true;
|
||||
ECS::modified<GUI>();
|
||||
return 0;
|
||||
});
|
||||
lua_setglobal(L, "main_menu");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
OgreAssert(lua_gettop(L) == 3, "Invalid parameters");
|
||||
luaL_checktype(L, 1, LUA_TSTRING);
|
||||
luaL_checktype(L, 2, LUA_TSTRING);
|
||||
luaL_checktype(L, 3, LUA_TBOOLEAN);
|
||||
bool enable = lua_toboolean(L, 3);
|
||||
flecs::entity e = ECS::get().lookup(lua_tostring(L, 1));
|
||||
Ogre::String what = lua_tostring(L, 2);
|
||||
OgreAssert(e.is_valid(), "Invalid character");
|
||||
OgreAssert(e.has<Character>(), "Not a character");
|
||||
if (what == "gravity") {
|
||||
/* clear momentum */
|
||||
e.get_mut<CharacterVelocity>().gvelocity.y = 0.0f;
|
||||
e.get_mut<CharacterVelocity>().velocity.y = 0.0f;
|
||||
e.modified<CharacterVelocity>();
|
||||
if (enable)
|
||||
e.add<CharacterGravity>();
|
||||
else
|
||||
e.remove<CharacterGravity>();
|
||||
} else if (what == "buoyancy") {
|
||||
if (enable)
|
||||
e.add<CharacterBuoyancy>();
|
||||
else
|
||||
e.remove<CharacterBuoyancy>();
|
||||
} else
|
||||
OgreAssert(false, "Bad parameter " + what);
|
||||
return 0;
|
||||
});
|
||||
lua_setglobal(L, "ecs_character_params_set");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
luaL_checktype(L, 1, LUA_TSTRING);
|
||||
luaL_checktype(L, 2, LUA_TNUMBER);
|
||||
luaL_checktype(L, 3, LUA_TNUMBER);
|
||||
luaL_checktype(L, 4, LUA_TNUMBER);
|
||||
luaL_checktype(L, 5, LUA_TNUMBER);
|
||||
Ogre::String what = lua_tostring(L, 1);
|
||||
if (what == "boat") {
|
||||
float yaw = lua_tonumber(L, 5);
|
||||
float x = lua_tonumber(L, 2);
|
||||
float y = lua_tonumber(L, 3);
|
||||
float z = lua_tonumber(L, 4);
|
||||
flecs::entity e = ECS::get().entity();
|
||||
Ogre::Quaternion orientation(Ogre::Radian(yaw),
|
||||
Ogre::Vector3(0, 1, 0));
|
||||
Ogre::Vector3 position(x, y, z);
|
||||
e.set<BoatType>({ "boat.glb", position, orientation });
|
||||
lua_pushinteger(L, idmap.add_entity(e));
|
||||
return 1;
|
||||
}
|
||||
lua_pushinteger(L, -1);
|
||||
return 1;
|
||||
});
|
||||
lua_setglobal(L, "ecs_vehicle_set");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
OgreAssert(lua_gettop(L) == 6, "bad parameters");
|
||||
luaL_checktype(L, 1, LUA_TSTRING); // event
|
||||
luaL_checktype(L, 2, LUA_TNUMBER); // x
|
||||
luaL_checktype(L, 3, LUA_TNUMBER); // y
|
||||
luaL_checktype(L, 4, LUA_TNUMBER); // z
|
||||
luaL_checktype(L, 5, LUA_TNUMBER); // halfh
|
||||
luaL_checktype(L, 6, LUA_TNUMBER); // radius
|
||||
flecs::entity e = ECS::get().entity();
|
||||
Ogre::String event = lua_tostring(L, 1);
|
||||
float x = lua_tonumber(L, 2);
|
||||
float y = lua_tonumber(L, 3);
|
||||
float z = lua_tonumber(L, 4);
|
||||
float h = lua_tonumber(L, 5);
|
||||
float r = lua_tonumber(L, 6);
|
||||
Ogre::Vector3 position(x, y, z);
|
||||
e.set<EventTrigger>({ nullptr, position, h, r, event });
|
||||
lua_pushinteger(L, idmap.add_entity(e));
|
||||
return 1;
|
||||
});
|
||||
lua_setglobal(L, "ecs_character_trigger");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
OgreAssert(lua_gettop(L) == 7, "bad parameters");
|
||||
luaL_checktype(L, 1, LUA_TNUMBER); // parent
|
||||
luaL_checktype(L, 2, LUA_TSTRING); // event
|
||||
luaL_checktype(L, 3, LUA_TNUMBER); // x
|
||||
luaL_checktype(L, 4, LUA_TNUMBER); // y
|
||||
luaL_checktype(L, 5, LUA_TNUMBER); // z
|
||||
luaL_checktype(L, 6, LUA_TNUMBER); // halfh
|
||||
luaL_checktype(L, 7, LUA_TNUMBER); // radius
|
||||
int parent = lua_tointeger(L, 1);
|
||||
flecs::entity parent_e = idmap.get_entity(parent);
|
||||
Ogre::SceneNode *parentNode = nullptr;
|
||||
if (parent_e.has<CharacterBase>())
|
||||
parentNode = parent_e.get<CharacterBase>().mBodyNode;
|
||||
|
||||
else if (parent_e.has<BoatBase>())
|
||||
parentNode = parent_e.get<BoatBase>().mNode;
|
||||
flecs::entity e = ECS::get().entity().child_of(parent_e);
|
||||
Ogre::String event = lua_tostring(L, 2);
|
||||
float x = lua_tonumber(L, 3);
|
||||
float y = lua_tonumber(L, 4);
|
||||
float z = lua_tonumber(L, 5);
|
||||
float h = lua_tonumber(L, 6);
|
||||
float r = lua_tonumber(L, 7);
|
||||
OgreAssert(parentNode, "bad parent");
|
||||
Ogre::Vector3 position(x, y, z);
|
||||
e.set<EventTrigger>({ parentNode, position, h, r, event });
|
||||
lua_pushinteger(L, idmap.add_entity(e));
|
||||
return 1;
|
||||
});
|
||||
lua_setglobal(L, "ecs_child_character_trigger");
|
||||
lua_pushcfunction(L, [](lua_State *L) -> int {
|
||||
OgreAssert(lua_gettop(L) == 5, "Invalid parameters");
|
||||
luaL_checktype(L, 1, LUA_TSTRING); // type
|
||||
luaL_checktype(L, 2, LUA_TNUMBER);
|
||||
luaL_checktype(L, 3, LUA_TNUMBER);
|
||||
luaL_checktype(L, 4, LUA_TNUMBER);
|
||||
luaL_checktype(L, 5, LUA_TNUMBER);
|
||||
Ogre::String type = lua_tostring(L, 1);
|
||||
float yaw = lua_tonumber(L, 5);
|
||||
float x = lua_tonumber(L, 2);
|
||||
float y = lua_tonumber(L, 3);
|
||||
float z = lua_tonumber(L, 4);
|
||||
flecs::entity e = ECS::get().entity();
|
||||
Ogre::Quaternion orientation(Ogre::Radian(yaw),
|
||||
Ogre::Vector3(0, 1, 0));
|
||||
Ogre::Vector3 npcPos(x, y, z);
|
||||
e.set<CharacterLocation>({ orientation, npcPos });
|
||||
e.set<CharacterConf>({ type });
|
||||
e.add<Character>();
|
||||
lua_pushinteger(L, idmap.add_entity(e));
|
||||
return 1;
|
||||
});
|
||||
lua_setglobal(L, "ecs_npc_set");
|
||||
}
|
||||
|
||||
LuaData::~LuaData()
|
||||
{
|
||||
lua_close(L);
|
||||
}
|
||||
|
||||
void LuaData::lateSetup()
|
||||
{
|
||||
#if 0
|
||||
Ogre::DataStreamList streams =
|
||||
Ogre::ResourceGroupManager::getSingleton().openResources(
|
||||
"*.lua", "LuaScripts");
|
||||
while (!streams.empty()) {
|
||||
Ogre::DataStreamPtr s = streams.front();
|
||||
std::cout << "stream: " << s->getAsString() << "\n";
|
||||
streams.pop_front();
|
||||
if (luaL_dostring(L, s->getAsString().c_str()) != LUA_OK) {
|
||||
std::cout << "error: " << lua_tostring(L, -1) << "\n";
|
||||
OgreAssert(false, "Script failure");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Ogre::DataStreamPtr stream =
|
||||
Ogre::ResourceGroupManager::getSingleton().openResource(
|
||||
"data.lua", "LuaScripts");
|
||||
std::cout << "stream: " << stream->getAsString() << "\n";
|
||||
if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) {
|
||||
std::cout << "error: " << lua_tostring(L, -1) << "\n";
|
||||
OgreAssert(false, "Script failure");
|
||||
}
|
||||
|
||||
const char *lua_code = "\n\
|
||||
function stuff()\n\
|
||||
return 4\n\
|
||||
end\n\
|
||||
x = stuff()\n\
|
||||
";
|
||||
luaL_dostring(L, lua_code);
|
||||
lua_getglobal(L, "x");
|
||||
int x = lua_tonumber(L, 1);
|
||||
std::cout << "lua: " << x << "\n";
|
||||
}
|
||||
|
||||
LuaModule::LuaModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.component<LuaBase>()
|
||||
.on_add([](LuaBase &lua) {
|
||||
lua.mLua = new LuaData;
|
||||
lua.setup_called = false;
|
||||
lua.startup_called = false;
|
||||
})
|
||||
.add(flecs::Singleton);
|
||||
ecs.add<LuaBase>();
|
||||
ecs.system<const EngineData, LuaBase>("LuaUpdate")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](const EngineData &eng, LuaBase &lua) {
|
||||
if (!lua.setup_called) {
|
||||
lua.mLua->lateSetup();
|
||||
lua.mLua->call_handler("setup");
|
||||
lua.setup_called = true;
|
||||
}
|
||||
if (!lua.startup_called) {
|
||||
if (eng.startupDelay <= 0.0f) {
|
||||
lua.mLua->call_handler("startup");
|
||||
lua.startup_called = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef LUA_DATA_H
|
||||
#define LUA_DATA_H
|
||||
#include "lua.hpp"
|
||||
#include <iostream>
|
||||
#include <Ogre.h>
|
||||
#include <OgreSerializer.h>
|
||||
#include <flecs.h>
|
||||
namespace ECS
|
||||
{
|
||||
struct LuaData {
|
||||
lua_State *L;
|
||||
std::vector<int> setup_handlers;
|
||||
int setup_handler();
|
||||
int call_handler(const Ogre::String &event);
|
||||
|
||||
LuaData();
|
||||
virtual ~LuaData();
|
||||
void lateSetup();
|
||||
};
|
||||
struct LuaBase {
|
||||
LuaData *mLua;
|
||||
bool setup_called;
|
||||
bool startup_called;
|
||||
};
|
||||
struct LuaModule {
|
||||
LuaModule(flecs::world &ecs);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -7,7 +7,6 @@ namespace ECS
|
||||
SunModule::SunModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.component<Sun>().add(flecs::Singleton);
|
||||
ecs.set<Sun>({ nullptr, nullptr, nullptr, nullptr });
|
||||
ecs.system<const EngineData, Sun>("UpdateSetupSun")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](const EngineData &eng, Sun &sun) {
|
||||
|
||||
+318
-142
@@ -17,7 +17,7 @@
|
||||
#include "TerrainModule.h"
|
||||
|
||||
#define TERRAIN_SIZE 129
|
||||
#define TERRAIN_WORLD_SIZE 4000.0f
|
||||
#define TERRAIN_WORLD_SIZE 1000.0f
|
||||
#define ENDLESS_TERRAIN_FILE_PREFIX Ogre::String("EndlessWorldTerrain")
|
||||
#define ENDLESS_TERRAIN_FILE_SUFFIX Ogre::String("dat")
|
||||
|
||||
@@ -31,40 +31,78 @@
|
||||
namespace ECS
|
||||
{
|
||||
|
||||
class FlatTerrainDefiner
|
||||
: public Ogre::TerrainPagedWorldSection::TerrainDefiner,
|
||||
public Ogre::FrameListener {
|
||||
Ogre::SceneManager *mScnMgr;
|
||||
Ogre::Bullet::DynamicsWorld *mWorld;
|
||||
struct gen_collider {
|
||||
Ogre::TerrainGroup *group;
|
||||
long x;
|
||||
long y;
|
||||
};
|
||||
std::deque<struct gen_collider> collider_queue;
|
||||
Ogre::Image img, img_noise, img_brushes;
|
||||
|
||||
public:
|
||||
FlatTerrainDefiner(Ogre::SceneManager *scm,
|
||||
Ogre::Bullet::DynamicsWorld *world)
|
||||
: Ogre::TerrainPagedWorldSection::TerrainDefiner()
|
||||
, Ogre::FrameListener()
|
||||
, mScnMgr(scm)
|
||||
, mWorld(world)
|
||||
#define BRUSH_SIZE 64
|
||||
struct HeightData {
|
||||
Ogre::Image img;
|
||||
Ogre::Image img_brushes;
|
||||
Ogre::Image img_noise;
|
||||
static HeightData *singleton;
|
||||
HeightData()
|
||||
{
|
||||
Ogre::Root::getSingleton().addFrameListener(this);
|
||||
img.load(
|
||||
"world_map.png",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
img_noise.load(
|
||||
"terrain.png",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
img_brushes.load(
|
||||
"brushes.png",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
img_noise.load(
|
||||
"terrain.png",
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
}
|
||||
static HeightData *get_singleton()
|
||||
{
|
||||
if (!singleton)
|
||||
singleton = new HeightData();
|
||||
return singleton;
|
||||
}
|
||||
float get_brush_height(int id, int x, int y)
|
||||
{
|
||||
int m = 0;
|
||||
switch (id) {
|
||||
case 0:
|
||||
m = 0;
|
||||
break;
|
||||
case 1:
|
||||
m = BRUSH_SIZE;
|
||||
break;
|
||||
default:
|
||||
OgreAssert(false, "bad brush id");
|
||||
break;
|
||||
}
|
||||
return img_brushes.getColourAt(x, y + m, 0).r;
|
||||
}
|
||||
float get_base_height(const Ogre::Vector2 &worldOffset, int x, int y)
|
||||
{
|
||||
float height = 0.0f;
|
||||
int world_x = worldOffset.x + x;
|
||||
int world_y = worldOffset.y + y;
|
||||
int world_img_x =
|
||||
world_x + (int)img.getWidth() * BRUSH_SIZE / 2;
|
||||
int world_img_y =
|
||||
world_y + (int)img.getHeight() * BRUSH_SIZE / 2;
|
||||
Ogre::ColourValue color, colorb1, colorb2;
|
||||
// float d;
|
||||
int div = 1;
|
||||
int map_img_x = world_img_x / (BRUSH_SIZE) / div;
|
||||
int map_img_y = world_img_y / (BRUSH_SIZE) / div;
|
||||
int brush_img_x = (world_img_x / div) % BRUSH_SIZE;
|
||||
int brush_img_y = (world_img_y / div) % BRUSH_SIZE;
|
||||
if (world_img_x < 0 ||
|
||||
world_img_x >= img.getWidth() * BRUSH_SIZE ||
|
||||
world_img_y < 0 ||
|
||||
world_img_y >= img.getWidth() * BRUSH_SIZE) {
|
||||
height = -1.0f;
|
||||
goto out;
|
||||
}
|
||||
color = img.getColourAt(map_img_x, map_img_y, 0);
|
||||
colorb1 = img_brushes.getColourAt(brush_img_x,
|
||||
brush_img_y + BRUSH_SIZE, 0);
|
||||
colorb2 = img_brushes.getColourAt(brush_img_x, brush_img_y, 0);
|
||||
// d = Ogre::Math::saturate(color.r - 0.05f);
|
||||
height = color.r;
|
||||
out:
|
||||
return height;
|
||||
}
|
||||
|
||||
private:
|
||||
float get_noise_height(const Ogre::Vector2 &worldOffset, int x, int y)
|
||||
{
|
||||
int h;
|
||||
@@ -101,55 +139,60 @@ private:
|
||||
}
|
||||
return noise_values[0] + noise_values[1];
|
||||
}
|
||||
#define BRUSH_SIZE 64
|
||||
float get_brush_height(int id, int x, int y)
|
||||
float get_height(Ogre::TerrainGroup *terrainGroup, long x, long y,
|
||||
int j, int i)
|
||||
{
|
||||
int m = 0;
|
||||
switch (id) {
|
||||
case 0:
|
||||
m = 0;
|
||||
break;
|
||||
case 1:
|
||||
m = BRUSH_SIZE;
|
||||
break;
|
||||
default:
|
||||
OgreAssert(false, "bad brush id");
|
||||
break;
|
||||
}
|
||||
return img_brushes.getColourAt(x, y + m, 0).r;
|
||||
}
|
||||
float get_base_height(const Ogre::Vector2 &worldOffset, int x, int y)
|
||||
{
|
||||
float height = 0.0f;
|
||||
int world_x = worldOffset.x + x;
|
||||
int world_y = worldOffset.y + y;
|
||||
int world_img_x =
|
||||
world_x + (int)img.getWidth() * BRUSH_SIZE / 2;
|
||||
int world_img_y =
|
||||
world_y + (int)img.getHeight() * BRUSH_SIZE / 2;
|
||||
Ogre::ColourValue color, colorb1, colorb2;
|
||||
// float d;
|
||||
int map_img_x = world_img_x / (BRUSH_SIZE);
|
||||
int map_img_y = world_img_y / (BRUSH_SIZE);
|
||||
int brush_img_x = world_img_x % BRUSH_SIZE;
|
||||
int brush_img_y = world_img_y % BRUSH_SIZE;
|
||||
if (world_img_x < 0 ||
|
||||
world_img_x >= img.getWidth() * BRUSH_SIZE ||
|
||||
world_img_y < 0 ||
|
||||
world_img_y >= img.getWidth() * BRUSH_SIZE) {
|
||||
height = -1.0f;
|
||||
goto out;
|
||||
}
|
||||
color = img.getColourAt(map_img_x, map_img_y, 0);
|
||||
colorb1 = img_brushes.getColourAt(brush_img_x,
|
||||
brush_img_y + BRUSH_SIZE, 0);
|
||||
colorb2 = img_brushes.getColourAt(brush_img_x, brush_img_y, 0);
|
||||
// d = Ogre::Math::saturate(color.r - 0.05f);
|
||||
height = color.r;
|
||||
out:
|
||||
uint16_t terrainSize = terrainGroup->getTerrainSize();
|
||||
Ogre::Vector2 worldOffset(Ogre::Real(x * (terrainSize - 1)),
|
||||
Ogre::Real(y * (terrainSize - 1)));
|
||||
float brush_height0 =
|
||||
HeightData::get_singleton()->get_brush_height(
|
||||
0, j % BRUSH_SIZE, i % BRUSH_SIZE) -
|
||||
0.55f;
|
||||
float brush_height1 =
|
||||
HeightData::get_singleton()->get_brush_height(
|
||||
1, j % BRUSH_SIZE, i % BRUSH_SIZE) -
|
||||
0.55f;
|
||||
float mheight =
|
||||
Ogre::Math::lerp(
|
||||
brush_height1, brush_height0,
|
||||
HeightData::get_singleton()->get_base_height(
|
||||
worldOffset, j, i)) *
|
||||
120.0f;
|
||||
float height = mheight;
|
||||
if (mheight > 0.5f)
|
||||
height += 2.0f + get_noise_height(worldOffset, j, i);
|
||||
else if (mheight < -0.5f)
|
||||
height -= 2.0f + get_noise_height(worldOffset, j, i);
|
||||
return height;
|
||||
}
|
||||
};
|
||||
HeightData *HeightData::singleton = nullptr;
|
||||
|
||||
class FlatTerrainDefiner
|
||||
: public Ogre::TerrainPagedWorldSection::TerrainDefiner,
|
||||
public Ogre::FrameListener {
|
||||
Ogre::SceneManager *mScnMgr;
|
||||
Ogre::Bullet::DynamicsWorld *mWorld;
|
||||
struct gen_collider {
|
||||
Ogre::TerrainGroup *group;
|
||||
long x;
|
||||
long y;
|
||||
};
|
||||
std::deque<struct gen_collider> collider_queue;
|
||||
|
||||
public:
|
||||
FlatTerrainDefiner(Ogre::SceneManager *scm,
|
||||
Ogre::Bullet::DynamicsWorld *world)
|
||||
: Ogre::TerrainPagedWorldSection::TerrainDefiner()
|
||||
, Ogre::FrameListener()
|
||||
, mScnMgr(scm)
|
||||
, mWorld(world)
|
||||
{
|
||||
Ogre::Root::getSingleton().addFrameListener(this);
|
||||
}
|
||||
|
||||
private:
|
||||
public:
|
||||
void define(Ogre::TerrainGroup *terrainGroup, long x, long y) override
|
||||
{
|
||||
@@ -158,37 +201,15 @@ public:
|
||||
MEMCATEGORY_GEOMETRY);
|
||||
// float *heightMapCollider = OGRE_ALLOC_T(
|
||||
// float, terrainSize *terrainSize, MEMCATEGORY_GEOMETRY);
|
||||
Ogre::Vector2 worldOffset(Ogre::Real(x * (terrainSize - 1)),
|
||||
Ogre::Real(y * (terrainSize - 1)));
|
||||
Ogre::Vector2 worldOrigin =
|
||||
Ogre::Vector2(img.getWidth(), img.getHeight()) * 0.5f;
|
||||
// Ogre::Vector2 worldOrigin =
|
||||
// Ogre::Vector2(img.getWidth(), img.getHeight()) * 0.5f;
|
||||
float chunk = 128.0f;
|
||||
Ogre::Vector2 revisedValuePoint;
|
||||
for (int i = 0; i < terrainSize; i++)
|
||||
for (int j = 0; j < terrainSize; j++) {
|
||||
float brush_height0 =
|
||||
get_brush_height(0, j % BRUSH_SIZE,
|
||||
i % BRUSH_SIZE) -
|
||||
0.55f;
|
||||
float brush_height1 =
|
||||
get_brush_height(1, j % BRUSH_SIZE,
|
||||
i % BRUSH_SIZE) -
|
||||
0.55f;
|
||||
float mheight =
|
||||
Ogre::Math::lerp(
|
||||
brush_height1, brush_height0,
|
||||
get_base_height(worldOffset, j,
|
||||
i)) *
|
||||
120.0f;
|
||||
float height = mheight;
|
||||
if (mheight > 0.5f)
|
||||
height += 2.0f +
|
||||
get_noise_height(worldOffset,
|
||||
j, i);
|
||||
else if (mheight < -0.5f)
|
||||
height -= 2.0f +
|
||||
get_noise_height(worldOffset,
|
||||
j, i);
|
||||
float height =
|
||||
HeightData::get_singleton()->get_height(
|
||||
terrainGroup, x, y, j, i);
|
||||
|
||||
// height = -2.0f;
|
||||
heightMap[i * terrainSize + j] = height;
|
||||
@@ -237,11 +258,14 @@ public:
|
||||
float maxH = terrain->getMaxHeight();
|
||||
int size = terrain->getSize();
|
||||
float worldSize = terrain->getWorldSize();
|
||||
if (!created || true) {
|
||||
if (true) {
|
||||
btRigidBody *body =
|
||||
mWorld->addTerrainRigidBody(
|
||||
group, x, y, 2,
|
||||
0x7ffffffd & (~16));
|
||||
OgreAssert(
|
||||
body,
|
||||
"Could not create RigidBody");
|
||||
Ogre::LogManager::getSingleton().logError(
|
||||
"created rigid body " +
|
||||
Ogre::StringConverter::toString(
|
||||
@@ -274,10 +298,40 @@ public:
|
||||
created = true;
|
||||
}
|
||||
collider_queue.pop_front();
|
||||
// FIXME: create entities and components instead
|
||||
Ogre::SceneNode *items =
|
||||
terrain->_getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
for (int i = 0; i < ECS::get<PlacementObjects>()
|
||||
.altar_items.size();
|
||||
i++) {
|
||||
const struct PlacementObjects::item &item =
|
||||
ECS::get<PlacementObjects>()
|
||||
.altar_items[i];
|
||||
Ogre::Entity *ent =
|
||||
group->getSceneManager()
|
||||
->createEntity(
|
||||
item.entity);
|
||||
Ogre::SceneNode *what =
|
||||
items->createChildSceneNode();
|
||||
what->attachObject(ent);
|
||||
what->setOrientation(item.rotation);
|
||||
what->setPosition(item.position);
|
||||
ECS::get<EngineData>()
|
||||
.mWorld->addRigidBody(
|
||||
0, ent,
|
||||
Ogre::Bullet::CT_TRIMESH,
|
||||
nullptr, 2, 0x7fffffff);
|
||||
}
|
||||
} else {
|
||||
output.push_back(collider_queue.front());
|
||||
collider_queue.pop_front();
|
||||
}
|
||||
if (collider_queue.empty() &&
|
||||
!ECS::get<Terrain>().mTerrainReady) {
|
||||
ECS::get_mut<Terrain>().mTerrainReady = true;
|
||||
ECS::modified<Terrain>();
|
||||
}
|
||||
}
|
||||
collider_queue = output;
|
||||
}
|
||||
@@ -319,6 +373,8 @@ struct TerrainPrivate {
|
||||
|
||||
TerrainModule::TerrainModule(flecs::world &ecs)
|
||||
{
|
||||
struct CanSetPlayerPosition {};
|
||||
ecs.component<CanSetPlayerPosition>().add(flecs::Singleton);
|
||||
ecs.component<Terrain>().add(flecs::Singleton);
|
||||
ecs.component<TerrainPrivate>().add(flecs::Singleton);
|
||||
ecs.set<TerrainPrivate>({ nullptr, {} });
|
||||
@@ -328,7 +384,7 @@ TerrainModule::TerrainModule(flecs::world &ecs)
|
||||
.each([](const EngineData &eng, const Camera &camera,
|
||||
const Sun &sun, Terrain &terrain,
|
||||
TerrainPrivate &priv) {
|
||||
if (!terrain.mTerrainGroup) {
|
||||
if (!terrain.mTerrainGroup && sun.mSun && eng.mScnMgr) {
|
||||
std::cout << "Terrain setup\n";
|
||||
if (!priv.mDummyPageProvider)
|
||||
priv.mDummyPageProvider =
|
||||
@@ -338,6 +394,9 @@ TerrainModule::TerrainModule(flecs::world &ecs)
|
||||
terrain.mTerrainGlobals =
|
||||
OGRE_NEW Ogre::TerrainGlobalOptions();
|
||||
|
||||
OgreAssert(terrain.mTerrainGlobals,
|
||||
"Failed to allocate global options");
|
||||
|
||||
Ogre::LogManager::getSingleton().setMinLogLevel(
|
||||
Ogre::LML_TRIVIAL);
|
||||
|
||||
@@ -422,48 +481,16 @@ TerrainModule::TerrainModule(flecs::world &ecs)
|
||||
|
||||
terrain.mTerrainGroup->freeTemporaryResources();
|
||||
std::cout << "Terrain setup done\n";
|
||||
ECS::get().set<PlacementObjects>({});
|
||||
}
|
||||
bool playerCheck = false;
|
||||
ECS::get()
|
||||
.query_builder<CharacterBase, CharacterBody>()
|
||||
.with<Character>()
|
||||
.with<Player>()
|
||||
.each([&playerCheck,
|
||||
terrain](flecs::entity e,
|
||||
CharacterBase &ch,
|
||||
CharacterBody &body) {
|
||||
if (!body.checkGround)
|
||||
body.checkGround = true;
|
||||
if (ch.mBodyNode &&
|
||||
body.checkGroundResult) {
|
||||
long x, y;
|
||||
Ogre::Vector3 pos =
|
||||
ch.mBodyNode
|
||||
->getPosition();
|
||||
terrain.mTerrainGroup
|
||||
->convertWorldPositionToTerrainSlot(
|
||||
pos, &x, &y);
|
||||
if (terrain.mTerrainGroup
|
||||
->getTerrain(x,
|
||||
y) &&
|
||||
terrain.mTerrainGroup
|
||||
->getTerrain(x, y)
|
||||
->isLoaded())
|
||||
playerCheck = true;
|
||||
}
|
||||
});
|
||||
if (playerCheck)
|
||||
terrain.mTerrainReady = true;
|
||||
if (priv.mSunUpdate.getMilliseconds() > 1000) {
|
||||
Ogre::TerrainGlobalOptions::getSingleton()
|
||||
.setCompositeMapAmbient(
|
||||
eng.mScnMgr->getAmbientLight());
|
||||
Ogre::TerrainGlobalOptions::getSingleton()
|
||||
.setCompositeMapDiffuse(
|
||||
sun.mSun->getDiffuseColour());
|
||||
Ogre::TerrainGlobalOptions::getSingleton()
|
||||
.setLightMapDirection(
|
||||
sun.mSun->getDerivedDirection());
|
||||
if (sun.mSun &&
|
||||
priv.mSunUpdate.getMilliseconds() > 1000) {
|
||||
terrain.mTerrainGlobals->setCompositeMapAmbient(
|
||||
eng.mScnMgr->getAmbientLight());
|
||||
terrain.mTerrainGlobals->setCompositeMapDiffuse(
|
||||
sun.mSun->getDiffuseColour());
|
||||
terrain.mTerrainGlobals->setLightMapDirection(
|
||||
sun.mSun->getDerivedDirection());
|
||||
std::cout << "sun pitch: "
|
||||
<< sun.mSunNode->getOrientation()
|
||||
.getPitch()
|
||||
@@ -471,5 +498,154 @@ TerrainModule::TerrainModule(flecs::world &ecs)
|
||||
priv.mSunUpdate.reset();
|
||||
}
|
||||
});
|
||||
ecs.system<const CharacterBase, const Terrain>("UpdateTerrainStatus")
|
||||
.kind(flecs::OnUpdate)
|
||||
.without<TerrainReady>()
|
||||
.each([](const CharacterBase &ch, const Terrain &terrain) {
|
||||
std::cout << "mTerrainReady: " << terrain.mTerrainReady
|
||||
<< "\n";
|
||||
std::cout << "mBodyNode: " << ch.mBodyNode << "\n";
|
||||
if (ch.mBodyNode && terrain.mTerrainReady) {
|
||||
long x, y;
|
||||
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
|
||||
terrain.mTerrainGroup
|
||||
->convertWorldPositionToTerrainSlot(
|
||||
pos, &x, &y);
|
||||
if (terrain.mTerrainGroup->getTerrain(x, y) &&
|
||||
terrain.mTerrainGroup->getTerrain(x, y)
|
||||
->isLoaded())
|
||||
ECS::get().add<TerrainReady>();
|
||||
}
|
||||
});
|
||||
ecs.system<const Terrain, PlacementObjects>("UpdatePlacementObjects")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](const Terrain &terrain, PlacementObjects &placement) {
|
||||
if (placement.altar_items.size() == 0) {
|
||||
struct PlacementObjects::item item;
|
||||
int i, j;
|
||||
int worldSize = terrain.mTerrainGroup
|
||||
->getTerrainWorldSize();
|
||||
uint16_t terrainSize =
|
||||
terrain.mTerrainGroup->getTerrainSize();
|
||||
item.entity = "altar.glb";
|
||||
item.rotation = Ogre::Quaternion(0, 0, 0, 1);
|
||||
item.position = Ogre::Vector3(0, 0, 0);
|
||||
float height =
|
||||
HeightData::get_singleton()->get_height(
|
||||
terrain.mTerrainGroup, 0, 0, 0,
|
||||
0);
|
||||
item.position.y = height;
|
||||
placement.altar_items.push_back(item);
|
||||
for (i = -64000; i < 64000; i += 2000)
|
||||
for (j = -64000; j < 64000; j += 2000) {
|
||||
if (i == 0 && j == 0)
|
||||
continue;
|
||||
Ogre::Vector3 position(i, 0, j);
|
||||
long xslot, yslot;
|
||||
terrain.mTerrainGroup
|
||||
->convertWorldPositionToTerrainSlot(
|
||||
position,
|
||||
&xslot, &yslot);
|
||||
Ogre::Vector3 slotpos;
|
||||
terrain.mTerrainGroup
|
||||
->convertTerrainSlotToWorldPosition(
|
||||
xslot, yslot,
|
||||
&slotpos);
|
||||
Ogre::Vector3 offset =
|
||||
(position - slotpos) *
|
||||
terrainSize / worldSize;
|
||||
height =
|
||||
HeightData::get_singleton()
|
||||
->get_height(
|
||||
terrain.mTerrainGroup,
|
||||
xslot,
|
||||
yslot,
|
||||
(int)offset.x +
|
||||
(terrainSize -
|
||||
1) / 2,
|
||||
(int)offset.z +
|
||||
(terrainSize -
|
||||
1) / 2);
|
||||
#if 0
|
||||
height =
|
||||
terrain.mTerrainGroup
|
||||
->getHeightAtWorldPosition(
|
||||
position);
|
||||
#endif
|
||||
if (height > -9.0f)
|
||||
continue;
|
||||
#ifdef VDEBUG
|
||||
std::cout << "worldSize: "
|
||||
<< worldSize - 1
|
||||
<< std::endl;
|
||||
std::cout << "height: " << i
|
||||
<< " " << j << " "
|
||||
<< height
|
||||
<< std::endl;
|
||||
#endif
|
||||
item.entity = "altar.glb";
|
||||
item.rotation =
|
||||
Ogre::Quaternion(0, 0,
|
||||
0, 1);
|
||||
position.y = height;
|
||||
item.position = position;
|
||||
placement.altar_items.push_back(
|
||||
item);
|
||||
}
|
||||
#ifdef VDEBUG
|
||||
for (i = 0; i < placement.altar_items.size();
|
||||
i++) {
|
||||
std::cout << "placement: " << i << " "
|
||||
<< placement.altar_items[i]
|
||||
.position
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
ECS::get().add<CanSetPlayerPosition>();
|
||||
}
|
||||
});
|
||||
ecs.system<const Terrain>("SetPlayerPosition")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<CanSetPlayerPosition>()
|
||||
.each([this](const Terrain &terrain) {
|
||||
flecs::entity player = ECS::player;
|
||||
if (!player.is_valid())
|
||||
return;
|
||||
if (!player.has<CharacterLocation>())
|
||||
return;
|
||||
if (!player.has<CharacterBase>())
|
||||
return;
|
||||
if (!player.has<Player>())
|
||||
return;
|
||||
CharacterLocation &loc =
|
||||
player.get_mut<CharacterLocation>();
|
||||
const CharacterBase &ch = player.get<CharacterBase>();
|
||||
if (!ch.mBodyNode) {
|
||||
std::cout << "no player yet";
|
||||
return;
|
||||
}
|
||||
float height =
|
||||
get_height(terrain.mTerrainGroup, loc.position);
|
||||
loc.position.y = height + 0.0f;
|
||||
ch.mBodyNode->setPosition(loc.position);
|
||||
ch.mBodyNode->setOrientation(Ogre::Quaternion());
|
||||
player.modified<CharacterLocation>();
|
||||
ECS::get().remove<CanSetPlayerPosition>();
|
||||
});
|
||||
}
|
||||
float TerrainModule::get_height(Ogre::TerrainGroup *group,
|
||||
const Ogre::Vector3 &position)
|
||||
{
|
||||
int worldSize = group->getTerrainWorldSize();
|
||||
uint16_t terrainSize = group->getTerrainSize();
|
||||
long xslot, yslot;
|
||||
group->convertWorldPositionToTerrainSlot(position, &xslot, &yslot);
|
||||
Ogre::Vector3 slotpos;
|
||||
group->convertTerrainSlotToWorldPosition(xslot, yslot, &slotpos);
|
||||
Ogre::Vector3 offset = (position - slotpos) * terrainSize / worldSize;
|
||||
float height = HeightData::get_singleton()->get_height(
|
||||
group, xslot, yslot, (int)offset.x + (terrainSize - 1) / 2,
|
||||
(int)offset.z + (terrainSize - 1) / 2);
|
||||
return height;
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,18 @@ struct Terrain {
|
||||
|
||||
Ogre::Vector3 mTerrainPos;
|
||||
};
|
||||
struct PlacementObjects {
|
||||
struct item {
|
||||
Ogre::String entity;
|
||||
Ogre::Quaternion rotation;
|
||||
Ogre::Vector3 position;
|
||||
};
|
||||
std::vector<struct item> altar_items;
|
||||
};
|
||||
struct TerrainModule {
|
||||
TerrainModule(flecs::world &ecs);
|
||||
static float get_height(Ogre::TerrainGroup *group,
|
||||
const Ogre::Vector3 &position);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
+743
-417
File diff suppressed because it is too large
Load Diff
@@ -38,14 +38,19 @@ struct WaterSurface {
|
||||
Ogre::Viewport *mViewports[4];
|
||||
};
|
||||
struct WaterBody {
|
||||
std::vector<btCollisionShape *> mChildShapes;
|
||||
btCollisionShape *mWaterShape;
|
||||
btPairCachingGhostObject *mWaterBody;
|
||||
std::set<btCollisionObject *> mInWater;
|
||||
std::unordered_map<btCollisionObject *, float> mSurface;
|
||||
btManifoldArray mManifoldArray;
|
||||
btVector3 mShapeAabbMin, mShapeAabbMax;
|
||||
int count;
|
||||
btActionInterface *action;
|
||||
bool isInWater(const btCollisionObject *body) const;
|
||||
};
|
||||
struct WaterModule {
|
||||
btPairCachingGhostObject *mGhostObject;
|
||||
WaterModule(flecs::world &ecs);
|
||||
void createWaterShape(WaterBody *water);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "WorldMapModule.h"
|
||||
|
||||
ECS::WorldMapModule::WorldMapModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.component<WorldMap>();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef WORLD_MAP_MODULE_H
|
||||
#define WORLD_MAP_MODULE_H
|
||||
#include <flecs.h>
|
||||
namespace ECS
|
||||
{
|
||||
struct WorldMap {};
|
||||
struct WorldMapModule {
|
||||
WorldMapModule(flecs::world &ecs);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
+15
-4
@@ -14,17 +14,28 @@ set(LUA_OBJ
|
||||
lmathlib.o loadlib.o loslib.o lstrlib.o ltablib.o
|
||||
lutf8lib.o linit.o)
|
||||
set(LUA_SRC)
|
||||
set(LPEG_OBJ
|
||||
lpvm.o lpcap.o lptree.o lpcode.o lpprint.o lpcset.o
|
||||
)
|
||||
set(LPEG_SRC)
|
||||
foreach(LUA_FILE ${LUA_OBJ})
|
||||
string(REPLACE ".o" ".c" LUA_SRC_ITEM ${LUA_FILE})
|
||||
list(APPEND LUA_SRC lua-5.4.8/src/${LUA_SRC_ITEM})
|
||||
endforeach()
|
||||
add_library(lua ${LUA_SRC})
|
||||
target_include_directories(lua PRIVATE lua-5.4.8/src)
|
||||
foreach(LPEG_FILE ${LPEG_OBJ})
|
||||
string(REPLACE ".o" ".c" LPEG_SRC_ITEM ${LPEG_FILE})
|
||||
list(APPEND LPEG_SRC lpeg-1.1.0/${LPEG_SRC_ITEM})
|
||||
endforeach()
|
||||
add_library(lua ${LUA_SRC} ${LPEG_SRC})
|
||||
target_include_directories(lua PUBLIC lua-5.4.8/src lpeg-1.1.0)
|
||||
add_executable(luavm lua-5.4.8/src/lua.c)
|
||||
target_link_libraries(luavm lua m)
|
||||
target_include_directories(luavm PRIVATE lua-5.4.8/src)
|
||||
add_executable(luac lua-5.4.8/src/luac.c ${LUA_SRC})
|
||||
add_executable(luac lua-5.4.8/src/luac.c ${LUA_SRC} ${LPEG_SRC})
|
||||
target_link_libraries(luac m)
|
||||
target_include_directories(luac PRIVATE lua-5.4.8/src)
|
||||
target_include_directories(luac PRIVATE lua-5.4.8/src lpeg-1.1.0)
|
||||
add_executable(lualpegvm lua.c)
|
||||
target_link_libraries(lualpegvm lua m)
|
||||
target_include_directories(lualpegvm PRIVATE lua-5.4.8/src)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
HISTORY for LPeg 1.1.0
|
||||
|
||||
* Changes from version 1.0.2 to 1.1.0
|
||||
---------------------------------
|
||||
+ accumulator capture
|
||||
+ UTF-8 ranges
|
||||
+ Larger limit for number of rules in a grammar
|
||||
+ Larger limit for number of captures in a match
|
||||
+ bug fixes
|
||||
+ other small improvements
|
||||
|
||||
* Changes from version 1.0.1 to 1.0.2
|
||||
---------------------------------
|
||||
+ some bugs fixed
|
||||
|
||||
* Changes from version 0.12 to 1.0.1
|
||||
---------------------------------
|
||||
+ group "names" can be any Lua value
|
||||
+ some bugs fixed
|
||||
+ other small improvements
|
||||
|
||||
* Changes from version 0.11 to 0.12
|
||||
---------------------------------
|
||||
+ no "unsigned short" limit for pattern sizes
|
||||
+ mathtime captures considered nullable
|
||||
+ some bugs fixed
|
||||
|
||||
* Changes from version 0.10 to 0.11
|
||||
-------------------------------
|
||||
+ complete reimplementation of the code generator
|
||||
+ new syntax for table captures
|
||||
+ new functions in module 're'
|
||||
+ other small improvements
|
||||
|
||||
* Changes from version 0.9 to 0.10
|
||||
-------------------------------
|
||||
+ backtrack stack has configurable size
|
||||
+ better error messages
|
||||
+ Notation for non-terminals in 're' back to A instead o <A>
|
||||
+ experimental look-behind pattern
|
||||
+ support for external extensions
|
||||
+ works with Lua 5.2
|
||||
+ consumes less C stack
|
||||
|
||||
- "and" predicates do not keep captures
|
||||
|
||||
* Changes from version 0.8 to 0.9
|
||||
-------------------------------
|
||||
+ The accumulator capture was replaced by a fold capture;
|
||||
programs that used the old 'lpeg.Ca' will need small changes.
|
||||
+ Some support for character classes from old C locales.
|
||||
+ A new named-group capture.
|
||||
|
||||
* Changes from version 0.7 to 0.8
|
||||
-------------------------------
|
||||
+ New "match-time" capture.
|
||||
+ New "argument capture" that allows passing arguments into the pattern.
|
||||
+ Better documentation for 're'.
|
||||
+ Several small improvements for 're'.
|
||||
+ The 're' module has an incompatibility with previous versions:
|
||||
now, any use of a non-terminal must be enclosed in angle brackets
|
||||
(like <B>).
|
||||
|
||||
* Changes from version 0.6 to 0.7
|
||||
-------------------------------
|
||||
+ Several improvements in module 're':
|
||||
- better documentation;
|
||||
- support for most captures (all but accumulator);
|
||||
- limited repetitions p{n,m}.
|
||||
+ Small improvements in efficiency.
|
||||
+ Several small bugs corrected (special thanks to Hans Hagen
|
||||
and Taco Hoekwater).
|
||||
|
||||
* Changes from version 0.5 to 0.6
|
||||
-------------------------------
|
||||
+ Support for non-numeric indices in grammars.
|
||||
+ Some bug fixes (thanks to the luatex team).
|
||||
+ Some new optimizations; (thanks to Mike Pall).
|
||||
+ A new page layout (thanks to Andre Carregal).
|
||||
+ Minimal documentation for module 're'.
|
||||
|
||||
* Changes from version 0.4 to 0.5
|
||||
-------------------------------
|
||||
+ Several optimizations.
|
||||
+ lpeg.P now accepts booleans.
|
||||
+ Some new examples.
|
||||
+ A proper license.
|
||||
+ Several small improvements.
|
||||
|
||||
* Changes from version 0.3 to 0.4
|
||||
-------------------------------
|
||||
+ Static check for loops in repetitions and grammars.
|
||||
+ Removed label option in captures.
|
||||
+ The implementation of captures uses less memory.
|
||||
|
||||
* Changes from version 0.2 to 0.3
|
||||
-------------------------------
|
||||
+ User-defined patterns in Lua.
|
||||
+ Several new captures.
|
||||
|
||||
* Changes from version 0.1 to 0.2
|
||||
-------------------------------
|
||||
+ Several small corrections.
|
||||
+ Handles embedded zeros like any other character.
|
||||
+ Capture "name" can be any Lua value.
|
||||
+ Unlimited number of captures.
|
||||
+ Match gets an optional initial position.
|
||||
|
||||
(end of HISTORY)
|
||||
@@ -0,0 +1,4 @@
|
||||
# LPeg - Parsing Expression Grammars For Lua
|
||||
|
||||
For more information,
|
||||
see [Lpeg](//www.inf.puc-rio.br/~roberto/lpeg/).
|
||||
@@ -0,0 +1,612 @@
|
||||
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#include "lpcap.h"
|
||||
#include "lpprint.h"
|
||||
#include "lptypes.h"
|
||||
|
||||
|
||||
#define getfromktable(cs,v) lua_rawgeti((cs)->L, ktableidx((cs)->ptop), v)
|
||||
|
||||
#define pushluaval(cs) getfromktable(cs, (cs)->cap->idx)
|
||||
|
||||
|
||||
|
||||
#define skipclose(cs,head) \
|
||||
if (isopencap(head)) { assert(isclosecap(cs->cap)); cs->cap++; }
|
||||
|
||||
|
||||
/*
|
||||
** Return the size of capture 'cap'. If it is an open capture, 'close'
|
||||
** must be its corresponding close.
|
||||
*/
|
||||
static Index_t capsize (Capture *cap, Capture *close) {
|
||||
if (isopencap(cap)) {
|
||||
assert(isclosecap(close));
|
||||
return close->index - cap->index;
|
||||
}
|
||||
else
|
||||
return cap->siz - 1;
|
||||
}
|
||||
|
||||
|
||||
static Index_t closesize (CapState *cs, Capture *head) {
|
||||
return capsize(head, cs->cap);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Put at the cache for Lua values the value indexed by 'v' in ktable
|
||||
** of the running pattern (if it is not there yet); returns its index.
|
||||
*/
|
||||
static int updatecache (CapState *cs, int v) {
|
||||
int idx = cs->ptop + 1; /* stack index of cache for Lua values */
|
||||
if (v != cs->valuecached) { /* not there? */
|
||||
getfromktable(cs, v); /* get value from 'ktable' */
|
||||
lua_replace(cs->L, idx); /* put it at reserved stack position */
|
||||
cs->valuecached = v; /* keep track of what is there */
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
static int pushcapture (CapState *cs);
|
||||
|
||||
|
||||
/*
|
||||
** Goes back in a list of captures looking for an open capture
|
||||
** corresponding to a close one.
|
||||
*/
|
||||
static Capture *findopen (Capture *cap) {
|
||||
int n = 0; /* number of closes waiting an open */
|
||||
for (;;) {
|
||||
cap--;
|
||||
if (isclosecap(cap)) n++; /* one more open to skip */
|
||||
else if (isopencap(cap))
|
||||
if (n-- == 0) return cap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Go to the next capture at the same level.
|
||||
*/
|
||||
static void nextcap (CapState *cs) {
|
||||
Capture *cap = cs->cap;
|
||||
if (isopencap(cap)) { /* must look for a close? */
|
||||
int n = 0; /* number of opens waiting a close */
|
||||
for (;;) { /* look for corresponding close */
|
||||
cap++;
|
||||
if (isopencap(cap)) n++;
|
||||
else if (isclosecap(cap))
|
||||
if (n-- == 0) break;
|
||||
}
|
||||
cs->cap = cap + 1; /* + 1 to skip last close */
|
||||
}
|
||||
else {
|
||||
Capture *next;
|
||||
for (next = cap + 1; capinside(cap, next); next++)
|
||||
; /* skip captures inside current one */
|
||||
cs->cap = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Push on the Lua stack all values generated by nested captures inside
|
||||
** the current capture. Returns number of values pushed. 'addextra'
|
||||
** makes it push the entire match after all captured values. The
|
||||
** entire match is pushed also if there are no other nested values,
|
||||
** so the function never returns zero.
|
||||
*/
|
||||
static int pushnestedvalues (CapState *cs, int addextra) {
|
||||
Capture *head = cs->cap++; /* original capture */
|
||||
int n = 0; /* number of pushed subvalues */
|
||||
/* repeat for all nested patterns */
|
||||
while (capinside(head, cs->cap))
|
||||
n += pushcapture(cs);
|
||||
if (addextra || n == 0) { /* need extra? */
|
||||
lua_pushlstring(cs->L, cs->s + head->index, closesize(cs, head));
|
||||
n++;
|
||||
}
|
||||
skipclose(cs, head);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Push only the first value generated by nested captures
|
||||
*/
|
||||
static void pushonenestedvalue (CapState *cs) {
|
||||
int n = pushnestedvalues(cs, 0);
|
||||
if (n > 1)
|
||||
lua_pop(cs->L, n - 1); /* pop extra values */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Checks whether group 'grp' is visible to 'ref', that is, 'grp' is
|
||||
** not nested inside a full capture that does not contain 'ref'. (We
|
||||
** only need to care for full captures because the search at 'findback'
|
||||
** skips open-end blocks; so, if 'grp' is nested in a non-full capture,
|
||||
** 'ref' is also inside it.) To check this, we search backward for the
|
||||
** inner full capture enclosing 'grp'. A full capture cannot contain
|
||||
** non-full captures, so a close capture means we cannot be inside a
|
||||
** full capture anymore.
|
||||
*/
|
||||
static int capvisible (CapState *cs, Capture *grp, Capture *ref) {
|
||||
Capture *cap = grp;
|
||||
int i = MAXLOP; /* maximum distance for an 'open' */
|
||||
while (i-- > 0 && cap-- > cs->ocap) {
|
||||
if (isclosecap(cap))
|
||||
return 1; /* can stop the search */
|
||||
else if (grp->index - cap->index >= UCHAR_MAX)
|
||||
return 1; /* can stop the search */
|
||||
else if (capinside(cap, grp)) /* is 'grp' inside cap? */
|
||||
return capinside(cap, ref); /* ok iff cap also contains 'ref' */
|
||||
}
|
||||
return 1; /* 'grp' is not inside any capture */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Try to find a named group capture with the name given at the top of
|
||||
** the stack; goes backward from 'ref'.
|
||||
*/
|
||||
static Capture *findback (CapState *cs, Capture *ref) {
|
||||
lua_State *L = cs->L;
|
||||
Capture *cap = ref;
|
||||
while (cap-- > cs->ocap) { /* repeat until end of list */
|
||||
if (isclosecap(cap))
|
||||
cap = findopen(cap); /* skip nested captures */
|
||||
else if (capinside(cap, ref))
|
||||
continue; /* enclosing captures are not visible to 'ref' */
|
||||
if (captype(cap) == Cgroup && capvisible(cs, cap, ref)) {
|
||||
getfromktable(cs, cap->idx); /* get group name */
|
||||
if (lp_equal(L, -2, -1)) { /* right group? */
|
||||
lua_pop(L, 2); /* remove reference name and group name */
|
||||
return cap;
|
||||
}
|
||||
else lua_pop(L, 1); /* remove group name */
|
||||
}
|
||||
}
|
||||
luaL_error(L, "back reference '%s' not found", lua_tostring(L, -1));
|
||||
return NULL; /* to avoid warnings */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Back-reference capture. Return number of values pushed.
|
||||
*/
|
||||
static int backrefcap (CapState *cs) {
|
||||
int n;
|
||||
Capture *curr = cs->cap;
|
||||
pushluaval(cs); /* reference name */
|
||||
cs->cap = findback(cs, curr); /* find corresponding group */
|
||||
n = pushnestedvalues(cs, 0); /* push group's values */
|
||||
cs->cap = curr + 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Table capture: creates a new table and populates it with nested
|
||||
** captures.
|
||||
*/
|
||||
static int tablecap (CapState *cs) {
|
||||
lua_State *L = cs->L;
|
||||
Capture *head = cs->cap++;
|
||||
int n = 0;
|
||||
lua_newtable(L);
|
||||
while (capinside(head, cs->cap)) {
|
||||
if (captype(cs->cap) == Cgroup && cs->cap->idx != 0) { /* named group? */
|
||||
pushluaval(cs); /* push group name */
|
||||
pushonenestedvalue(cs);
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
else { /* not a named group */
|
||||
int i;
|
||||
int k = pushcapture(cs);
|
||||
for (i = k; i > 0; i--) /* store all values into table */
|
||||
lua_rawseti(L, -(i + 1), n + i);
|
||||
n += k;
|
||||
}
|
||||
}
|
||||
skipclose(cs, head);
|
||||
return 1; /* number of values pushed (only the table) */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Table-query capture
|
||||
*/
|
||||
static int querycap (CapState *cs) {
|
||||
int idx = cs->cap->idx;
|
||||
pushonenestedvalue(cs); /* get nested capture */
|
||||
lua_gettable(cs->L, updatecache(cs, idx)); /* query cap. value at table */
|
||||
if (!lua_isnil(cs->L, -1))
|
||||
return 1;
|
||||
else { /* no value */
|
||||
lua_pop(cs->L, 1); /* remove nil */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Fold capture
|
||||
*/
|
||||
static int foldcap (CapState *cs) {
|
||||
int n;
|
||||
lua_State *L = cs->L;
|
||||
Capture *head = cs->cap++;
|
||||
int idx = head->idx;
|
||||
if (isclosecap(cs->cap) || /* no nested captures (large subject)? */
|
||||
(n = pushcapture(cs)) == 0) /* nested captures with no values? */
|
||||
return luaL_error(L, "no initial value for fold capture");
|
||||
if (n > 1)
|
||||
lua_pop(L, n - 1); /* leave only one result for accumulator */
|
||||
while (capinside(head, cs->cap)) {
|
||||
lua_pushvalue(L, updatecache(cs, idx)); /* get folding function */
|
||||
lua_insert(L, -2); /* put it before accumulator */
|
||||
n = pushcapture(cs); /* get next capture's values */
|
||||
lua_call(L, n + 1, 1); /* call folding function */
|
||||
}
|
||||
skipclose(cs, head);
|
||||
return 1; /* only accumulator left on the stack */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Function capture
|
||||
*/
|
||||
static int functioncap (CapState *cs) {
|
||||
int n;
|
||||
int top = lua_gettop(cs->L);
|
||||
pushluaval(cs); /* push function */
|
||||
n = pushnestedvalues(cs, 0); /* push nested captures */
|
||||
lua_call(cs->L, n, LUA_MULTRET); /* call function */
|
||||
return lua_gettop(cs->L) - top; /* return function's results */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Accumulator capture
|
||||
*/
|
||||
static int accumulatorcap (CapState *cs) {
|
||||
lua_State *L = cs->L;
|
||||
int n;
|
||||
if (lua_gettop(L) < cs->firstcap)
|
||||
luaL_error(L, "no previous value for accumulator capture");
|
||||
pushluaval(cs); /* push function */
|
||||
lua_insert(L, -2); /* previous value becomes first argument */
|
||||
n = pushnestedvalues(cs, 0); /* push nested captures */
|
||||
lua_call(L, n + 1, 1); /* call function */
|
||||
return 0; /* did not add any extra value */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Select capture
|
||||
*/
|
||||
static int numcap (CapState *cs) {
|
||||
int idx = cs->cap->idx; /* value to select */
|
||||
if (idx == 0) { /* no values? */
|
||||
nextcap(cs); /* skip entire capture */
|
||||
return 0; /* no value produced */
|
||||
}
|
||||
else {
|
||||
int n = pushnestedvalues(cs, 0);
|
||||
if (n < idx) /* invalid index? */
|
||||
return luaL_error(cs->L, "no capture '%d'", idx);
|
||||
else {
|
||||
lua_pushvalue(cs->L, -(n - idx + 1)); /* get selected capture */
|
||||
lua_replace(cs->L, -(n + 1)); /* put it in place of 1st capture */
|
||||
lua_pop(cs->L, n - 1); /* remove other captures */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Return the stack index of the first runtime capture in the given
|
||||
** list of captures (or zero if no runtime captures)
|
||||
*/
|
||||
int finddyncap (Capture *cap, Capture *last) {
|
||||
for (; cap < last; cap++) {
|
||||
if (cap->kind == Cruntime)
|
||||
return cap->idx; /* stack position of first capture */
|
||||
}
|
||||
return 0; /* no dynamic captures in this segment */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Calls a runtime capture. Returns number of captures "removed" by the
|
||||
** call, that is, those inside the group capture. Captures to be added
|
||||
** are on the Lua stack.
|
||||
*/
|
||||
int runtimecap (CapState *cs, Capture *close, const char *s, int *rem) {
|
||||
int n, id;
|
||||
lua_State *L = cs->L;
|
||||
int otop = lua_gettop(L);
|
||||
Capture *open = findopen(close); /* get open group capture */
|
||||
assert(captype(open) == Cgroup);
|
||||
id = finddyncap(open, close); /* get first dynamic capture argument */
|
||||
close->kind = Cclose; /* closes the group */
|
||||
close->index = s - cs->s;
|
||||
cs->cap = open; cs->valuecached = 0; /* prepare capture state */
|
||||
luaL_checkstack(L, 4, "too many runtime captures");
|
||||
pushluaval(cs); /* push function to be called */
|
||||
lua_pushvalue(L, SUBJIDX); /* push original subject */
|
||||
lua_pushinteger(L, s - cs->s + 1); /* push current position */
|
||||
n = pushnestedvalues(cs, 0); /* push nested captures */
|
||||
lua_call(L, n + 2, LUA_MULTRET); /* call dynamic function */
|
||||
if (id > 0) { /* are there old dynamic captures to be removed? */
|
||||
int i;
|
||||
for (i = id; i <= otop; i++)
|
||||
lua_remove(L, id); /* remove old dynamic captures */
|
||||
*rem = otop - id + 1; /* total number of dynamic captures removed */
|
||||
}
|
||||
else
|
||||
*rem = 0; /* no dynamic captures removed */
|
||||
return close - open - 1; /* number of captures to be removed */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Auxiliary structure for substitution and string captures: keep
|
||||
** information about nested captures for future use, avoiding to push
|
||||
** string results into Lua
|
||||
*/
|
||||
typedef struct StrAux {
|
||||
int isstring; /* whether capture is a string */
|
||||
union {
|
||||
Capture *cp; /* if not a string, respective capture */
|
||||
struct { /* if it is a string... */
|
||||
Index_t idx; /* starts here */
|
||||
Index_t siz; /* with this size */
|
||||
} s;
|
||||
} u;
|
||||
} StrAux;
|
||||
|
||||
#define MAXSTRCAPS 10
|
||||
|
||||
/*
|
||||
** Collect values from current capture into array 'cps'. Current
|
||||
** capture must be Cstring (first call) or Csimple (recursive calls).
|
||||
** (In first call, fills %0 with whole match for Cstring.)
|
||||
** Returns number of elements in the array that were filled.
|
||||
*/
|
||||
static int getstrcaps (CapState *cs, StrAux *cps, int n) {
|
||||
int k = n++;
|
||||
Capture *head = cs->cap++;
|
||||
cps[k].isstring = 1; /* get string value */
|
||||
cps[k].u.s.idx = head->index; /* starts here */
|
||||
while (capinside(head, cs->cap)) {
|
||||
if (n >= MAXSTRCAPS) /* too many captures? */
|
||||
nextcap(cs); /* skip extra captures (will not need them) */
|
||||
else if (captype(cs->cap) == Csimple) /* string? */
|
||||
n = getstrcaps(cs, cps, n); /* put info. into array */
|
||||
else {
|
||||
cps[n].isstring = 0; /* not a string */
|
||||
cps[n].u.cp = cs->cap; /* keep original capture */
|
||||
nextcap(cs);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
cps[k].u.s.siz = closesize(cs, head);
|
||||
skipclose(cs, head);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** add next capture value (which should be a string) to buffer 'b'
|
||||
*/
|
||||
static int addonestring (luaL_Buffer *b, CapState *cs, const char *what);
|
||||
|
||||
|
||||
/*
|
||||
** String capture: add result to buffer 'b' (instead of pushing
|
||||
** it into the stack)
|
||||
*/
|
||||
static void stringcap (luaL_Buffer *b, CapState *cs) {
|
||||
StrAux cps[MAXSTRCAPS];
|
||||
int n;
|
||||
size_t len, i;
|
||||
const char *fmt; /* format string */
|
||||
fmt = lua_tolstring(cs->L, updatecache(cs, cs->cap->idx), &len);
|
||||
n = getstrcaps(cs, cps, 0) - 1; /* collect nested captures */
|
||||
for (i = 0; i < len; i++) { /* traverse format string */
|
||||
if (fmt[i] != '%') /* not an escape? */
|
||||
luaL_addchar(b, fmt[i]); /* add it to buffer */
|
||||
else if (fmt[++i] < '0' || fmt[i] > '9') /* not followed by a digit? */
|
||||
luaL_addchar(b, fmt[i]); /* add to buffer */
|
||||
else {
|
||||
int l = fmt[i] - '0'; /* capture index */
|
||||
if (l > n)
|
||||
luaL_error(cs->L, "invalid capture index (%d)", l);
|
||||
else if (cps[l].isstring)
|
||||
luaL_addlstring(b, cs->s + cps[l].u.s.idx, cps[l].u.s.siz);
|
||||
else {
|
||||
Capture *curr = cs->cap;
|
||||
cs->cap = cps[l].u.cp; /* go back to evaluate that nested capture */
|
||||
if (!addonestring(b, cs, "capture"))
|
||||
luaL_error(cs->L, "no values in capture index %d", l);
|
||||
cs->cap = curr; /* continue from where it stopped */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Substitution capture: add result to buffer 'b'
|
||||
*/
|
||||
static void substcap (luaL_Buffer *b, CapState *cs) {
|
||||
const char *curr = cs->s + cs->cap->index;
|
||||
Capture *head = cs->cap++;
|
||||
while (capinside(head, cs->cap)) {
|
||||
Capture *cap = cs->cap;
|
||||
const char *caps = cs->s + cap->index;
|
||||
luaL_addlstring(b, curr, caps - curr); /* add text up to capture */
|
||||
if (addonestring(b, cs, "replacement"))
|
||||
curr = caps + capsize(cap, cs->cap - 1); /* continue after match */
|
||||
else /* no capture value */
|
||||
curr = caps; /* keep original text in final result */
|
||||
}
|
||||
/* add last piece of text */
|
||||
luaL_addlstring(b, curr, cs->s + head->index + closesize(cs, head) - curr);
|
||||
skipclose(cs, head);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Evaluates a capture and adds its first value to buffer 'b'; returns
|
||||
** whether there was a value
|
||||
*/
|
||||
static int addonestring (luaL_Buffer *b, CapState *cs, const char *what) {
|
||||
switch (captype(cs->cap)) {
|
||||
case Cstring:
|
||||
stringcap(b, cs); /* add capture directly to buffer */
|
||||
return 1;
|
||||
case Csubst:
|
||||
substcap(b, cs); /* add capture directly to buffer */
|
||||
return 1;
|
||||
case Cacc: /* accumulator capture? */
|
||||
return luaL_error(cs->L, "invalid context for an accumulator capture");
|
||||
default: {
|
||||
lua_State *L = cs->L;
|
||||
int n = pushcapture(cs);
|
||||
if (n > 0) {
|
||||
if (n > 1) lua_pop(L, n - 1); /* only one result */
|
||||
if (!lua_isstring(L, -1))
|
||||
return luaL_error(L, "invalid %s value (a %s)",
|
||||
what, luaL_typename(L, -1));
|
||||
luaL_addvalue(b);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if !defined(MAXRECLEVEL)
|
||||
#define MAXRECLEVEL 200
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** Push all values of the current capture into the stack; returns
|
||||
** number of values pushed
|
||||
*/
|
||||
static int pushcapture (CapState *cs) {
|
||||
lua_State *L = cs->L;
|
||||
int res;
|
||||
luaL_checkstack(L, 4, "too many captures");
|
||||
if (cs->reclevel++ > MAXRECLEVEL)
|
||||
return luaL_error(L, "subcapture nesting too deep");
|
||||
switch (captype(cs->cap)) {
|
||||
case Cposition: {
|
||||
lua_pushinteger(L, cs->cap->index + 1);
|
||||
cs->cap++;
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
case Cconst: {
|
||||
pushluaval(cs);
|
||||
cs->cap++;
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
case Carg: {
|
||||
int arg = (cs->cap++)->idx;
|
||||
if (arg + FIXEDARGS > cs->ptop)
|
||||
return luaL_error(L, "reference to absent extra argument #%d", arg);
|
||||
lua_pushvalue(L, arg + FIXEDARGS);
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
case Csimple: {
|
||||
int k = pushnestedvalues(cs, 1);
|
||||
lua_insert(L, -k); /* make whole match be first result */
|
||||
res = k;
|
||||
break;
|
||||
}
|
||||
case Cruntime: {
|
||||
lua_pushvalue(L, (cs->cap++)->idx); /* value is in the stack */
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
case Cstring: {
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
stringcap(&b, cs);
|
||||
luaL_pushresult(&b);
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
case Csubst: {
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
substcap(&b, cs);
|
||||
luaL_pushresult(&b);
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
case Cgroup: {
|
||||
if (cs->cap->idx == 0) /* anonymous group? */
|
||||
res = pushnestedvalues(cs, 0); /* add all nested values */
|
||||
else { /* named group: add no values */
|
||||
nextcap(cs); /* skip capture */
|
||||
res = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Cbackref: res = backrefcap(cs); break;
|
||||
case Ctable: res = tablecap(cs); break;
|
||||
case Cfunction: res = functioncap(cs); break;
|
||||
case Cacc: res = accumulatorcap(cs); break;
|
||||
case Cnum: res = numcap(cs); break;
|
||||
case Cquery: res = querycap(cs); break;
|
||||
case Cfold: res = foldcap(cs); break;
|
||||
default: assert(0); res = 0;
|
||||
}
|
||||
cs->reclevel--;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Prepare a CapState structure and traverse the entire list of
|
||||
** captures in the stack pushing its results. 's' is the subject
|
||||
** string, 'r' is the final position of the match, and 'ptop'
|
||||
** the index in the stack where some useful values were pushed.
|
||||
** Returns the number of results pushed. (If the list produces no
|
||||
** results, push the final position of the match.)
|
||||
*/
|
||||
int getcaptures (lua_State *L, const char *s, const char *r, int ptop) {
|
||||
Capture *capture = (Capture *)lua_touserdata(L, caplistidx(ptop));
|
||||
int n = 0;
|
||||
/* printcaplist(capture); */
|
||||
if (!isclosecap(capture)) { /* is there any capture? */
|
||||
CapState cs;
|
||||
cs.ocap = cs.cap = capture; cs.L = L; cs.reclevel = 0;
|
||||
cs.s = s; cs.valuecached = 0; cs.ptop = ptop;
|
||||
cs.firstcap = lua_gettop(L) + 1; /* where first value (if any) will go */
|
||||
do { /* collect their values */
|
||||
n += pushcapture(&cs);
|
||||
} while (!isclosecap(cs.cap));
|
||||
assert(lua_gettop(L) - cs.firstcap == n - 1);
|
||||
}
|
||||
if (n == 0) { /* no capture values? */
|
||||
lua_pushinteger(L, r - s + 1); /* return only end position */
|
||||
n = 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
#if !defined(lpcap_h)
|
||||
#define lpcap_h
|
||||
|
||||
|
||||
#include "lptypes.h"
|
||||
|
||||
|
||||
/* kinds of captures */
|
||||
typedef enum CapKind {
|
||||
Cclose, /* not used in trees */
|
||||
Cposition,
|
||||
Cconst, /* ktable[key] is Lua constant */
|
||||
Cbackref, /* ktable[key] is "name" of group to get capture */
|
||||
Carg, /* 'key' is arg's number */
|
||||
Csimple, /* next node is pattern */
|
||||
Ctable, /* next node is pattern */
|
||||
Cfunction, /* ktable[key] is function; next node is pattern */
|
||||
Cacc, /* ktable[key] is function; next node is pattern */
|
||||
Cquery, /* ktable[key] is table; next node is pattern */
|
||||
Cstring, /* ktable[key] is string; next node is pattern */
|
||||
Cnum, /* numbered capture; 'key' is number of value to return */
|
||||
Csubst, /* substitution capture; next node is pattern */
|
||||
Cfold, /* ktable[key] is function; next node is pattern */
|
||||
Cruntime, /* not used in trees (is uses another type for tree) */
|
||||
Cgroup /* ktable[key] is group's "name" */
|
||||
} CapKind;
|
||||
|
||||
|
||||
/*
|
||||
** An unsigned integer large enough to index any subject entirely.
|
||||
** It can be size_t, but that will double the size of the array
|
||||
** of captures in a 64-bit machine.
|
||||
*/
|
||||
#if !defined(Index_t)
|
||||
typedef uint Index_t;
|
||||
#endif
|
||||
|
||||
#define MAXINDT (~(Index_t)0)
|
||||
|
||||
|
||||
typedef struct Capture {
|
||||
Index_t index; /* subject position */
|
||||
unsigned short idx; /* extra info (group name, arg index, etc.) */
|
||||
byte kind; /* kind of capture */
|
||||
byte siz; /* size of full capture + 1 (0 = not a full capture) */
|
||||
} Capture;
|
||||
|
||||
|
||||
typedef struct CapState {
|
||||
Capture *cap; /* current capture */
|
||||
Capture *ocap; /* (original) capture list */
|
||||
lua_State *L;
|
||||
int ptop; /* stack index of last argument to 'match' */
|
||||
int firstcap; /* stack index of first capture pushed in the stack */
|
||||
const char *s; /* original string */
|
||||
int valuecached; /* value stored in cache slot */
|
||||
int reclevel; /* recursion level */
|
||||
} CapState;
|
||||
|
||||
|
||||
#define captype(cap) ((cap)->kind)
|
||||
|
||||
#define isclosecap(cap) (captype(cap) == Cclose)
|
||||
#define isopencap(cap) ((cap)->siz == 0)
|
||||
|
||||
/* true if c2 is (any number of levels) inside c1 */
|
||||
#define capinside(c1,c2) \
|
||||
(isopencap(c1) ? !isclosecap(c2) \
|
||||
: (c2)->index < (c1)->index + (c1)->siz - 1)
|
||||
|
||||
|
||||
/**
|
||||
** Maximum number of captures to visit when looking for an 'open'.
|
||||
*/
|
||||
#define MAXLOP 20
|
||||
|
||||
|
||||
|
||||
int runtimecap (CapState *cs, Capture *close, const char *s, int *rem);
|
||||
int getcaptures (lua_State *L, const char *s, const char *r, int ptop);
|
||||
int finddyncap (Capture *cap, Capture *last);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
|
||||
#if !defined(lpcode_h)
|
||||
#define lpcode_h
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lptypes.h"
|
||||
#include "lptree.h"
|
||||
#include "lpvm.h"
|
||||
|
||||
int checkaux (TTree *tree, int pred);
|
||||
int fixedlen (TTree *tree);
|
||||
int hascaptures (TTree *tree);
|
||||
int lp_gc (lua_State *L);
|
||||
Instruction *compile (lua_State *L, Pattern *p, uint size);
|
||||
void freecode (lua_State *L, Pattern *p);
|
||||
int sizei (const Instruction *i);
|
||||
|
||||
|
||||
#define PEnullable 0
|
||||
#define PEnofail 1
|
||||
|
||||
/*
|
||||
** nofail(t) implies that 't' cannot fail with any input
|
||||
*/
|
||||
#define nofail(t) checkaux(t, PEnofail)
|
||||
|
||||
/*
|
||||
** (not nullable(t)) implies 't' cannot match without consuming
|
||||
** something
|
||||
*/
|
||||
#define nullable(t) checkaux(t, PEnullable)
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
#include "lptypes.h"
|
||||
#include "lpcset.h"
|
||||
|
||||
|
||||
/*
|
||||
** Add to 'c' the index of the (only) bit set in byte 'b'
|
||||
*/
|
||||
static int onlybit (int c, int b) {
|
||||
if ((b & 0xF0) != 0) { c += 4; b >>= 4; }
|
||||
if ((b & 0x0C) != 0) { c += 2; b >>= 2; }
|
||||
if ((b & 0x02) != 0) { c += 1; }
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Check whether a charset is empty (returns IFail), singleton (IChar),
|
||||
** full (IAny), or none of those (ISet). When singleton, 'info.offset'
|
||||
** returns which character it is. When generic set, 'info' returns
|
||||
** information about its range.
|
||||
*/
|
||||
Opcode charsettype (const byte *cs, charsetinfo *info) {
|
||||
int low0, low1, high0, high1;
|
||||
for (low1 = 0; low1 < CHARSETSIZE && cs[low1] == 0; low1++)
|
||||
/* find lowest byte with a 1-bit */;
|
||||
if (low1 == CHARSETSIZE)
|
||||
return IFail; /* no characters in set */
|
||||
for (high1 = CHARSETSIZE - 1; cs[high1] == 0; high1--)
|
||||
/* find highest byte with a 1-bit; low1 is a sentinel */;
|
||||
if (low1 == high1) { /* only one byte with 1-bits? */
|
||||
int b = cs[low1];
|
||||
if ((b & (b - 1)) == 0) { /* does byte has only one 1-bit? */
|
||||
info->offset = onlybit(low1 * BITSPERCHAR, b); /* get that bit */
|
||||
return IChar; /* single character */
|
||||
}
|
||||
}
|
||||
for (low0 = 0; low0 < CHARSETSIZE && cs[low0] == 0xFF; low0++)
|
||||
/* find lowest byte with a 0-bit */;
|
||||
if (low0 == CHARSETSIZE)
|
||||
return IAny; /* set has all bits set */
|
||||
for (high0 = CHARSETSIZE - 1; cs[high0] == 0xFF; high0--)
|
||||
/* find highest byte with a 0-bit; low0 is a sentinel */;
|
||||
if (high1 - low1 <= high0 - low0) { /* range of 1s smaller than of 0s? */
|
||||
info->offset = low1;
|
||||
info->size = high1 - low1 + 1;
|
||||
info->deflt = 0; /* all discharged bits were 0 */
|
||||
}
|
||||
else {
|
||||
info->offset = low0;
|
||||
info->size = high0 - low0 + 1;
|
||||
info->deflt = 0xFF; /* all discharged bits were 1 */
|
||||
}
|
||||
info->cs = cs + info->offset;
|
||||
return ISet;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Get a byte from a compact charset. If index is inside the charset
|
||||
** range, get the byte from the supporting charset (correcting it
|
||||
** by the offset). Otherwise, return the default for the set.
|
||||
*/
|
||||
byte getbytefromcharset (const charsetinfo *info, int index) {
|
||||
if (index < info->size)
|
||||
return info->cs[index];
|
||||
else return info->deflt;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** If 'tree' is a 'char' pattern (TSet, TChar, TAny, TFalse), convert it
|
||||
** into a charset and return 1; else return 0.
|
||||
*/
|
||||
int tocharset (TTree *tree, Charset *cs) {
|
||||
switch (tree->tag) {
|
||||
case TChar: { /* only one char */
|
||||
assert(0 <= tree->u.n && tree->u.n <= UCHAR_MAX);
|
||||
clearset(cs->cs); /* erase all chars */
|
||||
setchar(cs->cs, tree->u.n); /* add that one */
|
||||
return 1;
|
||||
}
|
||||
case TAny: {
|
||||
fillset(cs->cs, 0xFF); /* add all characters to the set */
|
||||
return 1;
|
||||
}
|
||||
case TFalse: {
|
||||
clearset(cs->cs); /* empty set */
|
||||
return 1;
|
||||
}
|
||||
case TSet: { /* fill set */
|
||||
int i;
|
||||
fillset(cs->cs, tree->u.set.deflt);
|
||||
for (i = 0; i < tree->u.set.size; i++)
|
||||
cs->cs[tree->u.set.offset + i] = treebuffer(tree)[i];
|
||||
return 1;
|
||||
}
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void tree2cset (TTree *tree, charsetinfo *info) {
|
||||
assert(tree->tag == TSet);
|
||||
info->offset = tree->u.set.offset;
|
||||
info->size = tree->u.set.size;
|
||||
info->deflt = tree->u.set.deflt;
|
||||
info->cs = treebuffer(tree);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
#if !defined(lpset_h)
|
||||
#define lpset_h
|
||||
|
||||
#include "lpcset.h"
|
||||
#include "lpcode.h"
|
||||
#include "lptree.h"
|
||||
|
||||
|
||||
/*
|
||||
** Extra information for the result of 'charsettype'. When result is
|
||||
** IChar, 'offset' is the character. When result is ISet, 'cs' is the
|
||||
** supporting bit array (with offset included), 'offset' is the offset
|
||||
** (in bytes), 'size' is the size (in bytes), and 'delt' is the default
|
||||
** value for bytes outside the set.
|
||||
*/
|
||||
typedef struct {
|
||||
const byte *cs;
|
||||
int offset;
|
||||
int size;
|
||||
int deflt;
|
||||
} charsetinfo;
|
||||
|
||||
|
||||
int tocharset (TTree *tree, Charset *cs);
|
||||
Opcode charsettype (const byte *cs, charsetinfo *info);
|
||||
byte getbytefromcharset (const charsetinfo *info, int index);
|
||||
void tree2cset (TTree *tree, charsetinfo *info);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,298 @@
|
||||
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
#include "lptypes.h"
|
||||
#include "lpprint.h"
|
||||
#include "lpcode.h"
|
||||
|
||||
|
||||
#if defined(LPEG_DEBUG)
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Printing patterns (for debugging)
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
void printcharset (const byte *st) {
|
||||
int i;
|
||||
printf("[");
|
||||
for (i = 0; i <= UCHAR_MAX; i++) {
|
||||
int first = i;
|
||||
while (i <= UCHAR_MAX && testchar(st, i)) i++;
|
||||
if (i - 1 == first) /* unary range? */
|
||||
printf("(%02x)", first);
|
||||
else if (i - 1 > first) /* non-empty range? */
|
||||
printf("(%02x-%02x)", first, i - 1);
|
||||
}
|
||||
printf("]");
|
||||
}
|
||||
|
||||
|
||||
static void printIcharset (const Instruction *inst, const byte *buff) {
|
||||
byte cs[CHARSETSIZE];
|
||||
int i;
|
||||
printf("(%02x-%d) ", inst->i.aux2.set.offset, inst->i.aux2.set.size);
|
||||
clearset(cs);
|
||||
for (i = 0; i < CHARSETSIZE * 8; i++) {
|
||||
if (charinset(inst, buff, i))
|
||||
setchar(cs, i);
|
||||
}
|
||||
printcharset(cs);
|
||||
}
|
||||
|
||||
|
||||
static void printTcharset (TTree *tree) {
|
||||
byte cs[CHARSETSIZE];
|
||||
int i;
|
||||
printf("(%02x-%d) ", tree->u.set.offset, tree->u.set.size);
|
||||
fillset(cs, tree->u.set.deflt);
|
||||
for (i = 0; i < tree->u.set.size; i++)
|
||||
cs[tree->u.set.offset + i] = treebuffer(tree)[i];
|
||||
printcharset(cs);
|
||||
}
|
||||
|
||||
|
||||
static const char *capkind (int kind) {
|
||||
const char *const modes[] = {
|
||||
"close", "position", "constant", "backref",
|
||||
"argument", "simple", "table", "function", "accumulator",
|
||||
"query", "string", "num", "substitution", "fold",
|
||||
"runtime", "group"};
|
||||
return modes[kind];
|
||||
}
|
||||
|
||||
|
||||
static void printjmp (const Instruction *op, const Instruction *p) {
|
||||
printf("-> %d", (int)(p + (p + 1)->offset - op));
|
||||
}
|
||||
|
||||
|
||||
void printinst (const Instruction *op, const Instruction *p) {
|
||||
const char *const names[] = {
|
||||
"any", "char", "set",
|
||||
"testany", "testchar", "testset",
|
||||
"span", "utf-range", "behind",
|
||||
"ret", "end",
|
||||
"choice", "jmp", "call", "open_call",
|
||||
"commit", "partial_commit", "back_commit", "failtwice", "fail", "giveup",
|
||||
"fullcapture", "opencapture", "closecapture", "closeruntime",
|
||||
"--"
|
||||
};
|
||||
printf("%02ld: %s ", (long)(p - op), names[p->i.code]);
|
||||
switch ((Opcode)p->i.code) {
|
||||
case IChar: {
|
||||
printf("'%c' (%02x)", p->i.aux1, p->i.aux1);
|
||||
break;
|
||||
}
|
||||
case ITestChar: {
|
||||
printf("'%c' (%02x)", p->i.aux1, p->i.aux1); printjmp(op, p);
|
||||
break;
|
||||
}
|
||||
case IUTFR: {
|
||||
printf("%d - %d", p[1].offset, utf_to(p));
|
||||
break;
|
||||
}
|
||||
case IFullCapture: {
|
||||
printf("%s (size = %d) (idx = %d)",
|
||||
capkind(getkind(p)), getoff(p), p->i.aux2.key);
|
||||
break;
|
||||
}
|
||||
case IOpenCapture: {
|
||||
printf("%s (idx = %d)", capkind(getkind(p)), p->i.aux2.key);
|
||||
break;
|
||||
}
|
||||
case ISet: {
|
||||
printIcharset(p, (p+1)->buff);
|
||||
break;
|
||||
}
|
||||
case ITestSet: {
|
||||
printIcharset(p, (p+2)->buff); printjmp(op, p);
|
||||
break;
|
||||
}
|
||||
case ISpan: {
|
||||
printIcharset(p, (p+1)->buff);
|
||||
break;
|
||||
}
|
||||
case IOpenCall: {
|
||||
printf("-> %d", (p + 1)->offset);
|
||||
break;
|
||||
}
|
||||
case IBehind: {
|
||||
printf("%d", p->i.aux1);
|
||||
break;
|
||||
}
|
||||
case IJmp: case ICall: case ICommit: case IChoice:
|
||||
case IPartialCommit: case IBackCommit: case ITestAny: {
|
||||
printjmp(op, p);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
void printpatt (Instruction *p) {
|
||||
Instruction *op = p;
|
||||
uint n = op[-1].codesize - 1;
|
||||
while (p < op + n) {
|
||||
printinst(op, p);
|
||||
p += sizei(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void printcap (Capture *cap, int ident) {
|
||||
while (ident--) printf(" ");
|
||||
printf("%s (idx: %d - size: %d) -> %lu (%p)\n",
|
||||
capkind(cap->kind), cap->idx, cap->siz, (long)cap->index, (void*)cap);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Print a capture and its nested captures
|
||||
*/
|
||||
static Capture *printcap2close (Capture *cap, int ident) {
|
||||
Capture *head = cap++;
|
||||
printcap(head, ident); /* print head capture */
|
||||
while (capinside(head, cap))
|
||||
cap = printcap2close(cap, ident + 2); /* print nested captures */
|
||||
if (isopencap(head)) {
|
||||
assert(isclosecap(cap));
|
||||
printcap(cap++, ident); /* print and skip close capture */
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
|
||||
void printcaplist (Capture *cap) {
|
||||
{ /* for debugging, print first a raw list of captures */
|
||||
Capture *c = cap;
|
||||
while (c->index != MAXINDT) { printcap(c, 0); c++; }
|
||||
}
|
||||
printf(">======\n");
|
||||
while (!isclosecap(cap))
|
||||
cap = printcap2close(cap, 0);
|
||||
printf("=======\n");
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Printing trees (for debugging)
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
static const char *tagnames[] = {
|
||||
"char", "set", "any",
|
||||
"true", "false", "utf8.range",
|
||||
"rep",
|
||||
"seq", "choice",
|
||||
"not", "and",
|
||||
"call", "opencall", "rule", "xinfo", "grammar",
|
||||
"behind",
|
||||
"capture", "run-time"
|
||||
};
|
||||
|
||||
|
||||
void printtree (TTree *tree, int ident) {
|
||||
int i;
|
||||
int sibs = numsiblings[tree->tag];
|
||||
for (i = 0; i < ident; i++) printf(" ");
|
||||
printf("%s", tagnames[tree->tag]);
|
||||
switch (tree->tag) {
|
||||
case TChar: {
|
||||
int c = tree->u.n;
|
||||
if (isprint(c))
|
||||
printf(" '%c'\n", c);
|
||||
else
|
||||
printf(" (%02X)\n", c);
|
||||
break;
|
||||
}
|
||||
case TSet: {
|
||||
printTcharset(tree);
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
case TUTFR: {
|
||||
assert(sib1(tree)->tag == TXInfo);
|
||||
printf(" %d (%02x %d) - %d (%02x %d) \n",
|
||||
tree->u.n, tree->key, tree->cap,
|
||||
sib1(tree)->u.n, sib1(tree)->key, sib1(tree)->cap);
|
||||
break;
|
||||
}
|
||||
case TOpenCall: case TCall: {
|
||||
assert(sib1(sib2(tree))->tag == TXInfo);
|
||||
printf(" key: %d (rule: %d)\n", tree->key, sib1(sib2(tree))->u.n);
|
||||
break;
|
||||
}
|
||||
case TBehind: {
|
||||
printf(" %d\n", tree->u.n);
|
||||
break;
|
||||
}
|
||||
case TCapture: {
|
||||
printf(" kind: '%s' key: %d\n", capkind(tree->cap), tree->key);
|
||||
break;
|
||||
}
|
||||
case TRule: {
|
||||
printf(" key: %d\n", tree->key);
|
||||
sibs = 1; /* do not print 'sib2' (next rule) as a sibling */
|
||||
break;
|
||||
}
|
||||
case TXInfo: {
|
||||
printf(" n: %d\n", tree->u.n);
|
||||
break;
|
||||
}
|
||||
case TGrammar: {
|
||||
TTree *rule = sib1(tree);
|
||||
printf(" %d\n", tree->u.n); /* number of rules */
|
||||
for (i = 0; i < tree->u.n; i++) {
|
||||
printtree(rule, ident + 2);
|
||||
rule = sib2(rule);
|
||||
}
|
||||
assert(rule->tag == TTrue); /* sentinel */
|
||||
sibs = 0; /* siblings already handled */
|
||||
break;
|
||||
}
|
||||
default:
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
if (sibs >= 1) {
|
||||
printtree(sib1(tree), ident + 2);
|
||||
if (sibs >= 2)
|
||||
printtree(sib2(tree), ident + 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void printktable (lua_State *L, int idx) {
|
||||
int n, i;
|
||||
lua_getuservalue(L, idx);
|
||||
if (lua_isnil(L, -1)) /* no ktable? */
|
||||
return;
|
||||
n = lua_rawlen(L, -1);
|
||||
printf("[");
|
||||
for (i = 1; i <= n; i++) {
|
||||
printf("%d = ", i);
|
||||
lua_rawgeti(L, -1, i);
|
||||
if (lua_isstring(L, -1))
|
||||
printf("%s ", lua_tostring(L, -1));
|
||||
else
|
||||
printf("%s ", lua_typename(L, lua_type(L, -1)));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
printf("]\n");
|
||||
/* leave ktable at the stack */
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
#if !defined(lpprint_h)
|
||||
#define lpprint_h
|
||||
|
||||
|
||||
#include "lptree.h"
|
||||
#include "lpvm.h"
|
||||
|
||||
|
||||
#if defined(LPEG_DEBUG)
|
||||
|
||||
void printpatt (Instruction *p);
|
||||
void printtree (TTree *tree, int ident);
|
||||
void printktable (lua_State *L, int idx);
|
||||
void printcharset (const byte *st);
|
||||
void printcaplist (Capture *cap);
|
||||
void printinst (const Instruction *op, const Instruction *p);
|
||||
|
||||
#else
|
||||
|
||||
#define printktable(L,idx) \
|
||||
luaL_error(L, "function only implemented in debug mode")
|
||||
#define printtree(tree,i) \
|
||||
luaL_error(L, "function only implemented in debug mode")
|
||||
#define printpatt(p) \
|
||||
luaL_error(L, "function only implemented in debug mode")
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
|
||||
#if !defined(lptree_h)
|
||||
#define lptree_h
|
||||
|
||||
|
||||
#include "lptypes.h"
|
||||
|
||||
|
||||
/*
|
||||
** types of trees
|
||||
*/
|
||||
typedef enum TTag {
|
||||
TChar = 0, /* 'n' = char */
|
||||
TSet, /* the set is encoded in 'u.set' and the next 'u.set.size' bytes */
|
||||
TAny,
|
||||
TTrue,
|
||||
TFalse,
|
||||
TUTFR, /* range of UTF-8 codepoints; 'n' has initial codepoint;
|
||||
'cap' has length; 'key' has first byte;
|
||||
extra info is similar for end codepoint */
|
||||
TRep, /* 'sib1'* */
|
||||
TSeq, /* 'sib1' 'sib2' */
|
||||
TChoice, /* 'sib1' / 'sib2' */
|
||||
TNot, /* !'sib1' */
|
||||
TAnd, /* &'sib1' */
|
||||
TCall, /* ktable[key] is rule's key; 'sib2' is rule being called */
|
||||
TOpenCall, /* ktable[key] is rule's key */
|
||||
TRule, /* ktable[key] is rule's key (but key == 0 for unused rules);
|
||||
'sib1' is rule's pattern pre-rule; 'sib2' is next rule;
|
||||
extra info 'n' is rule's sequential number */
|
||||
TXInfo, /* extra info */
|
||||
TGrammar, /* 'sib1' is initial (and first) rule */
|
||||
TBehind, /* 'sib1' is pattern, 'n' is how much to go back */
|
||||
TCapture, /* captures: 'cap' is kind of capture (enum 'CapKind');
|
||||
ktable[key] is Lua value associated with capture;
|
||||
'sib1' is capture body */
|
||||
TRunTime /* run-time capture: 'key' is Lua function;
|
||||
'sib1' is capture body */
|
||||
} TTag;
|
||||
|
||||
|
||||
/*
|
||||
** Tree trees
|
||||
** The first child of a tree (if there is one) is immediately after
|
||||
** the tree. A reference to a second child (ps) is its position
|
||||
** relative to the position of the tree itself.
|
||||
*/
|
||||
typedef struct TTree {
|
||||
byte tag;
|
||||
byte cap; /* kind of capture (if it is a capture) */
|
||||
unsigned short key; /* key in ktable for Lua data (0 if no key) */
|
||||
union {
|
||||
int ps; /* occasional second child */
|
||||
int n; /* occasional counter */
|
||||
struct {
|
||||
byte offset; /* compact set offset (in bytes) */
|
||||
byte size; /* compact set size (in bytes) */
|
||||
byte deflt; /* default value */
|
||||
byte bitmap[1]; /* bitmap (open array) */
|
||||
} set; /* for compact sets */
|
||||
} u;
|
||||
} TTree;
|
||||
|
||||
|
||||
/* access to charset */
|
||||
#define treebuffer(t) ((t)->u.set.bitmap)
|
||||
|
||||
|
||||
/*
|
||||
** A complete pattern has its tree plus, if already compiled,
|
||||
** its corresponding code
|
||||
*/
|
||||
typedef struct Pattern {
|
||||
union Instruction *code;
|
||||
TTree tree[1];
|
||||
} Pattern;
|
||||
|
||||
|
||||
/* number of children for each tree */
|
||||
extern const byte numsiblings[];
|
||||
|
||||
/* access to children */
|
||||
#define sib1(t) ((t) + 1)
|
||||
#define sib2(t) ((t) + (t)->u.ps)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
** LPeg - PEG pattern matching for Lua
|
||||
** Copyright 2007-2023, Lua.org & PUC-Rio (see 'lpeg.html' for license)
|
||||
** written by Roberto Ierusalimschy
|
||||
*/
|
||||
|
||||
#if !defined(lptypes_h)
|
||||
#define lptypes_h
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
|
||||
#define VERSION "1.1.0"
|
||||
|
||||
|
||||
#define PATTERN_T "lpeg-pattern"
|
||||
#define MAXSTACKIDX "lpeg-maxstack"
|
||||
|
||||
|
||||
/*
|
||||
** compatibility with Lua 5.1
|
||||
*/
|
||||
#if (LUA_VERSION_NUM == 501)
|
||||
|
||||
#define lp_equal lua_equal
|
||||
|
||||
#define lua_getuservalue lua_getfenv
|
||||
#define lua_setuservalue lua_setfenv
|
||||
|
||||
#define lua_rawlen lua_objlen
|
||||
|
||||
#define luaL_setfuncs(L,f,n) luaL_register(L,NULL,f)
|
||||
#define luaL_newlib(L,f) luaL_register(L,"lpeg",f)
|
||||
|
||||
typedef size_t lua_Unsigned;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if !defined(lp_equal)
|
||||
#define lp_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ)
|
||||
#endif
|
||||
|
||||
|
||||
/* default maximum size for call/backtrack stack */
|
||||
#if !defined(MAXBACK)
|
||||
#define MAXBACK 400
|
||||
#endif
|
||||
|
||||
|
||||
/* maximum number of rules in a grammar (limited by 'unsigned short') */
|
||||
#if !defined(MAXRULES)
|
||||
#define MAXRULES 1000
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* initial size for capture's list */
|
||||
#define INITCAPSIZE 32
|
||||
|
||||
|
||||
/* index, on Lua stack, for subject */
|
||||
#define SUBJIDX 2
|
||||
|
||||
/* number of fixed arguments to 'match' (before capture arguments) */
|
||||
#define FIXEDARGS 3
|
||||
|
||||
/* index, on Lua stack, for capture list */
|
||||
#define caplistidx(ptop) ((ptop) + 2)
|
||||
|
||||
/* index, on Lua stack, for pattern's ktable */
|
||||
#define ktableidx(ptop) ((ptop) + 3)
|
||||
|
||||
/* index, on Lua stack, for backtracking stack */
|
||||
#define stackidx(ptop) ((ptop) + 4)
|
||||
|
||||
|
||||
|
||||
typedef unsigned char byte;
|
||||
|
||||
typedef unsigned int uint;
|
||||
|
||||
|
||||
#define BITSPERCHAR 8
|
||||
|
||||
#define CHARSETSIZE ((UCHAR_MAX/BITSPERCHAR) + 1)
|
||||
|
||||
|
||||
|
||||
typedef struct Charset {
|
||||
byte cs[CHARSETSIZE];
|
||||
} Charset;
|
||||
|
||||
|
||||
|
||||
#define loopset(v,b) { int v; for (v = 0; v < CHARSETSIZE; v++) {b;} }
|
||||
|
||||
#define fillset(s,c) memset(s,c,CHARSETSIZE)
|
||||
#define clearset(s) fillset(s,0)
|
||||
|
||||
/* number of slots needed for 'n' bytes */
|
||||
#define bytes2slots(n) (((n) - 1u) / (uint)sizeof(TTree) + 1u)
|
||||
|
||||
/* set 'b' bit in charset 'cs' */
|
||||
#define setchar(cs,b) ((cs)[(b) >> 3] |= (1 << ((b) & 7)))
|
||||
|
||||
|
||||
/*
|
||||
** in capture instructions, 'kind' of capture and its offset are
|
||||
** packed in field 'aux', 4 bits for each
|
||||
*/
|
||||
#define getkind(op) ((op)->i.aux1 & 0xF)
|
||||
#define getoff(op) (((op)->i.aux1 >> 4) & 0xF)
|
||||
#define joinkindoff(k,o) ((k) | ((o) << 4))
|
||||
|
||||
#define MAXOFF 0xF
|
||||
#define MAXAUX 0xFF
|
||||
|
||||
|
||||
/* maximum number of bytes to look behind */
|
||||
#define MAXBEHIND MAXAUX
|
||||
|
||||
|
||||
/* maximum size (in elements) for a pattern */
|
||||
#define MAXPATTSIZE (SHRT_MAX - 10)
|
||||
|
||||
|
||||
/* size (in instructions) for l bytes (l > 0) */
|
||||
#define instsize(l) ((int)(((l) + (uint)sizeof(Instruction) - 1u) \
|
||||
/ (uint)sizeof(Instruction)))
|
||||
|
||||
|
||||
/* size (in elements) for a ISet instruction */
|
||||
#define CHARSETINSTSIZE (1 + instsize(CHARSETSIZE))
|
||||
|
||||
/* size (in elements) for a IFunc instruction */
|
||||
#define funcinstsize(p) ((p)->i.aux + 2)
|
||||
|
||||
|
||||
|
||||
#define testchar(st,c) ((((uint)(st)[((c) >> 3)]) >> ((c) & 7)) & 1)
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,455 @@
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#include "lpcap.h"
|
||||
#include "lptypes.h"
|
||||
#include "lpvm.h"
|
||||
#include "lpprint.h"
|
||||
|
||||
|
||||
/* initial size for call/backtrack stack */
|
||||
#if !defined(INITBACK)
|
||||
#define INITBACK MAXBACK
|
||||
#endif
|
||||
|
||||
|
||||
#define getoffset(p) (((p) + 1)->offset)
|
||||
|
||||
static const Instruction giveup = {{IGiveup, 0, {0}}};
|
||||
|
||||
|
||||
int charinset (const Instruction *i, const byte *buff, uint c) {
|
||||
c -= i->i.aux2.set.offset;
|
||||
if (c >= ((uint)i->i.aux2.set.size /* size in instructions... */
|
||||
* (uint)sizeof(Instruction) /* in bytes... */
|
||||
* 8u)) /* in bits */
|
||||
return i->i.aux1; /* out of range; return default value */
|
||||
return testchar(buff, c);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid.
|
||||
*/
|
||||
static const char *utf8_decode (const char *o, int *val) {
|
||||
static const uint limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFFu};
|
||||
const unsigned char *s = (const unsigned char *)o;
|
||||
uint c = s[0]; /* first byte */
|
||||
uint res = 0; /* final result */
|
||||
if (c < 0x80) /* ascii? */
|
||||
res = c;
|
||||
else {
|
||||
int count = 0; /* to count number of continuation bytes */
|
||||
while (c & 0x40) { /* still have continuation bytes? */
|
||||
int cc = s[++count]; /* read next byte */
|
||||
if ((cc & 0xC0) != 0x80) /* not a continuation byte? */
|
||||
return NULL; /* invalid byte sequence */
|
||||
res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */
|
||||
c <<= 1; /* to test next bit */
|
||||
}
|
||||
res |= (c & 0x7F) << (count * 5); /* add first byte */
|
||||
if (count > 3 || res > 0x10FFFFu || res <= limits[count])
|
||||
return NULL; /* invalid byte sequence */
|
||||
s += count; /* skip continuation bytes read */
|
||||
}
|
||||
*val = res;
|
||||
return (const char *)s + 1; /* +1 to include first byte */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Virtual Machine
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
typedef struct Stack {
|
||||
const char *s; /* saved position (or NULL for calls) */
|
||||
const Instruction *p; /* next instruction */
|
||||
int caplevel;
|
||||
} Stack;
|
||||
|
||||
|
||||
#define getstackbase(L, ptop) ((Stack *)lua_touserdata(L, stackidx(ptop)))
|
||||
|
||||
|
||||
/*
|
||||
** Ensures the size of array 'capture' (with size '*capsize' and
|
||||
** 'captop' elements being used) is enough to accomodate 'n' extra
|
||||
** elements plus one. (Because several opcodes add stuff to the capture
|
||||
** array, it is simpler to ensure the array always has at least one free
|
||||
** slot upfront and check its size later.)
|
||||
*/
|
||||
|
||||
/* new size in number of elements cannot overflow integers, and new
|
||||
size in bytes cannot overflow size_t. */
|
||||
#define MAXNEWSIZE \
|
||||
(((size_t)INT_MAX) <= (~(size_t)0 / sizeof(Capture)) ? \
|
||||
((size_t)INT_MAX) : (~(size_t)0 / sizeof(Capture)))
|
||||
|
||||
static Capture *growcap (lua_State *L, Capture *capture, int *capsize,
|
||||
int captop, int n, int ptop) {
|
||||
if (*capsize - captop > n)
|
||||
return capture; /* no need to grow array */
|
||||
else { /* must grow */
|
||||
Capture *newc;
|
||||
uint newsize = captop + n + 1; /* minimum size needed */
|
||||
if (newsize < (MAXNEWSIZE / 3) * 2)
|
||||
newsize += newsize / 2; /* 1.5 that size, if not too big */
|
||||
else if (newsize < (MAXNEWSIZE / 9) * 8)
|
||||
newsize += newsize / 8; /* else, try 9/8 that size */
|
||||
else
|
||||
luaL_error(L, "too many captures");
|
||||
newc = (Capture *)lua_newuserdata(L, newsize * sizeof(Capture));
|
||||
memcpy(newc, capture, captop * sizeof(Capture));
|
||||
*capsize = newsize;
|
||||
lua_replace(L, caplistidx(ptop));
|
||||
return newc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Double the size of the stack
|
||||
*/
|
||||
static Stack *doublestack (lua_State *L, Stack **stacklimit, int ptop) {
|
||||
Stack *stack = getstackbase(L, ptop);
|
||||
Stack *newstack;
|
||||
int n = *stacklimit - stack; /* current stack size */
|
||||
int max, newn;
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX);
|
||||
max = lua_tointeger(L, -1); /* maximum allowed size */
|
||||
lua_pop(L, 1);
|
||||
if (n >= max) /* already at maximum size? */
|
||||
luaL_error(L, "backtrack stack overflow (current limit is %d)", max);
|
||||
newn = 2 * n; /* new size */
|
||||
if (newn > max) newn = max;
|
||||
newstack = (Stack *)lua_newuserdata(L, newn * sizeof(Stack));
|
||||
memcpy(newstack, stack, n * sizeof(Stack));
|
||||
lua_replace(L, stackidx(ptop));
|
||||
*stacklimit = newstack + newn;
|
||||
return newstack + n; /* return next position */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Interpret the result of a dynamic capture: false -> fail;
|
||||
** true -> keep current position; number -> next position.
|
||||
** Return new subject position. 'fr' is stack index where
|
||||
** is the result; 'curr' is current subject position; 'limit'
|
||||
** is subject's size.
|
||||
*/
|
||||
static int resdyncaptures (lua_State *L, int fr, int curr, int limit) {
|
||||
lua_Integer res;
|
||||
if (!lua_toboolean(L, fr)) { /* false value? */
|
||||
lua_settop(L, fr - 1); /* remove results */
|
||||
return -1; /* and fail */
|
||||
}
|
||||
else if (lua_isboolean(L, fr)) /* true? */
|
||||
res = curr; /* keep current position */
|
||||
else {
|
||||
res = lua_tointeger(L, fr) - 1; /* new position */
|
||||
if (res < curr || res > limit)
|
||||
luaL_error(L, "invalid position returned by match-time capture");
|
||||
}
|
||||
lua_remove(L, fr); /* remove first result (offset) */
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Add capture values returned by a dynamic capture to the list
|
||||
** 'capture', nested inside a group. 'fd' indexes the first capture
|
||||
** value, 'n' is the number of values (at least 1). The open group
|
||||
** capture is already in 'capture', before the place for the new entries.
|
||||
*/
|
||||
static void adddyncaptures (Index_t index, Capture *capture, int n, int fd) {
|
||||
int i;
|
||||
assert(capture[-1].kind == Cgroup && capture[-1].siz == 0);
|
||||
capture[-1].idx = 0; /* make group capture an anonymous group */
|
||||
for (i = 0; i < n; i++) { /* add runtime captures */
|
||||
capture[i].kind = Cruntime;
|
||||
capture[i].siz = 1; /* mark it as closed */
|
||||
capture[i].idx = fd + i; /* stack index of capture value */
|
||||
capture[i].index = index;
|
||||
}
|
||||
capture[n].kind = Cclose; /* close group */
|
||||
capture[n].siz = 1;
|
||||
capture[n].index = index;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Remove dynamic captures from the Lua stack (called in case of failure)
|
||||
*/
|
||||
static int removedyncap (lua_State *L, Capture *capture,
|
||||
int level, int last) {
|
||||
int id = finddyncap(capture + level, capture + last); /* index of 1st cap. */
|
||||
int top = lua_gettop(L);
|
||||
if (id == 0) return 0; /* no dynamic captures? */
|
||||
lua_settop(L, id - 1); /* remove captures */
|
||||
return top - id + 1; /* number of values removed */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Find the corresponding 'open' capture before 'cap', when that capture
|
||||
** can become a full capture. If a full capture c1 is followed by an
|
||||
** empty capture c2, there is no way to know whether c2 is inside
|
||||
** c1. So, full captures can enclose only captures that start *before*
|
||||
** its end.
|
||||
*/
|
||||
static Capture *findopen (Capture *cap, Index_t currindex) {
|
||||
int i;
|
||||
cap--; /* check last capture */
|
||||
/* Must it be inside current one, but starts where current one ends? */
|
||||
if (!isopencap(cap) && cap->index == currindex)
|
||||
return NULL; /* current one cannot be a full capture */
|
||||
/* else, look for an 'open' capture */
|
||||
for (i = 0; i < MAXLOP; i++, cap--) {
|
||||
if (currindex - cap->index >= UCHAR_MAX)
|
||||
return NULL; /* capture too long for a full capture */
|
||||
else if (isopencap(cap)) /* open capture? */
|
||||
return cap; /* that's the one to be closed */
|
||||
else if (cap->kind == Cclose)
|
||||
return NULL; /* a full capture should not nest a non-full one */
|
||||
}
|
||||
return NULL; /* not found within allowed search limit */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Opcode interpreter
|
||||
*/
|
||||
const char *match (lua_State *L, const char *o, const char *s, const char *e,
|
||||
Instruction *op, Capture *capture, int ptop) {
|
||||
Stack stackbase[INITBACK];
|
||||
Stack *stacklimit = stackbase + INITBACK;
|
||||
Stack *stack = stackbase; /* point to first empty slot in stack */
|
||||
int capsize = INITCAPSIZE;
|
||||
int captop = 0; /* point to first empty slot in captures */
|
||||
int ndyncap = 0; /* number of dynamic captures (in Lua stack) */
|
||||
const Instruction *p = op; /* current instruction */
|
||||
stack->p = &giveup; stack->s = s; stack->caplevel = 0; stack++;
|
||||
lua_pushlightuserdata(L, stackbase);
|
||||
for (;;) {
|
||||
#if defined(DEBUG)
|
||||
printf("-------------------------------------\n");
|
||||
printcaplist(capture, capture + captop);
|
||||
printf("s: |%s| stck:%d, dyncaps:%d, caps:%d ",
|
||||
s, (int)(stack - getstackbase(L, ptop)), ndyncap, captop);
|
||||
printinst(op, p);
|
||||
#endif
|
||||
assert(stackidx(ptop) + ndyncap == lua_gettop(L) && ndyncap <= captop);
|
||||
switch ((Opcode)p->i.code) {
|
||||
case IEnd: {
|
||||
assert(stack == getstackbase(L, ptop) + 1);
|
||||
capture[captop].kind = Cclose;
|
||||
capture[captop].index = MAXINDT;
|
||||
return s;
|
||||
}
|
||||
case IGiveup: {
|
||||
assert(stack == getstackbase(L, ptop));
|
||||
return NULL;
|
||||
}
|
||||
case IRet: {
|
||||
assert(stack > getstackbase(L, ptop) && (stack - 1)->s == NULL);
|
||||
p = (--stack)->p;
|
||||
continue;
|
||||
}
|
||||
case IAny: {
|
||||
if (s < e) { p++; s++; }
|
||||
else goto fail;
|
||||
continue;
|
||||
}
|
||||
case IUTFR: {
|
||||
int codepoint;
|
||||
if (s >= e)
|
||||
goto fail;
|
||||
s = utf8_decode (s, &codepoint);
|
||||
if (s && p[1].offset <= codepoint && codepoint <= utf_to(p))
|
||||
p += 2;
|
||||
else
|
||||
goto fail;
|
||||
continue;
|
||||
}
|
||||
case ITestAny: {
|
||||
if (s < e) p += 2;
|
||||
else p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case IChar: {
|
||||
if ((byte)*s == p->i.aux1 && s < e) { p++; s++; }
|
||||
else goto fail;
|
||||
continue;
|
||||
}
|
||||
case ITestChar: {
|
||||
if ((byte)*s == p->i.aux1 && s < e) p += 2;
|
||||
else p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case ISet: {
|
||||
uint c = (byte)*s;
|
||||
if (charinset(p, (p+1)->buff, c) && s < e)
|
||||
{ p += 1 + p->i.aux2.set.size; s++; }
|
||||
else goto fail;
|
||||
continue;
|
||||
}
|
||||
case ITestSet: {
|
||||
uint c = (byte)*s;
|
||||
if (charinset(p, (p + 2)->buff, c) && s < e)
|
||||
p += 2 + p->i.aux2.set.size;
|
||||
else p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case IBehind: {
|
||||
int n = p->i.aux1;
|
||||
if (n > s - o) goto fail;
|
||||
s -= n; p++;
|
||||
continue;
|
||||
}
|
||||
case ISpan: {
|
||||
for (; s < e; s++) {
|
||||
uint c = (byte)*s;
|
||||
if (!charinset(p, (p+1)->buff, c)) break;
|
||||
}
|
||||
p += 1 + p->i.aux2.set.size;
|
||||
continue;
|
||||
}
|
||||
case IJmp: {
|
||||
p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case IChoice: {
|
||||
if (stack == stacklimit)
|
||||
stack = doublestack(L, &stacklimit, ptop);
|
||||
stack->p = p + getoffset(p);
|
||||
stack->s = s;
|
||||
stack->caplevel = captop;
|
||||
stack++;
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
case ICall: {
|
||||
if (stack == stacklimit)
|
||||
stack = doublestack(L, &stacklimit, ptop);
|
||||
stack->s = NULL;
|
||||
stack->p = p + 2; /* save return address */
|
||||
stack++;
|
||||
p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case ICommit: {
|
||||
assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL);
|
||||
stack--;
|
||||
p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case IPartialCommit: {
|
||||
assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL);
|
||||
(stack - 1)->s = s;
|
||||
(stack - 1)->caplevel = captop;
|
||||
p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case IBackCommit: {
|
||||
assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL);
|
||||
s = (--stack)->s;
|
||||
if (ndyncap > 0) /* are there matchtime captures? */
|
||||
ndyncap -= removedyncap(L, capture, stack->caplevel, captop);
|
||||
captop = stack->caplevel;
|
||||
p += getoffset(p);
|
||||
continue;
|
||||
}
|
||||
case IFailTwice:
|
||||
assert(stack > getstackbase(L, ptop));
|
||||
stack--;
|
||||
/* FALLTHROUGH */
|
||||
case IFail:
|
||||
fail: { /* pattern failed: try to backtrack */
|
||||
do { /* remove pending calls */
|
||||
assert(stack > getstackbase(L, ptop));
|
||||
s = (--stack)->s;
|
||||
} while (s == NULL);
|
||||
if (ndyncap > 0) /* is there matchtime captures? */
|
||||
ndyncap -= removedyncap(L, capture, stack->caplevel, captop);
|
||||
captop = stack->caplevel;
|
||||
p = stack->p;
|
||||
#if defined(DEBUG)
|
||||
printf("**FAIL**\n");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
case ICloseRunTime: {
|
||||
CapState cs;
|
||||
int rem, res, n;
|
||||
int fr = lua_gettop(L) + 1; /* stack index of first result */
|
||||
cs.reclevel = 0; cs.L = L;
|
||||
cs.s = o; cs.ocap = capture; cs.ptop = ptop;
|
||||
n = runtimecap(&cs, capture + captop, s, &rem); /* call function */
|
||||
captop -= n; /* remove nested captures */
|
||||
ndyncap -= rem; /* update number of dynamic captures */
|
||||
fr -= rem; /* 'rem' items were popped from Lua stack */
|
||||
res = resdyncaptures(L, fr, s - o, e - o); /* get result */
|
||||
if (res == -1) /* fail? */
|
||||
goto fail;
|
||||
s = o + res; /* else update current position */
|
||||
n = lua_gettop(L) - fr + 1; /* number of new captures */
|
||||
ndyncap += n; /* update number of dynamic captures */
|
||||
if (n == 0) /* no new captures? */
|
||||
captop--; /* remove open group */
|
||||
else { /* new captures; keep original open group */
|
||||
if (fr + n >= SHRT_MAX)
|
||||
luaL_error(L, "too many results in match-time capture");
|
||||
/* add new captures + close group to 'capture' list */
|
||||
capture = growcap(L, capture, &capsize, captop, n + 1, ptop);
|
||||
adddyncaptures(s - o, capture + captop, n, fr);
|
||||
captop += n + 1; /* new captures + close group */
|
||||
}
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
case ICloseCapture: {
|
||||
Capture *open = findopen(capture + captop, s - o);
|
||||
assert(captop > 0);
|
||||
if (open) { /* if possible, turn capture into a full capture */
|
||||
open->siz = (s - o) - open->index + 1;
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
else { /* must create a close capture */
|
||||
capture[captop].siz = 1; /* mark entry as closed */
|
||||
capture[captop].index = s - o;
|
||||
goto pushcapture;
|
||||
}
|
||||
}
|
||||
case IOpenCapture:
|
||||
capture[captop].siz = 0; /* mark entry as open */
|
||||
capture[captop].index = s - o;
|
||||
goto pushcapture;
|
||||
case IFullCapture:
|
||||
capture[captop].siz = getoff(p) + 1; /* save capture size */
|
||||
capture[captop].index = s - o - getoff(p);
|
||||
/* goto pushcapture; */
|
||||
pushcapture: {
|
||||
capture[captop].idx = p->i.aux2.key;
|
||||
capture[captop].kind = getkind(p);
|
||||
captop++;
|
||||
capture = growcap(L, capture, &capsize, captop, 0, ptop);
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
default: assert(0); return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
#if !defined(lpvm_h)
|
||||
#define lpvm_h
|
||||
|
||||
#include "lpcap.h"
|
||||
|
||||
|
||||
/*
|
||||
** About Character sets in instructions: a set is a bit map with an
|
||||
** initial offset, in bits, and a size, in number of instructions.
|
||||
** aux1 has the default value for the bits outsize that range.
|
||||
*/
|
||||
|
||||
|
||||
/* Virtual Machine's instructions */
|
||||
typedef enum Opcode {
|
||||
IAny, /* if no char, fail */
|
||||
IChar, /* if char != aux1, fail */
|
||||
ISet, /* if char not in set, fail */
|
||||
ITestAny, /* in no char, jump to 'offset' */
|
||||
ITestChar, /* if char != aux1, jump to 'offset' */
|
||||
ITestSet, /* if char not in set, jump to 'offset' */
|
||||
ISpan, /* read a span of chars in set */
|
||||
IUTFR, /* if codepoint not in range [offset, utf_to], fail */
|
||||
IBehind, /* walk back 'aux1' characters (fail if not possible) */
|
||||
IRet, /* return from a rule */
|
||||
IEnd, /* end of pattern */
|
||||
IChoice, /* stack a choice; next fail will jump to 'offset' */
|
||||
IJmp, /* jump to 'offset' */
|
||||
ICall, /* call rule at 'offset' */
|
||||
IOpenCall, /* call rule number 'key' (must be closed to a ICall) */
|
||||
ICommit, /* pop choice and jump to 'offset' */
|
||||
IPartialCommit, /* update top choice to current position and jump */
|
||||
IBackCommit, /* backtrack like "fail" but jump to its own 'offset' */
|
||||
IFailTwice, /* pop one choice and then fail */
|
||||
IFail, /* go back to saved state on choice and jump to saved offset */
|
||||
IGiveup, /* internal use */
|
||||
IFullCapture, /* complete capture of last 'off' chars */
|
||||
IOpenCapture, /* start a capture */
|
||||
ICloseCapture,
|
||||
ICloseRunTime,
|
||||
IEmpty /* to fill empty slots left by optimizations */
|
||||
} Opcode;
|
||||
|
||||
|
||||
/*
|
||||
** All array of instructions has a 'codesize' as its first element
|
||||
** and is referred by a pointer to its second element, which is the
|
||||
** first actual opcode.
|
||||
*/
|
||||
typedef union Instruction {
|
||||
struct Inst {
|
||||
byte code;
|
||||
byte aux1;
|
||||
union {
|
||||
short key;
|
||||
struct {
|
||||
byte offset;
|
||||
byte size;
|
||||
} set;
|
||||
} aux2;
|
||||
} i;
|
||||
int offset;
|
||||
uint codesize;
|
||||
byte buff[1];
|
||||
} Instruction;
|
||||
|
||||
|
||||
/* extract 24-bit value from an instruction */
|
||||
#define utf_to(inst) (((inst)->i.aux2.key << 8) | (inst)->i.aux1)
|
||||
|
||||
|
||||
int charinset (const Instruction *i, const byte *buff, uint c);
|
||||
const char *match (lua_State *L, const char *o, const char *s, const char *e,
|
||||
Instruction *op, Capture *capture, int ptop);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
LIBNAME = lpeg
|
||||
LUADIR = ./lua/
|
||||
|
||||
COPT = -O2 -DNDEBUG
|
||||
# COPT = -O0 -DLPEG_DEBUG -g
|
||||
|
||||
CWARNS = -Wall -Wextra -pedantic \
|
||||
-Waggregate-return \
|
||||
-Wcast-align \
|
||||
-Wcast-qual \
|
||||
-Wdisabled-optimization \
|
||||
-Wpointer-arith \
|
||||
-Wshadow \
|
||||
-Wredundant-decls \
|
||||
-Wsign-compare \
|
||||
-Wundef \
|
||||
-Wwrite-strings \
|
||||
-Wbad-function-cast \
|
||||
-Wdeclaration-after-statement \
|
||||
-Wmissing-prototypes \
|
||||
-Wmissing-declarations \
|
||||
-Wnested-externs \
|
||||
-Wstrict-prototypes \
|
||||
-Wc++-compat \
|
||||
# -Wunreachable-code \
|
||||
|
||||
|
||||
CFLAGS = $(CWARNS) $(COPT) -std=c99 -I$(LUADIR) -fPIC
|
||||
CC = gcc
|
||||
|
||||
FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o lpcset.o
|
||||
|
||||
# For Linux
|
||||
linux:
|
||||
$(MAKE) lpeg.so "DLLFLAGS = -shared -fPIC"
|
||||
|
||||
# For Mac OS
|
||||
macosx:
|
||||
$(MAKE) lpeg.so "DLLFLAGS = -bundle -undefined dynamic_lookup"
|
||||
|
||||
lpeg.so: $(FILES)
|
||||
env $(CC) $(DLLFLAGS) $(FILES) -o lpeg.so
|
||||
|
||||
$(FILES): makefile
|
||||
|
||||
test: test.lua re.lua lpeg.so
|
||||
./test.lua
|
||||
|
||||
clean:
|
||||
rm -f $(FILES) lpeg.so
|
||||
|
||||
|
||||
lpcap.o: lpcap.c lpcap.h lptypes.h
|
||||
lpcode.o: lpcode.c lptypes.h lpcode.h lptree.h lpvm.h lpcap.h lpcset.h
|
||||
lpcset.o: lpcset.c lptypes.h lpcset.h lpcode.h lptree.h lpvm.h lpcap.h
|
||||
lpprint.o: lpprint.c lptypes.h lpprint.h lptree.h lpvm.h lpcap.h lpcode.h
|
||||
lptree.o: lptree.c lptypes.h lpcap.h lpcode.h lptree.h lpvm.h lpprint.h \
|
||||
lpcset.h
|
||||
lpvm.o: lpvm.c lpcap.h lptypes.h lpvm.h lpprint.h lptree.h
|
||||
@@ -0,0 +1,472 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"//www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>LPeg.re - Regex syntax for LPEG</title>
|
||||
<link rel="stylesheet"
|
||||
href="//www.inf.puc-rio.br/~roberto/lpeg/doc.css"
|
||||
type="text/css"/>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="container">
|
||||
|
||||
<div id="product">
|
||||
<div id="product_logo">
|
||||
<a href="//www.inf.puc-rio.br/~roberto/lpeg/">
|
||||
<img alt="LPeg logo" src="lpeg-128.gif"/>
|
||||
</a>
|
||||
</div>
|
||||
<div id="product_name"><big><strong>LPeg.re</strong></big></div>
|
||||
<div id="product_description">
|
||||
Regex syntax for LPEG
|
||||
</div>
|
||||
</div> <!-- id="product" -->
|
||||
|
||||
<div id="main">
|
||||
|
||||
<div id="navigation">
|
||||
<h1>re</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="#basic">Basic Constructions</a></li>
|
||||
<li><a href="#func">Functions</a></li>
|
||||
<li><a href="#ex">Some Examples</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div> <!-- id="navigation" -->
|
||||
|
||||
<div id="content">
|
||||
|
||||
<h2><a name="basic"></a>The <code>re</code> Module</h2>
|
||||
|
||||
<p>
|
||||
The <code>re</code> module
|
||||
(provided by file <code>re.lua</code> in the distribution)
|
||||
supports a somewhat conventional regex syntax
|
||||
for pattern usage within <a href="lpeg.html">LPeg</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The next table summarizes <code>re</code>'s syntax.
|
||||
A <code>p</code> represents an arbitrary pattern;
|
||||
<code>num</code> represents a number (<code>[0-9]+</code>);
|
||||
<code>name</code> represents an identifier
|
||||
(<code>[a-zA-Z][a-zA-Z0-9_]*</code>).
|
||||
Constructions are listed in order of decreasing precedence.
|
||||
<table border="1">
|
||||
<tbody><tr><td><b>Syntax</b></td><td><b>Description</b></td></tr>
|
||||
<tr><td><code>( p )</code></td> <td>grouping</td></tr>
|
||||
<tr><td><code>& p</code></td> <td>and predicate</td></tr>
|
||||
<tr><td><code>! p</code></td> <td>not predicate</td></tr>
|
||||
<tr><td><code>p1 p2</code></td> <td>concatenation</td></tr>
|
||||
<tr><td><code>p1 / p2</code></td> <td>ordered choice</td></tr>
|
||||
<tr><td><code>p ?</code></td> <td>optional match</td></tr>
|
||||
<tr><td><code>p *</code></td> <td>zero or more repetitions</td></tr>
|
||||
<tr><td><code>p +</code></td> <td>one or more repetitions</td></tr>
|
||||
<tr><td><code>p^num</code></td>
|
||||
<td>exactly <code>num</code> repetitions</td></tr>
|
||||
<tr><td><code>p^+num</code></td>
|
||||
<td>at least <code>num</code> repetitions</td></tr>
|
||||
<tr><td><code>p^-num</code></td>
|
||||
<td>at most <code>num</code> repetitions</td></tr>
|
||||
<tr><td>(<code>name <- p</code>)<sup>+</sup></td> <td>grammar</td></tr>
|
||||
<tr><td><code>'string'</code></td> <td>literal string</td></tr>
|
||||
<tr><td><code>"string"</code></td> <td>literal string</td></tr>
|
||||
<tr><td><code>[class]</code></td> <td>character class</td></tr>
|
||||
<tr><td><code>.</code></td> <td>any character</td></tr>
|
||||
<tr><td><code>%name</code></td>
|
||||
<td>pattern <code>defs[name]</code> or a pre-defined pattern</td></tr>
|
||||
<tr><td><code>name</code></td><td>non terminal</td></tr>
|
||||
<tr><td><code><name></code></td><td>non terminal</td></tr>
|
||||
|
||||
<tr><td><code>{}</code></td> <td>position capture</td></tr>
|
||||
<tr><td><code>{ p }</code></td> <td>simple capture</td></tr>
|
||||
<tr><td><code>{: p :}</code></td> <td>anonymous group capture</td></tr>
|
||||
<tr><td><code>{:name: p :}</code></td> <td>named group capture</td></tr>
|
||||
<tr><td><code>{~ p ~}</code></td> <td>substitution capture</td></tr>
|
||||
<tr><td><code>{| p |}</code></td> <td>table capture</td></tr>
|
||||
<tr><td><code>=name</code></td> <td>back reference</td></tr>
|
||||
|
||||
<tr><td><code>p -> 'string'</code></td> <td>string capture</td></tr>
|
||||
<tr><td><code>p -> "string"</code></td> <td>string capture</td></tr>
|
||||
<tr><td><code>p -> num</code></td> <td>numbered capture</td></tr>
|
||||
<tr><td><code>p -> name</code></td> <td>function/query/string capture
|
||||
equivalent to <code>p / defs[name]</code></td></tr>
|
||||
<tr><td><code>p => name</code></td> <td>match-time capture
|
||||
equivalent to <code>lpeg.Cmt(p, defs[name])</code></td></tr>
|
||||
<tr><td><code>p ~> name</code></td> <td>fold capture
|
||||
(deprecated)</td></tr>
|
||||
<tr><td><code>p >> name</code></td> <td>accumulator capture
|
||||
equivalent to <code>(p % defs[name])</code></td></tr>
|
||||
</tbody></table>
|
||||
<p>
|
||||
Any space appearing in a syntax description can be
|
||||
replaced by zero or more space characters and Lua-style short comments
|
||||
(<code>--</code> until end of line).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Character classes define sets of characters.
|
||||
An initial <code>^</code> complements the resulting set.
|
||||
A range <em>x</em><code>-</code><em>y</em> includes in the set
|
||||
all characters with codes between the codes of <em>x</em> and <em>y</em>.
|
||||
A pre-defined class <code>%</code><em>name</em> includes all
|
||||
characters of that class.
|
||||
A simple character includes itself in the set.
|
||||
The only special characters inside a class are <code>^</code>
|
||||
(special only if it is the first character);
|
||||
<code>]</code>
|
||||
(can be included in the set as the first character,
|
||||
after the optional <code>^</code>);
|
||||
<code>%</code> (special only if followed by a letter);
|
||||
and <code>-</code>
|
||||
(can be included in the set as the first or the last character).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Currently the pre-defined classes are similar to those from the
|
||||
Lua's string library
|
||||
(<code>%a</code> for letters,
|
||||
<code>%A</code> for non letters, etc.).
|
||||
There is also a class <code>%nl</code>
|
||||
containing only the newline character,
|
||||
which is particularly handy for grammars written inside long strings,
|
||||
as long strings do not interpret escape sequences like <code>\n</code>.
|
||||
</p>
|
||||
|
||||
|
||||
<h2><a name="func">Functions</a></h2>
|
||||
|
||||
<h3><code>re.compile (string, [, defs])</code></h3>
|
||||
<p>
|
||||
Compiles the given string and
|
||||
returns an equivalent LPeg pattern.
|
||||
The given string may define either an expression or a grammar.
|
||||
The optional <code>defs</code> table provides extra Lua values
|
||||
to be used by the pattern.
|
||||
</p>
|
||||
|
||||
<h3><code>re.find (subject, pattern [, init])</code></h3>
|
||||
<p>
|
||||
Searches the given pattern in the given subject.
|
||||
If it finds a match,
|
||||
returns the index where this occurrence starts and
|
||||
the index where it ends.
|
||||
Otherwise, returns nil.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
An optional numeric argument <code>init</code> makes the search
|
||||
starts at that position in the subject string.
|
||||
As usual in Lua libraries,
|
||||
a negative value counts from the end.
|
||||
</p>
|
||||
|
||||
<h3><code>re.gsub (subject, pattern, replacement)</code></h3>
|
||||
<p>
|
||||
Does a <em>global substitution</em>,
|
||||
replacing all occurrences of <code>pattern</code>
|
||||
in the given <code>subject</code> by <code>replacement</code>.
|
||||
|
||||
<h3><code>re.match (subject, pattern)</code></h3>
|
||||
<p>
|
||||
Matches the given pattern against the given subject,
|
||||
returning all captures.
|
||||
</p>
|
||||
|
||||
<h3><code>re.updatelocale ()</code></h3>
|
||||
<p>
|
||||
Updates the pre-defined character classes to the current locale.
|
||||
</p>
|
||||
|
||||
|
||||
<h2><a name="ex">Some Examples</a></h2>
|
||||
|
||||
<h3>A complete simple program</h3>
|
||||
<p>
|
||||
The next code shows a simple complete Lua program using
|
||||
the <code>re</code> module:
|
||||
</p>
|
||||
<pre class="example">
|
||||
local re = require"re"
|
||||
|
||||
-- find the position of the first numeral in a string
|
||||
print(re.find("the number 423 is odd", "[0-9]+")) --> 12 14
|
||||
|
||||
-- returns all words in a string
|
||||
print(re.match("the number 423 is odd", "({%a+} / .)*"))
|
||||
--> the number is odd
|
||||
|
||||
-- returns the first numeral in a string
|
||||
print(re.match("the number 423 is odd", "s <- {%d+} / . s"))
|
||||
--> 423
|
||||
|
||||
-- substitutes a dot for each vowel in a string
|
||||
print(re.gsub("hello World", "[aeiou]", "."))
|
||||
--> h.ll. W.rld
|
||||
</pre>
|
||||
|
||||
|
||||
<h3>Balanced parentheses</h3>
|
||||
<p>
|
||||
The following call will produce the same pattern produced by the
|
||||
Lua expression in the
|
||||
<a href="lpeg.html#balanced">balanced parentheses</a> example:
|
||||
</p>
|
||||
<pre class="example">
|
||||
b = re.compile[[ balanced <- "(" ([^()] / balanced)* ")" ]]
|
||||
</pre>
|
||||
|
||||
<h3>String reversal</h3>
|
||||
<p>
|
||||
The next example reverses a string:
|
||||
</p>
|
||||
<pre class="example">
|
||||
rev = re.compile[[ R <- (!.) -> '' / ({.} R) -> '%2%1']]
|
||||
print(rev:match"0123456789") --> 9876543210
|
||||
</pre>
|
||||
|
||||
<h3>CSV decoder</h3>
|
||||
<p>
|
||||
The next example replicates the <a href="lpeg.html#CSV">CSV decoder</a>:
|
||||
</p>
|
||||
<pre class="example">
|
||||
record = re.compile[[
|
||||
record <- {| field (',' field)* |} (%nl / !.)
|
||||
field <- escaped / nonescaped
|
||||
nonescaped <- { [^,"%nl]* }
|
||||
escaped <- '"' {~ ([^"] / '""' -> '"')* ~} '"'
|
||||
]]
|
||||
</pre>
|
||||
|
||||
<h3>Lua's long strings</h3>
|
||||
<p>
|
||||
The next example matches Lua long strings:
|
||||
</p>
|
||||
<pre class="example">
|
||||
c = re.compile([[
|
||||
longstring <- ('[' {:eq: '='* :} '[' close)
|
||||
close <- ']' =eq ']' / . close
|
||||
]])
|
||||
|
||||
print(c:match'[==[]]===]]]]==]===[]') --> 17
|
||||
</pre>
|
||||
|
||||
<h3>Abstract Syntax Trees</h3>
|
||||
<p>
|
||||
This example shows a simple way to build an
|
||||
abstract syntax tree (AST) for a given grammar.
|
||||
To keep our example simple,
|
||||
let us consider the following grammar
|
||||
for lists of names:
|
||||
</p>
|
||||
<pre class="example">
|
||||
p = re.compile[[
|
||||
listname <- (name s)*
|
||||
name <- [a-z][a-z]*
|
||||
s <- %s*
|
||||
]]
|
||||
</pre>
|
||||
<p>
|
||||
Now, we will add captures to build a corresponding AST.
|
||||
As a first step, the pattern will build a table to
|
||||
represent each non terminal;
|
||||
terminals will be represented by their corresponding strings:
|
||||
</p>
|
||||
<pre class="example">
|
||||
c = re.compile[[
|
||||
listname <- {| (name s)* |}
|
||||
name <- {| {[a-z][a-z]*} |}
|
||||
s <- %s*
|
||||
]]
|
||||
</pre>
|
||||
<p>
|
||||
Now, a match against <code>"hi hello bye"</code>
|
||||
results in the table
|
||||
<code>{{"hi"}, {"hello"}, {"bye"}}</code>.
|
||||
</p>
|
||||
<p>
|
||||
For such a simple grammar,
|
||||
this AST is more than enough;
|
||||
actually, the tables around each single name
|
||||
are already overkilling.
|
||||
More complex grammars,
|
||||
however, may need some more structure.
|
||||
Specifically,
|
||||
it would be useful if each table had
|
||||
a <code>tag</code> field telling what non terminal
|
||||
that table represents.
|
||||
We can add such a tag using
|
||||
<a href="lpeg.html#cap-g">named group captures</a>:
|
||||
</p>
|
||||
<pre class="example">
|
||||
x = re.compile[[
|
||||
listname <- {| {:tag: '' -> 'list':} (name s)* |}
|
||||
name <- {| {:tag: '' -> 'id':} {[a-z][a-z]*} |}
|
||||
s <- ' '*
|
||||
]]
|
||||
</pre>
|
||||
<p>
|
||||
With these group captures,
|
||||
a match against <code>"hi hello bye"</code>
|
||||
results in the following table:
|
||||
</p>
|
||||
<pre class="example">
|
||||
{tag="list",
|
||||
{tag="id", "hi"},
|
||||
{tag="id", "hello"},
|
||||
{tag="id", "bye"}
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
||||
<h3>Indented blocks</h3>
|
||||
<p>
|
||||
This example breaks indented blocks into tables,
|
||||
respecting the indentation:
|
||||
</p>
|
||||
<pre class="example">
|
||||
p = re.compile[[
|
||||
block <- {| {:ident:' '*:} line
|
||||
((=ident !' ' line) / &(=ident ' ') block)* |}
|
||||
line <- {[^%nl]*} %nl
|
||||
]]
|
||||
</pre>
|
||||
<p>
|
||||
As an example,
|
||||
consider the following text:
|
||||
</p>
|
||||
<pre class="example">
|
||||
t = p:match[[
|
||||
first line
|
||||
subline 1
|
||||
subline 2
|
||||
second line
|
||||
third line
|
||||
subline 3.1
|
||||
subline 3.1.1
|
||||
subline 3.2
|
||||
]]
|
||||
</pre>
|
||||
<p>
|
||||
The resulting table <code>t</code> will be like this:
|
||||
</p>
|
||||
<pre class="example">
|
||||
{'first line'; {'subline 1'; 'subline 2'; ident = ' '};
|
||||
'second line';
|
||||
'third line'; { 'subline 3.1'; {'subline 3.1.1'; ident = ' '};
|
||||
'subline 3.2'; ident = ' '};
|
||||
ident = ''}
|
||||
</pre>
|
||||
|
||||
<h3>Macro expander</h3>
|
||||
<p>
|
||||
This example implements a simple macro expander.
|
||||
Macros must be defined as part of the pattern,
|
||||
following some simple rules:
|
||||
</p>
|
||||
<pre class="example">
|
||||
p = re.compile[[
|
||||
text <- {~ item* ~}
|
||||
item <- macro / [^()] / '(' item* ')'
|
||||
arg <- ' '* {~ (!',' item)* ~}
|
||||
args <- '(' arg (',' arg)* ')'
|
||||
-- now we define some macros
|
||||
macro <- ('apply' args) -> '%1(%2)'
|
||||
/ ('add' args) -> '%1 + %2'
|
||||
/ ('mul' args) -> '%1 * %2'
|
||||
]]
|
||||
|
||||
print(p:match"add(mul(a,b), apply(f,x))") --> a * b + f(x)
|
||||
</pre>
|
||||
<p>
|
||||
A <code>text</code> is a sequence of items,
|
||||
wherein we apply a substitution capture to expand any macros.
|
||||
An <code>item</code> is either a macro,
|
||||
any character different from parentheses,
|
||||
or a parenthesized expression.
|
||||
A macro argument (<code>arg</code>) is a sequence
|
||||
of items different from a comma.
|
||||
(Note that a comma may appear inside an item,
|
||||
e.g., inside a parenthesized expression.)
|
||||
Again we do a substitution capture to expand any macro
|
||||
in the argument before expanding the outer macro.
|
||||
<code>args</code> is a list of arguments separated by commas.
|
||||
Finally we define the macros.
|
||||
Each macro is a string substitution;
|
||||
it replaces the macro name and its arguments by its corresponding string,
|
||||
with each <code>%</code><em>n</em> replaced by the <em>n</em>-th argument.
|
||||
</p>
|
||||
|
||||
<h3>Patterns</h3>
|
||||
<p>
|
||||
This example shows the complete syntax
|
||||
of patterns accepted by <code>re</code>.
|
||||
</p>
|
||||
<pre class="example">
|
||||
p = [=[
|
||||
|
||||
pattern <- exp !.
|
||||
exp <- S (grammar / alternative)
|
||||
|
||||
alternative <- seq ('/' S seq)*
|
||||
seq <- prefix*
|
||||
prefix <- '&' S prefix / '!' S prefix / suffix
|
||||
suffix <- primary S (([+*?]
|
||||
/ '^' [+-]? num
|
||||
/ '->' S (string / '{}' / name)
|
||||
/ '>>' S name
|
||||
/ '=>' S name) S)*
|
||||
|
||||
primary <- '(' exp ')' / string / class / defined
|
||||
/ '{:' (name ':')? exp ':}'
|
||||
/ '=' name
|
||||
/ '{}'
|
||||
/ '{~' exp '~}'
|
||||
/ '{|' exp '|}'
|
||||
/ '{' exp '}'
|
||||
/ '.'
|
||||
/ name S !arrow
|
||||
/ '<' name '>' -- old-style non terminals
|
||||
|
||||
grammar <- definition+
|
||||
definition <- name S arrow exp
|
||||
|
||||
class <- '[' '^'? item (!']' item)* ']'
|
||||
item <- defined / range / .
|
||||
range <- . '-' [^]]
|
||||
|
||||
S <- (%s / '--' [^%nl]*)* -- spaces and comments
|
||||
name <- [A-Za-z_][A-Za-z0-9_]*
|
||||
arrow <- '<-'
|
||||
num <- [0-9]+
|
||||
string <- '"' [^"]* '"' / "'" [^']* "'"
|
||||
defined <- '%' name
|
||||
|
||||
]=]
|
||||
|
||||
print(re.match(p, p)) -- a self description must match itself
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
<h2><a name="license">License</a></h2>
|
||||
|
||||
<p>
|
||||
This module is part of the <a href="lpeg.html">LPeg</a> package and shares
|
||||
its <a href="lpeg.html#license">license</a>.
|
||||
|
||||
|
||||
</div> <!-- id="content" -->
|
||||
|
||||
</div> <!-- id="main" -->
|
||||
|
||||
</div> <!-- id="container" -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,270 @@
|
||||
--
|
||||
-- Copyright 2007-2023, Lua.org & PUC-Rio (see 'lpeg.html' for license)
|
||||
-- written by Roberto Ierusalimschy
|
||||
--
|
||||
|
||||
-- imported functions and modules
|
||||
local tonumber, type, print, error = tonumber, type, print, error
|
||||
local setmetatable = setmetatable
|
||||
local m = require"lpeg"
|
||||
|
||||
-- 'm' will be used to parse expressions, and 'mm' will be used to
|
||||
-- create expressions; that is, 're' runs on 'm', creating patterns
|
||||
-- on 'mm'
|
||||
local mm = m
|
||||
|
||||
-- patterns' metatable
|
||||
local mt = getmetatable(mm.P(0))
|
||||
|
||||
|
||||
local version = _VERSION
|
||||
|
||||
-- No more global accesses after this point
|
||||
_ENV = nil -- does no harm in Lua 5.1
|
||||
|
||||
|
||||
local any = m.P(1)
|
||||
|
||||
|
||||
-- Pre-defined names
|
||||
local Predef = { nl = m.P"\n" }
|
||||
|
||||
|
||||
local mem
|
||||
local fmem
|
||||
local gmem
|
||||
|
||||
|
||||
local function updatelocale ()
|
||||
mm.locale(Predef)
|
||||
Predef.a = Predef.alpha
|
||||
Predef.c = Predef.cntrl
|
||||
Predef.d = Predef.digit
|
||||
Predef.g = Predef.graph
|
||||
Predef.l = Predef.lower
|
||||
Predef.p = Predef.punct
|
||||
Predef.s = Predef.space
|
||||
Predef.u = Predef.upper
|
||||
Predef.w = Predef.alnum
|
||||
Predef.x = Predef.xdigit
|
||||
Predef.A = any - Predef.a
|
||||
Predef.C = any - Predef.c
|
||||
Predef.D = any - Predef.d
|
||||
Predef.G = any - Predef.g
|
||||
Predef.L = any - Predef.l
|
||||
Predef.P = any - Predef.p
|
||||
Predef.S = any - Predef.s
|
||||
Predef.U = any - Predef.u
|
||||
Predef.W = any - Predef.w
|
||||
Predef.X = any - Predef.x
|
||||
mem = {} -- restart memoization
|
||||
fmem = {}
|
||||
gmem = {}
|
||||
local mt = {__mode = "v"}
|
||||
setmetatable(mem, mt)
|
||||
setmetatable(fmem, mt)
|
||||
setmetatable(gmem, mt)
|
||||
end
|
||||
|
||||
|
||||
updatelocale()
|
||||
|
||||
|
||||
|
||||
local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
|
||||
|
||||
|
||||
local function patt_error (s, i)
|
||||
local msg = (#s < i + 20) and s:sub(i)
|
||||
or s:sub(i,i+20) .. "..."
|
||||
msg = ("pattern error near '%s'"):format(msg)
|
||||
error(msg, 2)
|
||||
end
|
||||
|
||||
local function mult (p, n)
|
||||
local np = mm.P(true)
|
||||
while n >= 1 do
|
||||
if n%2 >= 1 then np = np * p end
|
||||
p = p * p
|
||||
n = n/2
|
||||
end
|
||||
return np
|
||||
end
|
||||
|
||||
local function equalcap (s, i, c)
|
||||
if type(c) ~= "string" then return nil end
|
||||
local e = #c + i
|
||||
if s:sub(i, e - 1) == c then return e else return nil end
|
||||
end
|
||||
|
||||
|
||||
local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
|
||||
|
||||
local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
|
||||
|
||||
local arrow = S * "<-"
|
||||
|
||||
local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1
|
||||
|
||||
name = m.C(name)
|
||||
|
||||
|
||||
-- a defined name only have meaning in a given environment
|
||||
local Def = name * m.Carg(1)
|
||||
|
||||
|
||||
local function getdef (id, defs)
|
||||
local c = defs and defs[id]
|
||||
if not c then error("undefined name: " .. id) end
|
||||
return c
|
||||
end
|
||||
|
||||
-- match a name and return a group of its corresponding definition
|
||||
-- and 'f' (to be folded in 'Suffix')
|
||||
local function defwithfunc (f)
|
||||
return m.Cg(Def / getdef * m.Cc(f))
|
||||
end
|
||||
|
||||
|
||||
local num = m.C(m.R"09"^1) * S / tonumber
|
||||
|
||||
local String = "'" * m.C((any - "'")^0) * "'" +
|
||||
'"' * m.C((any - '"')^0) * '"'
|
||||
|
||||
|
||||
local defined = "%" * Def / function (c,Defs)
|
||||
local cat = Defs and Defs[c] or Predef[c]
|
||||
if not cat then error ("name '" .. c .. "' undefined") end
|
||||
return cat
|
||||
end
|
||||
|
||||
local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
|
||||
|
||||
local item = (defined + Range + m.C(any)) / m.P
|
||||
|
||||
local Class =
|
||||
"["
|
||||
* (m.C(m.P"^"^-1)) -- optional complement symbol
|
||||
* (item * ((item % mt.__add) - "]")^0) /
|
||||
function (c, p) return c == "^" and any - p or p end
|
||||
* "]"
|
||||
|
||||
local function adddef (t, k, exp)
|
||||
if t[k] then
|
||||
error("'"..k.."' already defined as a rule")
|
||||
else
|
||||
t[k] = exp
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function firstdef (n, r) return adddef({n}, n, r) end
|
||||
|
||||
|
||||
local function NT (n, b)
|
||||
if not b then
|
||||
error("rule '"..n.."' used outside a grammar")
|
||||
else return mm.V(n)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local exp = m.P{ "Exp",
|
||||
Exp = S * ( m.V"Grammar"
|
||||
+ m.V"Seq" * ("/" * S * m.V"Seq" % mt.__add)^0 );
|
||||
Seq = (m.Cc(m.P"") * (m.V"Prefix" % mt.__mul)^0)
|
||||
* (#seq_follow + patt_error);
|
||||
Prefix = "&" * S * m.V"Prefix" / mt.__len
|
||||
+ "!" * S * m.V"Prefix" / mt.__unm
|
||||
+ m.V"Suffix";
|
||||
Suffix = m.V"Primary" * S *
|
||||
( ( m.P"+" * m.Cc(1, mt.__pow)
|
||||
+ m.P"*" * m.Cc(0, mt.__pow)
|
||||
+ m.P"?" * m.Cc(-1, mt.__pow)
|
||||
+ "^" * ( m.Cg(num * m.Cc(mult))
|
||||
+ m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow))
|
||||
)
|
||||
+ "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div))
|
||||
+ m.P"{}" * m.Cc(nil, m.Ct)
|
||||
+ defwithfunc(mt.__div)
|
||||
)
|
||||
+ "=>" * S * defwithfunc(mm.Cmt)
|
||||
+ ">>" * S * defwithfunc(mt.__mod)
|
||||
+ "~>" * S * defwithfunc(mm.Cf)
|
||||
) % function (a,b,f) return f(a,b) end * S
|
||||
)^0;
|
||||
Primary = "(" * m.V"Exp" * ")"
|
||||
+ String / mm.P
|
||||
+ Class
|
||||
+ defined
|
||||
+ "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
|
||||
function (n, p) return mm.Cg(p, n) end
|
||||
+ "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
|
||||
+ m.P"{}" / mm.Cp
|
||||
+ "{~" * m.V"Exp" * "~}" / mm.Cs
|
||||
+ "{|" * m.V"Exp" * "|}" / mm.Ct
|
||||
+ "{" * m.V"Exp" * "}" / mm.C
|
||||
+ m.P"." * m.Cc(any)
|
||||
+ (name * -arrow + "<" * name * ">") * m.Cb("G") / NT;
|
||||
Definition = name * arrow * m.V"Exp";
|
||||
Grammar = m.Cg(m.Cc(true), "G") *
|
||||
((m.V"Definition" / firstdef) * (m.V"Definition" % adddef)^0) / mm.P
|
||||
}
|
||||
|
||||
local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
|
||||
|
||||
|
||||
local function compile (p, defs)
|
||||
if mm.type(p) == "pattern" then return p end -- already compiled
|
||||
local cp = pattern:match(p, 1, defs)
|
||||
if not cp then error("incorrect pattern", 3) end
|
||||
return cp
|
||||
end
|
||||
|
||||
local function match (s, p, i)
|
||||
local cp = mem[p]
|
||||
if not cp then
|
||||
cp = compile(p)
|
||||
mem[p] = cp
|
||||
end
|
||||
return cp:match(s, i or 1)
|
||||
end
|
||||
|
||||
local function find (s, p, i)
|
||||
local cp = fmem[p]
|
||||
if not cp then
|
||||
cp = compile(p) / 0
|
||||
cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
|
||||
fmem[p] = cp
|
||||
end
|
||||
local i, e = cp:match(s, i or 1)
|
||||
if i then return i, e - 1
|
||||
else return i
|
||||
end
|
||||
end
|
||||
|
||||
local function gsub (s, p, rep)
|
||||
local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
|
||||
gmem[p] = g
|
||||
local cp = g[rep]
|
||||
if not cp then
|
||||
cp = compile(p)
|
||||
cp = mm.Cs((cp / rep + 1)^0)
|
||||
g[rep] = cp
|
||||
end
|
||||
return cp:match(s)
|
||||
end
|
||||
|
||||
|
||||
-- exported names
|
||||
local re = {
|
||||
compile = compile,
|
||||
match = match,
|
||||
find = find,
|
||||
gsub = gsub,
|
||||
updatelocale = updatelocale,
|
||||
}
|
||||
|
||||
if version == "Lua 5.1" then _G.re = re end
|
||||
|
||||
return re
|
||||
Executable
+1667
File diff suppressed because it is too large
Load Diff
+694
@@ -0,0 +1,694 @@
|
||||
/*
|
||||
** $Id: lua.c $
|
||||
** Lua stand-alone interpreter
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lua_c
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
|
||||
#if !defined(LUA_PROGNAME)
|
||||
#define LUA_PROGNAME "lua"
|
||||
#endif
|
||||
|
||||
#if !defined(LUA_INIT_VAR)
|
||||
#define LUA_INIT_VAR "LUA_INIT"
|
||||
#endif
|
||||
|
||||
#define LUA_INITVARVERSION LUA_INIT_VAR LUA_VERSUFFIX
|
||||
|
||||
|
||||
static lua_State *globalL = NULL;
|
||||
|
||||
static const char *progname = LUA_PROGNAME;
|
||||
|
||||
|
||||
#if defined(LUA_USE_POSIX) /* { */
|
||||
|
||||
/*
|
||||
** Use 'sigaction' when available.
|
||||
*/
|
||||
static void setsignal (int sig, void (*handler)(int)) {
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_flags = 0;
|
||||
sigemptyset(&sa.sa_mask); /* do not mask any signal */
|
||||
sigaction(sig, &sa, NULL);
|
||||
}
|
||||
|
||||
#else /* }{ */
|
||||
|
||||
#define setsignal signal
|
||||
|
||||
#endif /* } */
|
||||
|
||||
|
||||
/*
|
||||
** Hook set by signal function to stop the interpreter.
|
||||
*/
|
||||
static void lstop (lua_State *L, lua_Debug *ar) {
|
||||
(void)ar; /* unused arg. */
|
||||
lua_sethook(L, NULL, 0, 0); /* reset hook */
|
||||
luaL_error(L, "interrupted!");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Function to be called at a C signal. Because a C signal cannot
|
||||
** just change a Lua state (as there is no proper synchronization),
|
||||
** this function only sets a hook that, when called, will stop the
|
||||
** interpreter.
|
||||
*/
|
||||
static void laction (int i) {
|
||||
int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
|
||||
setsignal(i, SIG_DFL); /* if another SIGINT happens, terminate process */
|
||||
lua_sethook(globalL, lstop, flag, 1);
|
||||
}
|
||||
|
||||
|
||||
static void print_usage (const char *badoption) {
|
||||
lua_writestringerror("%s: ", progname);
|
||||
if (badoption[1] == 'e' || badoption[1] == 'l')
|
||||
lua_writestringerror("'%s' needs argument\n", badoption);
|
||||
else
|
||||
lua_writestringerror("unrecognized option '%s'\n", badoption);
|
||||
lua_writestringerror(
|
||||
"usage: %s [options] [script [args]]\n"
|
||||
"Available options are:\n"
|
||||
" -e stat execute string 'stat'\n"
|
||||
" -i enter interactive mode after executing 'script'\n"
|
||||
" -l mod require library 'mod' into global 'mod'\n"
|
||||
" -l g=mod require library 'mod' into global 'g'\n"
|
||||
" -v show version information\n"
|
||||
" -E ignore environment variables\n"
|
||||
" -W turn warnings on\n"
|
||||
" -- stop handling options\n"
|
||||
" - stop handling options and execute stdin\n"
|
||||
,
|
||||
progname);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Prints an error message, adding the program name in front of it
|
||||
** (if present)
|
||||
*/
|
||||
static void l_message (const char *pname, const char *msg) {
|
||||
if (pname) lua_writestringerror("%s: ", pname);
|
||||
lua_writestringerror("%s\n", msg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Check whether 'status' is not OK and, if so, prints the error
|
||||
** message on the top of the stack.
|
||||
*/
|
||||
static int report (lua_State *L, int status) {
|
||||
if (status != LUA_OK) {
|
||||
const char *msg = lua_tostring(L, -1);
|
||||
if (msg == NULL)
|
||||
msg = "(error message not a string)";
|
||||
l_message(progname, msg);
|
||||
lua_pop(L, 1); /* remove message */
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Message handler used to run all chunks
|
||||
*/
|
||||
static int msghandler (lua_State *L) {
|
||||
const char *msg = lua_tostring(L, 1);
|
||||
if (msg == NULL) { /* is error object not a string? */
|
||||
if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */
|
||||
lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */
|
||||
return 1; /* that is the message */
|
||||
else
|
||||
msg = lua_pushfstring(L, "(error object is a %s value)",
|
||||
luaL_typename(L, 1));
|
||||
}
|
||||
luaL_traceback(L, L, msg, 1); /* append a standard traceback */
|
||||
return 1; /* return the traceback */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Interface to 'lua_pcall', which sets appropriate message function
|
||||
** and C-signal handler. Used to run all chunks.
|
||||
*/
|
||||
static int docall (lua_State *L, int narg, int nres) {
|
||||
int status;
|
||||
int base = lua_gettop(L) - narg; /* function index */
|
||||
lua_pushcfunction(L, msghandler); /* push message handler */
|
||||
lua_insert(L, base); /* put it under function and args */
|
||||
globalL = L; /* to be available to 'laction' */
|
||||
setsignal(SIGINT, laction); /* set C-signal handler */
|
||||
status = lua_pcall(L, narg, nres, base);
|
||||
setsignal(SIGINT, SIG_DFL); /* reset C-signal handler */
|
||||
lua_remove(L, base); /* remove message handler from the stack */
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static void print_version (void) {
|
||||
lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT));
|
||||
lua_writeline();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Create the 'arg' table, which stores all arguments from the
|
||||
** command line ('argv'). It should be aligned so that, at index 0,
|
||||
** it has 'argv[script]', which is the script name. The arguments
|
||||
** to the script (everything after 'script') go to positive indices;
|
||||
** other arguments (before the script name) go to negative indices.
|
||||
** If there is no script name, assume interpreter's name as base.
|
||||
** (If there is no interpreter's name either, 'script' is -1, so
|
||||
** table sizes are zero.)
|
||||
*/
|
||||
static void createargtable (lua_State *L, char **argv, int argc, int script) {
|
||||
int i, narg;
|
||||
narg = argc - (script + 1); /* number of positive indices */
|
||||
lua_createtable(L, narg, script + 1);
|
||||
for (i = 0; i < argc; i++) {
|
||||
lua_pushstring(L, argv[i]);
|
||||
lua_rawseti(L, -2, i - script);
|
||||
}
|
||||
lua_setglobal(L, "arg");
|
||||
}
|
||||
|
||||
|
||||
static int dochunk (lua_State *L, int status) {
|
||||
if (status == LUA_OK) status = docall(L, 0, 0);
|
||||
return report(L, status);
|
||||
}
|
||||
|
||||
|
||||
static int dofile (lua_State *L, const char *name) {
|
||||
return dochunk(L, luaL_loadfile(L, name));
|
||||
}
|
||||
|
||||
|
||||
static int dostring (lua_State *L, const char *s, const char *name) {
|
||||
return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Receives 'globname[=modname]' and runs 'globname = require(modname)'.
|
||||
** If there is no explicit modname and globname contains a '-', cut
|
||||
** the suffix after '-' (the "version") to make the global name.
|
||||
*/
|
||||
static int dolibrary (lua_State *L, char *globname) {
|
||||
int status;
|
||||
char *suffix = NULL;
|
||||
char *modname = strchr(globname, '=');
|
||||
if (modname == NULL) { /* no explicit name? */
|
||||
modname = globname; /* module name is equal to global name */
|
||||
suffix = strchr(modname, *LUA_IGMARK); /* look for a suffix mark */
|
||||
}
|
||||
else {
|
||||
*modname = '\0'; /* global name ends here */
|
||||
modname++; /* module name starts after the '=' */
|
||||
}
|
||||
lua_getglobal(L, "require");
|
||||
lua_pushstring(L, modname);
|
||||
status = docall(L, 1, 1); /* call 'require(modname)' */
|
||||
if (status == LUA_OK) {
|
||||
if (suffix != NULL) /* is there a suffix mark? */
|
||||
*suffix = '\0'; /* remove suffix from global name */
|
||||
lua_setglobal(L, globname); /* globname = require(modname) */
|
||||
}
|
||||
return report(L, status);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Push on the stack the contents of table 'arg' from 1 to #arg
|
||||
*/
|
||||
static int pushargs (lua_State *L) {
|
||||
int i, n;
|
||||
if (lua_getglobal(L, "arg") != LUA_TTABLE)
|
||||
luaL_error(L, "'arg' is not a table");
|
||||
n = (int)luaL_len(L, -1);
|
||||
luaL_checkstack(L, n + 3, "too many arguments to script");
|
||||
for (i = 1; i <= n; i++)
|
||||
lua_rawgeti(L, -i, i);
|
||||
lua_remove(L, -i); /* remove table from the stack */
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
static int handle_script (lua_State *L, char **argv) {
|
||||
int status;
|
||||
const char *fname = argv[0];
|
||||
if (strcmp(fname, "-") == 0 && strcmp(argv[-1], "--") != 0)
|
||||
fname = NULL; /* stdin */
|
||||
status = luaL_loadfile(L, fname);
|
||||
if (status == LUA_OK) {
|
||||
int n = pushargs(L); /* push arguments to script */
|
||||
status = docall(L, n, LUA_MULTRET);
|
||||
}
|
||||
return report(L, status);
|
||||
}
|
||||
|
||||
|
||||
/* bits of various argument indicators in 'args' */
|
||||
#define has_error 1 /* bad option */
|
||||
#define has_i 2 /* -i */
|
||||
#define has_v 4 /* -v */
|
||||
#define has_e 8 /* -e */
|
||||
#define has_E 16 /* -E */
|
||||
|
||||
|
||||
/*
|
||||
** Traverses all arguments from 'argv', returning a mask with those
|
||||
** needed before running any Lua code or an error code if it finds any
|
||||
** invalid argument. In case of error, 'first' is the index of the bad
|
||||
** argument. Otherwise, 'first' is -1 if there is no program name,
|
||||
** 0 if there is no script name, or the index of the script name.
|
||||
*/
|
||||
static int collectargs (char **argv, int *first) {
|
||||
int args = 0;
|
||||
int i;
|
||||
if (argv[0] != NULL) { /* is there a program name? */
|
||||
if (argv[0][0]) /* not empty? */
|
||||
progname = argv[0]; /* save it */
|
||||
}
|
||||
else { /* no program name */
|
||||
*first = -1;
|
||||
return 0;
|
||||
}
|
||||
for (i = 1; argv[i] != NULL; i++) { /* handle arguments */
|
||||
*first = i;
|
||||
if (argv[i][0] != '-') /* not an option? */
|
||||
return args; /* stop handling options */
|
||||
switch (argv[i][1]) { /* else check option */
|
||||
case '-': /* '--' */
|
||||
if (argv[i][2] != '\0') /* extra characters after '--'? */
|
||||
return has_error; /* invalid option */
|
||||
*first = i + 1;
|
||||
return args;
|
||||
case '\0': /* '-' */
|
||||
return args; /* script "name" is '-' */
|
||||
case 'E':
|
||||
if (argv[i][2] != '\0') /* extra characters? */
|
||||
return has_error; /* invalid option */
|
||||
args |= has_E;
|
||||
break;
|
||||
case 'W':
|
||||
if (argv[i][2] != '\0') /* extra characters? */
|
||||
return has_error; /* invalid option */
|
||||
break;
|
||||
case 'i':
|
||||
args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */
|
||||
case 'v':
|
||||
if (argv[i][2] != '\0') /* extra characters? */
|
||||
return has_error; /* invalid option */
|
||||
args |= has_v;
|
||||
break;
|
||||
case 'e':
|
||||
args |= has_e; /* FALLTHROUGH */
|
||||
case 'l': /* both options need an argument */
|
||||
if (argv[i][2] == '\0') { /* no concatenated argument? */
|
||||
i++; /* try next 'argv' */
|
||||
if (argv[i] == NULL || argv[i][0] == '-')
|
||||
return has_error; /* no next argument or it is another option */
|
||||
}
|
||||
break;
|
||||
default: /* invalid option */
|
||||
return has_error;
|
||||
}
|
||||
}
|
||||
*first = 0; /* no script name */
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Processes options 'e' and 'l', which involve running Lua code, and
|
||||
** 'W', which also affects the state.
|
||||
** Returns 0 if some code raises an error.
|
||||
*/
|
||||
static int runargs (lua_State *L, char **argv, int n) {
|
||||
int i;
|
||||
for (i = 1; i < n; i++) {
|
||||
int option = argv[i][1];
|
||||
lua_assert(argv[i][0] == '-'); /* already checked */
|
||||
switch (option) {
|
||||
case 'e': case 'l': {
|
||||
int status;
|
||||
char *extra = argv[i] + 2; /* both options need an argument */
|
||||
if (*extra == '\0') extra = argv[++i];
|
||||
lua_assert(extra != NULL);
|
||||
status = (option == 'e')
|
||||
? dostring(L, extra, "=(command line)")
|
||||
: dolibrary(L, extra);
|
||||
if (status != LUA_OK) return 0;
|
||||
break;
|
||||
}
|
||||
case 'W':
|
||||
lua_warning(L, "@on", 0); /* warnings on */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int handle_luainit (lua_State *L) {
|
||||
const char *name = "=" LUA_INITVARVERSION;
|
||||
const char *init = getenv(name + 1);
|
||||
if (init == NULL) {
|
||||
name = "=" LUA_INIT_VAR;
|
||||
init = getenv(name + 1); /* try alternative name */
|
||||
}
|
||||
if (init == NULL) return LUA_OK;
|
||||
else if (init[0] == '@')
|
||||
return dofile(L, init+1);
|
||||
else
|
||||
return dostring(L, init, name);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Read-Eval-Print Loop (REPL)
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
#if !defined(LUA_PROMPT)
|
||||
#define LUA_PROMPT "> "
|
||||
#define LUA_PROMPT2 ">> "
|
||||
#endif
|
||||
|
||||
#if !defined(LUA_MAXINPUT)
|
||||
#define LUA_MAXINPUT 512
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** lua_stdin_is_tty detects whether the standard input is a 'tty' (that
|
||||
** is, whether we're running lua interactively).
|
||||
*/
|
||||
#if !defined(lua_stdin_is_tty) /* { */
|
||||
|
||||
#if defined(LUA_USE_POSIX) /* { */
|
||||
|
||||
#include <unistd.h>
|
||||
#define lua_stdin_is_tty() isatty(0)
|
||||
|
||||
#elif defined(LUA_USE_WINDOWS) /* }{ */
|
||||
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
|
||||
#define lua_stdin_is_tty() _isatty(_fileno(stdin))
|
||||
|
||||
#else /* }{ */
|
||||
|
||||
/* ISO C definition */
|
||||
#define lua_stdin_is_tty() 1 /* assume stdin is a tty */
|
||||
|
||||
#endif /* } */
|
||||
|
||||
#endif /* } */
|
||||
|
||||
|
||||
/*
|
||||
** lua_readline defines how to show a prompt and then read a line from
|
||||
** the standard input.
|
||||
** lua_saveline defines how to "save" a read line in a "history".
|
||||
** lua_freeline defines how to free a line read by lua_readline.
|
||||
*/
|
||||
#if !defined(lua_readline) /* { */
|
||||
|
||||
#if defined(LUA_USE_READLINE) /* { */
|
||||
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
#define lua_initreadline(L) ((void)L, rl_readline_name="lua")
|
||||
#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL)
|
||||
#define lua_saveline(L,line) ((void)L, add_history(line))
|
||||
#define lua_freeline(L,b) ((void)L, free(b))
|
||||
|
||||
#else /* }{ */
|
||||
|
||||
#define lua_initreadline(L) ((void)L)
|
||||
#define lua_readline(L,b,p) \
|
||||
((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \
|
||||
fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */
|
||||
#define lua_saveline(L,line) { (void)L; (void)line; }
|
||||
#define lua_freeline(L,b) { (void)L; (void)b; }
|
||||
|
||||
#endif /* } */
|
||||
|
||||
#endif /* } */
|
||||
|
||||
|
||||
/*
|
||||
** Return the string to be used as a prompt by the interpreter. Leave
|
||||
** the string (or nil, if using the default value) on the stack, to keep
|
||||
** it anchored.
|
||||
*/
|
||||
static const char *get_prompt (lua_State *L, int firstline) {
|
||||
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL)
|
||||
return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */
|
||||
else { /* apply 'tostring' over the value */
|
||||
const char *p = luaL_tolstring(L, -1, NULL);
|
||||
lua_remove(L, -2); /* remove original value */
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
/* mark in error messages for incomplete statements */
|
||||
#define EOFMARK "<eof>"
|
||||
#define marklen (sizeof(EOFMARK)/sizeof(char) - 1)
|
||||
|
||||
|
||||
/*
|
||||
** Check whether 'status' signals a syntax error and the error
|
||||
** message at the top of the stack ends with the above mark for
|
||||
** incomplete statements.
|
||||
*/
|
||||
static int incomplete (lua_State *L, int status) {
|
||||
if (status == LUA_ERRSYNTAX) {
|
||||
size_t lmsg;
|
||||
const char *msg = lua_tolstring(L, -1, &lmsg);
|
||||
if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0; /* else... */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Prompt the user, read a line, and push it into the Lua stack.
|
||||
*/
|
||||
static int pushline (lua_State *L, int firstline) {
|
||||
char buffer[LUA_MAXINPUT];
|
||||
char *b = buffer;
|
||||
size_t l;
|
||||
const char *prmt = get_prompt(L, firstline);
|
||||
int readstatus = lua_readline(L, b, prmt);
|
||||
lua_pop(L, 1); /* remove prompt */
|
||||
if (readstatus == 0)
|
||||
return 0; /* no input */
|
||||
l = strlen(b);
|
||||
if (l > 0 && b[l-1] == '\n') /* line ends with newline? */
|
||||
b[--l] = '\0'; /* remove it */
|
||||
if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */
|
||||
lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */
|
||||
else
|
||||
lua_pushlstring(L, b, l);
|
||||
lua_freeline(L, b);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Try to compile line on the stack as 'return <line>;'; on return, stack
|
||||
** has either compiled chunk or original line (if compilation failed).
|
||||
*/
|
||||
static int addreturn (lua_State *L) {
|
||||
const char *line = lua_tostring(L, -1); /* original line */
|
||||
const char *retline = lua_pushfstring(L, "return %s;", line);
|
||||
int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin");
|
||||
if (status == LUA_OK) {
|
||||
lua_remove(L, -2); /* remove modified line */
|
||||
if (line[0] != '\0') /* non empty? */
|
||||
lua_saveline(L, line); /* keep history */
|
||||
}
|
||||
else
|
||||
lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Read multiple lines until a complete Lua statement
|
||||
*/
|
||||
static int multiline (lua_State *L) {
|
||||
for (;;) { /* repeat until gets a complete statement */
|
||||
size_t len;
|
||||
const char *line = lua_tolstring(L, 1, &len); /* get what it has */
|
||||
int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */
|
||||
if (!incomplete(L, status) || !pushline(L, 0)) {
|
||||
lua_saveline(L, line); /* keep history */
|
||||
return status; /* should not or cannot try to add continuation line */
|
||||
}
|
||||
lua_remove(L, -2); /* remove error message (from incomplete line) */
|
||||
lua_pushliteral(L, "\n"); /* add newline... */
|
||||
lua_insert(L, -2); /* ...between the two lines */
|
||||
lua_concat(L, 3); /* join them */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Read a line and try to load (compile) it first as an expression (by
|
||||
** adding "return " in front of it) and second as a statement. Return
|
||||
** the final status of load/call with the resulting function (if any)
|
||||
** in the top of the stack.
|
||||
*/
|
||||
static int loadline (lua_State *L) {
|
||||
int status;
|
||||
lua_settop(L, 0);
|
||||
if (!pushline(L, 1))
|
||||
return -1; /* no input */
|
||||
if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */
|
||||
status = multiline(L); /* try as command, maybe with continuation lines */
|
||||
lua_remove(L, 1); /* remove line from the stack */
|
||||
lua_assert(lua_gettop(L) == 1);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Prints (calling the Lua 'print' function) any values on the stack
|
||||
*/
|
||||
static void l_print (lua_State *L) {
|
||||
int n = lua_gettop(L);
|
||||
if (n > 0) { /* any result to be printed? */
|
||||
luaL_checkstack(L, LUA_MINSTACK, "too many results to print");
|
||||
lua_getglobal(L, "print");
|
||||
lua_insert(L, 1);
|
||||
if (lua_pcall(L, n, 0, 0) != LUA_OK)
|
||||
l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)",
|
||||
lua_tostring(L, -1)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and
|
||||
** print any results.
|
||||
*/
|
||||
static void doREPL (lua_State *L) {
|
||||
int status;
|
||||
const char *oldprogname = progname;
|
||||
progname = NULL; /* no 'progname' on errors in interactive mode */
|
||||
lua_initreadline(L);
|
||||
while ((status = loadline(L)) != -1) {
|
||||
if (status == LUA_OK)
|
||||
status = docall(L, 0, LUA_MULTRET);
|
||||
if (status == LUA_OK) l_print(L);
|
||||
else report(L, status);
|
||||
}
|
||||
lua_settop(L, 0); /* clear stack */
|
||||
lua_writeline();
|
||||
progname = oldprogname;
|
||||
}
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** Main body of stand-alone interpreter (to be called in protected mode).
|
||||
** Reads the options and handles them all.
|
||||
*/
|
||||
extern int luaopen_lpeg(lua_State *L);
|
||||
static int pmain (lua_State *L) {
|
||||
int argc = (int)lua_tointeger(L, 1);
|
||||
char **argv = (char **)lua_touserdata(L, 2);
|
||||
int script;
|
||||
int args = collectargs(argv, &script);
|
||||
int optlim = (script > 0) ? script : argc; /* first argv not an option */
|
||||
luaL_checkversion(L); /* check that interpreter has correct version */
|
||||
if (args == has_error) { /* bad arg? */
|
||||
print_usage(argv[script]); /* 'script' has index of bad arg. */
|
||||
return 0;
|
||||
}
|
||||
if (args & has_v) /* option '-v'? */
|
||||
print_version();
|
||||
if (args & has_E) { /* option '-E'? */
|
||||
lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
|
||||
}
|
||||
luaL_openlibs(L); /* open standard libraries */
|
||||
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
|
||||
lua_pushcfunction(L, luaopen_lpeg);
|
||||
lua_setfield(L, -2, "lpeg");
|
||||
lua_pop(L, 1); // remove PRELOAD table
|
||||
|
||||
createargtable(L, argv, argc, script); /* create table 'arg' */
|
||||
lua_gc(L, LUA_GCRESTART); /* start GC... */
|
||||
lua_gc(L, LUA_GCGEN, 0, 0); /* ...in generational mode */
|
||||
if (!(args & has_E)) { /* no option '-E'? */
|
||||
if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */
|
||||
return 0; /* error running LUA_INIT */
|
||||
}
|
||||
if (!runargs(L, argv, optlim)) /* execute arguments -e and -l */
|
||||
return 0; /* something failed */
|
||||
if (script > 0) { /* execute main script (if there is one) */
|
||||
if (handle_script(L, argv + script) != LUA_OK)
|
||||
return 0; /* interrupt in case of error */
|
||||
}
|
||||
if (args & has_i) /* -i option? */
|
||||
doREPL(L); /* do read-eval-print loop */
|
||||
else if (script < 1 && !(args & (has_e | has_v))) { /* no active option? */
|
||||
if (lua_stdin_is_tty()) { /* running in interactive mode? */
|
||||
print_version();
|
||||
doREPL(L); /* do read-eval-print loop */
|
||||
}
|
||||
else dofile(L, NULL); /* executes stdin as a file */
|
||||
}
|
||||
lua_pushboolean(L, 1); /* signal no errors */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
int status, result;
|
||||
lua_State *L = luaL_newstate(); /* create state */
|
||||
if (L == NULL) {
|
||||
l_message(argv[0], "cannot create state: not enough memory");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
lua_gc(L, LUA_GCSTOP); /* stop GC while building state */
|
||||
lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */
|
||||
lua_pushinteger(L, argc); /* 1st argument */
|
||||
lua_pushlightuserdata(L, argv); /* 2nd argument */
|
||||
status = lua_pcall(L, 2, 1, 0); /* do the call */
|
||||
result = lua_toboolean(L, -1); /* get result */
|
||||
report(L, status);
|
||||
lua_close(L);
|
||||
return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
+461
-369
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
project(tests)
|
||||
find_package(OGRE REQUIRED COMPONENTS Bullet CONFIG)
|
||||
find_package(Bullet REQUIRED)
|
||||
add_executable(compound_shapes compound_shapes.cpp)
|
||||
target_link_libraries(compound_shapes OgreBullet ${BULLET_DYNAMICS_LIBRARY} ${BULLET_COLLISION_LIBRARY} ${BULLET_MATH_LIBRARY})
|
||||
include_directories(${BULLET_INCLUDE_DIRS})
|
||||
|
||||
add_custom_target(tests ALL DEPENDS compound_shapes)
|
||||
@@ -0,0 +1,290 @@
|
||||
#include <iostream>
|
||||
#include <OgreBullet.h>
|
||||
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||
#include <btBulletCollisionCommon.h>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
|
||||
struct objects {
|
||||
btDynamicsWorld *world;
|
||||
btRigidBody *groundBody;
|
||||
btPairCachingGhostObject *characterBody;
|
||||
btPairCachingGhostObject *waterBody;
|
||||
};
|
||||
void setupGround(btDynamicsWorld *world, struct objects *objects)
|
||||
{
|
||||
// Static ground
|
||||
btCollisionShape *groundShape =
|
||||
new btBoxShape(btVector3(1000, 1, 1000));
|
||||
btDefaultMotionState *groundMotionState = new btDefaultMotionState(
|
||||
btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, -10, 0)));
|
||||
btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI(
|
||||
0, groundMotionState, groundShape, btVector3(0, 0, 0));
|
||||
btRigidBody *groundRigidBody = new btRigidBody(groundRigidBodyCI);
|
||||
world->addRigidBody(groundRigidBody, 2, 0x7fffffff & ~16);
|
||||
objects->groundBody = groundRigidBody;
|
||||
}
|
||||
void setupCharacter(btDynamicsWorld *world, struct objects *objects)
|
||||
{
|
||||
btCompoundShape *shape = new btCompoundShape();
|
||||
btCapsuleShape *subshape = new btCapsuleShape(0.2f, 1.5f);
|
||||
btTransform shapePos;
|
||||
shapePos.setIdentity();
|
||||
shapePos.setOrigin(btVector3(0, 0.75f, 0));
|
||||
shape->addChildShape(shapePos, subshape);
|
||||
btPairCachingGhostObject *characterBody =
|
||||
new btPairCachingGhostObject();
|
||||
characterBody->setCollisionFlags(
|
||||
characterBody->getCollisionFlags() |
|
||||
btCollisionObject::CF_NO_CONTACT_RESPONSE |
|
||||
btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
btTransform bodyPos;
|
||||
bodyPos.setIdentity();
|
||||
bodyPos.setOrigin(btVector3(0, 0.4f, 0));
|
||||
characterBody->setWorldTransform(bodyPos);
|
||||
characterBody->setCollisionShape(shape);
|
||||
world->addCollisionObject(characterBody, 1, 0x7fffffff);
|
||||
objects->characterBody = characterBody;
|
||||
}
|
||||
void setupWater(btDynamicsWorld *world, struct objects *objects)
|
||||
{
|
||||
btCompoundShape *shape = new btCompoundShape();
|
||||
btBoxShape *subshape =
|
||||
new btBoxShape(btVector3(1000.0f, 100.0f, 1000.0f));
|
||||
btTransform shapePos;
|
||||
shapePos.setIdentity();
|
||||
shapePos.setOrigin(btVector3(0, -100, 0));
|
||||
shape->addChildShape(shapePos, subshape);
|
||||
btPairCachingGhostObject *waterBody = new btPairCachingGhostObject();
|
||||
waterBody->setCollisionFlags(waterBody->getCollisionFlags() |
|
||||
btCollisionObject::CF_NO_CONTACT_RESPONSE |
|
||||
btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
btTransform bodyPos;
|
||||
bodyPos.setIdentity();
|
||||
waterBody->setWorldTransform(bodyPos);
|
||||
waterBody->setCollisionShape(shape);
|
||||
world->addCollisionObject(waterBody, 16, 0x7fffffff & ~2);
|
||||
objects->waterBody = waterBody;
|
||||
}
|
||||
struct DeepPenetrationContactResultCallback : public btManifoldResult {
|
||||
DeepPenetrationContactResultCallback(
|
||||
const btCollisionObjectWrapper *body0Wrap,
|
||||
const btCollisionObjectWrapper *body1Wrap)
|
||||
: btManifoldResult(body0Wrap, body1Wrap)
|
||||
, mPenetrationDistance(0)
|
||||
, mOtherIndex(0)
|
||||
{
|
||||
}
|
||||
float mPenetrationDistance;
|
||||
int mOtherIndex;
|
||||
btVector3 mNormal, mPoint;
|
||||
void reset()
|
||||
{
|
||||
mPenetrationDistance = 0.0f;
|
||||
}
|
||||
bool hasHit()
|
||||
{
|
||||
return mPenetrationDistance < 0.0f;
|
||||
}
|
||||
virtual void addContactPoint(const btVector3 &normalOnBInWorld,
|
||||
const btVector3 &pointInWorldOnB,
|
||||
btScalar depth)
|
||||
{
|
||||
std::cout
|
||||
<< "contact: " << Ogre::Bullet::convert(pointInWorldOnB)
|
||||
<< " " << Ogre::Bullet::convert(normalOnBInWorld)
|
||||
<< "\n";
|
||||
if (mPenetrationDistance > depth) { // Has penetration?
|
||||
|
||||
const bool isSwapped =
|
||||
m_manifoldPtr->getBody0() !=
|
||||
m_body0Wrap->getCollisionObject();
|
||||
mPenetrationDistance = depth;
|
||||
mOtherIndex = isSwapped ? m_index0 : m_index1;
|
||||
mPoint = isSwapped ? (pointInWorldOnB +
|
||||
(normalOnBInWorld * depth)) :
|
||||
pointInWorldOnB;
|
||||
|
||||
mNormal = isSwapped ? normalOnBInWorld * -1 :
|
||||
normalOnBInWorld;
|
||||
}
|
||||
}
|
||||
};
|
||||
void dumpCompoundShape(const char *what, const btCollisionObject *body)
|
||||
{
|
||||
if (body->getCollisionShape()->isCompound()) {
|
||||
const btCompoundShape *shape =
|
||||
static_cast<const btCompoundShape *>(
|
||||
body->getCollisionShape());
|
||||
int i;
|
||||
for (i = 0; i < shape->getNumChildShapes(); i++) {
|
||||
btTransform transform = body->getWorldTransform() *
|
||||
shape->getChildTransform(i);
|
||||
std::cout << what << ": " << " shape: " << i << ": "
|
||||
<< transform.getOrigin().getX() << ", ";
|
||||
std::cout << transform.getOrigin().getY() << ", ";
|
||||
std::cout << transform.getOrigin().getZ();
|
||||
std::cout << " convex: "
|
||||
<< shape->getChildShape(i)->isConvex();
|
||||
std::cout << " shape name: "
|
||||
<< shape->getChildShape(i)->getName();
|
||||
switch (shape->getChildShape(i)->getShapeType()) {
|
||||
case 0: {
|
||||
const btBoxShape *box =
|
||||
static_cast<const btBoxShape *>(
|
||||
shape->getChildShape(i));
|
||||
btVector3 hextents =
|
||||
box->getHalfExtentsWithoutMargin();
|
||||
std::cout << " box: " << hextents.getX() << ", "
|
||||
<< hextents.getY() << ", "
|
||||
<< hextents.getZ();
|
||||
} break;
|
||||
case 10: {
|
||||
const btCapsuleShape *capsule =
|
||||
static_cast<const btCapsuleShape *>(
|
||||
shape->getChildShape(i));
|
||||
float hh = capsule->getHalfHeight();
|
||||
float r = capsule->getRadius();
|
||||
std::cout << " capsule: " << hh << ", " << r;
|
||||
} break;
|
||||
default:
|
||||
std::cout << " shape type: "
|
||||
<< shape->getChildShape(i)
|
||||
->getShapeType();
|
||||
break;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
void showContacts(btCollisionWorld *world, struct objects *objects)
|
||||
{
|
||||
int numManifolds = world->getDispatcher()->getNumManifolds();
|
||||
if (numManifolds == 0)
|
||||
std::cout << "No contacts in world\n";
|
||||
for (int i = 0; i < numManifolds; i++) {
|
||||
btPersistentManifold *contactManifold =
|
||||
world->getDispatcher()->getManifoldByIndexInternal(i);
|
||||
const btCollisionObject *obA = contactManifold->getBody0();
|
||||
const btCollisionObject *obB = contactManifold->getBody1();
|
||||
|
||||
int numContacts = contactManifold->getNumContacts();
|
||||
for (int j = 0; j < numContacts; j++) {
|
||||
btManifoldPoint &pt =
|
||||
contactManifold->getContactPoint(j);
|
||||
if (pt.getDistance() < 0.f) {
|
||||
const btVector3 &ptA = pt.getPositionWorldOnA();
|
||||
const btVector3 &ptB = pt.getPositionWorldOnB();
|
||||
const btVector3 &normalOnB =
|
||||
pt.m_normalWorldOnB;
|
||||
std::cout << "contact: " << i << " " << j << " "
|
||||
<< ptA.getX() << ", " << ptA.getY()
|
||||
<< ", " << ptA.getZ() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int main()
|
||||
{
|
||||
struct objects objects;
|
||||
btDefaultCollisionConfiguration collisionConfig;
|
||||
btCollisionDispatcher dispatcher(&collisionConfig);
|
||||
btCollisionDispatcher *dispatch = &dispatcher;
|
||||
btDbvtBroadphase broadphase;
|
||||
btSequentialImpulseConstraintSolver solver;
|
||||
btDiscreteDynamicsWorld world(&dispatcher, &broadphase, &solver,
|
||||
&collisionConfig);
|
||||
world.setGravity(btVector3(0, -9.81, 0));
|
||||
setupGround(&world, &objects);
|
||||
setupCharacter(&world, &objects);
|
||||
setupWater(&world, &objects);
|
||||
|
||||
#if 0
|
||||
btCompoundShape *compoundShape = new btCompoundShape();
|
||||
|
||||
// Add shapes to the compound shape
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
btBoxShape *box = new btBoxShape(btVector3(1, 1, 1));
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
transform.setOrigin(btVector3(0, 1 - i, 0));
|
||||
compoundShape->addChildShape(transform, box);
|
||||
}
|
||||
|
||||
btDefaultMotionState *compoundMotionState = new btDefaultMotionState(
|
||||
btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, 10, 0)));
|
||||
btScalar mass = 5.0;
|
||||
btVector3 inertia(0, 0, 0);
|
||||
compoundShape->calculateLocalInertia(mass, inertia);
|
||||
btRigidBody::btRigidBodyConstructionInfo compoundRigidBodyCI(
|
||||
mass, compoundMotionState, compoundShape, inertia);
|
||||
btRigidBody *compoundRigidBody = new btRigidBody(compoundRigidBodyCI);
|
||||
world.addRigidBody(compoundRigidBody);
|
||||
#endif
|
||||
|
||||
// Simulation
|
||||
btVector3 velocity(0, 0, 0);
|
||||
for (int i = 0; i < 300; i++) {
|
||||
world.stepSimulation(1.f / 60.f, 10);
|
||||
showContacts(&world, &objects);
|
||||
|
||||
#if 0
|
||||
btTransform transform;
|
||||
compoundRigidBody->getMotionState()->getWorldTransform(
|
||||
transform);
|
||||
std::cout << "Step " << i
|
||||
<< ": Position = " << transform.getOrigin().getX()
|
||||
<< ", " << transform.getOrigin().getY() << ", "
|
||||
<< transform.getOrigin().getZ() << std::endl;
|
||||
std::cout << "water overlaps:"
|
||||
<< objects.waterBody->getOverlappingPairCache()
|
||||
->getNumOverlappingPairs()
|
||||
<< "\n";
|
||||
#endif
|
||||
btTransform transform;
|
||||
transform = objects.characterBody->getWorldTransform();
|
||||
std::cout << "Step " << i << " ";
|
||||
std::cout << "Character: "
|
||||
<< "Position = " << transform.getOrigin().getX()
|
||||
<< ", " << transform.getOrigin().getY() << ", "
|
||||
<< transform.getOrigin().getZ() << std::endl;
|
||||
transform = objects.waterBody->getWorldTransform();
|
||||
std::cout << "Water: "
|
||||
<< "Position = " << transform.getOrigin().getX()
|
||||
<< ", " << transform.getOrigin().getY() << ", "
|
||||
<< transform.getOrigin().getZ() << std::endl;
|
||||
std::cout << "water overlaps:"
|
||||
<< objects.waterBody->getOverlappingPairCache()
|
||||
->getNumOverlappingPairs()
|
||||
<< "\n";
|
||||
dumpCompoundShape("Character", objects.characterBody);
|
||||
dumpCompoundShape("Water", objects.waterBody);
|
||||
btCollisionObjectWrapper obA(
|
||||
NULL, objects.waterBody->getCollisionShape(),
|
||||
objects.waterBody,
|
||||
objects.waterBody->getWorldTransform(), -1, 0);
|
||||
btCollisionObjectWrapper obB(
|
||||
NULL, objects.characterBody->getCollisionShape(),
|
||||
objects.characterBody,
|
||||
objects.characterBody->getWorldTransform(), -1, 0);
|
||||
std::cout << __func__ << ": call findAlgorithm: ";
|
||||
btCollisionAlgorithm *algorithm = dispatch->findAlgorithm(
|
||||
&obA, &obB, NULL, BT_CONTACT_POINT_ALGORITHMS);
|
||||
std::cout << "found algorithm: " << algorithm << "\n";
|
||||
if (!algorithm)
|
||||
continue;
|
||||
std::cout << "algorithm: " << algorithm << "\n";
|
||||
DeepPenetrationContactResultCallback contactPointResult(&obA,
|
||||
&obB);
|
||||
std::cout << "process collision\n";
|
||||
algorithm->processCollision(&obA, &obB, world.getDispatchInfo(),
|
||||
&contactPointResult);
|
||||
algorithm->~btCollisionAlgorithm();
|
||||
dispatch->freeCollisionAlgorithm(algorithm);
|
||||
if (contactPointResult.hasHit()) {
|
||||
std::cout << "InWater!!1\n";
|
||||
}
|
||||
velocity += btVector3(0, -9.8, 0) * 1.0f / 16.0f;
|
||||
objects.characterBody->getWorldTransform().getOrigin() += velocity * 1.0f / 16.0f;
|
||||
std::cout << "process collision done\n";
|
||||
}
|
||||
}
|
||||
LFS
BIN
Binary file not shown.
Reference in New Issue
Block a user