Files
pke/src/game/game.cpp
2026-03-08 16:50:54 +03:00

732 lines
18 KiB
C++

#include "ifilesystem.h"
#include "core.h"
#include "log.h"
#include "engine.h"
#include "game.h"
#include "game_lua_help.h"
#include "game_ui.h"
#include "inputmanager.h"
#include "ientity.h"
#include "entitymanager.h"
#include "world.h"
#include "game_object.h"
#include "soundsystem.h"
#include "animation.h"
#include "scenemanager.h"
#include "game_world_objects.h"
#include "actor_base.h"
#include "physics/physicsworld.h"
#include <pugixml.hpp>
#include <map>
#include <string>
static Game s_game;
Game* g_game = &s_game;
class GameSoundSystem
{
public:
static GameSoundSystem& GetInstance();
public:
void PlaySound2D(const std::string& filename);
void PlaySound3D(const std::string& filename, const glm::vec3& pos);
void CacheSound(const std::string& filename);
private:
std::map<std::string, SoundHandle> m_sounds;
};
GameSoundSystem& GameSoundSystem::GetInstance()
{
static GameSoundSystem inst;
return inst;
}
void GameSoundSystem::PlaySound2D(const std::string& filename)
{
auto it = m_sounds.find(filename);
if (it == m_sounds.end())
{
CacheSound(filename);
it = m_sounds.find(filename);
}
SDL_assert(it != m_sounds.end());
// .hack due unimplemented sound sources^
if (g_soundSystem.IsPlaying(it->second))
g_soundSystem.Stop(it->second);
g_soundSystem.Play(it->second, false);
}
void GameSoundSystem::PlaySound3D(const std::string& filename, const glm::vec3& pos)
{
auto it = m_sounds.find(filename);
if (it == m_sounds.end())
{
CacheSound(filename);
it = m_sounds.find(filename);
}
SDL_assert(it != m_sounds.end());
// .hack due unimplemented sound sources^
if (g_soundSystem.IsPlaying(it->second))
g_soundSystem.Stop(it->second);
g_soundSystem.Play3D(it->second, pos.x, pos.y, pos.z, true);
}
void GameSoundSystem::CacheSound(const std::string& filename)
{
m_sounds.emplace(filename, g_soundSystem.LoadSound(filename.c_str()));
}
void consoleMsg(const char* msg)
{
Logger::Msg("%s", msg);
}
void engineError(const char* msg)
{
Core::Error("%s", msg);
}
void engineWarning(const char* msg)
{
Core::Warning("%s", msg);
}
void enginePlaySound(const char* filename)
{
GameSoundSystem::GetInstance().PlaySound2D(filename);
}
void enginePlaySound3D(const char* filename, float x, float y, float z)
{
GameSoundSystem::GetInstance().PlaySound3D(filename, glm::vec3(x, y, z));
}
LuaPlus::LuaObject engineCreateEntity(const char* classname)
{
Entity* entity = static_cast<Entity*>(g_game->Lua_CreateEntity(classname));
SDL_assert_always(entity);
entity->Init();
return entity->GetLuaObject();
}
LuaPlus::LuaObject engineGetEntityFromID(lua_Integer id)
{
LuaPlus::LuaObject lookup = GetLuaState().GetGlobal("g_entities");
LuaPlus::LuaObject entityTable = lookup.GetByIndex(id);
return entityTable;
}
LuaPlus::LuaObject engineFindEntityByName(const char* name)
{
Entity* entity = (Entity*)g_game->FindEntityByName(name);
if (!entity)
{
LuaPlus::LuaObject table;
table.AssignNil(GetLuaState());
return table;
}
return entity->GetLuaObject();
}
void engineAddEntityToWorld(LuaPlus::LuaObject& object)
{
LuaPlus::LuaObject cppclass = object["__object"];
SDL_assert_always(cppclass.IsLightUserdata());
Entity* entity = static_cast<Entity*>(cppclass.GetLightUserdata());
g_world->AddEntity(entity);
}
LuaPlus::LuaObject engineTraceRay(float rayBeginX, float rayBeginY, float rayBeginZ,
float rayEndX, float rayEndY, float rayEndZ, const LuaPlus::LuaObject& ignoreTable)
{
IEntityBase* pIgnoreEntity = nullptr;
if (ignoreTable.IsTable())
{
LuaPlus::LuaObject ignoreEntityTable = ignoreTable.GetByName("__object");
if (ignoreEntityTable.IsLightUserdata())
pIgnoreEntity = (IEntityBase*)ignoreEntityTable.GetLightUserdata();
}
TraceRayResult result = {};
glm::vec3 rayBegin = glm::vec3(rayBeginX, rayBeginY, rayBeginZ);
glm::vec3 rayEnd = glm::vec3(rayEndX, rayEndY, rayEndZ);
if (g_PhysicsWorld)
g_PhysicsWorld->TraceRay(result, rayBegin, rayEnd, pIgnoreEntity);
else
Logger::Msg("engine.trace_ray(): no server started or game loaded!");
LuaPlus::LuaObject resultTable = GetLuaState().CreateTable();
resultTable.SetNumber("pos_x", result.position.x);
resultTable.SetNumber("pos_y", result.position.y);
resultTable.SetNumber("pos_z", result.position.z);
resultTable.SetNumber("normal_x", result.normal.x);
resultTable.SetNumber("normal_y", result.normal.y);
resultTable.SetNumber("normal_z", result.normal.z);
resultTable.SetInteger("entity_id", result.pEntity ? result.pEntity->GetID() : -1);
resultTable.SetInteger("hit", result.hit);
return resultTable;
}
float engineGetDelta()
{
return GetEngine()->GetDelta();
}
void registerEngine()
{
using namespace LuaPlus;
// register engine functions
LuaObject engineTable = GetLuaState().GetGlobals().CreateTable("engine");
engineTable.RegisterDirect("error", &engineError);
engineTable.RegisterDirect("warning", &engineWarning);
engineTable.RegisterDirect("create_entity", &engineCreateEntity);
engineTable.RegisterDirect("add_entity_to_world", &engineAddEntityToWorld);
engineTable.RegisterDirect("get_entity_from_id", &engineGetEntityFromID);
engineTable.RegisterDirect("find_entity_by_name", &engineFindEntityByName);
engineTable.RegisterDirect("play_sound", &enginePlaySound);
engineTable.RegisterDirect("play_sound_3d", &enginePlaySound3D);
engineTable.RegisterDirect("get_delta", &engineGetDelta);
engineTable.RegisterDirect("trace_ray", &engineTraceRay);
LuaObject consoleTable = GetLuaState().GetGlobals().CreateTable("console");
consoleTable.RegisterDirect("print", &consoleMsg);
registerCamera();
registerInput();
registerEngineUI();
// action globals
GetLuaState().DoString("ACTION_FIRE = 0");
GetLuaState().DoString("ACTION_ALT_FIRE = 1");
GetLuaState().DoString("ACTION_RELOAD = 2");
GetLuaState().DoString("ACTION_USE = 3");
// animations globals
GetLuaState().DoString("ANIM_PLAYBACK_NONE = 0");
GetLuaState().DoString("ANIM_PLAYBACK_REPEAT = 1");
char buffer[64];
#define REGISTER_CONSTANT(constant) \
snprintf(buffer, sizeof(buffer), #constant" = %d", constant); \
GetLuaState().DoString(buffer)
REGISTER_CONSTANT(EMovementDir_None);
REGISTER_CONSTANT(EMovementDir_Forward);
REGISTER_CONSTANT(EMovementDir_Backward);
REGISTER_CONSTANT(EMovementDir_Left);
REGISTER_CONSTANT(EMovementDir_Right);
REGISTER_CONSTANT(EMovementDir_Jump);
// Physics
REGISTER_CONSTANT(PhysicsFilter_None);
REGISTER_CONSTANT(PhysicsFilter_Player);
REGISTER_CONSTANT(PhysicsFilter_Triggers);
REGISTER_CONSTANT(PhysicsFilter_Usable);
REGISTER_CONSTANT(PhysicsFilter_NPC);
REGISTER_CONSTANT(PhysicsFilter_Obstacle);
REGISTER_CONSTANT(kPhysicsFilter_AllAux);
#undef REGISTER_CONSTANT
}
int inputGetMousePos(LuaPlus::LuaState* state)
{
const glm::vec2& pos = g_inputManager.GetMousePos();
state->PushNumber(pos.x);
state->PushNumber(pos.y);
return 2;
}
int inputGetDeltaMousePos(LuaPlus::LuaState* state)
{
const glm::vec2& pos = g_inputManager.GetMouseDeltaPos();
state->PushNumber(pos.x);
state->PushNumber(pos.y);
return 2;
}
void inputLock(bool value)
{
g_inputManager.SetRelativeMouseMode(value);
}
bool inputGetLock()
{
return g_inputManager.GetRelativeMouseMode();
}
void registerInput()
{
LuaPlus::LuaObject inputTable = GetLuaState().GetGlobals().CreateTable("input");
inputTable.Register("get_mouse_pos", &inputGetMousePos);
inputTable.Register("get_delta_mouse_pos", &inputGetDeltaMousePos);
inputTable.RegisterDirect("lock_mouse", &inputLock);
inputTable.RegisterDirect("get_lock_mouse", &inputGetLock);
}
void toggleNoclip()
{
static bool s_noclip = false;
s_noclip = !s_noclip;
LuaPlus::LuaObject player = GetLuaState().GetGlobal("g_player");
if (player.IsTable())
{
ActorBase* actor = (ActorBase*)player["__object"].GetLightUserdata();
actor->SetNoclip(s_noclip);
}
}
int cameraGetPos(LuaPlus::LuaState* state)
{
glm::vec3 v = glm::vec3(0.0f);
Camera* camera = g_cameraManager.GetActiveCamera();
if (camera)
v = camera->GetPosition();
state->PushNumber(v.x);
state->PushNumber(v.y);
state->PushNumber(v.z);
return 3;
}
int cameraGetFront(LuaPlus::LuaState* state)
{
glm::vec3 v = glm::vec3(0.0f);
Camera* camera = g_cameraManager.GetActiveCamera();
if (camera)
v = camera->GetFront();
state->PushNumber(v.x);
state->PushNumber(v.y);
state->PushNumber(v.z);
return 3;
}
int cameraGetRight(LuaPlus::LuaState* state)
{
glm::vec3 v = glm::vec3(0.0f);
Camera* camera = g_cameraManager.GetActiveCamera();
if (camera)
v = camera->GetRight();
state->PushNumber(v.x);
state->PushNumber(v.y);
state->PushNumber(v.z);
return 3;
}
int cameraGetUp(LuaPlus::LuaState* state)
{
glm::vec3 v = glm::vec3(0.0f);
Camera* camera = g_cameraManager.GetActiveCamera();
if (camera)
v = camera->GetUp();
state->PushNumber(v.x);
state->PushNumber(v.y);
state->PushNumber(v.z);
return 3;
}
int cameraGetYaw(LuaPlus::LuaState* state)
{
float v = 0.0f;
Camera* camera = g_cameraManager.GetActiveCamera();
if (camera)
v = camera->GetYaw();
state->PushNumber(v);
return 1;
}
int cameraGetPitch(LuaPlus::LuaState* state)
{
float v = 0.0f;
Camera* camera = g_cameraManager.GetActiveCamera();
if (camera)
v = camera->GetPitch();
state->PushNumber(v);
return 1;
}
void cameraSetYawPitch(float yaw, float pitch)
{
Camera* camera = g_cameraManager.GetActiveCamera();
if (camera)
camera->SetYawPitch(yaw, pitch);
}
void registerCamera()
{
LuaPlus::LuaObject cameraTable = GetLuaState().GetGlobals().CreateTable("camera");
cameraTable.Register("get_position", &cameraGetPos);
cameraTable.Register("get_front", &cameraGetFront);
cameraTable.Register("get_right", &cameraGetRight);
cameraTable.Register("get_up", &cameraGetUp);
cameraTable.Register("get_yaw", &cameraGetYaw);
cameraTable.Register("get_pitch", &cameraGetPitch);
cameraTable.RegisterDirect("set_yaw_pitch", &cameraSetYawPitch);
}
void registerClasses()
{
using namespace LuaPlus;
// base thing
GetLuaState().GetGlobals().RegisterDirect("load_script", &luaLoadScript);
GetLuaState().DoString("g_factory = {}");
GetLuaState().DoString("g_entities = {}");
registerEngine();
}
struct LuaPrototype
{
std::string m_luaname;
std::string m_enginename;
std::string m_description;
};
std::vector<LuaPrototype> g_prototypes;
void initializeEntityPrototypesFromLua()
{
using namespace LuaPlus;
LuaObject entitiesTable = GetLuaState().GetGlobal("g_entity_table");
for (LuaTableIterator it(entitiesTable); it; it.Next()) {
LuaObject entityTable = it.GetValue();
assert(entityTable.IsTable());
if (entityTable.GetTableCount() < 3)
{
Core::Error("Entry in entity prototype table is bad, missing argument");
}
LuaObject luaname = entityTable[1];
LuaObject classname = entityTable[2];
LuaObject description = entityTable[3];
Logger::Msg("lua ent: name: %s classname: %s description: %s", luaname.ToString(), classname.ToString(), description.ToString());
LuaPrototype prototype;
prototype.m_luaname = luaname.ToString();
prototype.m_enginename = classname.ToString();
prototype.m_description = description.ToString();
g_prototypes.push_back(prototype);
// get a prototype
LuaObject prototypeTable = GetLuaState().GetGlobal(luaname.ToString());
SDL_assert_always(!prototypeTable.IsNil());
prototypeTable.SetObject("__index", prototypeTable);
/*for (LuaTableIterator it2(entityTable); it2; it2.Next()) {
Logger::Msg("%s", it2.GetValue().ToString());
}*/
}
}
void initializeEntitiesTable()
{
using namespace LuaPlus;
LuaObject entitiesTable = GetLuaState().GetGlobal("g_entity_table");
if (entitiesTable.IsNil()) {
Core::Error("initializeEntitiesTable: failed initialize the entity table! g_entity_table is nil");
}
if (!entitiesTable.IsTable()) {
Core::Error("initializeEntitiesTable: failed initialize the entity table! g_entity_table is not a table");
}
initializeEntityPrototypesFromLua();
}
void initializeLua()
{
using namespace LuaPlus;
// register base classes
registerClasses();
// load game base script
luaLoadScript("game_init.lua");
}
void Game::Init()
{
initializeLua();
initializeEntitiesTable();
luaCallFunction("sv_game_init");
}
void Game::InitForNewMap(const char* mapname)
{
LoadLevelXML(mapname);
luaCallFunction("sv_on_game_start");
}
void Game::LoadLevelXML(const char* mapname)
{
// Load XML file of the level
char buffer[kMaxPathLength];
snprintf(buffer, kMaxPathLength, "data/levels/%s/%s.xml", mapname, mapname);
FileHandle_t file = GetFileSystem()->OpenFile(buffer, "rb");
SDL_assert_always(file != kInvalidFileHandleValue);
size_t length = GetFileSystem()->GetFileLength(file);
char* filedata = new char[length + 1];
GetFileSystem()->ReadFile(file, filedata, length);
filedata[length] = '\0';
GetFileSystem()->CloseFile(file);
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_buffer(filedata, length);
delete[] filedata;
if (!result) {
Core::Error("Game::LoadLevelXML: Error while reading level description file '%s'\nError: %s:%i",
buffer, result.description(), result.offset);
}
// std::map<std::string, std::string> properties;
for (pugi::xml_node entitynode : doc.child("Level").child("Entities").children("Entity")) {
pugi::xml_attribute entityname = entitynode.attribute("name");
pugi::xml_attribute classname = entitynode.attribute("classname");
if (classname.empty()) {
if (!entityname.empty()) {
Core::Error(" Game::LoadLevelXML: Classname is not specified for entity '%s'", entityname.as_string());
}
else {
Core::Error(" Game::LoadLevelXML: Classname is not specified for entity");
}
}
// Create entity and expose it to the engine
Entity* entity = static_cast<Entity*>(Lua_CreateEntity(classname.as_string()));
if (!entityname.empty())
entity->SetName(entityname.as_string());
pugi::xml_node position = entitynode.child("Position");
if (position)
{
float x = position.attribute("x").as_float();
float y = position.attribute("y").as_float();
float z = position.attribute("z").as_float();
entity->SetPosition(glm::vec3(x, y, z));
}
pugi::xml_node model = entitynode.child("Model");
if (model)
{
const char* filename = model.attribute("filename").as_string();
if (filename)
entity->LoadModel(filename);
}
pugi::xml_node animation = entitynode.child("Animation");
if (animation)
{
const char* name = animation.attribute("name").as_string();
int mode = animation.attribute("mode").as_int();
SkeletonInstance* skeleton = entity->GetSkeletonInstance();
if (skeleton)
skeleton->PlayAnimation(skeleton->FindAnimation(name), mode);
}
pugi::xml_node physics = entitynode.child("Physics");
if (physics)
{
bool value = physics.attribute("value").as_bool();
if (value)
entity->CreateTestBody();
}
Light* light = dynamic_cast<Light*>(entity); // BAD!!!
if (light)
light->InitFromXML(entitynode);
entity->Init();
//IEntityBase* entity = g_entityManager->CreateEntity(classname.as_string());
g_world->AddEntity(entity);
}
}
IEntityBase* Game::Lua_CreateEntity(const char* classname)
{
using namespace LuaPlus;
SDL_assert_always(classname);
// find prototype
LuaPrototype* luaprototype = Lua_FindPrototype(classname);
// create an entity
Entity* entity = nullptr;
if (luaprototype)
{
entity = static_cast<Entity*>(g_entityManager->CreateEntity(luaprototype->m_enginename.c_str()));
// override classname because of entity system is so dumb
entity->SetClassname(luaprototype->m_luaname.c_str());
}
else
{
entity = static_cast<Entity*>(g_entityManager->CreateEntity(classname));
}
SDL_assert_always(entity);
// get the factrory
LuaObject factory = GetLuaState().GetGlobal("g_factory");
// and the lookup table
LuaObject lookup = GetLuaState().GetGlobal("g_entities");
// generate name
char entityname[256];
snprintf(entityname, sizeof(entityname), "%s_%d", classname, factory.GetTableCount());
// create an table
LuaObject entityTable = GetLuaState().CreateTable();
entityTable.SetString("__private_name", entityname);
entityTable.SetInteger("m_id", entity->GetID());
// assign prototype
if (luaprototype)
{
LuaObject prototype = GetLuaState().GetGlobal(luaprototype->m_luaname.c_str());
SDL_assert_always(!prototype.IsNil());
entityTable.SetMetatable(prototype);
}
// link to the entity
entity->InitFromTable(entityTable);
// push in to the factory
factory.SetObject(entityname, entityTable);
lookup.SetObject(entity->GetID(), entityTable);
return entity;
}
LuaPrototype* Game::Lua_FindPrototype(const char* classname)
{
// find a prototype
for (std::vector<LuaPrototype>::iterator it = g_prototypes.begin();
it != g_prototypes.end();
++it)
{
if (strcmp((*it).m_luaname.c_str(), classname) == 0)
{
return &(*it);
}
}
return nullptr;
}
IEntityBase* Game::FindEntityByName(const char* name)
{
if (!g_world)
return nullptr;
int numEntities = g_world->GetNumEntities();
for (int i = 0; i < numEntities; i++)
{
Entity* entity = dynamic_cast<Entity*>(g_world->GetEntity(i));
if (entity && entity->GetName() == name)
return entity;
}
return nullptr;
}
void Game::Shutdown()
{
}
void Game::Render2D()
{
gameRenderUI();
}
//LuaPrototype* pluaprototype = Lua_FindPrototype(classname);
//
//if (pluaprototype)
//{
// Entity* entity = static_cast<Entity*>(g_entityManager->CreateEntity(pluaprototype->m_enginename.c_str()));
// SDL_assert_always(entity);
// // get a prototype
// LuaObject prototype = GetLuaState().GetGlobal(pluaprototype->m_luaname.c_str());
// SDL_assert_always(!prototype.IsNil());
// //prototype.SetObject("__index", prototype);
// // generate table
// LuaObject factory = GetLuaState().GetGlobal("g_factory");
// // generate name
// std::string entityname = pluaprototype->m_luaname + "_" + std::to_string(factory.GetTableCount());
//
// // create an table
// LuaObject entityTable = GetLuaState().GetGlobals().CreateTable(entityname.c_str());
// entityTable.SetMetatable(prototype);
// entityTable.SetString("m_name", entityname.c_str());
//
// // push in to the factory
// factory.SetObject(entityname.c_str(), entityTable);
// // link to the entity
// entity->InitFromTable(entityTable);
// return entity;
//}
//IEntityBase* entity = g_entityManager->CreateEntity(classname);