Compare commits

...

12 Commits

Author SHA1 Message Date
62e14cf075 Lots of updates - lua, narrator, logic, models 2025-09-19 04:35:20 +03:00
4249a0238b Adding complete copy 2025-09-18 17:49:15 +03:00
cfd9ed8708 Lua works; narrator works 2025-09-17 18:08:26 +03:00
1977a12d8b Added lpeg for narrator 2025-09-16 22:31:16 +03:00
190318e5c4 Updates 2025-09-16 20:38:29 +03:00
1aa002d8ba Animation fixes + fonts 2025-09-15 23:55:31 +03:00
1bc0f298fc Added treading water animation 2025-09-15 03:18:46 +03:00
e6463fc264 Added female; added swimming animation 2025-09-15 03:11:41 +03:00
7621607152 rename swimming 2025-09-15 02:08:45 +03:00
82ac145e87 Swimming animation 2025-09-15 02:08:15 +03:00
ff780c34b3 Add swimming animation 2025-09-15 02:07:18 +03:00
5c03f0cd2c Water works! 2025-09-15 01:51:38 +03:00
90 changed files with 17305 additions and 1877 deletions

1
.gitattributes vendored
View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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})

441
Game.cpp
View File

@@ -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();

View File

@@ -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);
{

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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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

BIN
assets/fonts/Jupiteroid-Bold.ttf LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/fonts/Jupiteroid-Light.ttf LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/fonts/Kenney Bold.ttf LFS Normal file

Binary file not shown.

View File

@@ -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})

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

View File

@@ -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

View File

@@ -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={}}

View File

@@ -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

View File

@@ -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

View File

@@ -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>();
});
}
}

28
src/gamedata/BoatModule.h Normal file
View File

@@ -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

View File

@@ -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})

View File

@@ -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>();
});

View File

@@ -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);

View File

@@ -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

View File

@@ -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++;
}
});
}

View File

@@ -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

View File

@@ -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";
}

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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()

418
src/gamedata/LuaData.cpp Normal file
View File

@@ -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;
}
}
});
}
}

29
src/gamedata/LuaData.h Normal file
View File

@@ -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

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -0,0 +1,6 @@
#include "WorldMapModule.h"
ECS::WorldMapModule::WorldMapModule(flecs::world &ecs)
{
ecs.component<WorldMap>();
}

View File

@@ -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

View File

@@ -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)

109
src/lua/lpeg-1.1.0/HISTORY Normal file
View File

@@ -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)

View File

@@ -0,0 +1,4 @@
# LPeg - Parsing Expression Grammars For Lua
For more information,
see [Lpeg](//www.inf.puc-rio.br/~roberto/lpeg/).

612
src/lua/lpeg-1.1.0/lpcap.c Normal file
View File

@@ -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;
}

View File

@@ -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

1051
src/lua/lpeg-1.1.0/lpcode.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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

110
src/lua/lpeg-1.1.0/lpcset.c Normal file
View File

@@ -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);
}

View File

@@ -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

1430
src/lua/lpeg-1.1.0/lpeg.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

1399
src/lua/lpeg-1.1.0/lptree.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

455
src/lua/lpeg-1.1.0/lpvm.c Normal file
View File

@@ -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;
}
}
}
/* }====================================================== */

79
src/lua/lpeg-1.1.0/lpvm.h Normal file
View File

@@ -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

View File

@@ -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

472
src/lua/lpeg-1.1.0/re.html Normal file
View File

@@ -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>&amp; 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 &lt;- 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>&lt;name&gt;</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 -&gt; 'string'</code></td> <td>string capture</td></tr>
<tr><td><code>p -&gt; "string"</code></td> <td>string capture</td></tr>
<tr><td><code>p -&gt; num</code></td> <td>numbered capture</td></tr>
<tr><td><code>p -&gt; name</code></td> <td>function/query/string capture
equivalent to <code>p / defs[name]</code></td></tr>
<tr><td><code>p =&gt; name</code></td> <td>match-time capture
equivalent to <code>lpeg.Cmt(p, defs[name])</code></td></tr>
<tr><td><code>p ~&gt; name</code></td> <td>fold capture
(deprecated)</td></tr>
<tr><td><code>p &gt;&gt; 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]+")) --&gt; 12 14
-- returns all words in a string
print(re.match("the number 423 is odd", "({%a+} / .)*"))
--&gt; the number is odd
-- returns the first numeral in a string
print(re.match("the number 423 is odd", "s &lt;- {%d+} / . s"))
--&gt; 423
-- substitutes a dot for each vowel in a string
print(re.gsub("hello World", "[aeiou]", "."))
--&gt; 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 &lt;- "(" ([^()] / balanced)* ")" ]]
</pre>
<h3>String reversal</h3>
<p>
The next example reverses a string:
</p>
<pre class="example">
rev = re.compile[[ R &lt;- (!.) -&gt; '' / ({.} R) -&gt; '%2%1']]
print(rev:match"0123456789") --&gt; 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 &lt;- {| field (',' field)* |} (%nl / !.)
field &lt;- escaped / nonescaped
nonescaped &lt;- { [^,"%nl]* }
escaped &lt;- '"' {~ ([^"] / '""' -&gt; '"')* ~} '"'
]]
</pre>
<h3>Lua's long strings</h3>
<p>
The next example matches Lua long strings:
</p>
<pre class="example">
c = re.compile([[
longstring &lt;- ('[' {:eq: '='* :} '[' close)
close &lt;- ']' =eq ']' / . close
]])
print(c:match'[==[]]===]]]]==]===[]') --&gt; 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 &lt;- (name s)*
name &lt;- [a-z][a-z]*
s &lt;- %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 &lt;- {| (name s)* |}
name &lt;- {| {[a-z][a-z]*} |}
s &lt;- %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 &lt;- {| {:ident:' '*:} line
((=ident !' ' line) / &amp;(=ident ' ') block)* |}
line &lt;- {[^%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 &lt;- {~ item* ~}
item &lt;- macro / [^()] / '(' item* ')'
arg &lt;- ' '* {~ (!',' item)* ~}
args &lt;- '(' arg (',' arg)* ')'
-- now we define some macros
macro &lt;- ('apply' args) -&gt; '%1(%2)'
/ ('add' args) -&gt; '%1 + %2'
/ ('mul' args) -&gt; '%1 * %2'
]]
print(p:match"add(mul(a,b), apply(f,x))") --&gt; 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 &lt;- exp !.
exp &lt;- S (grammar / alternative)
alternative &lt;- seq ('/' S seq)*
seq &lt;- prefix*
prefix &lt;- '&amp;' S prefix / '!' S prefix / suffix
suffix &lt;- primary S (([+*?]
/ '^' [+-]? num
/ '-&gt;' S (string / '{}' / name)
/ '&gt&gt;' S name
/ '=&gt;' S name) S)*
primary &lt;- '(' exp ')' / string / class / defined
/ '{:' (name ':')? exp ':}'
/ '=' name
/ '{}'
/ '{~' exp '~}'
/ '{|' exp '|}'
/ '{' exp '}'
/ '.'
/ name S !arrow
/ '&lt;' name '&gt;' -- old-style non terminals
grammar &lt;- definition+
definition &lt;- name S arrow exp
class &lt;- '[' '^'? item (!']' item)* ']'
item &lt;- defined / range / .
range &lt;- . '-' [^]]
S &lt;- (%s / '--' [^%nl]*)* -- spaces and comments
name &lt;- [A-Za-z_][A-Za-z0-9_]*
arrow &lt;- '&lt;-'
num &lt;- [0-9]+
string &lt;- '"' [^"]* '"' / "'" [^']* "'"
defined &lt;- '%' 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>

270
src/lua/lpeg-1.1.0/re.lua Normal file
View File

@@ -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

1667
src/lua/lpeg-1.1.0/test.lua Executable file

File diff suppressed because it is too large Load Diff

694
src/lua/lua.c Normal file
View File

@@ -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;
}

File diff suppressed because it is too large Load Diff

8
tests/CMakeLists.txt Normal file
View File

@@ -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)

290
tests/compound_shapes.cpp Normal file
View File

@@ -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";
}
}

BIN
world_map.kra LFS

Binary file not shown.