285 lines
11 KiB
C++
285 lines
11 KiB
C++
#pragma once
|
|
|
|
#include "render/Renderer.h"
|
|
#include "Environment.h"
|
|
#include "render/TextureManager.h"
|
|
#include "render/TextRenderer.h"
|
|
#include "items/GameObjectLoader.h"
|
|
#include "items/InteractiveObject.h"
|
|
#include "navigation/PathFinder.h"
|
|
#include "render/ShadowMap.h"
|
|
#include "render/PathVisualization.h"
|
|
|
|
#include "ScriptEngine.h"
|
|
#include "dialogue/DialogueSystem.h"
|
|
#include "SparkEmitter.h"
|
|
#include "TeleportZone.h"
|
|
#include "LocationEditor.h"
|
|
#include "LocationState.h"
|
|
#include "LocationGameState.h"
|
|
#include <functional>
|
|
#include <cstdint>
|
|
#include <chrono>
|
|
#include <unordered_map>
|
|
#include "Character.h"
|
|
|
|
namespace FRG
|
|
{
|
|
class IGameServer;
|
|
|
|
struct PointLight
|
|
{
|
|
std::string id;
|
|
Eigen::Vector3f position; // world space
|
|
Eigen::Vector3f direction; // world space, unit vector the cone points toward
|
|
Eigen::Vector3f color; // RGB, values > 1.0 increase intensity
|
|
bool autoLight = false; // if true, only activates when nearest light to player
|
|
float autoLightDistance = -1.0f; // max distance for autoLight activation; <=0 means unlimited
|
|
// Rectangular limits — keep light confined to a room-sized AABB centred on the light
|
|
float limitX = 99999.f; // shader fragment limit: half-width in world X
|
|
float limitY = 99999.f; // shader fragment limit: half-width in world Z
|
|
float autoLightLimitX = -1.0f; // activation rect half-width X; <0 = use nearest-light logic
|
|
float autoLightLimitY = -1.0f; // activation rect half-width Z
|
|
};
|
|
|
|
struct TriggerZone
|
|
{
|
|
std::string id;
|
|
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
|
float radius = 1.5f;
|
|
float hysteresis = 0.3f; // exit fires at radius + hysteresis to prevent flickering
|
|
bool enabled = true;
|
|
bool playerInside = false; // runtime tracking state, not serialized (mirrored in LocationNetworkState)
|
|
};
|
|
|
|
struct LocationSetup
|
|
{
|
|
std::shared_ptr<const LocationDefinition> definition;
|
|
std::string gameObjectsJsonPath;
|
|
std::string interactiveObjectsJsonPath;
|
|
std::string npcsJsonPath;
|
|
std::string dialoguesJsonPath;
|
|
//std::vector<std::string> navigationJsonPaths;
|
|
std::string scriptPath;
|
|
std::string teleportsJsonPath;
|
|
std::string triggerZonesJsonPath;
|
|
std::string lightsJsonPath;
|
|
Vec3fp playerPosition;
|
|
// Player shared by all locations of a campaign (see Location::createPlayer);
|
|
// setup() creates a private one when this is null.
|
|
std::shared_ptr<Character> player;
|
|
LocationGameState* gameState = nullptr;
|
|
std::shared_ptr<LocationLocalState> localState;
|
|
};
|
|
|
|
class Location
|
|
{
|
|
public:
|
|
Location(Renderer& iRenderer, IGameServer& server,
|
|
std::string locationId);
|
|
// Detaches the (shared, longer-lived) player from this location.
|
|
~Location();
|
|
const std::string& locationId() const { return locationId_; }
|
|
IGameServer& gameServer() { return gameServer_; }
|
|
std::shared_ptr<const LocationNetworkState> stateSnapshot() const;
|
|
LocationLocalState& localState() { return *localState_; }
|
|
const LocationLocalState& localState() const { return *localState_; }
|
|
bool isDarklands() const;
|
|
bool isNight() const;
|
|
bool isDawn() const;
|
|
int globalInt(const std::string& key) const;
|
|
float globalFloat(const std::string& key) const;
|
|
void setGlobalInt(const std::string& key, int value);
|
|
void setGlobalFloat(const std::string& key, float value);
|
|
|
|
std::unordered_map<std::string, LoadedGameObject> gameObjects;
|
|
|
|
// Repeated static objects (same mesh + textures) drawn with one instanced call each.
|
|
std::vector<InstancedBatch> instancedBatches;
|
|
|
|
std::vector<InteractiveObject> interactiveObjects;
|
|
|
|
// Shared: the same player moves from location to location and outlives them.
|
|
std::shared_ptr<Character> player;
|
|
std::vector<std::unique_ptr<Character>> npcs;
|
|
|
|
// Builds the player character (models, animations, weapon, textures).
|
|
static std::shared_ptr<Character> createPlayer(Renderer& renderer, AudioPlayerAsync* audioPlayer,
|
|
IGameServer& server, const std::string& playerId);
|
|
|
|
std::shared_ptr<ShadowMap> shadowMap;
|
|
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
|
|
|
|
std::unique_ptr<TextRenderer> npcNameText;
|
|
|
|
ScriptEngine scriptEngine;
|
|
Dialogue::DialogueSystem dialogueSystem;
|
|
|
|
std::vector<TeleportZone> teleportZones;
|
|
std::function<void(const std::string&, const Eigen::Vector3f&, float)> onTeleport;
|
|
|
|
std::vector<TriggerZone> triggerZones;
|
|
|
|
std::vector<PointLight> pointLights;
|
|
|
|
// Called when the player successfully taps the ground and a floor walk target is set.
|
|
// Used by the tutorial system to detect the "tap to walk" gesture.
|
|
std::function<void()> onPlayerFloorWalk;
|
|
|
|
std::function<void()> onPlayerTaxiRequired;
|
|
|
|
// Set by Game after setup(). Lua and onDeathAnimComplete call this to
|
|
// request the transition without coupling Location to Game directly.
|
|
std::function<bool()> requestDarklandsTransition;
|
|
|
|
std::function<void(bool, bool)> requestNightDayTransition;
|
|
|
|
// Set by Game after setup(). Lua calls this to advance the uni_interior darklands
|
|
// tutorial to the attack hint (trigger-zone encounter).
|
|
std::function<void()> requestAdvanceDarklandsHud;
|
|
|
|
std::function<void()> requestClosePhone;
|
|
std::function<void()> requestReturnToMainMenu;
|
|
std::function<void(int, bool, const std::string&)> requestSetChatUnread;
|
|
|
|
std::function<void()> requestDarklandsPlayBattleMusic;
|
|
std::function<void()> requestDarklandsPlayNormalMusic;
|
|
|
|
|
|
// Navigation editor — toggle with 'N', save with 'B', right-click to finalize polygon
|
|
EditorMode editorMode = EditorMode::None;
|
|
LocationEditor editor;
|
|
|
|
//std::vector<std::string> navigationMapPaths;
|
|
|
|
// Set by Game when the user's primary pointer (left mouse / single touch)
|
|
// has crossed the tap-vs-drag threshold and is now rotating the camera.
|
|
bool cameraDragging = false;
|
|
int lastMouseX = 0;
|
|
int lastMouseY = 0;
|
|
|
|
bool playBattleMusic = false;
|
|
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<Texture> carTexture;
|
|
VertexRenderStruct carMesh;
|
|
std::shared_ptr<Texture> carWheelTexture;
|
|
VertexRenderStruct carWheelMesh;
|
|
|
|
|
|
|
|
void setup(const LocationSetup& params, AudioPlayerAsync* audioPlayer);
|
|
//void setupNavigation(const std::vector<std::string>& paths);
|
|
bool switchNavigation(int index);
|
|
void setCharacterTarget(Character& character, const Vec3fp& target,
|
|
std::string callbackName = "");
|
|
void clearCharacterAttackTarget(Character& character);
|
|
void stopCharacter(Character& character);
|
|
void faceCharacter(Character& character, std::string targetId);
|
|
void replanCharacter(Character& character);
|
|
void resetCharacterAfterDeath(Character& character);
|
|
void tryCharacterAttackCombo(Character& character);
|
|
void setCharacterHp(Character& character, float hp);
|
|
void refillCharacterHp(Character& character);
|
|
bool hasInventoryItem(const std::string& itemId) const;
|
|
std::size_t inventoryItemCount() const;
|
|
void addInventoryItem(std::string itemId);
|
|
void removeInventoryItem(std::string itemId);
|
|
bool unlockQuest(const std::string& questId);
|
|
bool completeQuest(const std::string& questId);
|
|
bool failQuest(const std::string& questId);
|
|
bool setQuestObjectiveCompleted(
|
|
const std::string& questId, const std::string& objectiveId, bool completed);
|
|
bool setQuestObjectiveVisible(
|
|
const std::string& questId, const std::string& objectiveId, bool visible);
|
|
bool setQuestActiveObjective(const std::string& questId, int objectiveIndex);
|
|
InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir);
|
|
Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f);
|
|
|
|
void drawGame();
|
|
void drawShadowDepthPass();
|
|
void drawGameWithShadows();
|
|
void drawGameDarklands();
|
|
void drawGameNight();
|
|
void drawNightShadowDepthPass(const PointLight& light);
|
|
void drawNpcCar();
|
|
|
|
bool setNavigationAreaAvailable(const std::string& areaName, bool available);
|
|
|
|
void update(int64_t deltaMs, const LocationSimulationEvents& events);
|
|
void updatePresentation(int64_t deltaMs);
|
|
// Purely visual feedback for click-triggered player routes. Disabling it
|
|
// also cancels an animation that is currently playing.
|
|
void setPathVisualizationEnabled(bool enabled);
|
|
bool isPathVisualizationEnabled() const { return pathVisualization.isEnabled(); }
|
|
|
|
void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
|
void handleUp(int64_t fingerId, int mx, int my);
|
|
void handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
|
|
|
bool requestDialogueStart(const std::string& dialogueId);
|
|
bool requestCutsceneStart(const std::string& cutsceneId);
|
|
void setOnCutsceneFinished(std::function<void(const std::string&)> cb);
|
|
void setDialogueFlag(const std::string& flag, int value);
|
|
int getDialogueFlag(const std::string& flag) const;
|
|
|
|
protected:
|
|
friend class LocationEditor;
|
|
friend class Game;
|
|
friend class ScriptEngine;
|
|
friend class LocalGameServerHost;
|
|
friend class RemoteGameServerHost;
|
|
Renderer& renderer;
|
|
AudioPlayerAsync* audioPlayer;
|
|
|
|
private:
|
|
IGameServer& gameServer_;
|
|
std::string locationId_;
|
|
std::shared_ptr<LocationLocalState> localState_;
|
|
std::unordered_map<std::string, std::shared_ptr<Character>> remotePlayers_;
|
|
void synchronizeRemotePlayers();
|
|
void drawRemotePlayerNames(
|
|
const Eigen::Matrix4f& cameraViewMatrix,
|
|
const Eigen::Matrix4f& projectionMatrix);
|
|
// ---- Pointer-to-index helpers ----
|
|
TeleportZone* getTargetTeleportZone(const LocationNetworkState& state);
|
|
|
|
enum class InstancedTexture { None, Color, Darklands };
|
|
|
|
// Draws instancedBatches with the instancing variant of the current pass shader
|
|
// (e.g. "fog_inst"). The variant is a separate program, so setUniforms must re-send
|
|
// the pass uniforms; the matrix stack tops are uploaded here.
|
|
void drawInstancedStatics(const std::string& instancedShaderName, InstancedTexture textureKind,
|
|
const std::function<void()>& setUniforms = {});
|
|
|
|
void loadTeleportZones(const std::string& jsonPath, const char* zipFile);
|
|
void loadTriggerZones(const std::string& jsonPath, const char* zipFile);
|
|
void loadPointLights(const std::string& jsonPath, const char* zipFile);
|
|
void updateTriggerZones(const Eigen::Vector3f& playerPos);
|
|
void captureAuxiliaryState(LocationGameState& out) const;
|
|
void applyAuxiliaryState(const LocationGameState& in);
|
|
|
|
PathVisualization pathVisualization;
|
|
|
|
struct NpcCarPresentationSample {
|
|
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
|
float rotation = 0.f;
|
|
float wheelAngle = 0.f;
|
|
float steeringAngle = 0.f;
|
|
};
|
|
|
|
NpcCarPresentationSample previousNpcCarPresentation_;
|
|
NpcCarPresentationSample currentNpcCarPresentation_;
|
|
std::chrono::steady_clock::time_point lastNpcCarPresentationUpdate_;
|
|
bool hasNpcCarPresentationSample_ = false;
|
|
|
|
void captureNpcCarPresentation(const NpcCar& car);
|
|
NpcCarPresentationSample interpolatedNpcCarPresentation(const NpcCar& car) const;
|
|
|
|
};
|
|
|
|
} // namespace FRG
|