-
-
Save robjinman/f5326ee40e99181b71553bf82083fa2e to your computer and use it in GitHub Desktop.
Code for Pro Office Calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef __PROCALC_BUTTON_GRID_HPP__ | |
#define __PROCALC_BUTTON_GRID_HPP__ | |
#include <memory> | |
#include <vector> | |
#include <QWidget> | |
#include <QGridLayout> | |
#include "qt_obj_ptr.hpp" | |
enum buttonId_t { | |
BTN_ZERO = 0, | |
BTN_ONE = 1, | |
BTN_TWO = 2, | |
BTN_THREE = 3, | |
BTN_FOUR = 4, | |
BTN_FIVE = 5, | |
BTN_SIX = 6, | |
BTN_SEVEN = 7, | |
BTN_EIGHT = 8, | |
BTN_NINE = 9, | |
BTN_PLUS, | |
BTN_MINUS, | |
BTN_TIMES, | |
BTN_DIVIDE, | |
BTN_POINT, | |
BTN_CLEAR, | |
BTN_EQUALS, | |
BTN_NULL = 1000 | |
}; | |
class QGridLayout; | |
class QButtonGroup; | |
class QPushButton; | |
class ButtonGrid : public QWidget { | |
Q_OBJECT | |
public: | |
ButtonGrid(QWidget* parent); | |
virtual ~ButtonGrid(); | |
QtObjPtr<QGridLayout> grid; | |
std::vector<QtObjPtr<QPushButton>> buttons; | |
QtObjPtr<QButtonGroup> buttonGroup; | |
private slots: | |
void onBtnClick(int id); | |
void onBtnPress(int id); | |
signals: | |
void buttonClicked(int id); | |
void buttonPressed(int id); | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENT_SPEC_HPP__ | |
#define __PROCALC_FRAGMENT_SPEC_HPP__ | |
#include <vector> | |
#include <map> | |
#include <string> | |
class FragmentSpec { | |
public: | |
FragmentSpec(const std::string& type, std::vector<const FragmentSpec*> specs); | |
FragmentSpec(const std::string& type, int id, std::vector<const FragmentSpec*> specs); | |
const std::map<std::string, const FragmentSpec*>& specs() const; | |
const FragmentSpec& spec(const std::string& name) const; | |
std::string name() const; | |
const std::string& type() const; | |
void setEnabled(bool b); | |
bool isEnabled() const; | |
virtual ~FragmentSpec() = 0; | |
private: | |
void populateChildrenMap() const; | |
bool m_enabled; | |
std::string m_type; | |
int m_id; | |
mutable std::vector<const FragmentSpec*> m_tmpChildren; | |
mutable std::map<std::string, const FragmentSpec*> m_children; | |
}; | |
#endif | |
#ifndef __PROCALC_EXCEPTION_HPP__ | |
#define __PROCALC_EXCEPTION_HPP__ | |
#include <stdexcept> | |
#include <sstream> | |
#define EXCEPTION(x) { \ | |
std::stringstream ss; \ | |
ss << x; \ | |
throw Exception(ss.str(), __FILE__, __LINE__); \ | |
} | |
#define PC_ASSERT(cond) \ | |
if (!(cond)) { \ | |
EXCEPTION("Assertion failed"); \ | |
} | |
#define PC_ASSERT_EQ(actual, expected) \ | |
if ((actual) != (expected)) { \ | |
EXCEPTION("Assertion failed (" << actual << " != " << expected << ")"); \ | |
} | |
class Exception : public std::runtime_error { | |
public: | |
Exception(const std::string& msg); | |
Exception(const std::string& msg, const char* file, int line); | |
virtual const char* what() const throw(); | |
void append(const std::string& text) throw(); | |
void prepend(const std::string& text) throw(); | |
virtual ~Exception() throw(); | |
private: | |
std::string m_msg; | |
std::string m_file; | |
int m_line; | |
}; | |
#endif | |
#ifndef __PROCALC_APP_CONFIG_HPP__ | |
#define __PROCALC_APP_CONFIG_HPP__ | |
#include <string> | |
#include <map> | |
#include <vector> | |
#include <QFont> | |
#include "event.hpp" | |
struct RequestStateChangeEvent : public Event { | |
RequestStateChangeEvent(int stateId, bool hardReset = false) | |
: Event("requestStateChange"), | |
stateId(stateId), | |
hardReset(hardReset) {} | |
int stateId; | |
bool hardReset; | |
}; | |
struct SetConfigParamEvent : public Event { | |
SetConfigParamEvent(const std::string& name, const std::string& value) | |
: Event("setConfigParam"), | |
name(name), | |
value(value) {} | |
std::string name; | |
std::string value; | |
}; | |
class AppConfig { | |
public: | |
typedef std::vector<std::string> CommandLineArgs; | |
AppConfig(int argc, char** argv); | |
void persistState(); | |
std::string version; | |
int stateId = 0; | |
CommandLineArgs args; | |
QFont normalFont; | |
QFont monoFont; | |
const std::string& getParam(const std::string& name) const; | |
void setParam(const std::string& name, const std::string& value); | |
std::string dataPath(const std::string& relPath) const; | |
std::string saveDataPath(const std::string& relPath) const; | |
std::string getStringArg(unsigned int idx, const std::string& defaultVal) const; | |
double getDoubleArg(unsigned int idx, double defaultVal) const; | |
int getIntArg(unsigned int idx, int defaultVal) const; | |
private: | |
void loadState(); | |
std::string versionFilePath() const; | |
void readVersionFile(); | |
std::map<std::string, std::string> m_params; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_CALCULATOR_WIDGET_HPP__ | |
#define __PROCALC_FRAGMENTS_CALCULATOR_WIDGET_HPP__ | |
#include <QVBoxLayout> | |
#include <QWidget> | |
#include <QLineEdit> | |
#include <QLabel> | |
#include "button_grid.hpp" | |
#include "calculator.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "event.hpp" | |
struct CalculatorButtonPressEvent : public Event { | |
CalculatorButtonPressEvent(int buttonId, Calculator& calculator) | |
: Event("calculatorButtonPress"), | |
buttonId(buttonId), | |
calculator(calculator) {} | |
int buttonId; | |
Calculator& calculator; | |
}; | |
class QMainWindow; | |
class EventSystem; | |
class CalculatorWidget : public QWidget { | |
Q_OBJECT | |
public: | |
CalculatorWidget(EventSystem& eventSystem); | |
Calculator calculator; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QLineEdit> wgtDigitDisplay; | |
QtObjPtr<QLabel> wgtOpDisplay; | |
QtObjPtr<ButtonGrid> wgtButtonGrid; | |
virtual ~CalculatorWidget() override; | |
public slots: | |
void onButtonClick(int id); | |
private: | |
EventSystem& m_eventSystem; | |
}; | |
#endif | |
#ifndef __PROCALC_CALCULATOR_HPP__ | |
#define __PROCALC_CALCULATOR_HPP__ | |
#include <stack> | |
#include <string> | |
#ifdef DEBUG | |
# include <ostream> | |
#endif | |
class Calculator { | |
public: | |
void number(int n); | |
void point(); | |
void plus(); | |
void times(); | |
void divide(); | |
void minus(); | |
double equals(); | |
void clear(); | |
const std::string& display() const; | |
private: | |
class OpStack { | |
public: | |
enum operator_t { | |
OP_NONE, | |
OP_PLUS, | |
OP_MINUS, | |
OP_TIMES, | |
OP_DIVIDE | |
}; | |
void putValue(double val); | |
void putOperator(operator_t op); | |
void clear(); | |
double evaluate(); | |
operator_t op() const; | |
#ifdef DEBUG | |
void dbg_print(std::ostream& os) const; | |
#endif | |
private: | |
struct Expr { | |
double lhs; | |
operator_t op; | |
}; | |
double apply(Expr, double rhs) const; | |
void collapseStack(); | |
std::stack<Expr> m_stack; | |
static const std::map<operator_t, int> PRECEDENCE; | |
}; | |
OpStack m_opStack; | |
std::string m_display; | |
bool m_reset; | |
}; | |
#endif | |
#ifndef __PROCALC_PLATFORM_HPP__ | |
#define __PROCALC_PLATFORM_HPP__ | |
#include <string> | |
const std::string sep = "/"; | |
void sleepThread(long millis); | |
#endif | |
#ifndef __PROCALC_EVENT_SYSTEM_HPP__ | |
#define __PROCALC_EVENT_SYSTEM_HPP__ | |
#include <functional> | |
#include <map> | |
#include <set> | |
#include <QObject> | |
#include "event.hpp" | |
typedef std::function<void(const Event&)> handlerFunc_t; | |
class EventSystem : public QObject, public std::enable_shared_from_this<EventSystem> { | |
Q_OBJECT | |
public: | |
class Handle { | |
public: | |
Handle() | |
: id(-1) {} | |
Handle(std::weak_ptr<EventSystem> eventSystem, int id) | |
: id(id), | |
m_eventSystem(eventSystem) {} | |
Handle(const Handle& cpy) = delete; | |
Handle& operator=(const Handle& rhs) = delete; | |
Handle(Handle&& cpy) { | |
*this = std::move(cpy); | |
} | |
void forget() { | |
auto es = m_eventSystem.lock(); | |
if (es) { | |
es->forget(*this); | |
} | |
} | |
Handle& operator=(Handle&& rhs) { | |
if (id != rhs.id) { | |
forget(); | |
} | |
id = rhs.id; | |
m_eventSystem = rhs.m_eventSystem; | |
rhs.id = -1; | |
return *this; | |
} | |
int id; | |
~Handle() { | |
forget(); | |
} | |
private: | |
std::weak_ptr<EventSystem> m_eventSystem; | |
}; | |
Handle listen(const std::string& name, handlerFunc_t fn); | |
void forget(Handle& handle); | |
void fire(pEvent_t event); | |
private: | |
bool event(QEvent* event) override; | |
void processEvent(const Event& event); | |
void processEvent_(const std::string& name, const Event& event); | |
void forget_(int id); | |
void processingStart(); | |
void processingEnd(); | |
void forgetPending(); | |
void addPending(); | |
std::map<std::string, std::map<int, handlerFunc_t>> m_handlers; | |
bool m_processingEvent = false; | |
std::map<std::string, std::map<int, handlerFunc_t>> m_pendingAddition; | |
std::set<int> m_pendingForget; | |
}; | |
typedef EventSystem::Handle EventHandle; | |
#endif | |
#ifndef __PROCALC_FRAGMENT_HPP__ | |
#define __PROCALC_FRAGMENT_HPP__ | |
#include <string> | |
#include <map> | |
#include <memory> | |
#include "app_config.hpp" | |
class FragmentData { | |
public: | |
virtual ~FragmentData() = 0; | |
}; | |
class EventSystem; | |
class UpdateLoop; | |
class CommonFragData { | |
public: | |
AppConfig& appConfig; | |
EventSystem& eventSystem; | |
UpdateLoop& updateLoop; | |
}; | |
class FragmentSpec; | |
class Fragment; | |
typedef std::unique_ptr<Fragment> pFragment_t; | |
class Fragment { | |
public: | |
// Use constructor to initialise the fragment and attach to / modify the parent | |
Fragment(const std::string& name, FragmentData& ownData, const CommonFragData& commonData); | |
Fragment(const std::string& name, Fragment& parent, FragmentData& parentData, | |
FragmentData& ownData, const CommonFragData& commonData); | |
// Rebuild fragment tree by adding/removing children and calling their respective | |
// lifecycle functions | |
void rebuild(const FragmentSpec& spec, bool hardReset); | |
// Re-initialise fragment with new spec. Called when the app state changes and the | |
// fragment tree is rebuilt | |
virtual void reload(const FragmentSpec& spec) = 0; | |
// Detach from and reverse modifications to parent. Called prior to being removed | |
// from the fragment tree and destroyed | |
virtual void cleanUp() = 0; | |
template<class T> | |
T& parentFragData() { | |
return dynamic_cast<T&>(*m_parentData); | |
} | |
template<class T> | |
T& parentFrag() { | |
return dynamic_cast<T&>(*m_parent); | |
} | |
const std::string& name() const; | |
virtual ~Fragment(); | |
protected: | |
CommonFragData commonData; | |
private: | |
std::string m_name; | |
Fragment* m_parent; | |
FragmentData* m_parentData; | |
FragmentData& m_ownData; | |
std::map<std::string, pFragment_t> m_children; | |
void detach(); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_SPRITE_FACTORY_HPP__ | |
#define __PROCALC_RAYCAST_SPRITE_FACTORY_HPP__ | |
#include "raycast/game_object_factory.hpp" | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class Matrix; | |
class RootFactory; | |
namespace parser { struct Object; } | |
class SpriteFactory : public GameObjectFactory { | |
public: | |
SpriteFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
AudioService& audioService, TimeService& timeService); | |
const std::set<std::string>& types() const override; | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t region, const Matrix& parentTransform) override; | |
private: | |
RootFactory& m_rootFactory; | |
EntityManager& m_entityManager; | |
AudioService& m_audioService; | |
TimeService& m_timeService; | |
bool constructSprite(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructAmmo(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructHealthPack(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructBadGuy(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructCivilian(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_C_SWITCH_BEHAVIOUR_HPP_ | |
#define __PROCALC_RAYCAST_C_SWITCH_BEHAVIOUR_HPP_ | |
#include <string> | |
#include "raycast/behaviour_system.hpp" | |
#include "raycast/timing.hpp" | |
class EntityManager; | |
class TimeService; | |
class CWallDecal; | |
enum class SwitchState { ON, OFF }; | |
class ESwitchActivate : public GameEvent { | |
public: | |
ESwitchActivate(entityId_t switchEntityId, SwitchState state, const std::string& message) | |
: GameEvent("switch_activated"), | |
switchEntityId(switchEntityId), | |
message(message), | |
state(state) {} | |
entityId_t switchEntityId; | |
std::string message; | |
SwitchState state; | |
}; | |
class CSwitchBehaviour : public CBehaviour { | |
public: | |
CSwitchBehaviour(entityId_t entityId, EntityManager& entityManager, TimeService& TimeService, | |
const std::string& message, SwitchState initialState, bool toggleable, double toggleDelay); | |
std::string requiredItemType; | |
std::string requiredItemName; | |
bool disabled = false; | |
entityId_t target = -1; | |
void update() override; | |
void handleBroadcastedEvent(const GameEvent&) override {} | |
void handleTargetedEvent(const GameEvent& event) override; | |
void setState(SwitchState state); | |
SwitchState getState() const; | |
~CSwitchBehaviour() override {} | |
private: | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
std::string m_message; | |
SwitchState m_state; | |
bool m_toggleable; | |
Debouncer m_timer; | |
CWallDecal* getDecal() const; | |
void setDecal(); | |
void showCaption(); | |
void deleteCaption(); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_INVENTORY_SYSTEM_HPP_ | |
#define __PROCALC_RAYCAST_INVENTORY_SYSTEM_HPP_ | |
#include <functional> | |
#include <memory> | |
#include <map> | |
#include <set> | |
#include "raycast/system.hpp" | |
#include "raycast/component.hpp" | |
enum class CInventoryKind { | |
COLLECTOR, | |
COLLECTABLE | |
}; | |
enum class BucketKind { | |
COUNTER_BUCKET, | |
ITEM_BUCKET | |
}; | |
struct CInventory : public Component { | |
CInventory(CInventoryKind kind, entityId_t entityId) | |
: Component(entityId, ComponentKind::C_INVENTORY), | |
kind(kind) {} | |
CInventoryKind kind; | |
}; | |
typedef std::unique_ptr<CInventory> pCInventory_t; | |
struct Bucket { | |
Bucket(BucketKind kind) | |
: bucketKind(kind) {} | |
BucketKind bucketKind; | |
virtual ~Bucket() = 0; | |
}; | |
typedef std::unique_ptr<Bucket> pBucket_t; | |
struct CCollector : public CInventory { | |
CCollector(entityId_t entityId) | |
: CInventory(CInventoryKind::COLLECTOR, entityId) {} | |
std::map<std::string, pBucket_t> buckets; | |
}; | |
typedef std::unique_ptr<CCollector> pCCollector_t; | |
struct CCollectable : public CInventory { | |
CCollectable(entityId_t entityId, const std::string& collectableType) | |
: CInventory(CInventoryKind::COLLECTABLE, entityId), | |
collectableType(collectableType) {} | |
std::string collectableType; | |
int value = 1; | |
std::string name; | |
}; | |
typedef std::unique_ptr<CCollectable> pCCollectable_t; | |
struct CounterBucket : public Bucket { | |
CounterBucket(int capacity) | |
: Bucket(BucketKind::COUNTER_BUCKET), | |
capacity(capacity) {} | |
int capacity; | |
int count = 0; | |
}; | |
typedef std::unique_ptr<CounterBucket> pCounterBucket_t; | |
struct ItemBucket : public Bucket { | |
public: | |
ItemBucket(int capacity) | |
: Bucket(BucketKind::ITEM_BUCKET), | |
capacity(capacity) {} | |
int capacity; | |
std::map<std::string, entityId_t> items; | |
}; | |
typedef std::unique_ptr<ItemBucket> pItemBucket_t; | |
struct ECollectableEncountered : public GameEvent { | |
ECollectableEncountered(entityId_t collectorId, const CCollectable& item) | |
: GameEvent("collectable_encountered"), | |
collectorId(collectorId), | |
item(item) {} | |
entityId_t collectorId; | |
const CCollectable& item; | |
}; | |
struct EItemCollected : public GameEvent { | |
EItemCollected(entityId_t collectorId, const CCollectable& item) | |
: GameEvent("item_collected"), | |
collectorId(collectorId), | |
item(item) {} | |
entityId_t collectorId; | |
const CCollectable& item; | |
}; | |
struct EBucketCountChange : public GameEvent { | |
EBucketCountChange(entityId_t entityId, const std::string& collectableType, | |
const CounterBucket& bucket, int prevCount) | |
: GameEvent("bucket_count_change"), | |
entityId(entityId), | |
collectableType(collectableType), | |
bucket(bucket), | |
prevCount(prevCount) {} | |
entityId_t entityId; | |
std::string collectableType; | |
const CounterBucket& bucket; | |
int prevCount; | |
}; | |
struct EBucketItemsChange : public GameEvent { | |
EBucketItemsChange(entityId_t entityId, const std::string& collectableType, | |
const ItemBucket& bucket, int prevCount) | |
: GameEvent("bucket_items_change"), | |
entityId(entityId), | |
collectableType(collectableType), | |
bucket(bucket), | |
prevCount(prevCount) {} | |
entityId_t entityId; | |
std::string collectableType; | |
const ItemBucket& bucket; | |
int prevCount; | |
}; | |
class EntityManager; | |
class InventorySystem : public System { | |
public: | |
InventorySystem(EntityManager& entityManager) | |
: m_entityManager(entityManager) {} | |
void update() override; | |
void handleEvent(const GameEvent&) override {}; | |
void handleEvent(const GameEvent& event, const std::set<entityId_t>& entities) override; | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CInventory& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
void addToBucket(entityId_t collectorId, const CCollectable& item); | |
// Counter buckets only | |
int getBucketValue(entityId_t collectorId, const std::string& collectableType) const; | |
int subtractFromBucket(entityId_t collectorId, const std::string& collectableType, int value); | |
// Item buckets only | |
const std::map<std::string, entityId_t>& getBucketItems(entityId_t collectorId, | |
const std::string& collectableType) const; | |
void removeFromBucket(entityId_t collectorId, const std::string& collectableType, | |
const std::string& name); | |
private: | |
EntityManager& m_entityManager; | |
std::map<entityId_t, pCCollector_t> m_collectors; | |
std::map<entityId_t, pCCollectable_t> m_collectables; | |
void addCollector(CInventory* component); | |
void addCollectable(CInventory* component); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_RENDERER_HPP__ | |
#define __PROCALC_RAYCAST_RENDERER_HPP__ | |
#include <array> | |
#include <list> | |
#include <set> | |
#include <thread> | |
#include <QPainter> | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_graph.hpp" | |
#include "app_config.hpp" | |
class QImage; | |
class EntityManager; | |
class Camera; | |
class RenderSystem; | |
class Renderer { | |
public: | |
Renderer(const AppConfig& appConfig, EntityManager& entityManager, QImage& target); | |
void renderScene(const RenderGraph& rg, const Camera& cam); | |
private: | |
struct Slice { | |
double sliceBottom_wd; | |
double sliceTop_wd; | |
double projSliceBottom_wd; | |
double projSliceTop_wd; | |
double viewportBottom_wd; | |
double viewportTop_wd; | |
bool visible = true; | |
}; | |
struct ScreenSlice { | |
int sliceBottom_px; | |
int sliceTop_px; | |
int viewportBottom_px; | |
int viewportTop_px; | |
}; | |
enum class XWrapperKind { | |
JOIN, | |
WALL, | |
SPRITE | |
}; | |
struct XWrapper { | |
XWrapper(XWrapperKind kind, pIntersection_t X) | |
: kind(kind), | |
X(std::move(X)) {} | |
XWrapperKind kind; | |
pIntersection_t X; | |
virtual ~XWrapper() {} | |
}; | |
typedef std::unique_ptr<XWrapper> pXWrapper_t; | |
struct CastResult { | |
std::list<pXWrapper_t> intersections; | |
}; | |
struct JoinX : public XWrapper { | |
JoinX(pIntersection_t X) | |
: XWrapper(XWrapperKind::JOIN, std::move(X)) {} | |
const CSoftEdge* softEdge = nullptr; | |
const CJoin* join = nullptr; | |
Slice slice0; | |
Slice slice1; | |
const CZone* nearZone; | |
const CZone* farZone; | |
virtual ~JoinX() override {} | |
}; | |
struct WallX : public XWrapper { | |
WallX(pIntersection_t X) | |
: XWrapper(XWrapperKind::WALL, std::move(X)) {} | |
const CHardEdge* hardEdge = nullptr; | |
const CWall* wall = nullptr; | |
Slice slice; | |
const CZone* nearZone; | |
virtual ~WallX() override {} | |
}; | |
struct SpriteX : public XWrapper { | |
SpriteX(pIntersection_t X) | |
: XWrapper(XWrapperKind::SPRITE, std::move(X)) {} | |
const CVRect* vRect = nullptr; | |
const CSprite* sprite = nullptr; | |
Slice slice; | |
virtual ~SpriteX() override {} | |
}; | |
AppConfig m_appConfig; | |
EntityManager& m_entityManager; | |
QImage& m_target; | |
const double ATAN_MIN = -10.0; | |
const double ATAN_MAX = 10.0; | |
typedef std::array<double, 10000> tanMap_t; | |
typedef std::array<double, 10000> atanMap_t; | |
tanMap_t m_tanMap_rp; | |
atanMap_t m_atanMap; | |
std::vector<std::thread> m_threads; | |
int m_numWorkerThreads; | |
void renderColumns(const RenderGraph& rg, const Camera& cam, const SpatialSystem& spatialSystem, | |
const RenderSystem& renderSystem, int from, int to) const; | |
void drawImage(const QRect& trgRect, const QImage& tex, const QRect& srcRect, | |
double distance = 0) const; | |
void drawSprite(const RenderGraph& rg, const Camera& camera, const SpriteX& X, | |
double screenX_px) const; | |
ScreenSlice drawSlice(const RenderGraph& rg, const Camera& cam, const Intersection& X, | |
const Slice& slice, const std::string& texture, const QRectF& texRect, double screenX_px, | |
double targetH_wd = 0) const; | |
void drawFloorSlice(const RenderGraph& rg, const Camera& cam, | |
const SpatialSystem& spatialSystem, const Intersection& X, const CRegion& region, | |
double floorHeight, const ScreenSlice& slice, int screenX_px, double projX_wd) const; | |
void drawCeilingSlice(const RenderGraph& rg, const Camera& cam, const Intersection& X, | |
const CRegion& region, double ceilingHeight, const ScreenSlice& slice, int screenX_px, | |
double projX_wd) const; | |
void drawSkySlice(const RenderGraph& rg, const Camera& cam, const ScreenSlice& slice, | |
int screenX_px) const; | |
void drawWallDecal(const RenderGraph& rg, const Camera& cam, const SpatialSystem& spatialSystem, | |
const CWallDecal& decal, const Intersection& X, const Slice& slice, const CZone& zone, | |
int screenX_px) const; | |
void drawColourOverlay(const RenderGraph& rg, QPainter& painter, | |
const CColourOverlay& overlay) const; | |
void drawImageOverlay(const RenderGraph& rg, QPainter& painter, | |
const CImageOverlay& overlay) const; | |
void drawTextOverlay(const RenderGraph& rg, QPainter& painter, | |
const CTextOverlay& overlay) const; | |
void sampleWallTexture(const RenderGraph& rg, const Camera& cam, double screenX_px, | |
double texAnchor_wd, double distanceAlongTarget, const Slice& slice, const Size& texSz, | |
const QRectF& frameRect, const Size& tileSz_wd, std::vector<QRect>& trgRects, | |
std::vector<QRect>& srcRects) const; | |
QRect sampleSpriteTexture(const Camera& cam, const QRect& rect, const SpriteX& X, | |
const Size& size_wd, double y_wd) const; | |
XWrapper* constructXWrapper(const SpatialSystem& spatialSystem, | |
const RenderSystem& renderSystem, pIntersection_t X) const; | |
Slice computeSlice(const LineSegment& rotProjPlane, const LineSegment& wall, double subview0, | |
double subview1, const LineSegment& projRay0, const LineSegment& projRay1, Point& projX0, | |
Point& projX1) const; | |
void castRay(const RenderGraph& rg, const Camera& cam, const SpatialSystem& spatialSystem, | |
const RenderSystem& renderSystem, const Vec2f& dir, CastResult& result) const; | |
inline double projToScreenY(const RenderGraph& rg, double y) const; | |
inline double fastTan_rp(double a) const; | |
inline double fastATan(double x) const; | |
}; | |
//=========================================== | |
// Renderer::projToScreenY | |
//=========================================== | |
inline double Renderer::projToScreenY(const RenderGraph& rg, double y) const { | |
return rg.viewport_px.y - (y * rg.vWorldUnit_px); | |
} | |
//=========================================== | |
// Renderer::fastTan_rp | |
// | |
// Retrieves the reciprocal of tan(a) from the lookup table | |
//=========================================== | |
inline double Renderer::fastTan_rp(double a) const { | |
static const double x = static_cast<double>(m_tanMap_rp.size()) / (2.0 * PI); | |
return m_tanMap_rp[static_cast<int>(normaliseAngle(a) * x)]; | |
} | |
//=========================================== | |
// Renderer::fastATan | |
// | |
// Retrieves atan(x) from the lookup table | |
//=========================================== | |
inline double Renderer::fastATan(double x) const { | |
if (x < ATAN_MIN) { | |
x = ATAN_MIN; | |
} | |
if (x > ATAN_MAX) { | |
x = ATAN_MAX; | |
} | |
static double dx = (ATAN_MAX - ATAN_MIN) / static_cast<double>(m_atanMap.size()); | |
return m_atanMap[static_cast<int>((x - ATAN_MIN) / dx)]; | |
} | |
#endif | |
#ifndef __PROCALC_RAYCAST_RENDER_SYSTEM_HPP__ | |
#define __PROCALC_RAYCAST_RENDER_SYSTEM_HPP__ | |
#include "raycast/system.hpp" | |
#include "raycast/renderer.hpp" | |
class QImage; | |
class EntityManager; | |
class Player; | |
class AppConfig; | |
class RenderSystem : public System { | |
public: | |
RenderSystem(const AppConfig& appConfig, EntityManager& entityManager, QImage& target); | |
RenderGraph rg; | |
void connectRegions(); | |
void render(); | |
void update() override {} | |
void handleEvent(const GameEvent& event) override; | |
void handleEvent(const GameEvent&, const std::set<entityId_t>&) override {} | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CRender& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
inline const std::set<entityId_t>& children(entityId_t entityId) const; | |
double textOverlayWidth(const CTextOverlay& overlay) const; | |
void centreTextOverlay(CTextOverlay& overlay) const; | |
private: | |
const AppConfig& m_appConfig; | |
EntityManager& m_entityManager; | |
Renderer m_renderer; | |
std::map<entityId_t, CRender*> m_components; | |
std::map<entityId_t, std::set<entityId_t>> m_entityChildren; | |
bool isRoot(const CRender& c) const; | |
void removeEntity_r(entityId_t id); | |
void crossRegions(RenderGraph& rg, entityId_t entityId, entityId_t oldZone, entityId_t newZone); | |
}; | |
inline const std::set<entityId_t>& RenderSystem::children(entityId_t entityId) const { | |
static const std::set<entityId_t> emptySet; | |
auto it = m_entityChildren.find(entityId); | |
return it != m_entityChildren.end() ? it->second : emptySet; | |
} | |
#endif | |
#ifndef __PROCALC_RAYCAST_MAP_PARSER_HPP__ | |
#define __PROCALC_RAYCAST_MAP_PARSER_HPP__ | |
#include <list> | |
#include <vector> | |
#include <map> | |
#include <memory> | |
#include <tinyxml2.h> | |
#include "raycast/geometry.hpp" | |
#include "raycast/component.hpp" | |
namespace parser { | |
struct Path { | |
std::vector<Point> points; | |
bool closed = false; | |
}; | |
struct Object; | |
typedef std::unique_ptr<Object> pObject_t; | |
struct Object { | |
Object(const Object* parent) | |
: parent(parent) {} | |
// For printing debug information on parse error | |
const Object* parent = nullptr; | |
Matrix groupTransform; | |
Matrix pathTransform; | |
Path path; | |
std::string type; | |
std::map<std::string, std::string> dict; | |
std::list<pObject_t> children; | |
Object* clone(const Object* parent = nullptr) const { | |
Object* cpy = new Object(nullptr); | |
cpy->parent = parent; | |
cpy->groupTransform = groupTransform; | |
cpy->pathTransform = pathTransform; | |
cpy->path = path; | |
cpy->type = type; | |
cpy->dict = dict; | |
for (auto& c: children) { | |
cpy->children.push_back(pObject_t(c->clone(cpy))); | |
} | |
return cpy; | |
} | |
}; | |
void parse(const std::string& file, std::list<pObject_t>& objects); | |
Object* firstObjectOfType(const std::list<pObject_t>& objects, const std::string& type); | |
struct PathStreamToken { | |
enum { SET_RELATIVE, SET_ABSOLUTE, CLOSE_PATH, POINT } kind; | |
Point p; | |
}; | |
struct ParseError { | |
const Object* object; | |
std::string message; | |
}; | |
typedef std::list<ParseError> ParseErrors; | |
Matrix transformFromTriangle(const Path& path); | |
PathStreamToken getNextToken(std::istream& is); | |
void constructPath(const tinyxml2::XMLElement& e, Path& path); | |
std::pair<std::string, std::string> parseKvpString(const std::string& s); | |
void extractKvPairs(const tinyxml2::XMLElement& e, std::map<std::string, std::string>& kv); | |
Matrix parseTranslateTransform(const std::string& s); | |
Matrix parseMatrixTransform(const std::string& s); | |
Matrix parseTransform(const std::string& s); | |
void extractGeometry(const tinyxml2::XMLElement& node, Path& path, Matrix& transform); | |
Object* constructObject_r(const Object* parent, const tinyxml2::XMLElement& node, | |
ParseErrors& errors); | |
} | |
const std::string& getValue(const std::map<std::string, std::string>& m, const std::string& key); | |
const std::string& getValue(const std::map<std::string, std::string>& m, const std::string& key, | |
const std::string& default_); | |
entityId_t makeIdForObj(const parser::Object& obj); | |
#endif | |
#ifndef __PROCALC_RAYCAST_FOCUS_SYSTEM_HPP_ | |
#define __PROCALC_RAYCAST_FOCUS_SYSTEM_HPP_ | |
#include <functional> | |
#include <memory> | |
#include <map> | |
#include "raycast/system.hpp" | |
#include "raycast/component.hpp" | |
struct CFocus : public Component { | |
CFocus(entityId_t entityId) | |
: Component(entityId, ComponentKind::C_FOCUS) {} | |
std::string hoverText; | |
std::string captionText; | |
std::function<void()> onHover; | |
}; | |
typedef std::unique_ptr<CFocus> pCFocus_t; | |
class EntityManager; | |
class TimeService; | |
class AppConfig; | |
class FocusSystem : public System { | |
public: | |
FocusSystem(const AppConfig& appConfig, EntityManager& entityManager, TimeService& timeService); | |
void update() override; | |
void handleEvent(const GameEvent&) override {} | |
void handleEvent(const GameEvent&, const std::set<entityId_t>&) override {} | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CFocus& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
void showCaption(entityId_t entity); | |
private: | |
void initialise(); | |
void deleteCaption(); | |
const AppConfig& m_appConfig; | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
bool m_initialised = false; | |
entityId_t m_toolTipId = -1; | |
double m_focusDistance = 0; | |
entityId_t m_captionBgId = -1; | |
entityId_t m_captionTextId = -1; | |
long m_captionTimeoutId = -1; | |
std::map<entityId_t, pCFocus_t> m_components; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_RENDER_COMPONENTS_HPP__ | |
#define __PROCALC_RAYCAST_RENDER_COMPONENTS_HPP__ | |
#include <string> | |
#include <list> | |
#include <memory> | |
#include <QImage> | |
#include <QColor> | |
#include "raycast/geometry.hpp" | |
#include "raycast/component.hpp" | |
#include "raycast/spatial_components.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
struct Texture { | |
QImage image; | |
Size size_wd; | |
}; | |
enum class CRenderKind { | |
REGION, | |
WALL, | |
JOIN, | |
SPRITE, | |
FLOOR_DECAL, | |
WALL_DECAL, | |
OVERLAY | |
}; | |
class CRender : public Component { | |
public: | |
CRender(CRenderKind kind, entityId_t entityId, entityId_t parentId) | |
: Component(entityId, ComponentKind::C_RENDER), | |
kind(kind), | |
parentId(parentId) {} | |
CRenderKind kind; | |
entityId_t parentId; | |
}; | |
typedef std::unique_ptr<CRender> pCRender_t; | |
enum class COverlayKind { | |
IMAGE, | |
TEXT, | |
COLOUR | |
}; | |
class COverlay : public CRender { | |
public: | |
COverlay(COverlayKind kind, entityId_t entityId, const Point& pos, int zIndex) | |
: CRender(CRenderKind::OVERLAY, entityId, -1), | |
kind(kind), | |
pos(pos), | |
zIndex(zIndex) {} | |
COverlayKind kind; | |
Point pos; | |
int zIndex; | |
}; | |
typedef std::unique_ptr<COverlay> pCOverlay_t; | |
inline bool operator<(const pCOverlay_t& lhs, const pCOverlay_t& rhs) { | |
return lhs->zIndex < rhs->zIndex; | |
} | |
class CColourOverlay : public COverlay { | |
public: | |
CColourOverlay(entityId_t entityId, const QColor& colour, const Point& pos, const Size& size, | |
int zIndex = 0) | |
: COverlay(COverlayKind::COLOUR, entityId, pos, zIndex), | |
colour(colour), | |
size(size) {} | |
QColor colour; | |
Size size; | |
}; | |
typedef std::unique_ptr<COverlay> pCOverlay_t; | |
class CImageOverlay : public COverlay { | |
public: | |
CImageOverlay(entityId_t entityId, const std::string& texture, const Point& pos, | |
const Size& size, int zIndex = 0) | |
: COverlay(COverlayKind::IMAGE, entityId, pos, zIndex), | |
texture(texture), | |
size(size) {} | |
std::string texture; | |
QRectF texRect = QRectF(0, 0, 1, 1); | |
Size size; | |
}; | |
typedef std::unique_ptr<CImageOverlay> pCImageOverlay_t; | |
class CTextOverlay : public COverlay { | |
public: | |
CTextOverlay(entityId_t entityId, std::string text, const Point& pos, double height, | |
const QColor& colour, int zIndex) | |
: COverlay(COverlayKind::TEXT, entityId, pos, zIndex), | |
text(text), | |
height(height), | |
colour(colour) {} | |
std::string text; | |
double height; | |
QColor colour; | |
}; | |
typedef std::unique_ptr<CTextOverlay> pCTextOverlay_t; | |
class CRegion; | |
class CSprite : public CRender { | |
public: | |
CSprite(entityId_t entityId, entityId_t parentId, const std::string& texture) | |
: CRender(CRenderKind::SPRITE, entityId, parentId), | |
texture(texture) {} | |
const QRectF& getView(const CVRect& vRect, const Point& camPos) const { | |
if (texViews.empty()) { | |
EXCEPTION("Cannot get view; texViews is empty"); | |
} | |
Vec2f v = vRect.pos - camPos; | |
double a = PI - atan2(v.y, v.x) + vRect.angle; | |
size_t n = texViews.size(); | |
double da = 2.0 * PI / n; | |
int idx = static_cast<int>(round(normaliseAngle(a) / da)) % n; | |
return texViews[idx]; | |
} | |
std::string texture; | |
std::vector<QRectF> texViews; | |
}; | |
typedef std::unique_ptr<CSprite> pCSprite_t; | |
class CWallDecal : public CRender { | |
public: | |
CWallDecal(entityId_t entityId, entityId_t parentId) | |
: CRender(CRenderKind::WALL_DECAL, entityId, parentId) {} | |
std::string texture; | |
QRectF texRect = QRectF(0, 0, 1, 1); | |
int zIndex = 0; | |
}; | |
typedef std::unique_ptr<CWallDecal> pCWallDecal_t; | |
class CBoundary : public CRender { | |
public: | |
CBoundary(CRenderKind kind, entityId_t entityId, entityId_t parentId) | |
: CRender(kind, entityId, parentId) {} | |
std::list<pCWallDecal_t> decals; | |
}; | |
typedef std::unique_ptr<CBoundary> pCBoundary_t; | |
class CFloorDecal : public CRender { | |
public: | |
CFloorDecal(entityId_t entityId, entityId_t parentId) | |
: CRender(CRenderKind::FLOOR_DECAL, entityId, parentId) {} | |
std::string texture; | |
}; | |
typedef std::unique_ptr<CFloorDecal> pCFloorDecal_t; | |
class CRegion; | |
typedef std::unique_ptr<CRegion> pCRegion_t; | |
class CRegion : public CRender { | |
public: | |
CRegion(entityId_t entityId, entityId_t parentId) | |
: CRender(CRenderKind::REGION, entityId, parentId) {} | |
bool hasCeiling = true; // TODO: Remove | |
std::string floorTexture; | |
QRectF floorTexRect = QRectF(0, 0, 1, 1); | |
std::string ceilingTexture; | |
QRectF ceilingTexRect = QRectF(0, 0, 1, 1); | |
std::list<pCRegion_t> children; | |
std::list<CBoundary*> boundaries; | |
std::list<pCSprite_t> sprites; | |
std::list<pCFloorDecal_t> floorDecals; | |
}; | |
class CWall : public CBoundary { | |
public: | |
CWall(entityId_t entityId, entityId_t parentId) | |
: CBoundary(CRenderKind::WALL, entityId, parentId) {} | |
std::string texture; | |
QRectF texRect = QRectF(0, 0, 1, 1); | |
CRegion* region; | |
}; | |
class CJoin : public CBoundary { | |
public: | |
CJoin(entityId_t entityId, entityId_t parentId, entityId_t joinId) | |
: CBoundary(CRenderKind::JOIN, entityId, parentId), | |
joinId(joinId) {} | |
void mergeIn(const CJoin& other) { | |
if (topTexture == "default") { | |
topTexture = other.topTexture; | |
} | |
if (bottomTexture == "default") { | |
bottomTexture = other.bottomTexture; | |
} | |
} | |
entityId_t joinId = 0; | |
std::string topTexture = "default"; | |
std::string bottomTexture = "default"; | |
QRectF topTexRect = QRectF(0, 0, 1, 1); | |
QRectF bottomTexRect = QRectF(0, 0, 1, 1); | |
CRegion* regionA = nullptr; | |
CRegion* regionB = nullptr; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_SPATIAL_SYSTEM_HPP__ | |
#define __PROCALC_RAYCAST_SPATIAL_SYSTEM_HPP__ | |
#include <string> | |
#include <vector> | |
#include <map> | |
#include <ostream> | |
#include <set> | |
#include <memory> | |
#include <QImage> | |
#include "raycast/scene_graph.hpp" | |
#include "raycast/system.hpp" | |
namespace tinyxml2 { class XMLElement; } | |
namespace parser { struct Object; } | |
struct Intersection { | |
Intersection(CSpatialKind kind, CSpatialKind parentKind) | |
: kind(kind), | |
parentKind(parentKind) {} | |
CSpatialKind kind; | |
CSpatialKind parentKind; | |
entityId_t entityId; | |
Point point_rel; | |
Point point_wld; | |
Point viewPoint; | |
double distanceFromOrigin; | |
double distanceAlongTarget; | |
entityId_t zoneB; | |
entityId_t zoneA; | |
double height = 0; | |
std::pair<Range, Range> heightRanges; | |
}; | |
typedef std::unique_ptr<Intersection> pIntersection_t; | |
struct EChangedZone : public GameEvent { | |
EChangedZone(entityId_t entityId, entityId_t oldZone, entityId_t newZone, | |
const std::set<entityId_t>& zonesLeft, const std::set<entityId_t>& zonesEntered) | |
: GameEvent("entity_changed_zone"), | |
entityId(entityId), | |
oldZone(oldZone), | |
newZone(newZone), | |
zonesLeft(zonesLeft), | |
zonesEntered(zonesEntered) {} | |
entityId_t entityId; | |
entityId_t oldZone; | |
entityId_t newZone; | |
std::set<entityId_t> zonesLeft; | |
std::set<entityId_t> zonesEntered; | |
}; | |
struct EPlayerMove : public GameEvent { | |
EPlayerMove(const Player& player) | |
: GameEvent("player_move"), | |
player(player) {} | |
const Player& player; | |
}; | |
struct EPlayerActivateEntity : public GameEvent { | |
EPlayerActivateEntity(const Player& player) | |
: GameEvent("player_activate_entity"), | |
player(player) {} | |
const Player& player; | |
std::set<entityId_t> inRadius; | |
std::set<entityId_t> lookingAt; | |
}; | |
class EntityManager; | |
class TimeService; | |
class SpatialSystem : public System { | |
public: | |
SpatialSystem(EntityManager& entityManager, TimeService& timeService, double frameRate); | |
SceneGraph sg; | |
void connectZones(); | |
void update() override; | |
void handleEvent(const GameEvent& event) override; | |
void handleEvent(const GameEvent&, const std::set<entityId_t>&) override {} | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CSpatial& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
void moveEntity(entityId_t id, Vec2f dv, double heightAboveFloor = 0); | |
void relocateEntity(entityId_t id, CZone& zone, const Point& point); | |
std::set<entityId_t> entitiesInRadius(const CZone& zone, const Point& pos, double radius, | |
double heightAboveFloor = 0.0) const; | |
std::vector<pIntersection_t> entitiesAlongRay(const CZone& zone, const Point& pos, | |
const Vec2f& dir, const Matrix& matrix, double distance = 10000) const; | |
std::vector<pIntersection_t> entitiesAlongRay(const Vec2f& dir, double distance = 10000) const; | |
std::vector<pIntersection_t> entitiesAlong3dRay(const CZone& zone, const Point& pos, | |
double height, const Vec2f& dir, double vAngle, const Matrix& matrix, | |
double distance = 10000) const; | |
std::vector<pIntersection_t> entitiesAlong3dRay(const Vec2f& dir, double camSpaceVAngle, | |
double distance = 10000) const; | |
std::set<entityId_t> getAncestors(entityId_t entityId) const; | |
std::vector<Point> shortestPath(entityId_t entityA, entityId_t entityB, double radius) const; | |
std::vector<Point> shortestPath(const Point& A, const Point& B, double radius) const; | |
void vRotateCamera(double da); | |
void hRotateCamera(double da); | |
void movePlayer(const Vec2f& v); | |
// TODO: Move this | |
void jump(); | |
CZone& zone(entityId_t entity); | |
const CZone& constZone(entityId_t entity) const; | |
private: | |
bool isRoot(const CSpatial& c) const; | |
void removeEntity_r(entityId_t id); | |
void crossZones(entityId_t entityId, entityId_t oldZone, entityId_t newZone); | |
void connectSubzones(CZone& zone); | |
bool areTwins(const CSoftEdge& se1, const CSoftEdge& se2) const; | |
bool isAncestor(entityId_t a, entityId_t b) const; | |
void findIntersections_r(const Point& point, const Vec2f& dir, const Matrix& matrix, | |
const CZone& zone, const CSpatial& parent, std::vector<pIntersection_t>& intersections, | |
std::vector<entityId_t>& visited, double cullNearerThan, double& cullFartherThan) const; | |
void addChildToComponent(CSpatial& parent, pCSpatial_t child); | |
bool removeChildFromComponent(CSpatial& parent, const CSpatial& child, bool keepAlive = false); | |
inline CZone& getCurrentZone() const { | |
return dynamic_cast<CZone&>(getComponent(sg.player->region())); | |
} | |
void buoyancy(); | |
void gravity(); | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
double m_frameRate; | |
std::map<entityId_t, CSpatial*> m_components; | |
std::map<entityId_t, std::set<CSpatial*>> m_entityChildren; | |
Vec2i m_playerCell; | |
}; | |
std::ostream& operator<<(std::ostream& os, CSpatialKind kind); | |
#endif | |
#ifndef __PROCALC_RAYCAST_AGENT_SYSTEM_HPP_ | |
#define __PROCALC_RAYCAST_AGENT_SYSTEM_HPP_ | |
#include <memory> | |
#include <map> | |
#include <functional> | |
#include <vector> | |
#include "raycast/system.hpp" | |
#include "raycast/component.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/timing.hpp" | |
class TimeService; | |
class AudioService; | |
class DamageSystem; | |
class SpatialSystem; | |
class AgentSystem; | |
class CVRect; | |
class EntityManager; | |
class CAgent : public Component { | |
friend class AgentSystem; | |
public: | |
CAgent(entityId_t entityId); | |
bool isHostile = true; | |
entityId_t patrolPath = -1; | |
std::string stPatrollingTrigger; | |
std::string stChasingTrigger; | |
virtual ~CAgent() override {} | |
private: | |
enum state_t { | |
ST_STATIONARY, | |
ST_ON_FIXED_PATH, | |
ST_CHASING_OBJECT, | |
ST_SHOOTING | |
}; | |
state_t m_state = ST_STATIONARY; | |
std::unique_ptr<TimePattern> m_gunfireTiming; | |
entityId_t m_targetObject = -1; | |
std::vector<Point> m_path; | |
bool m_pathClosed = true; | |
int m_waypointIdx = -1; | |
std::function<void(CAgent&)> m_onFinish; | |
void navigateTo(EntityManager& entityManager, const Point& p, | |
std::function<void(CAgent&)> onFinish); | |
void startPatrol(EntityManager& entityManager); | |
void startChase(EntityManager& entityManager); | |
bool hasLineOfSight(const SpatialSystem& spatialSystem, Matrix& m, Vec2f& ray, double& hAngle, | |
double& vAngle, double& height) const; | |
void followPath(EntityManager& entityManager, TimeService& timeService); | |
void attemptShot(EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService); | |
virtual void update(EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService); | |
void handleEvent(const GameEvent& event, EntityManager& entityManager); | |
void onDamage(EntityManager& entityManager); | |
void setAnimation(EntityManager& entityManager); | |
void setState(EntityManager& entityManager, state_t state); | |
}; | |
typedef std::unique_ptr<CAgent> pCAgent_t; | |
class EntityManager; | |
class AgentSystem : public System { | |
public: | |
AgentSystem(EntityManager& entityManager, TimeService& timeService, AudioService& audioService) | |
: m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_audioService(audioService) {} | |
void update() override; | |
void handleEvent(const GameEvent& event) override; | |
void handleEvent(const GameEvent&, const std::set<entityId_t>&) override {} | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CAgent& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
void navigateTo(entityId_t entityId, const Point& point); | |
private: | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
AudioService& m_audioService; | |
std::map<entityId_t, pCAgent_t> m_components; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_ENTITY_MANAGER_HPP_ | |
#define __PROCALC_RAYCAST_ENTITY_MANAGER_HPP_ | |
#include <map> | |
#include <set> | |
#include "raycast/component.hpp" | |
#include "raycast/system.hpp" | |
class EEntityDeleted : public GameEvent { | |
public: | |
EEntityDeleted(entityId_t entityId) | |
: GameEvent("entity_deleted"), | |
entityId(entityId) {} | |
entityId_t entityId; | |
}; | |
class EntityManager { | |
public: | |
entityId_t getNextId() const; | |
void addSystem(ComponentKind kind, pSystem_t system); | |
bool hasComponent(entityId_t entityId, ComponentKind kind) const; | |
void addComponent(pComponent_t component); | |
void deleteEntity(entityId_t entityId); | |
void purgeEntities(); | |
void broadcastEvent(const GameEvent& event) const; | |
void fireEvent(const GameEvent& event, const std::set<entityId_t>& entities) const; | |
void update(); | |
template<class T> | |
T& getComponent(entityId_t entityId, ComponentKind kind) const { | |
System& sys = *m_systems.at(kind); | |
Component& c = sys.getComponent(entityId); | |
return dynamic_cast<T&>(c); | |
} | |
template<class T> | |
T& system(ComponentKind kind) const { | |
return dynamic_cast<T&>(*m_systems.at(kind)); | |
} | |
private: | |
std::map<ComponentKind, pSystem_t> m_systems; | |
std::set<entityId_t> m_pendingDelete; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_ROOT_FACTORY_HPP__ | |
#define __PROCALC_RAYCAST_ROOT_FACTORY_HPP__ | |
#include <map> | |
#include <string> | |
#include <vector> | |
#include "raycast/game_object_factory.hpp" | |
class RootFactory : public GameObjectFactory { | |
public: | |
RootFactory(); | |
const std::set<std::string>& types() const override; | |
void addFactory(pGameObjectFactory_t factory); | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) override; | |
private: | |
std::vector<pGameObjectFactory_t> m_factories; | |
std::map<std::string, GameObjectFactory*> m_factoriesByType; | |
std::set<std::string> m_types; | |
#ifdef DEBUG | |
int m_dbgIndentLvl = 0; | |
#endif | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_GAME_OBJECT_FACTORY_HPP__ | |
#define __PROCALC_RAYCAST_GAME_OBJECT_FACTORY_HPP__ | |
#include <string> | |
#include <memory> | |
#include <set> | |
#include "raycast/component.hpp" | |
class Matrix; | |
namespace parser { struct Object; } | |
class GameObjectFactory { | |
public: | |
virtual bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) = 0; | |
virtual const std::set<std::string>& types() const = 0; | |
virtual ~GameObjectFactory() {} | |
}; | |
typedef std::unique_ptr<GameObjectFactory> pGameObjectFactory_t; | |
#endif | |
#ifndef __PROCALC_RAYCAST_SYSTEM_HPP__ | |
#define __PROCALC_RAYCAST_SYSTEM_HPP__ | |
#include <memory> | |
#include <set> | |
#include "raycast/component.hpp" | |
#include "raycast/game_event.hpp" | |
class System { | |
public: | |
virtual void update() = 0; | |
virtual void handleEvent(const GameEvent& event) = 0; | |
virtual void handleEvent(const GameEvent& event, const std::set<entityId_t>& entities) = 0; | |
virtual void addComponent(pComponent_t component) = 0; | |
virtual bool hasComponent(entityId_t entityId) const = 0; | |
virtual Component& getComponent(entityId_t entityId) const = 0; | |
virtual void removeEntity(entityId_t id) = 0; | |
virtual ~System() {} | |
}; | |
typedef std::unique_ptr<System> pSystem_t; | |
#endif | |
#ifndef __PROCALC_RAYCAST_GEOMETRY_FACTORY_HPP__ | |
#define __PROCALC_RAYCAST_GEOMETRY_FACTORY_HPP__ | |
#include <map> | |
#include <vector> | |
#include "raycast/game_object_factory.hpp" | |
#include "raycast/geometry.hpp" | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class Matrix; | |
class RootFactory; | |
namespace parser { struct Object; } | |
class GeometryFactory : public GameObjectFactory { | |
public: | |
GeometryFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
AudioService& audioService, TimeService& timeService); | |
const std::set<std::string>& types() const override; | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) override; | |
private: | |
RootFactory& m_rootFactory; | |
EntityManager& m_entityManager; | |
AudioService& m_audioService; | |
TimeService& m_timeService; | |
std::map<Point, bool> m_endpoints; | |
bool constructVRect(entityId_t, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructWallDecal(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructWalls(parser::Object& obj, entityId_t parentId, const Matrix& parentTransform); | |
bool constructFloorDecal(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructBoundaries(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructPortal(parser::Object& obj, entityId_t parentId, const Matrix& parentTransform); | |
bool constructRegion_r(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructRootRegion(parser::Object& obj); | |
bool constructRegion(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructPath(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_RAYCAST_WIDGET_HPP__ | |
#define __PROCALC_RAYCAST_RAYCAST_WIDGET_HPP__ | |
#include <memory> | |
#include <map> | |
#include <QWidget> | |
#include <QTimer> | |
#include <QImage> | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "qt_obj_ptr.hpp" | |
#ifdef DEBUG | |
# include <chrono> | |
#endif | |
class QPaintEvent; | |
class AppConfig; | |
class EventSystem; | |
struct RenderGraph; | |
namespace parser { struct Object; } | |
class RaycastWidget : public QWidget { | |
Q_OBJECT | |
public: | |
RaycastWidget(const AppConfig& appConfig, EventSystem& eventSystem, int width = 320, | |
int height = 240, int frameRate = 60); | |
void initialise(const std::string& mapFile); | |
void start(); | |
EntityManager& entityManager() { | |
return m_entityManager; | |
} | |
RootFactory& rootFactory() { | |
return m_rootFactory; | |
} | |
TimeService& timeService() { | |
return m_timeService; | |
} | |
AudioService& audioService() { | |
return m_audioService; | |
} | |
~RaycastWidget() override; | |
protected: | |
void paintEvent(QPaintEvent* event) override; | |
void keyPressEvent(QKeyEvent* event) override; | |
void keyReleaseEvent(QKeyEvent* event) override; | |
void mouseMoveEvent(QMouseEvent* event) override; | |
void mousePressEvent(QMouseEvent* event) override; | |
void mouseReleaseEvent(QMouseEvent* event) override; | |
private slots: | |
void tick(); | |
private: | |
void loadMap(const std::string& mapFilePath); | |
void configure(RenderGraph& rg, const parser::Object& config); | |
void configureAudioService(const parser::Object& obj); | |
void loadSoundAssets(const parser::Object& obj); | |
void loadMusicAssets(const parser::Object& obj); | |
void loadTextures(RenderGraph& rg, const parser::Object& obj); | |
void uncaptureCursor(); | |
const AppConfig& m_appConfig; | |
EventSystem& m_eventSystem; | |
EntityManager m_entityManager; | |
TimeService m_timeService; | |
AudioService m_audioService; | |
RootFactory m_rootFactory; | |
int m_width; | |
int m_height; | |
int m_frameRate; | |
QtObjPtr<QTimer> m_timer; | |
QImage m_buffer; | |
std::map<int, bool> m_keyStates; | |
bool m_mouseBtnState; | |
Point m_cursor; | |
bool m_cursorCaptured; | |
Qt::CursorShape m_defaultCursor; | |
entityId_t m_entityId; | |
bool m_playerImmobilised = false; | |
#ifdef DEBUG | |
std::chrono::high_resolution_clock::time_point m_t = std::chrono::high_resolution_clock::now(); | |
double m_measuredFrameRate = 0; | |
long m_frame = 0; | |
#endif | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_C_DOOR_BEHAVIOUR_HPP_ | |
#define __PROCALC_RAYCAST_C_DOOR_BEHAVIOUR_HPP_ | |
#include "raycast/behaviour_system.hpp" | |
#include "raycast/timing.hpp" | |
struct EDoorOpenStart : public GameEvent { | |
explicit EDoorOpenStart(entityId_t entityId) | |
: GameEvent("door_open_start"), | |
entityId(entityId) {} | |
entityId_t entityId; | |
}; | |
struct EDoorOpenFinish : public GameEvent { | |
explicit EDoorOpenFinish(entityId_t entityId) | |
: GameEvent("door_open_finish"), | |
entityId(entityId) {} | |
entityId_t entityId; | |
}; | |
struct EDoorCloseStart : public GameEvent { | |
explicit EDoorCloseStart(entityId_t entityId) | |
: GameEvent("door_close_start"), | |
entityId(entityId) {} | |
entityId_t entityId; | |
}; | |
struct EDoorCloseFinish : public GameEvent { | |
explicit EDoorCloseFinish(entityId_t entityId) | |
: GameEvent("door_close_finish"), | |
entityId(entityId) {} | |
entityId_t entityId; | |
}; | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class CDoorBehaviour : public CBehaviour { | |
public: | |
CDoorBehaviour(entityId_t entityId, EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService); | |
virtual void update() override; | |
virtual void handleBroadcastedEvent(const GameEvent& e) override; | |
virtual void handleTargetedEvent(const GameEvent& e) override; | |
double speed = 100.0; | |
bool isPlayerActivated = true; | |
bool closeAutomatically = true; | |
std::string openOnEvent; | |
void setPauseTime(double t); | |
private: | |
enum state_t { | |
ST_OPENING, | |
ST_CLOSING, | |
ST_OPEN, | |
ST_CLOSED | |
}; | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
AudioService& m_audioService; | |
state_t m_state = ST_CLOSED; | |
double m_y0; | |
double m_y1; | |
Debouncer m_timer; | |
int m_soundId = -1; | |
void playSound(); | |
void stopSound() const; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_COMPONENT_KINDS_HPP__ | |
#define __PROCALC_RAYCAST_COMPONENT_KINDS_HPP__ | |
enum class ComponentKind { | |
C_BEHAVIOUR, | |
C_SPATIAL, | |
C_RENDER, | |
C_ANIMATION, | |
C_INVENTORY, | |
C_EVENT_HANDLER, | |
C_DAMAGE, | |
C_SPAWN, | |
C_AGENT, | |
C_FOCUS | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_C_ELEVATOR_BEHAVIOUR_HPP_ | |
#define __PROCALC_RAYCAST_C_ELEVATOR_BEHAVIOUR_HPP_ | |
#include <vector> | |
#include "raycast/behaviour_system.hpp" | |
struct EElevatorStopped : public GameEvent { | |
explicit EElevatorStopped(entityId_t entityId) | |
: GameEvent("elevator_stopped"), | |
entityId(entityId) {} | |
entityId_t entityId; | |
virtual ~EElevatorStopped() override {} | |
}; | |
class EntityManager; | |
class AudioService; | |
class CElevatorBehaviour : public CBehaviour { | |
public: | |
CElevatorBehaviour(entityId_t entityId, EntityManager& entityManager, | |
AudioService& audioService, double frameRate, const std::vector<double>& levels, | |
int initLevelIdx); | |
bool isPlayerActivated = false; | |
virtual void update() override; | |
virtual void handleBroadcastedEvent(const GameEvent&) override {} | |
virtual void handleTargetedEvent(const GameEvent&) override; | |
void move(int level); | |
void setSpeed(double speed); | |
private: | |
void playSound(); | |
void stopSound() const; | |
enum state_t { | |
ST_STOPPED, | |
ST_MOVING | |
}; | |
EntityManager& m_entityManager; | |
AudioService& m_audioService; | |
double m_frameRate; | |
state_t m_state = ST_STOPPED; | |
int m_target = 0; | |
double m_speed = 60.0; | |
std::vector<double> m_levels; | |
int m_soundId = -1; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_SCENE_GRAPH_HPP__ | |
#define __PROCALC_RAYCAST_SCENE_GRAPH_HPP__ | |
#include "raycast/spatial_components.hpp" | |
#include "raycast/player.hpp" | |
struct SceneDefaults { | |
double floorHeight = 0; | |
double ceilingHeight = 100; | |
}; | |
struct SceneGraph { | |
SceneDefaults defaults; | |
pCZone_t rootZone; | |
std::list<pCEdge_t> edges; // Why? | |
std::unique_ptr<Player> player; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_GEOMETRY_HPP__ | |
#define __PROCALC_RAYCAST_GEOMETRY_HPP__ | |
#include <cmath> | |
#include <array> | |
#include <string> | |
#include <memory> | |
#include <vector> | |
#include <limits> | |
#include "utils.hpp" | |
#undef minor | |
template<class T> | |
struct Vec2 { | |
Vec2() | |
: x(0), y(0) {} | |
Vec2(T x, T y) | |
: x(x), y(y) {} | |
T x; | |
T y; | |
bool operator==(const Vec2<T>& rhs) const { | |
return x == rhs.x && y == rhs.y; | |
} | |
bool operator!=(const Vec2<T>& rhs) const { | |
return !(*this == rhs); | |
} | |
Vec2 operator-() const { | |
return Vec2<T>(-x, -y); | |
} | |
T dot(const Vec2<T>& rhs) const { | |
return x * rhs.x + y * rhs.y; | |
} | |
}; | |
typedef Vec2<int> Vec2i; | |
typedef Vec2<double> Vec2f; | |
typedef Vec2f Point; | |
typedef Vec2f Size; | |
template<class T> | |
struct Vec3 { | |
Vec3() | |
: x(0), y(0), z(0) {} | |
Vec3(T x, T y, T z) | |
: x(x), y(y), z(z) {} | |
T x; | |
T y; | |
T z; | |
bool operator==(const Vec3<T>& rhs) const { | |
return x == rhs.x && y == rhs.y && z == rhs.z; | |
} | |
bool operator!=(const Vec3<T>& rhs) const { | |
return !(*this == rhs); | |
} | |
Vec3 operator-() const { | |
return Vec3<T>(-x, -y, -z); | |
} | |
T dot(const Vec3<T>& rhs) const { | |
return x * rhs.x + y * rhs.y + z * rhs.z; | |
} | |
}; | |
typedef Vec3<double> Vec3f; | |
class Matrix { | |
public: | |
Matrix(); | |
Matrix(double a, Vec2f t); | |
std::array<std::array<double, 3>, 3> data; | |
double tx() const { | |
return data[0][2]; | |
} | |
double ty() const { | |
return data[1][2]; | |
} | |
double a() const { | |
return atan2(data[1][0], data[1][1]); | |
} | |
std::array<double, 3>& operator[](int idx) { | |
return data[idx]; | |
} | |
const std::array<double, 3>& operator[](int idx) const { | |
return data[idx]; | |
} | |
Matrix& operator=(const Matrix& rhs) { | |
data = rhs.data; | |
return *this; | |
} | |
Matrix inverse() const; | |
double determinant() const; | |
double minor(int row, int col) const; | |
}; | |
inline Matrix operator*(const Matrix& A, const Matrix& B) { | |
Matrix m; | |
m.data = {{ | |
{{A[0][0] * B[0][0] + A[0][1] * B[1][0] + A[0][2] * B[2][0], | |
A[0][0] * B[0][1] + A[0][1] * B[1][1] + A[0][2] * B[2][1], | |
A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2] * B[2][2]}}, | |
{{A[1][0] * B[0][0] + A[1][1] * B[1][0] + A[1][2] * B[2][0], | |
A[1][0] * B[0][1] + A[1][1] * B[1][1] + A[1][2] * B[2][1], | |
A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2] * B[2][2]}}, | |
{{A[2][0] * B[0][0] + A[2][1] * B[1][0] + A[2][2] * B[2][0], | |
A[2][0] * B[0][1] + A[2][1] * B[1][1] + A[2][2] * B[2][1], | |
A[2][0] * B[0][2] + A[2][1] * B[1][2] + A[2][2] * B[2][2]}} | |
}}; | |
return m; | |
} | |
inline Matrix::Matrix() | |
: data{{ | |
{{1.0, 0.0, 0.0}}, | |
{{0.0, 1.0, 0.0}}, | |
{{0.0, 0.0, 1.0}} | |
}} {} | |
inline Matrix::Matrix(double a, Vec2f t) | |
: data{{ | |
{{cos(a), -sin(a), t.x}}, | |
{{sin(a), cos(a), t.y}}, | |
{{0.0, 0.0, 1.0}} | |
}} {} | |
inline double Matrix::minor(int row, int col) const { | |
int r1, r2, c1, c2; | |
switch (row) { | |
case 0: | |
r1 = 1; | |
r2 = 2; | |
break; | |
case 1: | |
r1 = 0; | |
r2 = 2; | |
break; | |
case 2: | |
r1 = 0; | |
r2 = 1; | |
break; | |
} | |
switch (col) { | |
case 0: | |
c1 = 1; | |
c2 = 2; | |
break; | |
case 1: | |
c1 = 0; | |
c2 = 2; | |
break; | |
case 2: | |
c1 = 0; | |
c2 = 1; | |
break; | |
} | |
return data[r1][c1] * data[r2][c2] - data[r2][c1] * data[r1][c2]; | |
} | |
inline double Matrix::determinant() const { | |
return data[0][0] * minor(0, 0) - data[0][1] * minor(0, 1) + data[0][2] * minor(0, 2); | |
} | |
inline Matrix Matrix::inverse() const { | |
Matrix m; | |
double det_rp = 1.0 / determinant(); | |
for (int row = 0; row < 3; ++row) { | |
for (int col = 0; col < 3; ++col) { | |
int sign = (row + col) % 2 == 0 ? 1.0 : -1.0; | |
m[col][row] = sign * det_rp * minor(row, col); | |
} | |
} | |
return m; | |
} | |
// Old, cheap inverse function that works for rotation + translation matrices, but not scale | |
/* | |
inline Matrix Matrix::inverse() const { | |
Matrix m; | |
m.data = {{ | |
{{data[0][0], data[1][0], -data[0][0] * data[0][2] - data[1][0] * data[1][2]}}, | |
{{data[0][1], data[1][1], -data[0][1] * data[0][2] - data[1][1] * data[1][2]}}, | |
{{0.0, 0.0, 1.0}} | |
}}; | |
return m; | |
} | |
*/ | |
struct Range { | |
Range() | |
: a(0), b(0) {} | |
Range(double a, double b) | |
: a(a), b(b) {} | |
double a; | |
double b; | |
bool operator==(const Range& rhs) const { | |
return a == rhs.a && b == rhs.b; | |
} | |
bool operator!=(const Range& rhs) const { | |
return !(*this == rhs); | |
} | |
}; | |
inline Point operator+(const Point& lhs, const Point& rhs) { | |
return Point(lhs.x + rhs.x, lhs.y + rhs.y); | |
} | |
inline Point operator-(const Point& lhs, const Point& rhs) { | |
return Point(lhs.x - rhs.x, lhs.y - rhs.y); | |
} | |
inline Point operator/(const Point& lhs, double rhs) { | |
return Point(lhs.x / rhs, lhs.y / rhs); | |
} | |
inline Point operator*(const Point& lhs, double rhs) { | |
return Point(lhs.x * rhs, lhs.y * rhs); | |
} | |
inline Point operator*(const Matrix& lhs, const Point& rhs) { | |
Point p; | |
p.x = lhs[0][0] * rhs.x + lhs[0][1] * rhs.y + lhs[0][2]; | |
p.y = lhs[1][0] * rhs.x + lhs[1][1] * rhs.y + lhs[1][2]; | |
return p; | |
} | |
inline Vec3f operator*(const Matrix& lhs, const Vec3f& rhs) { | |
Vec3f p; | |
p.x = lhs[0][0] * rhs.x + lhs[0][1] * rhs.y + lhs[0][2]; | |
p.y = lhs[1][0] * rhs.x + lhs[1][1] * rhs.y + lhs[1][2]; | |
p.y = lhs[2][0] * rhs.x + lhs[2][1] * rhs.y + lhs[2][2]; | |
return p; | |
} | |
inline Vec3f operator*(const Vec3f& lhs, const Matrix& rhs) { | |
Vec3f p; | |
p.x = lhs.x * rhs[0][0] + lhs.y * rhs[1][0] + lhs.z * rhs[2][0]; | |
p.y = lhs.x * rhs[0][1] + lhs.y * rhs[1][1] + lhs.z * rhs[2][1]; | |
p.z = lhs.x * rhs[0][2] + lhs.y * rhs[1][2] + lhs.z * rhs[2][2]; | |
return p; | |
} | |
inline bool operator<(const Point& lhs, const Point& rhs) { | |
return lhs.x * lhs.x + lhs.y * lhs.y < rhs.x * rhs.x + rhs.y * rhs.y; | |
} | |
inline Point operator*(double lhs, const Point& rhs) { | |
return rhs * lhs; | |
} | |
inline bool pointsEqual(const Point& lhs, const Point& rhs, double delta) { | |
return fabs(rhs.x - lhs.x) <= delta && fabs(rhs.y - lhs.y) <= delta; | |
} | |
inline double distance(const Point& A, const Point& B) { | |
return sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); | |
} | |
inline double length(const Vec2f& v) { | |
return sqrt(v.x * v.x + v.y * v.y); | |
} | |
inline double dotProduct(const Vec2f& A, const Vec2f& B) { | |
return A.x * B.x + A.y * B.y; | |
} | |
inline double angle(const Vec2f& A, const Vec2f& B) { | |
return acos(dotProduct(A, B) / (length(A) * length(B))); | |
} | |
struct Line { | |
Line(const Point& A, const Point& B) { | |
a = B.y - A.y; | |
b = A.x - B.x; | |
c = -b * A.y - a * A.x; | |
} | |
Line(double a, double b, double c) | |
: a(a), b(b), c(c) {} | |
bool hasSteepGradient() const { | |
return fabs(a / b) > 999.9; | |
} | |
double a; | |
double b; | |
double c; | |
}; | |
struct LineSegment { | |
LineSegment() | |
: A(0, 0), | |
B(0, 0) {} | |
LineSegment(const Point& A_, const Point& B_) { | |
A = A_; | |
B = B_; | |
} | |
Line line() const { | |
return Line(A, B); | |
} | |
double length() const; | |
double angle() const { | |
return atan2(B.y - A.y, B.x - A.x); | |
} | |
// Signed distance from A. Assumes p lies on line. | |
double signedDistance(const Point& p) const; | |
Point A; | |
Point B; | |
}; | |
inline double LineSegment::length() const { | |
return distance(A, B); | |
} | |
inline double LineSegment::signedDistance(const Point& p) const { | |
Vec2f AB = B - A; | |
Vec2f P = p - A; | |
double theta = acos(AB.dot(P) / (::length(P) * ::length(AB))); | |
double sign = theta > 0.5 * PI ? -1 : 1; | |
return distance(A, p) * sign; | |
} | |
struct Circle { | |
Point pos; | |
double radius; | |
}; | |
inline bool isBetween(double x, double a, double b, double delta = 0.00001) { | |
if (a < b) { | |
return x >= a - delta && x <= b + delta; | |
} | |
return x >= b - delta && x <= a + delta; | |
} | |
Point lineIntersect(const Line& l0, const Line& l1, int depth = 0); | |
bool lineSegmentCircleIntersect(const Circle& circle, const LineSegment& lseg); | |
inline bool lineSegmentIntersect(const LineSegment& l0, const LineSegment& l1, Point& p) { | |
if (largest(l0.A.x, l0.B.x) < smallest(l1.A.x, l1.B.x) | |
|| largest(l0.A.y, l0.B.y) < smallest(l1.A.y, l1.B.y)) { | |
return false; | |
} | |
p = lineIntersect(l0.line(), l1.line()); | |
return (isBetween(p.x, l0.A.x, l0.B.x) && isBetween(p.x, l1.A.x, l1.B.x)) | |
&& (isBetween(p.y, l0.A.y, l0.B.y) && isBetween(p.y, l1.A.y, l1.B.y)); | |
} | |
inline LineSegment transform(const LineSegment& lseg, const Matrix& m) { | |
return LineSegment(m * lseg.A, m * lseg.B); | |
} | |
inline Point projectionOntoLine(const Line& l, const Point& p) { | |
double a = -l.b; | |
double b = l.a; | |
double c = -p.y * b - p.x * a; | |
Line m(a, b, c); | |
return lineIntersect(l, m); | |
} | |
inline Point clipToLineSegment(const Point& p, const LineSegment& lseg) { | |
Point p_ = projectionOntoLine(lseg.line(), p); | |
double d = lseg.signedDistance(p_); | |
if (d < 0) { | |
return lseg.A; | |
} | |
if (d > lseg.length()) { | |
return lseg.B; | |
} | |
return p_; | |
} | |
inline double clipNumber(double x, const Range& range) { | |
if (x < range.a) { | |
x = range.a; | |
} | |
else if (x > range.b) { | |
x = range.b; | |
} | |
return x; | |
} | |
inline double distanceFromLine(const Line& l, const Point& p) { | |
return distance(p, projectionOntoLine(l, p)); | |
} | |
inline Vec2f normalise(const Vec2f& v) { | |
double l = sqrt(v.x * v.x + v.y * v.y); | |
return Vec2f(v.x / l, v.y / l); | |
} | |
inline double normaliseAngle(double angle) { | |
static const double pi2 = 2.0 * PI; | |
static const double pi2_rp = 1.0 / pi2; | |
double n = trunc(angle * pi2_rp); | |
angle -= pi2 * n; | |
if (angle < 0.0) { | |
return pi2 + angle; | |
} | |
else { | |
return angle; | |
} | |
} | |
enum clipResult_t { CLIPPED_TO_TOP, CLIPPED_TO_BOTTOM, NOT_CLIPPED }; | |
inline clipResult_t clipNumber(double x, const Range& range, double& result) { | |
if (x < range.a) { | |
result = range.a; | |
return CLIPPED_TO_BOTTOM; | |
} | |
else if (x > range.b) { | |
result = range.b; | |
return CLIPPED_TO_TOP; | |
} | |
else { | |
result = x; | |
return NOT_CLIPPED; | |
} | |
} | |
#ifdef DEBUG | |
#include <ostream> | |
std::ostream& operator<<(std::ostream& os, const Point& pt); | |
std::ostream& operator<<(std::ostream& os, const LineSegment& lseg); | |
std::ostream& operator<<(std::ostream& os, const Line& line); | |
std::ostream& operator<<(std::ostream& os, const Circle& circ); | |
std::ostream& operator<<(std::ostream& os, const Matrix& mat); | |
std::ostream& operator<<(std::ostream& os, const Range& range); | |
#endif | |
#endif | |
#ifndef __PROCALC_RAYCAST_COMPONENT_HPP__ | |
#define __PROCALC_RAYCAST_COMPONENT_HPP__ | |
#include <memory> | |
#include <string> | |
#include "raycast/component_kinds.hpp" | |
typedef long entityId_t; | |
class Component { | |
public: | |
static entityId_t getNextId(); | |
static entityId_t getIdFromString(const std::string& s); | |
Component(entityId_t entityId, ComponentKind kind) | |
: m_entityId(entityId), | |
m_kind(kind) {} | |
Component(const Component& cpy) = delete; | |
entityId_t entityId() const { | |
return m_entityId; | |
} | |
ComponentKind kind() const { | |
return m_kind; | |
} | |
virtual ~Component() {} | |
private: | |
static entityId_t nextId; | |
entityId_t m_entityId; | |
ComponentKind m_kind; | |
}; | |
typedef std::unique_ptr<Component> pComponent_t; | |
#endif | |
#ifndef __PROCALC_RAYCAST_PLAYER_HPP__ | |
#define __PROCALC_RAYCAST_PLAYER_HPP__ | |
#include <memory> | |
#include "raycast/component.hpp" | |
#include "raycast/timing.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/camera.hpp" | |
const double PLAYER_STEP_HEIGHT = 16.0; | |
const double PLAYER_MAX_PITCH = 20.0; | |
class CZone; | |
class CVRect; | |
class EntityManager; | |
class AudioService; | |
namespace parser { struct Object; } | |
class TimeService; | |
class SpatialSystem; | |
class RenderSystem; | |
class AnimationSystem; | |
class DamageSystem; | |
class BehaviourSystem; | |
class InventorySystem; | |
class EventHandlerSystem; | |
class Player { | |
public: | |
Player(EntityManager& entityManager, AudioService& audioService, TimeService& timeService, | |
const parser::Object& obj, entityId_t parentId, const Matrix& parentTransform); | |
bool alive = true; | |
double vVelocity = 0; | |
double activationRadius = 80.0; | |
double collectionRadius = 50.0; | |
bool invincible = false; | |
entityId_t crosshair = -1; | |
entityId_t sprite = -1; | |
entityId_t red = -1; | |
entityId_t body; | |
entityId_t region() const; | |
bool aboveGround() const; | |
bool belowGround() const; | |
double feetHeight() const; | |
double headHeight() const; | |
double eyeHeight() const; | |
double getTallness() const; | |
void changeTallness(double delta); | |
void setFeetHeight(double h); | |
void setEyeHeight(double h); | |
void changeHeight(const CZone& zone, double deltaH); | |
const Point& pos() const; | |
void setPosition(const Point& pos); | |
Vec2f dir() const; | |
void hRotate(double da); | |
void vRotate(double da); | |
void shoot(); | |
const Camera& camera() const; | |
private: | |
void constructPlayer(const parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform, SpatialSystem& spatialSystem, RenderSystem& renderSystem, | |
AnimationSystem& animationSystem, DamageSystem& damageSystem, | |
BehaviourSystem& behaviourSystem, EventHandlerSystem& eventHandlerSystem); | |
void constructInventory(RenderSystem& renderSystem, InventorySystem& inventorySystem, | |
EventHandlerSystem& eventHandlerSystem, DamageSystem& damageSystem); | |
void setupHudShowHide(RenderSystem& renderSystem, EventHandlerSystem& eventHandlerSystem); | |
CVRect& getBody() const; | |
EntityManager& m_entityManager; | |
AudioService& m_audioService; | |
TimeService& m_timeService; | |
std::unique_ptr<Camera> m_camera; | |
Debouncer m_shootTimer; | |
entityId_t m_ammoId; | |
entityId_t m_healthId; | |
entityId_t m_itemsId; | |
entityId_t m_hudBgId; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_CAMERA_HPP__ | |
#define __PROCALC_RAYCAST_CAMERA_HPP__ | |
#include "utils.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/component.hpp" | |
#include "raycast/spatial_components.hpp" | |
class Camera { | |
public: | |
Camera(double vpW, double hFov, double vFov, CVRect& body, double height) | |
: hFov(hFov), | |
vFov(vFov), | |
F(vpW / (2.0 * tan(0.5 * hFov))), | |
height(height), | |
m_body(body) {} | |
double vAngle = 0.001; | |
double hFov = DEG_TO_RAD(60.0); | |
double vFov = DEG_TO_RAD(50.0); | |
const double F; | |
double height; | |
const CZone& zone() const { | |
return *m_body.zone; | |
} | |
entityId_t zoneId() const { | |
return zone().entityId(); | |
} | |
void setTransform(const Matrix& m) { | |
m_body.pos.x = m.tx(); | |
m_body.pos.y = m.ty(); | |
m_body.angle = m.a(); | |
} | |
Matrix matrix() const { | |
return Matrix(m_body.angle, Vec2f(m_body.pos.x, m_body.pos.y)); | |
} | |
double angle() const { | |
return m_body.angle; | |
} | |
const Point& pos() const { | |
return m_body.pos; | |
} | |
private: | |
CVRect& m_body; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_TWEEN_CURVES_HPP__ | |
#define __PROCALC_RAYCAST_TWEEN_CURVES_HPP__ | |
#include <functional> | |
std::function<double(int)> cubicOut(double from, double to, int n) { | |
return [=](int i) { | |
return (to - from) * (pow(static_cast<double>(i) / n - 1, 3) + 1) + from; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_RAYCAST_C_SOUND_SOURCE_BEHAVIOUR_HPP_ | |
#define __PROCALC_RAYCAST_C_SOUND_SOURCE_BEHAVIOUR_HPP_ | |
#include "raycast/behaviour_system.hpp" | |
#include "raycast/geometry.hpp" | |
class AudioService; | |
class CSoundSourceBehaviour : public CBehaviour { | |
public: | |
CSoundSourceBehaviour(entityId_t entityId, const std::string& name, const Point& pos, | |
double radius, AudioService& audioService); | |
std::string name; | |
Point pos; | |
double radius; | |
void update() override {} | |
void handleBroadcastedEvent(const GameEvent& event) override; | |
void handleTargetedEvent(const GameEvent& event) override; | |
void setDisabled(bool disabled); | |
~CSoundSourceBehaviour() override; | |
private: | |
AudioService& m_audioService; | |
bool m_disabled = false; | |
int m_soundId = -1; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_MISC_FACTORY_HPP__ | |
#define __PROCALC_RAYCAST_MISC_FACTORY_HPP__ | |
#include "raycast/game_object_factory.hpp" | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class Matrix; | |
class RootFactory; | |
namespace parser { struct Object; } | |
class MiscFactory : public GameObjectFactory { | |
public: | |
MiscFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
AudioService& audioService, TimeService& timeService); | |
const std::set<std::string>& types() const override; | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) override; | |
private: | |
RootFactory& m_rootFactory; | |
EntityManager& m_entityManager; | |
AudioService& m_audioService; | |
TimeService& m_timeService; | |
bool constructPlayer(parser::Object& obj, entityId_t parentId, const Matrix& parentTransform); | |
bool constructDoor(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructSwitch(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructElevator(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructSpawnPoint(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructCollectableItem(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructComputerScreen(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructWater(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructSoundSource(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_C_PLAYER_BEHAVIOUR_HPP_ | |
#define __PROCALC_RAYCAST_C_PLAYER_BEHAVIOUR_HPP_ | |
#include <vector> | |
#include "raycast/behaviour_system.hpp" | |
class EntityManager; | |
class TimeService; | |
class CPlayerBehaviour : public CBehaviour { | |
public: | |
CPlayerBehaviour(entityId_t entityId, EntityManager& entityManager, TimeService& timeService); | |
virtual void update() override; | |
virtual void handleBroadcastedEvent(const GameEvent&) override {} | |
virtual void handleTargetedEvent(const GameEvent& e) override; | |
private: | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_TIME_SERVICE_HPP__ | |
#define __PROCALC_RAYCAST_TIME_SERVICE_HPP__ | |
#include <functional> | |
#include <map> | |
#include <set> | |
#include <string> | |
struct Tween { | |
// Args: frame, elapsed, frameRate | |
// Returns true to continue, false to finish | |
std::function<bool(long, double, double)> tick; | |
std::function<void(long, double, double)> finish; | |
}; | |
class TimeService { | |
public: | |
TimeService(double frameRate) | |
: frameRate(frameRate) {} | |
void addTween(const Tween& tween, std::string = ""); | |
void removeTween(const std::string& name); | |
long onTimeout(std::function<void()> fn, double seconds); | |
long atIntervals(std::function<bool()> fn, double seconds); | |
void cancelTimeout(long id); | |
void update(); | |
double now() const; | |
const double frameRate; | |
private: | |
struct TweenWrap { | |
Tween tween; | |
long long start; | |
}; | |
struct Timeout { | |
std::function<void()> fn; | |
double duration; | |
long long start; | |
}; | |
struct Interval { | |
std::function<bool()> fn; | |
double duration; | |
long long start; | |
}; | |
long long m_frame = 0; | |
std::map<std::string, TweenWrap> m_tweens; | |
std::map<long, Timeout> m_timeouts; | |
std::map<long, Interval> m_intervals; | |
std::set<long> m_pendingDeletion; | |
void deletePending(); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_SPAWN_SYSTEM_HPP_ | |
#define __PROCALC_RAYCAST_SPAWN_SYSTEM_HPP_ | |
#include <memory> | |
#include <map> | |
#include "raycast/system.hpp" | |
#include "raycast/component.hpp" | |
#include "raycast/map_parser.hpp" | |
enum class CSpawnKind { | |
SPAWNABLE, | |
SPAWN_POINT | |
}; | |
struct CSpawn : public Component { | |
CSpawn(entityId_t entityId, CSpawnKind kind) | |
: Component(entityId, ComponentKind::C_SPAWN), | |
kind(kind) {} | |
CSpawnKind kind; | |
}; | |
struct CSpawnable : public CSpawn { | |
CSpawnable(entityId_t entityId, entityId_t spawnPoint, const std::string& typeName, | |
parser::pObject_t obj, entityId_t parentId, const Matrix& parentTransform) | |
: CSpawn(entityId, CSpawnKind::SPAWNABLE), | |
spawnPoint(spawnPoint), | |
typeName(typeName), | |
object(std::move(obj)), | |
parentId(parentId), | |
parentTransform(parentTransform) {} | |
entityId_t spawnPoint; | |
std::string typeName; | |
parser::pObject_t object; | |
entityId_t parentId; | |
Matrix parentTransform; | |
double delay = 5; | |
}; | |
struct CSpawnPoint : public CSpawn { | |
CSpawnPoint(entityId_t entityId) | |
: CSpawn(entityId, CSpawnKind::SPAWN_POINT) {} | |
}; | |
typedef std::unique_ptr<CSpawn> pCSpawn_t; | |
typedef std::unique_ptr<CSpawnPoint> pCSpawnPoint_t; | |
typedef std::unique_ptr<CSpawnable> pCSpawnable_t; | |
class EntityManager; | |
class RootFactory; | |
class TimeService; | |
class SpawnSystem : public System { | |
public: | |
SpawnSystem(EntityManager& entityManager, RootFactory& rootFactory, TimeService& timeService) | |
: m_entityManager(entityManager), | |
m_rootFactory(rootFactory), | |
m_timeService(timeService) {} | |
void update() override {} | |
void handleEvent(const GameEvent& event) override; | |
void handleEvent(const GameEvent&, const std::set<entityId_t>&) override {} | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CSpawn& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
private: | |
EntityManager& m_entityManager; | |
RootFactory& m_rootFactory; | |
TimeService& m_timeService; | |
std::map<entityId_t, pCSpawnPoint_t> m_spawnPoints; | |
std::map<entityId_t, pCSpawnable_t> m_spawnables; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_TIMING_HPP_ | |
#define __PROCALC_RAYCAST_TIMING_HPP_ | |
#include <functional> | |
#include <random> | |
class Debouncer { | |
public: | |
Debouncer(double seconds); | |
bool ready(); | |
void reset(); | |
private: | |
double m_duration; | |
double m_start; | |
}; | |
class TimePattern { | |
public: | |
virtual bool doIfReady(std::function<void()> fn) = 0; | |
virtual ~TimePattern() {} | |
}; | |
class TRandomIntervals : public TimePattern { | |
public: | |
TRandomIntervals(unsigned long min, unsigned long max); | |
virtual bool doIfReady(std::function<void()> fn) override; | |
virtual ~TRandomIntervals() override {} | |
private: | |
std::mt19937 m_randEngine; | |
std::uniform_real_distribution<> m_distribution; | |
unsigned long m_dueTime; | |
void calcDueTime(); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_BEHAVIOUR_SYSTEM_HPP_ | |
#define __PROCALC_RAYCAST_BEHAVIOUR_SYSTEM_HPP_ | |
#include <functional> | |
#include <memory> | |
#include <map> | |
#include "raycast/system.hpp" | |
#include "raycast/component.hpp" | |
struct CBehaviour : public Component { | |
CBehaviour(entityId_t entityId) | |
: Component(entityId, ComponentKind::C_BEHAVIOUR) {} | |
virtual void update() = 0; | |
virtual void handleBroadcastedEvent(const GameEvent& event) = 0; | |
virtual void handleTargetedEvent(const GameEvent& event) = 0; | |
}; | |
typedef std::unique_ptr<CBehaviour> pCBehaviour_t; | |
class BehaviourSystem : public System { | |
public: | |
void update() override; | |
void handleEvent(const GameEvent& event) override; | |
void handleEvent(const GameEvent& event, const std::set<entityId_t>& entities) override; | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CBehaviour& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
private: | |
std::map<entityId_t, pCBehaviour_t> m_components; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_ANIMATION_SYSTEM_HPP__ | |
#define __PROCALC_RAYCAST_ANIMATION_SYSTEM_HPP__ | |
#include <map> | |
#include <vector> | |
#include <QRectF> | |
#include "raycast/system.hpp" | |
#include "exception.hpp" | |
struct AnimationFrame { | |
std::vector<QRectF> texViews; | |
}; | |
enum class AnimState { | |
STOPPED, | |
RUNNING | |
}; | |
class Animation { | |
public: | |
Animation(const std::string& name, double gameFrameRate, double duration, | |
const std::vector<AnimationFrame>& frames) | |
: name(name), | |
frames(frames), | |
m_gameFrameRate(gameFrameRate), | |
m_duration(duration) {} | |
std::string name; | |
std::vector<AnimationFrame> frames; | |
void start(bool loop); | |
void stop(); | |
bool update(); | |
const AnimationFrame& currentFrame() const { | |
return frames[m_currentFrameIdx]; | |
} | |
AnimState state() const { | |
return m_state; | |
} | |
private: | |
const double m_gameFrameRate; | |
const double m_duration; | |
double m_elapsed = 0.0; | |
unsigned int m_currentFrameIdx = 0; | |
AnimState m_state = AnimState::STOPPED; | |
bool m_loop = false; | |
}; | |
typedef std::unique_ptr<Animation> pAnimation_t; | |
struct EAnimationFinished : public GameEvent { | |
explicit EAnimationFinished(entityId_t entityId, const std::string& animName) | |
: GameEvent("animation_finished"), | |
entityId(entityId), | |
animName(animName) {} | |
entityId_t entityId; | |
std::string animName; | |
virtual ~EAnimationFinished() override {} | |
}; | |
class CAnimation : public Component { | |
friend class AnimationSystem; | |
public: | |
CAnimation(entityId_t entityId) | |
: Component(entityId, ComponentKind::C_ANIMATION) {} | |
// For CRegions, anim1 and anim2 are for floors and ceilings, respectively. | |
// For CJoins, anim1 and anim2 are for the bottom wall and the top wall, respectively. | |
void addAnimation(pAnimation_t anim1, pAnimation_t anim2 = nullptr) { | |
m_animations[anim1->name].first = std::move(anim1); | |
if (anim2) { | |
if (anim2->name != anim1->name) { | |
EXCEPTION("Pair of animations '" << anim1->name << "' and '" << anim2->name | |
<< "' do not have same name"); | |
} | |
m_animations[anim2->name].second = std::move(anim2); | |
} | |
} | |
private: | |
std::string m_active; | |
std::map<std::string, std::pair<pAnimation_t, pAnimation_t>> m_animations; | |
}; | |
typedef std::unique_ptr<CAnimation> pCAnimation_t; | |
class EntityManager; | |
class AnimationSystem : public System { | |
public: | |
AnimationSystem(EntityManager& entityManager) | |
: m_entityManager(entityManager) {} | |
void playAnimation(entityId_t entityId, const std::string& anim, bool loop); | |
void stopAnimation(entityId_t entityId, bool recurseIntoChildren = true); | |
inline AnimState animationState(entityId_t entityId, const std::string& name) const; | |
void update() override; | |
void handleEvent(const GameEvent&) override {} | |
void handleEvent(const GameEvent&, const std::set<entityId_t>&) override {} | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CAnimation& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
private: | |
EntityManager& m_entityManager; | |
std::map<entityId_t, pCAnimation_t> m_components; | |
void updateAnimation(CAnimation& c, int which); | |
}; | |
inline AnimState AnimationSystem::animationState(entityId_t entityId, | |
const std::string& name) const { | |
return m_components.at(entityId)->m_animations.at(name).first->state(); | |
} | |
std::vector<AnimationFrame> constructFrames(int W, int H, const std::vector<int>& rows); | |
#endif | |
#ifndef __PROCALC_RAYCAST_GAME_EVENT_HPP_ | |
#define __PROCALC_RAYCAST_GAME_EVENT_HPP_ | |
#include <string> | |
class GameEvent { | |
public: | |
explicit GameEvent(const std::string& name) | |
: name(name) {} | |
std::string name; | |
virtual ~GameEvent() {} | |
}; | |
class EActivateEntity : public GameEvent { | |
public: | |
EActivateEntity(entityId_t entityId) | |
: GameEvent("activate_entity"), | |
entityId(entityId) {} | |
entityId_t entityId; | |
}; | |
struct EKeyPressed : public GameEvent { | |
EKeyPressed(int key) | |
: GameEvent("key_pressed"), | |
key(key) {} | |
int key = -1; | |
}; | |
struct EMouseCaptured : public GameEvent { | |
EMouseCaptured() | |
: GameEvent("mouse_captured") {} | |
}; | |
struct EMouseUncaptured : public GameEvent { | |
EMouseUncaptured() | |
: GameEvent("mouse_uncaptured") {} | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_EVENT_HANDLER_SYSTEM_HPP__ | |
#define __PROCALC_RAYCAST_EVENT_HANDLER_SYSTEM_HPP__ | |
#include <functional> | |
#include <memory> | |
#include <map> | |
#include <list> | |
#include "raycast/system.hpp" | |
#include "raycast/component.hpp" | |
struct EventHandler { | |
EventHandler(const EventHandler&) = delete; | |
EventHandler& operator=(const EventHandler&) = delete; | |
typedef std::function<void(const GameEvent& event)> handlerFn_t; | |
EventHandler(const std::string& name, handlerFn_t&& handler) | |
: name(name), | |
handler(handler) {} | |
EventHandler(EventHandler&& cpy) { | |
name = std::move(cpy.name); | |
handler = std::move(cpy.handler); | |
} | |
std::string name; | |
handlerFn_t handler; | |
}; | |
struct CEventHandler : public Component { | |
explicit CEventHandler(entityId_t entityId) | |
: Component(entityId, ComponentKind::C_EVENT_HANDLER) {} | |
std::list<EventHandler> broadcastedEventHandlers; | |
std::list<EventHandler> targetedEventHandlers; | |
virtual ~CEventHandler() {} | |
}; | |
typedef std::unique_ptr<CEventHandler> pCEventHandler_t; | |
class EventHandlerSystem : public System { | |
public: | |
void update() override {} | |
void handleEvent(const GameEvent& event) override; | |
void handleEvent(const GameEvent& event, const std::set<entityId_t>& entities) override; | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CEventHandler& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
private: | |
std::map<entityId_t, pCEventHandler_t> m_components; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_RENDER_GRAPH_HPP__ | |
#define __PROCALC_RAYCAST_RENDER_GRAPH_HPP__ | |
#include <set> | |
#include "raycast/render_components.hpp" | |
struct RenderDefaults { | |
std::string floorTexture = "default"; | |
std::string ceilingTexture = "default"; | |
}; | |
struct RenderGraph { | |
RenderDefaults defaults; | |
Size viewport; | |
std::map<std::string, Texture> textures; | |
Size viewport_px; | |
double hWorldUnit_px; | |
double vWorldUnit_px; | |
pCRegion_t rootRegion; | |
std::list<pCBoundary_t> boundaries; | |
std::multiset<pCOverlay_t> overlays; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_DAMAGE_SYSTEM_HPP_ | |
#define __PROCALC_RAYCAST_DAMAGE_SYSTEM_HPP_ | |
#include <functional> | |
#include <memory> | |
#include <map> | |
#include "raycast/system.hpp" | |
#include "raycast/component.hpp" | |
#include "raycast/geometry.hpp" | |
struct EEntityDestroyed : public GameEvent { | |
EEntityDestroyed(entityId_t id) | |
: GameEvent("entity_destroyed"), | |
entityId(id) {} | |
entityId_t entityId; | |
Point point_wld; | |
Point point_rel; | |
}; | |
struct EEntityDamaged : public GameEvent { | |
EEntityDamaged(entityId_t id, int health, int prevHealth) | |
: GameEvent("entity_damaged"), | |
entityId(id), | |
health(health), | |
prevHealth(prevHealth) {} | |
entityId_t entityId; | |
Point point_wld; | |
Point point_rel; | |
int health; | |
int prevHealth; | |
}; | |
struct CDamage : public Component { | |
CDamage(entityId_t entityId, int maxHealth, int health) | |
: Component(entityId, ComponentKind::C_DAMAGE), | |
maxHealth(maxHealth), | |
health(smallest(health, maxHealth)) {} | |
int maxHealth; | |
int health; | |
virtual ~CDamage() override {} | |
}; | |
typedef std::unique_ptr<CDamage> pCDamage_t; | |
enum class AttenuationCurve { | |
CONSTANT, | |
LINEAR | |
}; | |
class EntityManager; | |
class CZone; | |
struct Intersection; | |
class DamageSystem : public System { | |
public: | |
DamageSystem(EntityManager& entityManager) | |
: m_entityManager(entityManager) {} | |
void update() override {} | |
void handleEvent(const GameEvent&) override {} | |
void handleEvent(const GameEvent&, const std::set<entityId_t>&) override {} | |
void addComponent(pComponent_t component) override; | |
bool hasComponent(entityId_t entityId) const override; | |
CDamage& getComponent(entityId_t entityId) const override; | |
void removeEntity(entityId_t id) override; | |
void damageEntity(entityId_t id, int damage); | |
void damageWithinRadius(const CZone& zone, const Point& pos, double radius, int damage, | |
AttenuationCurve attenuation = AttenuationCurve::LINEAR); | |
void damageAtIntersection(const Vec2f& ray, double camSpaceVAngle, int damage); | |
void damageAtIntersection(const CZone& zone, const Point& pos, double height, const Vec2f& dir, | |
double vAngle, const Matrix& matrix, int damage); | |
int getHealth(entityId_t entityId) const; | |
int getMaxHealth(entityId_t entityId) const; | |
private: | |
EntityManager& m_entityManager; | |
std::map<entityId_t, pCDamage_t> m_components; | |
void damageAtIntersection_(const Intersection& X, int damage); | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_AUDIO_MANAGER_HPP__ | |
#define __PROCALC_RAYCAST_AUDIO_MANAGER_HPP__ | |
#include <string> | |
#include <memory> | |
#include <array> | |
#include <map> | |
#include <QSoundEffect> | |
#include <QMediaPlayer> | |
#include <QMediaPlaylist> | |
#include <QMediaContent> | |
#include "raycast/geometry.hpp" | |
#include "raycast/component.hpp" | |
// Number of instances of each sound effect | |
const int CONCURRENT_SOUNDS = 8; | |
struct SoundInstance { | |
QSoundEffect sound; | |
bool positional = false; | |
Point pos; | |
}; | |
struct SoundEffect { | |
std::array<SoundInstance, CONCURRENT_SOUNDS> instances; | |
std::array<int, CONCURRENT_SOUNDS> soundIds; | |
double volume = 1.0; | |
}; | |
typedef std::unique_ptr<SoundEffect> pSoundEffect_t; | |
class EntityManager; | |
class TimeService; | |
class Player; | |
class AudioService { | |
public: | |
AudioService(EntityManager& entityManager, TimeService& timeService); | |
void initialise(); | |
void addSound(const std::string& name, const std::string& resourcePath); | |
void addMusicTrack(const std::string& name, const std::string& resourcePath); | |
int playSound(const std::string& name, bool loop = false); | |
int playSoundAtPos(const std::string& name, const Point& pos, bool loop = false); | |
void stopSound(const std::string& name, int id); | |
bool soundIsPlaying(const std::string& name, int id) const; | |
void playMusic(const std::string& name, bool loop, double fadeDuration = -1); | |
void stopMusic(double fadeDuration = -1); | |
void setMusicVolume(double volume); | |
void setMasterVolume(double volume); | |
private: | |
void checkInitialised() const; | |
void onPlayerMove(); | |
double calcVolume(const SoundEffect& sound, const SoundInstance& instance) const; | |
int nextAvailable(const std::array<SoundInstance, CONCURRENT_SOUNDS>& sounds); | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
bool m_initialised; | |
entityId_t m_entityId; | |
QMediaPlayer m_mediaPlayer; | |
QMediaPlaylist m_playlist; | |
std::map<std::string, pSoundEffect_t> m_sounds; | |
std::map<std::string, QMediaContent> m_musicTracks; | |
double m_masterVolume; | |
double m_musicVolume; | |
}; | |
#endif | |
#ifndef __PROCALC_RAYCAST_SPATIAL_COMPONENTS_HPP__ | |
#define __PROCALC_RAYCAST_SPATIAL_COMPONENTS_HPP__ | |
#include <string> | |
#include <functional> | |
#include <list> | |
#include <map> | |
#include <memory> | |
#include <QImage> | |
#include "raycast/geometry.hpp" | |
#include "raycast/component.hpp" | |
enum class CSpatialKind { | |
ZONE, | |
HARD_EDGE, | |
SOFT_EDGE, | |
V_RECT, | |
H_RECT, | |
PATH | |
}; | |
class CSpatial : public Component { | |
public: | |
CSpatial(CSpatialKind kind, entityId_t entityId, entityId_t parentId) | |
: Component(entityId, ComponentKind::C_SPATIAL), | |
kind(kind), | |
parentId(parentId) {} | |
CSpatialKind kind; | |
entityId_t parentId; | |
}; | |
typedef std::unique_ptr<CSpatial> pCSpatial_t; | |
class CPath : public CSpatial { | |
public: | |
CPath(entityId_t entityId, entityId_t parentId) | |
: CSpatial(CSpatialKind::PATH, entityId, parentId) {} | |
std::vector<Point> points; | |
}; | |
typedef std::unique_ptr<CPath> pCPath_t; | |
class CZone; | |
class CVRect : public CSpatial { | |
public: | |
CVRect(entityId_t entityId, entityId_t parentId, const Size& size) | |
: CSpatial(CSpatialKind::V_RECT, entityId, parentId), | |
size(size) {} | |
void setTransform(const Matrix& m) { | |
pos.x = m.tx(); | |
pos.y = m.ty(); | |
angle = m.a(); | |
} | |
CZone* zone = nullptr; | |
Vec2f pos; | |
double angle; | |
Size size; | |
// Height above floor | |
double y = 0; | |
}; | |
typedef std::unique_ptr<CVRect> pCVRect_t; | |
class CEdge : public CSpatial { | |
public: | |
CEdge(CSpatialKind kind, entityId_t entityId, entityId_t parentId) | |
: CSpatial(kind, entityId, parentId) {} | |
LineSegment lseg; | |
std::list<pCVRect_t> vRects; | |
}; | |
typedef std::unique_ptr<CEdge> pCEdge_t; | |
class CHRect : public CSpatial { | |
public: | |
CHRect(entityId_t entityId, entityId_t parentId) | |
: CSpatial(CSpatialKind::H_RECT, entityId, parentId) {} | |
Size size; | |
Matrix transform; | |
}; | |
typedef std::unique_ptr<CHRect> pCHRect_t; | |
typedef std::unique_ptr<CZone> pCZone_t; | |
class CZone : public CSpatial { | |
public: | |
CZone(entityId_t entityId, entityId_t parentId) | |
: CSpatial(CSpatialKind::ZONE, entityId, parentId) {} | |
double floorHeight = 0; | |
double ceilingHeight = 100; | |
bool hasCeiling = true; | |
std::list<pCZone_t> children; | |
std::list<CEdge*> edges; | |
std::list<pCVRect_t> vRects; | |
std::list<pCHRect_t> hRects; | |
std::list<pCPath_t> paths; | |
CZone* parent = nullptr; | |
}; | |
class CHardEdge : public CEdge { | |
public: | |
CHardEdge(entityId_t entityId, entityId_t parentId) | |
: CEdge(CSpatialKind::HARD_EDGE, entityId, parentId) {} | |
CZone* zone = nullptr; | |
double height() const { | |
return zone->ceilingHeight - zone->floorHeight; | |
} | |
}; | |
class CSoftEdge : public CEdge { | |
public: | |
CSoftEdge(entityId_t entityId, entityId_t parentId, entityId_t joinId) | |
: CEdge(CSpatialKind::SOFT_EDGE, entityId, parentId), | |
joinId(joinId) {} | |
entityId_t joinId = -1; | |
entityId_t twinId = -1; | |
bool isPortal = false; | |
Matrix toTwin; | |
CZone* zoneA = nullptr; | |
CZone* zoneB = nullptr; | |
}; | |
#endif | |
#ifndef __PROCALC_EVASIVE_BUTTON_HPP__ | |
#define __PROCALC_EVASIVE_BUTTON_HPP__ | |
#include <QPushButton> | |
#include <QTimer> | |
#include "qt_obj_ptr.hpp" | |
class EvasiveButton : public QPushButton { | |
Q_OBJECT | |
public: | |
EvasiveButton(const QString& caption); | |
// Must be called after the button's position has been set, e.g. by being added to a layout | |
void reset(); | |
void onMouseMove(); | |
~EvasiveButton() override; | |
protected: | |
void mouseMoveEvent(QMouseEvent* event) override; | |
private slots: | |
void tick(); | |
private: | |
bool cursorInRange(QPoint cursor) const; | |
bool m_active; | |
QPoint m_originalPos; | |
QtObjPtr<QTimer> m_timer; | |
}; | |
#endif | |
#ifndef __PROCALC_FILE_EXCEPTION_HPP__ | |
#define __PROCALC_FILE_EXCEPTION_HPP__ | |
#include "exception.hpp" | |
#define FILE_EXCEPTION(x, file) { \ | |
stringstream ss; \ | |
ss << x; \ | |
throw FileException(ss.str(), file, __FILE__, __LINE__); \ | |
} | |
class FileException : public Exception { | |
public: | |
FileException(const std::string& msg) | |
: Exception(msg) {} | |
FileException(const std::string& msg, const char* file, int line) | |
: Exception(msg, file, line) {} | |
FileException(const std::string& msg, const std::string& path, const char* file, int line) | |
: Exception(msg, file, line), m_file(path) { | |
append(" (path: "); | |
append(path); | |
append(")"); | |
} | |
std::string getFilePath() const throw() { | |
return m_file; | |
} | |
virtual ~FileException() throw() {} | |
private: | |
std::string m_file; | |
}; | |
#endif | |
#ifndef __PROCALC_STATE_IDS_HPP__ | |
#define __PROCALC_STATE_IDS_HPP__ | |
enum { | |
ST_NORMAL_CALCULATOR_0, // 2018 | |
ST_NORMAL_CALCULATOR_1, | |
ST_NORMAL_CALCULATOR_2, | |
ST_NORMAL_CALCULATOR_3, | |
ST_NORMAL_CALCULATOR_4, | |
ST_NORMAL_CALCULATOR_5, | |
ST_NORMAL_CALCULATOR_6, | |
ST_NORMAL_CALCULATOR_7, | |
ST_NORMAL_CALCULATOR_8, | |
ST_NORMAL_CALCULATOR_9, | |
ST_DANGER_INFINITY, | |
ST_SHUFFLED_KEYS, // 1992 | |
ST_ARE_YOU_SURE, // 1993 | |
ST_ITS_RAINING_TETROMINOS, // 1994 | |
ST_MAKING_PROGRESS, // 1995 | |
ST_YOUVE_GOT_MAIL, // 1996 | |
ST_GOING_IN_CIRCLES, // 1997 | |
ST_DOOMSWEEPER, // 1998 | |
ST_T_MINUS_TWO_MINUTES, // 1999 | |
ST_BACK_TO_NORMAL, // 2018 | |
ST_TEST | |
}; | |
#endif | |
#ifndef __PROCALC_EFFECTS_HPP__ | |
#define __PROCALC_EFFECTS_HPP__ | |
#include <functional> | |
#include <QColor> | |
#include <QPalette> | |
class UpdateLoop; | |
class QWidget; | |
class QString; | |
class QImage; | |
QColor tweenColour(const QColor& a, const QColor& b, double i); | |
void setColour(QWidget& widget, const QColor& colour, QPalette::ColorRole colourRole); | |
void transitionColour(UpdateLoop& updateLoop, QWidget& widget, const QColor& colour, | |
QPalette::ColorRole colourRole, double duration, std::function<void()> fnOnFinish = []() {}); | |
void setBackgroundImage(QWidget& widget, const QString& path); | |
void garbleImage(const QImage& src, QImage& dest); | |
void rotateHue(QImage& img, int deg); | |
void colourize(QImage& img, const QColor& c, double i); | |
#endif | |
#ifndef __PROCALC_UPDATE_LOOP_HPP__ | |
#define __PROCALC_UPDATE_LOOP_HPP__ | |
#include <list> | |
#include <functional> | |
#include <QObject> | |
#include <QTimer> | |
#include "qt_obj_ptr.hpp" | |
class UpdateLoop : QObject { | |
Q_OBJECT | |
public: | |
UpdateLoop(int interval); | |
void add(std::function<bool()> fn, std::function<void()> fnOnFinish = []() {}); | |
int size() const; | |
double fps() const; | |
void finishAll(); | |
private slots: | |
void tick(); | |
private: | |
struct FuncPair { | |
std::function<bool()> fnPeriodic; | |
std::function<void()> fnFinish; | |
}; | |
QtObjPtr<QTimer> m_timer; | |
int m_interval; | |
std::list<FuncPair> m_functions; | |
}; | |
#endif | |
#ifndef __PROCALC_CONSOLE_WIDGET_HPP__ | |
#define __PROCALC_CONSOLE_WIDGET_HPP__ | |
#include <string> | |
#include <map> | |
#include <vector> | |
#include <functional> | |
#include <deque> | |
#include <QPlainTextEdit> | |
class AppConfig; | |
class ConsoleWidget : public QPlainTextEdit { | |
Q_OBJECT | |
public: | |
typedef std::vector<std::string> ArgList; | |
typedef std::function<std::string(const ArgList&)> CommandFn; | |
ConsoleWidget(const AppConfig& appConfig, const std::string& initialContent, | |
std::vector<std::string> initialHistory = {}); | |
void addCommand(const std::string& name, const CommandFn& fn); | |
protected: | |
void keyPressEvent(QKeyEvent* event) override; | |
private: | |
struct OffscreenTextEdit : public QPlainTextEdit { | |
void keyPressEvent(QKeyEvent* event) { | |
QPlainTextEdit::keyPressEvent(event); | |
} | |
} m_buffer; | |
void applyCommand(); | |
std::string executeCommand(const std::string& cmd); | |
void resetCursorPos(); | |
void cursorToEnd(); | |
void syncCommandText(); | |
int m_commandPos; | |
std::deque<std::string> m_commandHistory; | |
int m_historyIdx = -1; | |
std::map<std::string, CommandFn> m_commandFns; | |
}; | |
#endif | |
#ifndef __PROCALC_F_MAIN_SPEC_FACTORY_HPP__ | |
#define __PROCALC_F_MAIN_SPEC_FACTORY_HPP__ | |
struct FMainSpec; | |
class AppConfig; | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig); | |
#endif | |
#ifndef __PROCALC_EVENT_HPP__ | |
#define __PROCALC_EVENT_HPP__ | |
#include <memory> | |
#include <string> | |
class Event { | |
public: | |
Event(const char* name) | |
: name(name) {} | |
Event(const std::string& name) | |
: name(name) {} | |
std::string name; | |
virtual ~Event() {} | |
}; | |
typedef std::unique_ptr<Event> pEvent_t; | |
#endif | |
#ifndef __PROCALC_UTILS_HPP__ | |
#define __PROCALC_UTILS_HPP__ | |
#include <istream> | |
#include <sstream> | |
#include <string> | |
#include <memory> | |
#include <map> | |
#include <vector> | |
#include <QPointer> | |
#include "exception.hpp" | |
template<class T> | |
inline typename T::mapped_type& getValueFromMap(T& map, const typename T::key_type& key, | |
const char* file, int line) { | |
auto it = map.find(key); | |
if (it == map.end()) { | |
std::stringstream ss; | |
ss << "Map does not contain key '" << key << "'"; | |
throw Exception(ss.str(), file, line); | |
} | |
return it->second; | |
} | |
template<class T> | |
inline const typename T::mapped_type& getValueFromMap(const T& map, const typename T::key_type& key, | |
const char* file, int line) { | |
return getValueFromMap<T>(const_cast<T&>(map), key, file, line); | |
} | |
//=========================================== | |
// Debug Build | |
// | |
#ifdef DEBUG | |
#include <iostream> | |
#define DBG_PRINT(msg) std::cout << msg; | |
#define DBG_PRINT_VAR(var) std::cout << #var" = " << var << "\n"; | |
#define DYNAMIC_CAST dynamic_cast | |
#define GET_VALUE(mapInst, keyName) \ | |
getValueFromMap(mapInst, keyName, __FILE__, __LINE__) | |
//=========================================== | |
//=========================================== | |
// Release Build | |
// | |
#else | |
#define DBG_PRINT(msg) | |
#define DBG_PRINT_VAR(var) | |
#define DYNAMIC_CAST static_cast | |
#define GET_VALUE(mapInst, keyName) \ | |
mapInst.at(keyName) | |
#endif | |
//=========================================== | |
#include "platform.hpp" | |
#define PI 3.141592653589793 | |
#define DEG_TO_RAD(x) (x * PI / 180.0) | |
#define RAD_TO_DEG(x) (x * 180.0 / PI) | |
std::string readString(std::istream& is); | |
void writeString(std::ostream& os, const std::string& s); | |
std::vector<std::string> splitString(const std::string& s, char delim); | |
template<class T> | |
bool ltelte(T a, T b, T c) { | |
return a <= b && b <= c; | |
} | |
template<class T> | |
T smallest(T a, T b) { | |
return a < b ? a : b; | |
} | |
template<class T> | |
T largest(T a, T b) { | |
return a > b ? a : b; | |
} | |
template<class T, class U> | |
bool contains(const std::map<T, U>& map, const T& key) { | |
return map.find(key) != map.end(); | |
} | |
template<class T> | |
void erase(T& container, const typename T::iterator& it) { | |
if (it != container.end()) { | |
container.erase(it); | |
} | |
} | |
long randomSeed(); | |
typedef std::hash<std::string> hashString; | |
inline char asciiToUpper(char c) { | |
return c >= 'a' && c <= 'z' ? c + ('A' - 'a') : c; | |
} | |
#ifdef DEBUG | |
class QRect; | |
class QRectF; | |
class QPoint; | |
class QPointF; | |
std::ostream& operator<<(std::ostream& os, const QRect& rect); | |
std::ostream& operator<<(std::ostream& os, const QRectF& rect); | |
std::ostream& operator<<(std::ostream& os, const QPoint& p); | |
std::ostream& operator<<(std::ostream& os, const QPointF& p); | |
std::ostream& operator<<(std::ostream& os, const QSize& sz); | |
#endif | |
#endif | |
#ifndef __PROCALC_STATES_ST_SHUFFLED_KEYS_HPP__ | |
#define __PROCALC_STATES_ST_SHUFFLED_KEYS_HPP__ | |
#include <cstdlib> | |
#include <ctime> | |
#include <iomanip> | |
#include "app_config.hpp" | |
#include "fragments/f_main/f_main_spec.hpp" | |
namespace st_shuffled_keys { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
srand(time(nullptr)); | |
std::stringstream ss; | |
ss << std::setfill('0') << std::setw(7) << std::fixed << std::setprecision(2) | |
<< static_cast<double>(rand() % 1000000) * 0.01; | |
std::string targetValue = ss.str(); | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->bgColour = QColor(180, 180, 180); | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->glitchSpec.glitchFreqMax = 10.0; | |
mainSpec->shuffledCalcSpec.setEnabled(true); | |
mainSpec->shuffledCalcSpec.targetValue = targetValue; | |
mainSpec->shuffledCalcSpec.displayColour = QColor(200, 200, 180); | |
mainSpec->shuffledCalcSpec.symbols = "☉☿♀⊕♂♃♄⛢♅♆⚳⚴⚵⚶⚘⚕♇"; | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1992 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->settingsDialogSpec.setEnabled(true); | |
mainSpec->settingsDialogSpec.width = 400; | |
mainSpec->settingsDialogSpec.height = 300; | |
mainSpec->settingsDialogSpec.backgroundImage = | |
appConfig.dataPath("shuffled_keys/loading.png").c_str(); | |
mainSpec->settingsDialogSpec.glitchSpec.setEnabled(true); | |
mainSpec->settingsDialogSpec.loadingScreenSpec.setEnabled(true); | |
mainSpec->settingsDialogSpec.loadingScreenSpec.targetValue = targetValue; | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_DANGER_INFINITY_HPP__ | |
#define __PROCALC_STATES_ST_DANGER_INFINITY_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
namespace st_danger_infinity { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->aboutDialogText += QString() + "<p align='center'><big>Pro Office Calculator</big>" | |
"<br>Version " + appConfig.version.c_str() + "</p>" | |
"<p align='center'>Copyright (c) 2018 Rob Jinman. All rights reserved.</p>" | |
"<font size=6>⚠∞</font>"; | |
mainSpec->calculatorSpec.setEnabled(true); | |
mainSpec->calculatorSpec.normalCalcTriggerSpec.setEnabled(true); | |
mainSpec->calculatorSpec.normalCalcTriggerSpec.targetWindowColour = QColor(180, 180, 180); | |
mainSpec->calculatorSpec.normalCalcTriggerSpec.targetDisplayColour = QColor(200, 200, 180); | |
mainSpec->calculatorSpec.normalCalcTriggerSpec.symbols = "☉☿♀⊕♂♃♄⛢♅♆⚳⚴⚵⚶⚘⚕♇"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->glitchSpec.glitchFreqMin = 10.0; | |
mainSpec->glitchSpec.glitchFreqMax = 20.0; | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_NORMAL_CALC_HPP__ | |
#define __PROCALC_STATES_ST_NORMAL_CALC_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
namespace st_normal_calc { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->calculatorSpec.setEnabled(true); | |
mainSpec->aboutDialogText = ""; | |
mainSpec->aboutDialogText += QString() + "<p align='center'><big>Pro Office Calculator</big>" | |
"<br>Version " + appConfig.version.c_str() + "</p>" | |
"<p align='center'>Copyright (c) 2018 Rob Jinman. All rights reserved.</p>" | |
"<i>" + QString::number(10 - appConfig.stateId) + "</i>"; | |
mainSpec->countdownToStartSpec.setEnabled(true); | |
mainSpec->countdownToStartSpec.stateId = appConfig.stateId; | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_T_MINUS_TWO_MINUTES_HPP__ | |
#define __PROCALC_STATES_ST_T_MINUS_TWO_MINUTES_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
namespace st_t_minus_two_minutes { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->backgroundImage = appConfig.dataPath("common/images/bliss.png").c_str(); | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1999 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->desktopSpec.setEnabled(true); | |
mainSpec->desktopSpec.icons = { | |
{appConfig.dataPath("t_minus_two_minutes/file_browser.png"), "File Browser", | |
"fileBrowser2Launch"}, | |
}; | |
mainSpec->appDialogSpec0.setEnabled(true); | |
mainSpec->appDialogSpec0.name = "fileBrowser"; | |
mainSpec->appDialogSpec0.titleText = "File Browser"; | |
mainSpec->appDialogSpec0.width = 320; | |
mainSpec->appDialogSpec0.height = 240; | |
mainSpec->appDialogSpec0.showOnEvent = "fileBrowser2Launch"; | |
mainSpec->appDialogSpec0.fileSystem2Spec.setEnabled(true); | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_DOOMSWEEPER_HPP__ | |
#define __PROCALC_STATES_ST_DOOMSWEEPER_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
namespace st_doomsweeper { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->backgroundImage = appConfig.dataPath("common/images/bliss.png").c_str(); | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1998 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->desktopSpec.setEnabled(true); | |
mainSpec->desktopSpec.icons = { | |
{appConfig.dataPath("doomsweeper/skull_crossbones.png"), "Dooom", "doomLaunch"}, | |
{appConfig.dataPath("doomsweeper/minesweeper.png"), "Minesweeper", "mineweeperLaunch"}, | |
{appConfig.dataPath("doomsweeper/console.png"), "Terminal", "terminalLaunch"} | |
}; | |
mainSpec->appDialogSpec0.setEnabled(true); | |
mainSpec->appDialogSpec0.name = "doom"; | |
mainSpec->appDialogSpec0.titleText = "Dooom"; | |
mainSpec->appDialogSpec0.width = 320; | |
mainSpec->appDialogSpec0.height = 240; | |
mainSpec->appDialogSpec0.showOnEvent = "doomLaunch"; | |
mainSpec->appDialogSpec0.kernelSpec.setEnabled(true); | |
mainSpec->appDialogSpec1.setEnabled(true); | |
mainSpec->appDialogSpec1.titleText = "Minesweeper"; | |
mainSpec->appDialogSpec1.width = 280; | |
mainSpec->appDialogSpec1.height = 240; | |
mainSpec->appDialogSpec1.showOnEvent = "mineweeperLaunch"; | |
mainSpec->appDialogSpec1.minesweeperSpec.setEnabled(true); | |
mainSpec->appDialogSpec2.setEnabled(true); | |
mainSpec->appDialogSpec2.titleText = "Terminal"; | |
mainSpec->appDialogSpec2.width = 400; | |
mainSpec->appDialogSpec2.height = 240; | |
mainSpec->appDialogSpec2.showOnEvent = "terminalLaunch"; | |
mainSpec->appDialogSpec2.consoleSpec.setEnabled(true); | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_GOING_IN_CIRCLES_HPP__ | |
#define __PROCALC_STATES_ST_GOING_IN_CIRCLES_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
namespace st_going_in_circles { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->backgroundImage = appConfig.dataPath("common/images/bliss.png").c_str(); | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1997 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->desktopSpec.setEnabled(true); | |
mainSpec->desktopSpec.icons = { | |
{appConfig.dataPath("going_in_circles/file_browser.png"), "File Browser", "fileBrowserLaunch"}, | |
}; | |
mainSpec->appDialogSpec0.setEnabled(true); | |
mainSpec->appDialogSpec0.titleText = "File Browser"; | |
mainSpec->appDialogSpec0.width = 320; | |
mainSpec->appDialogSpec0.height = 240; | |
mainSpec->appDialogSpec0.showOnEvent = "fileBrowserLaunch"; | |
mainSpec->appDialogSpec0.fileSystemSpec.setEnabled(true); | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_ARE_YOU_SURE_HPP__ | |
#define __PROCALC_STATES_ST_ARE_YOU_SURE_HPP__ | |
#include <cstdlib> | |
#include <ctime> | |
#include <iomanip> | |
#include "fragments/f_main/f_main_spec.hpp" | |
#include "app_config.hpp" | |
namespace st_are_you_sure { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->backgroundImage = appConfig.dataPath("are_you_sure/login.png").c_str(); | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1993 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->loginScreenSpec.setEnabled(true); | |
mainSpec->settingsDialogSpec.setEnabled(true); | |
mainSpec->settingsDialogSpec.width = 400; | |
mainSpec->settingsDialogSpec.height = 300; | |
mainSpec->settingsDialogSpec.configMazeSpec.setEnabled(true); | |
mainSpec->settingsDialogSpec.configMazeSpec.symbols = "☉☿♀⊕♂♃♄⛢♅♆⚳⚴⚵⚶⚘⚕♇"; | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_ITS_RAINING_TETROMINOS_HPP__ | |
#define __PROCALC_STATES_ST_ITS_RAINING_TETROMINOS_HPP__ | |
#include <cstdlib> | |
#include <ctime> | |
#include <iomanip> | |
#include "fragments/f_main/f_main_spec.hpp" | |
#include "app_config.hpp" | |
namespace st_its_raining_tetrominos { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->backgroundImage = appConfig.dataPath("common/images/bliss.png").c_str(); | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1994 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->tetrominosSpec.setEnabled(true); | |
mainSpec->troubleshooterDialogSpec.setEnabled(true); | |
//mainSpec->troubleshooterDialogSpec.glitchSpec.setEnabled(true); | |
mainSpec->troubleshooterDialogSpec.tetrominosSpec.setEnabled(true); | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_YOUVE_GOT_MAIL_HPP__ | |
#define __PROCALC_STATES_ST_YOUVE_GOT_MAIL_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
#include "app_config.hpp" | |
namespace st_youve_got_mail { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->backgroundImage = appConfig.dataPath("common/images/bliss.png").c_str(); | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1996 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->desktopSpec.setEnabled(true); | |
mainSpec->desktopSpec.icons = { | |
{appConfig.dataPath("common/images/procalc.png"), "Pro Office Calculator", "procalcLaunch"}, | |
{appConfig.dataPath("youve_got_mail/text_file.png"), "y2k_threat.doc", "y2kThreatLaunch"}, | |
{appConfig.dataPath("youve_got_mail/mail.png"), "Email Client", "mailClientLaunch"} | |
}; | |
mainSpec->desktopSpec.serverRoomInitSpec.setEnabled(true); | |
mainSpec->appDialogSpec0.setEnabled(true); | |
mainSpec->appDialogSpec0.titleText = "Pro Office Calculator"; | |
mainSpec->appDialogSpec0.name = "procalc"; | |
mainSpec->appDialogSpec0.width = 320; | |
mainSpec->appDialogSpec0.height = 240; | |
mainSpec->appDialogSpec0.showOnEvent = "procalcLaunch"; | |
mainSpec->appDialogSpec0.calculatorSpec.setEnabled(true); | |
mainSpec->appDialogSpec0.serverRoomSpec.setEnabled(true); | |
mainSpec->appDialogSpec1.setEnabled(true); | |
mainSpec->appDialogSpec1.titleText = "Mail Client"; | |
mainSpec->appDialogSpec1.width = 500; | |
mainSpec->appDialogSpec1.height = 375; | |
mainSpec->appDialogSpec1.showOnEvent = "mailClientLaunch"; | |
mainSpec->appDialogSpec1.mailClientSpec.setEnabled(true); | |
mainSpec->appDialogSpec1.glitchSpec.setEnabled(true); | |
mainSpec->appDialogSpec2.setEnabled(true); | |
mainSpec->appDialogSpec2.titleText = "y2k_threat.doc"; | |
mainSpec->appDialogSpec2.width = 400; | |
mainSpec->appDialogSpec2.height = 300; | |
mainSpec->appDialogSpec2.showOnEvent = "y2kThreatLaunch"; | |
mainSpec->appDialogSpec2.textEditorSpec.setEnabled(true); | |
mainSpec->appDialogSpec2.textEditorSpec.content = "<h2>The Y2K threat</h2>" | |
"There is a growing concern amongst a minority of academics and industry experts whether the " | |
"various systems in use today can cope with their clocks transitioning from this century into " | |
"the next - that is, from 11:59:59 PM on 31st December 1999 to 12:00:00 AM on 1st January " | |
"2000. Whilst there's little consensus on what if any deleterious effects may result, " | |
"developers and administrators are urged to consider how their systems may be affected and to " | |
"implement any precautionary measures well in advance."; | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_TEST_HPP__ | |
#define __PROCALC_STATES_ST_TEST_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
#include "app_config.hpp" | |
namespace st_test { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->width = 640; | |
mainSpec->height = 500; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1992 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->maze3dSpec.setEnabled(true); | |
mainSpec->maze3dSpec.mapFile = appConfig.dataPath("common/maps/test.svg"); | |
mainSpec->maze3dSpec.width = 640; | |
mainSpec->maze3dSpec.height = 480; | |
mainSpec->maze3dSpec.frameRate = 200; | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_BACK_TO_NORMAL_HPP__ | |
#define __PROCALC_STATES_ST_BACK_TO_NORMAL_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
namespace st_back_to_normal { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->calculatorSpec.setEnabled(true); | |
mainSpec->aboutDialogText = ""; | |
mainSpec->aboutDialogText += QString("<p align='center'><big>Pro Office Calculator</big>") + | |
"<br>Version " + appConfig.version.c_str() + "</p>" | |
"<p align='center'>Copyright (c) 2018 Rob Jinman. All rights reserved.</p>" | |
"<p>With help from " + appConfig.getParam("player-name").c_str() + "</p>"; | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_STATES_ST_MAKING_PROGRESS_HPP__ | |
#define __PROCALC_STATES_ST_MAKING_PROGRESS_HPP__ | |
#include "fragments/f_main/f_main_spec.hpp" | |
#include "app_config.hpp" | |
namespace st_making_progress { | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
FMainSpec* mainSpec = new FMainSpec; | |
mainSpec->windowTitle = "Pro O҉f̶fic͡e Calc͠u͜l̡ator͏"; | |
mainSpec->glitchSpec.setEnabled(true); | |
mainSpec->fileLabel = "Fi͝l̨e"; | |
mainSpec->quitLabel = "Qui͢t"; | |
mainSpec->backgroundImage = appConfig.dataPath("common/images/bliss.png").c_str(); | |
mainSpec->helpLabel = "H͠e͘l͢p"; | |
mainSpec->aboutLabel = "A͡b҉ou͞t̵"; | |
mainSpec->aboutDialogTitle = "A͞b̶out"; | |
mainSpec->aboutDialogText = ""; | |
mainSpec->aboutDialogText = QString() + | |
"<div>" | |
" <img src='" + appConfig.dataPath("common/images/apex.png").c_str() + "'>" | |
" <p align='center'><big>P̸ro͡ ͏Office͟ ̀Ca͘l̶cu҉l̴at͘or̛</big>" | |
" <br>V̧e̶r̷s̷i͡o̕n " + appConfig.version.c_str() + "</p>" | |
" <p align='center'>C͞opyri̵g͏ht ̨(c)͟ 1995 ̡A̵pe̡x ̢S͢yst̴e̡ms̀ In͝c̷. All͞ ri̛ghts ̷r͢e͠s̷erved̨.͏</p>" | |
"</div>"; | |
mainSpec->desktopSpec.setEnabled(true); | |
mainSpec->desktopSpec.icons = { | |
{appConfig.dataPath("common/images/procalc.png"), "Pro Office Calculator", "procalcLaunch"} | |
}; | |
mainSpec->appDialogSpec0.setEnabled(true); | |
mainSpec->appDialogSpec0.name = "procalcSetup"; | |
mainSpec->appDialogSpec0.titleText = "Installation Setup"; | |
mainSpec->appDialogSpec0.width = 320; | |
mainSpec->appDialogSpec0.height = 240; | |
mainSpec->appDialogSpec0.showOnEvent = "procalcLaunch"; | |
mainSpec->appDialogSpec0.procalcSetupSpec.setEnabled(true); | |
mainSpec->appDialogSpec1.setEnabled(true); | |
mainSpec->appDialogSpec1.titleText = "Pro Office Calculator"; | |
mainSpec->appDialogSpec1.width = 400; | |
mainSpec->appDialogSpec1.height = 300; | |
mainSpec->appDialogSpec1.showOnEvent = "makingProgress/setupComplete"; | |
mainSpec->appDialogSpec1.calculatorSpec.setEnabled(true); | |
mainSpec->appDialogSpec1.calculatorSpec.partialCalcSpec.setEnabled(true); | |
return mainSpec; | |
} | |
} | |
#endif | |
#ifndef __PROCALC_QT_OBJ_PTR_HPP__ | |
#define __PROCALC_QT_OBJ_PTR_HPP__ | |
#include <type_traits> | |
#include <functional> | |
#include <memory> | |
#include <QPointer> | |
template <class T> | |
using CustomQtObjDeleter = std::function<void(const QObject*)>; | |
template <class T> | |
using QtObjPtr = std::unique_ptr<T, CustomQtObjDeleter<T>>; | |
template <class T, typename... Args> | |
auto makeQtObjPtr(Args&&... args) { | |
static_assert(std::is_base_of<QObject, T>::value, "Template arg must derive from QObject"); | |
// Instantiate the object in an exception-safe way | |
auto tmp = std::make_unique<T>(std::forward<Args>(args)...); | |
// A QPointer will keep track of whether the object is still alive | |
QPointer<T> qPtr(tmp.get()); | |
CustomQtObjDeleter<T> deleter = [qPtr](const QObject*) { | |
if (qPtr) { | |
qPtr->deleteLater(); | |
} | |
}; | |
return QtObjPtr<T>(tmp.release(), std::move(deleter)); | |
} | |
template <class T> | |
auto makeQtObjPtrFromRawPtr(T* rawPtr) { | |
static_assert(std::is_base_of<QObject, T>::value, "Template arg must derive from QObject"); | |
QPointer<T> qPtr(rawPtr); | |
CustomQtObjDeleter<T> deleter = [qPtr](const QObject*) { | |
if (qPtr) { | |
qPtr->deleteLater(); | |
} | |
}; | |
return QtObjPtr<T>(rawPtr, std::move(deleter)); | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENT_FACTORY_HPP__ | |
#define __PROCALC_FRAGMENT_FACTORY_HPP__ | |
#include <string> | |
class Fragment; | |
class FragmentData; | |
class CommonFragData; | |
Fragment* constructFragment(const std::string& name, Fragment& parent, FragmentData& parentData, | |
const CommonFragData& commonData); | |
#endif | |
#ifndef __PROCALC_APPLICATION_HPP__ | |
#define __PROCALC_APPLICATION_HPP__ | |
#include <QApplication> | |
class Application : public QApplication { | |
public: | |
Application(int& argc, char** argv) | |
: QApplication(argc, argv) {} | |
virtual bool notify(QObject* receiver, QEvent* event); | |
}; | |
#endif | |
#ifndef __PROCALC_STRINGS_HPP__ | |
#define __PROCALC_STRINGS_HPP__ | |
#include <cstdint> | |
#include <string> | |
typedef std::basic_string<uint32_t> ucs4string_t; | |
typedef std::string utf8string_t; | |
utf8string_t ucs4ToUtf8(const ucs4string_t& ucs); | |
ucs4string_t utf8ToUcs4(const utf8string_t& utf); | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MAZE_3D_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MAZE_3D_HPP__ | |
#include <QWidget> | |
#include <QMargins> | |
#include <QVBoxLayout> | |
#include "raycast/raycast_widget.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FMaze3dData : public FragmentData { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<RaycastWidget> wgtRaycast; | |
}; | |
class FMaze3d : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FMaze3d(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FMaze3d() override; | |
private: | |
FMaze3dData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MAZE_3D_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MAZE_3D_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FMaze3dSpec : public FragmentSpec { | |
FMaze3dSpec() | |
: FragmentSpec("FMaze3d", {}) {} | |
std::string mapFile; | |
int width = 320; | |
int height = 240; | |
int frameRate = 60; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MAIN_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MAIN_HPP__ | |
#include <functional> | |
#include <QMainWindow> | |
#include <QMenu> | |
#include <QAction> | |
#include <QVBoxLayout> | |
#include "update_loop.hpp" | |
#include "fragment.hpp" | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FMainData : public WidgetFragData { | |
FMainData() | |
: WidgetFragData(makeQtObjPtr<QVBoxLayout>()) {} | |
QtObjPtr<QWidget> wgtCentral; | |
QtObjPtr<QMenu> mnuFile; | |
QtObjPtr<QAction> actQuit; | |
QtObjPtr<QMenu> mnuHelp; | |
QtObjPtr<QAction> actAbout; | |
std::function<void()> fnOnQuit = []() {}; | |
}; | |
class FMain : public QMainWindow, public Fragment { | |
Q_OBJECT | |
public: | |
FMain(const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FMain(); | |
protected: | |
virtual void closeEvent(QCloseEvent*) override; | |
private slots: | |
void showAbout(); | |
private: | |
FMainData m_data; | |
QString m_aboutDialogTitle; | |
QString m_aboutDialogText; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SHUFFLED_CALC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SHUFFLED_CALC_HPP__ | |
#include <string> | |
#include <map> | |
#include <random> | |
#include <QWidget> | |
#include <QLineEdit> | |
#include <QVBoxLayout> | |
#include <QImage> | |
#include <QTimer> | |
#include <QMargins> | |
#include <QLabel> | |
#include "fragment.hpp" | |
#include "button_grid.hpp" | |
#include "calculator.hpp" | |
#include "qt_obj_ptr.hpp" | |
class QMainWindow; | |
struct FShuffledCalcData : public FragmentData {}; | |
class FShuffledCalc : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FShuffledCalc(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FShuffledCalc() override; | |
public slots: | |
void onButtonClick(int id); | |
private: | |
QString translateToSymbols(const QString& str) const; | |
FShuffledCalcData m_data; | |
Calculator m_calculator; | |
QtObjPtr<QVBoxLayout> m_vbox; | |
QtObjPtr<QLineEdit> m_wgtDigitDisplay; | |
QtObjPtr<QLabel> m_wgtOpDisplay; | |
QtObjPtr<ButtonGrid> m_wgtButtonGrid; | |
std::string m_targetValue; | |
std::map<QChar, QChar> m_symbols; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SHUFFLED_CALC_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SHUFFLED_CALC_SPEC_HPP__ | |
#include <string> | |
#include <QColor> | |
#include "fragment_spec.hpp" | |
struct FShuffledCalcSpec : public FragmentSpec { | |
FShuffledCalcSpec() | |
: FragmentSpec("FShuffledCalc", {}) {} | |
std::string targetValue = ""; | |
QColor displayColour = QColor(255, 255, 255); | |
QString symbols = "1234567890.+-/*=C"; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_TROUBLESHOOTER_DIALOG_GAME_LOGIC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_TROUBLESHOOTER_DIALOG_GAME_LOGIC_HPP__ | |
#include "raycast/component.hpp" | |
class EventSystem; | |
class EntityManager; | |
class GameEvent; | |
namespace its_raining_tetrominos { | |
class GameLogic { | |
public: | |
GameLogic(EventSystem& eventSystem, EntityManager& entityManager); | |
~GameLogic(); | |
private: | |
void onSwitchActivate(const GameEvent& event); | |
void onChangeZone(const GameEvent& event); | |
EventSystem& m_eventSystem; | |
EntityManager& m_entityManager; | |
entityId_t m_entityId; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_TROUBLESHOOTER_DIALOG_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_TROUBLESHOOTER_DIALOG_SPEC_HPP__ | |
#include <QString> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_tetrominos/f_tetrominos_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
struct FTroubleshooterDialogSpec : public FragmentSpec { | |
FTroubleshooterDialogSpec() | |
: FragmentSpec("FTroubleshooterDialog", { | |
&glitchSpec, | |
&tetrominosSpec | |
}) {} | |
FGlitchSpec glitchSpec; | |
FTetrominosSpec tetrominosSpec; | |
QString titleText = "Troubleshooter"; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_TROUBLESHOOTER_DIALOG_HPP__ | |
#define __PROCALC_FRAGMENTS_F_TROUBLESHOOTER_DIALOG_HPP__ | |
#include <QDialog> | |
#include <QAction> | |
#include <QVBoxLayout> | |
#include <QHBoxLayout> | |
#include <QLabel> | |
#include <QPushButton> | |
#include <QProgressBar> | |
#include <QGroupBox> | |
#include <QTextBrowser> | |
#include <QTabWidget> | |
#include <QTimer> | |
#include "raycast/raycast_widget.hpp" | |
#include "fragments/f_main/f_troubleshooter_dialog/game_logic.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FTroubleshooterDialogData : public FragmentData { | |
QtObjPtr<QAction> actPreferences; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QTabWidget> wgtTabs; | |
struct { | |
QtObjPtr<QWidget> page; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QLabel> wgtCaption; | |
QtObjPtr<QPushButton> wgtRunTroubleshooter; | |
QtObjPtr<QProgressBar> wgtProgressBar; | |
QtObjPtr<QGroupBox> wgtGroupbox; | |
QtObjPtr<QVBoxLayout> resultsVbox; | |
QtObjPtr<QHBoxLayout> btnsHbox; | |
QtObjPtr<QLabel> wgtNoProblemsFound; | |
QtObjPtr<QLabel> wgtProblemResolved; | |
QtObjPtr<QPushButton> wgtYes; | |
QtObjPtr<QPushButton> wgtNo; | |
QtObjPtr<QTimer> timer; | |
} tab1; | |
struct { | |
QtObjPtr<QWidget> page; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QTextBrowser> wgtTextBrowser; | |
} tab2; | |
struct { | |
QtObjPtr<QWidget> page; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<RaycastWidget> wgtRaycast; | |
std::unique_ptr<its_raining_tetrominos::GameLogic> gameLogic; | |
} tab3; | |
}; | |
class FTroubleshooterDialog : public QDialog, public Fragment { | |
Q_OBJECT | |
public: | |
FTroubleshooterDialog(Fragment& parent, FragmentData& parentData, | |
const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FTroubleshooterDialog() override; | |
private slots: | |
void showTroubleshooterDialog(); | |
void onRunTroubleshooter(); | |
void onTick(); | |
void onNoClick(); | |
void onYesClick(); | |
private: | |
FTroubleshooterDialogData m_data; | |
void setupTab1(); | |
void setupTab2(); | |
void setupTab3(); | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MAIN_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MAIN_SPEC_HPP__ | |
#include <QColor> | |
#include <QString> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
#include "fragments/relocatable/f_tetrominos/f_tetrominos_spec.hpp" | |
#include "fragments/relocatable/f_calculator/f_calculator_spec.hpp" | |
#include "fragments/f_main/f_shuffled_calc/f_shuffled_calc_spec.hpp" | |
#include "fragments/f_main/f_countdown_to_start/f_countdown_to_start_spec.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_settings_dialog_spec.hpp" | |
#include "fragments/f_main/f_login_screen/f_login_screen_spec.hpp" | |
#include "fragments/f_main/f_desktop/f_desktop_spec.hpp" | |
#include "fragments/f_main/f_troubleshooter_dialog/f_troubleshooter_dialog_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_app_dialog_spec.hpp" | |
#include "fragments/f_main/f_maze_3d/f_maze_3d_spec.hpp" | |
struct FMainSpec : public FragmentSpec { | |
FMainSpec() | |
: FragmentSpec("FMain", { | |
&glitchSpec, | |
&calculatorSpec, | |
&shuffledCalcSpec, | |
&loginScreenSpec, | |
&desktopSpec, | |
&countdownToStartSpec, | |
&settingsDialogSpec, | |
&troubleshooterDialogSpec, | |
&appDialogSpec0, | |
&appDialogSpec1, | |
&appDialogSpec2, | |
&appDialogSpec3, | |
&appDialogSpec4, | |
&tetrominosSpec, | |
&maze3dSpec | |
}), | |
appDialogSpec0(0), | |
appDialogSpec1(1), | |
appDialogSpec2(2), | |
appDialogSpec3(3), | |
appDialogSpec4(4) {} | |
FGlitchSpec glitchSpec; | |
FCalculatorSpec calculatorSpec; | |
FShuffledCalcSpec shuffledCalcSpec; | |
FLoginScreenSpec loginScreenSpec; | |
FDesktopSpec desktopSpec; | |
FCountdownToStartSpec countdownToStartSpec; | |
FSettingsDialogSpec settingsDialogSpec; | |
FAppDialogSpec appDialogSpec0; | |
FAppDialogSpec appDialogSpec1; | |
FAppDialogSpec appDialogSpec2; | |
FAppDialogSpec appDialogSpec3; | |
FAppDialogSpec appDialogSpec4; | |
FTroubleshooterDialogSpec troubleshooterDialogSpec; | |
FTetrominosSpec tetrominosSpec; | |
FMaze3dSpec maze3dSpec; | |
QString windowTitle = "Pro Office Calculator"; | |
int width = 400; | |
int height = 300; | |
QColor bgColour = QColor(240, 240, 240); | |
QString backgroundImage; | |
QString fileLabel = "File"; | |
QString quitLabel = "Quit"; | |
QString helpLabel = "Help"; | |
QString aboutLabel = "About"; | |
QString aboutDialogTitle = "About"; | |
QString aboutDialogText; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CONFIG_MAZE_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CONFIG_MAZE_HPP__ | |
#include <array> | |
#include <QGridLayout> | |
#include <QPushButton> | |
#include <QLabel> | |
#include <QStackedLayout> | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/are_you_sure_widget.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/config_page.hpp" | |
#include "console_widget.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FConfigMazeData : public FragmentData { | |
QtObjPtr<QStackedLayout> stackedLayout; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<ConsoleWidget> wgtConsole; | |
QtObjPtr<QPushButton> wgtBack; | |
} consolePage; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QPushButton> wgtToConsole; | |
} consoleLaunchPage; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<AreYouSureWidget> wgtAreYouSure; | |
} consoleAreYouSurePage; | |
std::array<QtObjPtr<ConfigPage>, 16> pages; | |
QtObjPtr<QLabel> wgtMap; | |
}; | |
class FConfigMaze : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FConfigMaze(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FConfigMaze() override; | |
private slots: | |
void onEnterConsoleClick(); | |
void onExitConsoleClick(); | |
void onAreYouSureFinish(bool passed); | |
void onPageNextClick(int pageIdx); | |
private: | |
FConfigMazeData m_data; | |
int m_layoutIdxOfConsoleLaunchPage; | |
int m_layoutIdxOfAreYouSurePage; | |
int m_layoutIdxOfConsolePage; | |
int m_layoutIdxOfFirstConfigPage; | |
void constructConsoleLaunchPage(); | |
void constructAreYouSurePage(); | |
void constructConsolePage(); | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CONFIG_MAZE_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CONFIG_MAZE_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FConfigMazeSpec : public FragmentSpec { | |
FConfigMazeSpec() | |
: FragmentSpec("FConfigMaze", {}) {} | |
std::string symbols = "1234567890.+-/*=C"; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CONFIG_MAZE_ARE_YOU_SURE_WIDGET_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CONFIG_MAZE_ARE_YOU_SURE_WIDGET_HPP__ | |
#include <map> | |
#include <QPushButton> | |
#include <QLabel> | |
#include <QStackedLayout> | |
#include <QGridLayout> | |
#include <QHBoxLayout> | |
#include <QVBoxLayout> | |
#include "evasive_button.hpp" | |
#include "qt_obj_ptr.hpp" | |
class AppConfig; | |
class AreYouSureWidget : public QWidget { | |
Q_OBJECT | |
public: | |
AreYouSureWidget(const AppConfig& appConfig); | |
void restart(); | |
signals: | |
void finished(bool pass); | |
protected: | |
void mouseMoveEvent(QMouseEvent* event) override; | |
private slots: | |
void onYesClick(); | |
void onNoClick(); | |
void onFinalYesClick(); | |
void onFinalNoClick(); | |
private: | |
void nextQuestion(); | |
class Template; | |
typedef std::map<std::string, Template> TemplateMap; | |
class Template { | |
public: | |
Template(const std::string& text1, const std::string& text2) | |
: text1(text1), | |
text2(text2) {} | |
explicit Template(const std::string& text1) | |
: text1(text1), | |
text2("") {} | |
Template() {} | |
std::string generate(const TemplateMap& templates, int maxDepth = 10) const; | |
std::string generate_(const TemplateMap& templates, const std::string& text, | |
int maxDepth) const; | |
std::string text1; | |
std::string text2; | |
}; | |
QtObjPtr<QStackedLayout> m_pages; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QGridLayout> grid; | |
QtObjPtr<QPushButton> wgtYes; | |
QtObjPtr<QPushButton> wgtNo; | |
QtObjPtr<QLabel> wgtPrompt; | |
QtObjPtr<QLabel> wgtWarning; | |
} m_page1; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<EvasiveButton> wgtProceed; | |
QtObjPtr<QPushButton> wgtBackToSafety; | |
QtObjPtr<QLabel> wgtPrompt; | |
QtObjPtr<QLabel> wgtConsole; | |
} m_page2; | |
TemplateMap m_templates; | |
int m_count; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CONFIG_MAZE_CONFIG_PAGE_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CONFIG_MAZE_CONFIG_PAGE_HPP__ | |
#include <vector> | |
#include <list> | |
#include <QWidget> | |
#include <QRadioButton> | |
#include <QGridLayout> | |
#include <QPushButton> | |
#include <QButtonGroup> | |
#include <QLabel> | |
#include "qt_obj_ptr.hpp" | |
class ConfigPage : public QWidget { | |
Q_OBJECT | |
public: | |
ConfigPage(QChar symbol, std::vector<int> neighbours = {}); | |
void reset(); | |
QtObjPtr<QGridLayout> grid; | |
signals: | |
void nextClicked(int pageIdx); | |
private slots: | |
void onNextClick(); | |
private: | |
std::vector<int> m_neighbours; | |
QtObjPtr<QLabel> m_label; | |
std::list<QtObjPtr<QRadioButton>> m_radioBtns; | |
QtObjPtr<QButtonGroup> m_btnGroup; | |
QtObjPtr<QPushButton> m_wgtNext; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SETTINGS_DIALOG_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SETTINGS_DIALOG_HPP__ | |
#include <QDialog> | |
#include <QAction> | |
#include <QVBoxLayout> | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FSettingsDialogData : public FragmentData { | |
QtObjPtr<QAction> actSettings; | |
QtObjPtr<QVBoxLayout> vbox; | |
}; | |
class FSettingsDialog : public QDialog, public Fragment { | |
Q_OBJECT | |
public: | |
FSettingsDialog(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FSettingsDialog() override; | |
private slots: | |
void showSettingsDialog(); | |
private: | |
FSettingsDialogData m_data; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_LOADING_SCREEN_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_LOADING_SCREEN_SPEC_HPP__ | |
#include <string> | |
#include "fragment_spec.hpp" | |
struct FLoadingScreenSpec : public FragmentSpec { | |
FLoadingScreenSpec() | |
: FragmentSpec("FLoadingScreen", {}) {} | |
std::string targetValue; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_LOADING_SCREEN_HPP__ | |
#define __PROCALC_FRAGMENTS_F_LOADING_SCREEN_HPP__ | |
#include <memory> | |
#include <QLabel> | |
#include <QMargins> | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FLoadingScreenData : public FragmentData { | |
QtObjPtr<QLabel> label; | |
}; | |
class FLoadingScreen : public QLabel, public Fragment { | |
Q_OBJECT | |
public: | |
FLoadingScreen(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FLoadingScreen() override; | |
private: | |
FLoadingScreenData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SETTINGS_DIALOG_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SETTINGS_DIALOG_SPEC_HPP__ | |
#include <QString> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_loading_screen/f_loading_screen_spec.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/f_config_maze_spec.hpp" | |
struct FSettingsDialogSpec : public FragmentSpec { | |
FSettingsDialogSpec() | |
: FragmentSpec("FSettingsDialog", { | |
&glitchSpec, | |
&loadingScreenSpec, | |
&configMazeSpec | |
}) {} | |
FGlitchSpec glitchSpec; | |
FLoadingScreenSpec loadingScreenSpec; | |
FConfigMazeSpec configMazeSpec; | |
QString titleText = "Settings"; | |
int width = 400; | |
int height = 300; | |
QString backgroundImage; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_COUNTDOWN_TO_START_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_COUNTDOWN_TO_START_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FCountdownToStartSpec : public FragmentSpec { | |
FCountdownToStartSpec() | |
: FragmentSpec("FCountdownToStart", {}) {} | |
int stateId; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_COUNTDOWN_TO_START_HPP__ | |
#define __PROCALC_FRAGMENTS_F_COUNTDOWN_TO_START_HPP__ | |
#include <functional> | |
#include "fragment.hpp" | |
struct FCountdownToStartData : public FragmentData {}; | |
class FCountdownToStart : public Fragment { | |
public: | |
FCountdownToStart(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FCountdownToStart() override; | |
private: | |
void onQuit(); | |
struct { | |
std::function<void()> fnOnQuit; | |
} m_origParentState; | |
FCountdownToStartData m_data; | |
int m_stateId; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_APP_DIALOG_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_APP_DIALOG_SPEC_HPP__ | |
#include <string> | |
#include <QString> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
#include "fragments/relocatable/f_calculator/f_calculator_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_mail_client/f_mail_client_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_server_room/f_server_room_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/f_procalc_setup_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_text_editor/f_text_editor_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system/f_file_system_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/f_file_system_2_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/f_minesweeper_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_console/f_console_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/f_doomsweeper_spec.hpp" | |
struct FAppDialogSpec : public FragmentSpec { | |
FAppDialogSpec(int id) | |
: FragmentSpec("FAppDialog", id, { | |
&glitchSpec, | |
&calculatorSpec, | |
&mailClientSpec, | |
&serverRoomSpec, | |
&procalcSetupSpec, | |
&textEditorSpec, | |
&fileSystemSpec, | |
&fileSystem2Spec, | |
&minesweeperSpec, | |
&kernelSpec, | |
&consoleSpec | |
}) {} | |
FGlitchSpec glitchSpec; | |
FCalculatorSpec calculatorSpec; | |
FMailClientSpec mailClientSpec; | |
FServerRoomSpec serverRoomSpec; | |
FProcalcSetupSpec procalcSetupSpec; | |
FTextEditorSpec textEditorSpec; | |
FFileSystemSpec fileSystemSpec; | |
FFileSystem2Spec fileSystem2Spec; | |
FMinesweeperSpec minesweeperSpec; | |
FDoomsweeperSpec kernelSpec; | |
FConsoleSpec consoleSpec; | |
std::string name = "dialog"; | |
QString titleText = "Application"; | |
int width = 640; | |
int height = 480; | |
std::string showOnEvent = "doesNotExist"; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_PROCALC_SETUP_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_PROCALC_SETUP_SPEC_HPP__ | |
#include <QString> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
struct FProcalcSetupSpec : public FragmentSpec { | |
FProcalcSetupSpec() | |
: FragmentSpec("FProcalcSetup", { | |
&glitchSpec | |
}) {} | |
FGlitchSpec glitchSpec; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_PROCALC_SETUP_GAME_LOGIC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_PROCALC_SETUP_GAME_LOGIC_HPP__ | |
#include <set> | |
#include <QObject> | |
#include <QEvent> | |
#include "button_grid.hpp" | |
#include "event_system.hpp" | |
#include "raycast/component.hpp" | |
class Event; | |
class EntityManager; | |
class GameEvent; | |
namespace making_progress { | |
class GameLogic : public QObject { | |
Q_OBJECT | |
public: | |
GameLogic(QDialog& dialog, EventSystem& eventSystem, EntityManager& entityManager); | |
GameLogic(const GameLogic& cpy) = delete; | |
void setFeatures(const std::set<buttonId_t>& features); | |
~GameLogic(); | |
protected: | |
void customEvent(QEvent* event) override; | |
private: | |
void onElevatorStopped(const GameEvent& event); | |
void onEntityChangeZone(const GameEvent& event); | |
void onButtonPress(const Event& event); | |
void setElevatorSpeed(); | |
void generateTargetNumber(); | |
QDialog& m_dialog; | |
EventSystem& m_eventSystem; | |
EntityManager& m_entityManager; | |
entityId_t m_entityId; | |
EventHandle m_hButtonPress; | |
QEvent::Type m_raiseDialogEvent; | |
double m_targetNumber = 0; | |
int m_numKeysPressed = 0; | |
bool m_success = false; | |
std::set<buttonId_t> m_features; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_PROCALC_SETUP_HPP__ | |
#define __PROCALC_FRAGMENTS_F_PROCALC_SETUP_HPP__ | |
#include <map> | |
#include <QWidget> | |
#include <QListWidget> | |
#include <QPushButton> | |
#include <QStackedLayout> | |
#include <QMargins> | |
#include "fragment.hpp" | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "raycast/raycast_widget.hpp" | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/game_logic.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "button_grid.hpp" | |
struct FProcalcSetupData : public FragmentData { | |
QtObjPtr<QStackedLayout> stackedLayout; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QListWidget> wgtList; | |
QtObjPtr<QPushButton> wgtNext; | |
} page1; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<RaycastWidget> wgtRaycast; | |
QtObjPtr<making_progress::GameLogic> gameLogic; | |
} page2; | |
}; | |
class FProcalcSetup : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FProcalcSetup(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FProcalcSetup() override; | |
protected: | |
void resizeEvent(QResizeEvent* e) override; | |
private slots: | |
void onNextClick(); | |
private: | |
FProcalcSetupData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
std::map<buttonId_t, int> m_featureIndices; | |
void setupPage1(); | |
void setupPage2(); | |
void populateListWidget(); | |
void addCheckableItem(QListWidget& wgtList, const QString& text, buttonId_t btnId = BTN_NULL); | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_PROCALC_SETUP_EVENTS_HPP__ | |
#define __PROCALC_FRAGMENTS_F_PROCALC_SETUP_EVENTS_HPP__ | |
#include <set> | |
#include "event.hpp" | |
#include "button_grid.hpp" | |
namespace making_progress { | |
struct SetupCompleteEvent : public Event { | |
SetupCompleteEvent(const std::set<buttonId_t>& features) | |
: Event("makingProgress/setupComplete"), | |
features(features) {} | |
std::set<buttonId_t> features; | |
}; | |
struct ButtonPressEvent : public Event { | |
ButtonPressEvent(const std::string& display) | |
: Event("makingProgress/buttonPress"), | |
calcDisplay(display) {} | |
std::string calcDisplay; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_OBJECT_FACTORY_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_OBJECT_FACTORY_HPP__ | |
#include "raycast/game_object_factory.hpp" | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class Matrix; | |
class RootFactory; | |
namespace parser { struct Object; } | |
namespace going_in_circles { | |
class ObjectFactory : public GameObjectFactory { | |
public: | |
ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService); | |
const std::set<std::string>& types() const override; | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t region, const Matrix& parentTransform) override; | |
private: | |
RootFactory& m_rootFactory; | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
AudioService& m_audioService; | |
bool constructJeff(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructDonald(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FFileSystemSpec : public FragmentSpec { | |
FFileSystemSpec() | |
: FragmentSpec("FFileSystem", {}) {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_GAME_LOGIC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_GAME_LOGIC_HPP__ | |
#include <string> | |
#include "raycast/component.hpp" | |
class EventSystem; | |
class Event; | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class EventHandlerSystem; | |
class GameEvent; | |
class CSwitchBehaviour; | |
namespace going_in_circles { | |
class GameLogic { | |
public: | |
GameLogic(EventSystem& eventSystem, AudioService& audioService, TimeService& timeService, | |
EntityManager& entityManager); | |
GameLogic(const GameLogic& cpy) = delete; | |
~GameLogic(); | |
private: | |
void onEntityDestroyed(const GameEvent& event); | |
void onEntityChangeZone(const GameEvent& event); | |
void onSwitchActivated(const GameEvent& event); | |
void setupLarry(EventHandlerSystem& eventHandlerSystem); | |
void resetSwitches(); | |
CSwitchBehaviour& getSwitch(entityId_t) const; | |
EventSystem& m_eventSystem; | |
AudioService& m_audioService; | |
TimeService& m_timeService; | |
EntityManager& m_entityManager; | |
entityId_t m_entityId = -1; | |
int m_switchSoundId = -1; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_HPP__ | |
#include <QWidget> | |
#include <QMargins> | |
#include <QVBoxLayout> | |
#include "raycast/raycast_widget.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system/game_logic.hpp" | |
struct FFileSystemData : public FragmentData { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<RaycastWidget> wgtRaycast; | |
std::unique_ptr<going_in_circles::GameLogic> gameLogic; | |
}; | |
class FFileSystem : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FFileSystem(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FFileSystem() override; | |
private: | |
FFileSystemData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SERVER_ROOM_OBJECT_FACTORY_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SERVER_ROOM_OBJECT_FACTORY_HPP__ | |
#include "raycast/game_object_factory.hpp" | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class Matrix; | |
class RootFactory; | |
class CalculatorWidget; | |
namespace parser { struct Object; } | |
namespace youve_got_mail { | |
class ObjectFactory : public GameObjectFactory { | |
public: | |
ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService, CalculatorWidget& wgtCalculator); | |
const std::set<std::string>& types() const override; | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t region, const Matrix& parentTransform) override; | |
private: | |
RootFactory& m_rootFactory; | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
AudioService& m_audioService; | |
CalculatorWidget& m_wgtCalculator; | |
int m_electricitySoundId = -1; | |
bool constructBigScreen(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructCalculator(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructServerRack(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
void renderCalc() const; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SERVER_ROOM_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SERVER_ROOM_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FServerRoomSpec : public FragmentSpec { | |
FServerRoomSpec() | |
: FragmentSpec("FServerRoom", {}) {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SERVER_ROOM_GAME_LOGIC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SERVER_ROOM_GAME_LOGIC_HPP__ | |
#include <string> | |
#include "event_system.hpp" | |
class Event; | |
class EntityManager; | |
namespace youve_got_mail { | |
class GameLogic { | |
public: | |
GameLogic(EventSystem& eventSystem, EntityManager& entityManager); | |
GameLogic(const GameLogic& cpy) = delete; | |
~GameLogic(); | |
private: | |
void onDivByZero(const Event& event); | |
void drawExitDoorDigitDisplay(); | |
EventSystem& m_eventSystem; | |
EntityManager& m_entityManager; | |
EventHandle m_hDivByZero; | |
EventHandle m_hDialogClosed; | |
char m_exitDoorSelectedNum = '\0'; | |
std::string m_exitDoorInput; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SERVER_ROOM_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SERVER_ROOM_HPP__ | |
#include <QWidget> | |
#include <QMargins> | |
#include <QVBoxLayout> | |
#include "raycast/raycast_widget.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "calculator_widget.hpp" | |
#include "event_system.hpp" | |
#include "fragments/f_main/f_app_dialog/f_server_room/game_logic.hpp" | |
struct FServerRoomData : public FragmentData { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<RaycastWidget> wgtRaycast; | |
QtObjPtr<CalculatorWidget> wgtCalculator; | |
std::unique_ptr<youve_got_mail::GameLogic> gameLogic; | |
}; | |
class FServerRoom : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FServerRoom(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FServerRoom() override; | |
private: | |
FServerRoomData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
EventHandle m_hLaunch; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_OBJECT_FACTORY_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_OBJECT_FACTORY_HPP__ | |
#include "raycast/game_object_factory.hpp" | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class Matrix; | |
class RootFactory; | |
namespace parser { struct Object; } | |
namespace t_minus_two_minutes { | |
class ObjectFactory : public GameObjectFactory { | |
public: | |
ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService); | |
const std::set<std::string>& types() const override; | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t region, const Matrix& parentTransform) override; | |
private: | |
bool constructCovfefe(entityId_t entityId, parser::Object& obj, entityId_t region, | |
const Matrix& parentTransform); | |
bool constructCog(entityId_t entityId, parser::Object& obj, entityId_t region, | |
const Matrix& parentTransform); | |
bool constructSmoke(entityId_t entityId, parser::Object& obj, entityId_t region, | |
const Matrix& parentTransform); | |
bool constructBridgeSection(entityId_t entityId, parser::Object& obj, entityId_t region, | |
const Matrix& parentTransform); | |
RootFactory& m_rootFactory; | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
AudioService& m_audioService; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FFileSystem2Spec : public FragmentSpec { | |
FFileSystem2Spec() | |
: FragmentSpec("FFileSystem2", {}) {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_GAME_LOGIC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_GAME_LOGIC_HPP__ | |
#include <string> | |
#include "raycast/component.hpp" | |
class EventSystem; | |
class Event; | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class EventHandlerSystem; | |
class GameEvent; | |
namespace t_minus_two_minutes { | |
class GameLogic { | |
public: | |
GameLogic(EventSystem& eventSystem, AudioService& audioService, TimeService& timeService, | |
EntityManager& entityManager); | |
GameLogic(const GameLogic& cpy) = delete; | |
~GameLogic(); | |
private: | |
void useCovfefe(); | |
void setupTimer(); | |
void updateTimer(); | |
void fadeToBlack(); | |
void onMidnight(); | |
EventSystem& m_eventSystem; | |
AudioService& m_audioService; | |
TimeService& m_timeService; | |
EntityManager& m_entityManager; | |
entityId_t m_entityId = -1; | |
long m_timerHandle = -1; | |
long m_fadeOutHandle = -1; | |
int m_timeRemaining = 120; | |
bool m_missionAccomplished = false; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_HPP__ | |
#define __PROCALC_FRAGMENTS_F_FILE_SYSTEM_2_HPP__ | |
#include <QWidget> | |
#include <QMargins> | |
#include <QVBoxLayout> | |
#include "raycast/raycast_widget.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "event_system.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/game_logic.hpp" | |
struct FFileSystem2Data : public FragmentData { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<RaycastWidget> wgtRaycast; | |
std::unique_ptr<t_minus_two_minutes::GameLogic> gameLogic; | |
}; | |
class FFileSystem2 : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FFileSystem2(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FFileSystem2() override; | |
private: | |
FFileSystem2Data m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
EventHandle m_hDialogClosed; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MAIL_CLIENT_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MAIL_CLIENT_HPP__ | |
#include <QWidget> | |
#include <QVBoxLayout> | |
#include <QGridLayout> | |
#include <QTabWidget> | |
#include <QTableWidget> | |
#include <QTextBrowser> | |
#include <QLabel> | |
#include <QMargins> | |
#include "fragment.hpp" | |
#include "event_system.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FMailClientData : public FragmentData { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QTabWidget> wgtTabs; | |
struct { | |
QtObjPtr<QWidget> page; | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QTableWidget> wgtTable; | |
} inboxTab; | |
struct { | |
QtObjPtr<QWidget> page; | |
QtObjPtr<QGridLayout> grid; | |
QtObjPtr<QTextBrowser> wgtText; | |
QtObjPtr<QLabel> wgtFrom; | |
QtObjPtr<QLabel> wgtDate; | |
QtObjPtr<QLabel> wgtSubject; | |
QtObjPtr<QLabel> wgtTo; | |
QtObjPtr<QLabel> wgtAttachments; | |
} emailTab; | |
}; | |
class FMailClient : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FMailClient(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FMailClient() override; | |
private slots: | |
void onCellDoubleClick(int row, int col); | |
void onTabClose(int idx); | |
private: | |
FMailClientData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
bool m_serverRoomLaunched = false; | |
EventHandle m_hLarryKilled; | |
EventHandle m_hServersDestroyed; | |
const int ST_INITIAL = 0; | |
const int ST_LARRY_DEAD = 1; | |
const int ST_SERVERS_DESTROYED = 2; | |
int m_inboxState = 0; | |
void setupInboxTab(); | |
void setupEmailTab(); | |
void populateInbox(); | |
void enableEmails(int startIdx, int num); | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MAIL_CLIENT_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MAIL_CLIENT_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
struct FMailClientSpec : public FragmentSpec { | |
FMailClientSpec() | |
: FragmentSpec("FMailClient", { | |
&glitchSpec | |
}) {} | |
FGlitchSpec glitchSpec; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CONSOLE_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CONSOLE_SPEC_HPP__ | |
#include <QString> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
struct FConsoleSpec : public FragmentSpec { | |
FConsoleSpec() | |
: FragmentSpec("FConsole", { | |
&glitchSpec | |
}) {} | |
FGlitchSpec glitchSpec; | |
QString content; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CONSOLE_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CONSOLE_HPP__ | |
#include <QVBoxLayout> | |
#include <QMargins> | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "event_system.hpp" | |
#include "console_widget.hpp" | |
struct FConsoleData : public FragmentData { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<ConsoleWidget> wgtConsole; | |
}; | |
class FConsole : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FConsole(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FConsole() override; | |
private: | |
void addCommands(const std::vector<std::vector<std::string>>& commands); | |
FConsoleData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
int m_commandsEntered = 0; | |
EventHandle m_hCommandsGenerated; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_APP_DIALOG_HPP__ | |
#define __PROCALC_FRAGMENTS_F_APP_DIALOG_HPP__ | |
#include <memory> | |
#include <QDialog> | |
#include "fragment.hpp" | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "event_system.hpp" | |
class DialogClosedEvent : public Event { | |
public: | |
DialogClosedEvent(const std::string& name) | |
: Event("dialogClosed"), | |
name(name) {} | |
std::string name; | |
}; | |
class FAppDialogData : public WidgetFragData { | |
public: | |
FAppDialogData() | |
: WidgetFragData(makeQtObjPtr<QVBoxLayout>()) {} | |
}; | |
class FAppDialog : public QDialog, public Fragment { | |
Q_OBJECT | |
public: | |
FAppDialog(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FAppDialog() override; | |
protected: | |
void keyPressEvent(QKeyEvent* e) override; | |
void closeEvent(QCloseEvent* e) override; | |
private: | |
FAppDialogData m_data; | |
std::string m_name; | |
EventHandle m_hShow; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_TEXT_EDITOR_HPP__ | |
#define __PROCALC_FRAGMENTS_F_TEXT_EDITOR_HPP__ | |
#include <QVBoxLayout> | |
#include <QTextBrowser> | |
#include <QMargins> | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
struct FTextEditorData : public FragmentData { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QTextBrowser> wgtTextBrowser; | |
}; | |
class FTextEditor : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FTextEditor(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FTextEditor() override; | |
private: | |
FTextEditorData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_TEXT_EDITOR_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_TEXT_EDITOR_SPEC_HPP__ | |
#include <QString> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
struct FTextEditorSpec : public FragmentSpec { | |
FTextEditorSpec() | |
: FragmentSpec("FTextEditor", { | |
&glitchSpec | |
}) {} | |
FGlitchSpec glitchSpec; | |
QString content; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DOOMSWEEPER_OBJECT_FACTORY_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DOOMSWEEPER_OBJECT_FACTORY_HPP__ | |
#include <map> | |
#include <array> | |
#include "raycast/game_object_factory.hpp" | |
#include "raycast/map_parser.hpp" | |
class EntityManager; | |
class AudioService; | |
class TimeService; | |
class Matrix; | |
class RootFactory; | |
namespace doomsweeper { | |
class ObjectFactory : public GameObjectFactory { | |
public: | |
ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService); | |
const std::set<std::string>& types() const override; | |
bool constructObject(const std::string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t region, const Matrix& parentTransform) override; | |
struct CellDoors { | |
typedef std::array<entityId_t, 4> idList_t; | |
idList_t ids = {{-1, -1, -1, -1}}; | |
entityId_t& north = ids[0]; | |
entityId_t& east = ids[1]; | |
entityId_t& south = ids[2]; | |
entityId_t& west = ids[3]; | |
idList_t::iterator begin() { | |
return ids.begin(); | |
} | |
idList_t::iterator end() { | |
return ids.end(); | |
} | |
}; | |
entityId_t region; | |
Matrix parentTransform; | |
std::map<entityId_t, parser::pObject_t> objects; | |
std::map<entityId_t, Point> objectPositions; | |
std::map<entityId_t, CellDoors> cellDoors; | |
bool firstPassComplete = false; | |
private: | |
bool constructCell(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructCellCorner(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructSlime(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructCellInner(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructCellDoor(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
bool constructCommandScreen(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform); | |
RootFactory& m_rootFactory; | |
EntityManager& m_entityManager; | |
TimeService& m_timeService; | |
AudioService& m_audioService; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DOOMSWEEPER_GAME_LOGIC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DOOMSWEEPER_GAME_LOGIC_HPP__ | |
#include <string> | |
#include <future> | |
#include <atomic> | |
#include <stdexcept> | |
#include <map> | |
#include <list> | |
#include <set> | |
#include "raycast/component.hpp" | |
#include "event_system.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/events.hpp" | |
class Event; | |
class EntityManager; | |
class EventHandlerSystem; | |
class GameEvent; | |
class RootFactory; | |
class TimeService; | |
class CZone; | |
namespace doomsweeper { | |
class ObjectFactory; | |
template<class T, int SIZE> | |
class FixedSizeList { | |
public: | |
const T& oldest() const { | |
return m_list.front(); | |
} | |
const T& newest() const { | |
return m_list.back(); | |
} | |
const T& nthNewest(unsigned int n) const { | |
if (n >= m_list.size()) { | |
throw std::out_of_range("Index out of range"); | |
} | |
unsigned int i = 0; | |
for (const T& e : m_list) { | |
if (n + 1 + i == m_list.size()) { | |
return e; | |
} | |
++i; | |
} | |
throw std::out_of_range("Index out of range"); | |
} | |
void push(const T& element) { | |
m_list.push_back(element); | |
if (m_list.size() > SIZE) { | |
m_list.pop_front(); | |
} | |
} | |
int size() const { | |
return static_cast<int>(m_list.size()); | |
} | |
private: | |
std::list<T> m_list; | |
}; | |
class GameLogic { | |
public: | |
GameLogic(EventSystem& eventSystem, EntityManager& entityManager, RootFactory& rootFactory, | |
ObjectFactory& objectFactory, TimeService& timeService); | |
GameLogic(const GameLogic& cpy) = delete; | |
std::future<void> initialise(const std::set<Coord>& mineCoords); | |
~GameLogic(); | |
private: | |
void onPlayerEnterCellInner(entityId_t cellId); | |
void onCellDoorOpened(entityId_t cellId); | |
void onEntityChangeZone(const GameEvent& e_); | |
void onClickMine(const Event&); | |
void onCommandsEntered(const Event&); | |
void onDoomWindowClose(); | |
void lockDoors(); | |
void sealDoor(entityId_t doorId); | |
void drawMazeMap(const std::set<Coord>& clueCells); | |
void generateCommands(); | |
void drawCommandScreens(const std::vector<std::vector<std::string>>& commands) const; | |
std::atomic<bool> m_initialised; | |
EventSystem& m_eventSystem; | |
EntityManager& m_entityManager; | |
RootFactory& m_rootFactory; | |
ObjectFactory& m_objectFactory; | |
TimeService& m_timeService; | |
entityId_t m_entityId = -1; | |
EventHandle m_hClickMine; | |
EventHandle m_hCommandsEntered; | |
EventHandle m_hDoomClosed; | |
std::map<entityId_t, Coord> m_cellIds; | |
FixedSizeList<entityId_t, 3> m_history; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DOOMSWEEPER_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DOOMSWEEPER_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FDoomsweeperSpec : public FragmentSpec { | |
FDoomsweeperSpec() | |
: FragmentSpec("FDoomsweeper", {}) {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DOOMSWEEPER_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DOOMSWEEPER_HPP__ | |
#include <QWidget> | |
#include <QMargins> | |
#include <QLabel> | |
#include <QTableWidget> | |
#include <QStackedLayout> | |
#include <QPushButton> | |
#include "raycast/raycast_widget.hpp" | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "event_system.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/game_logic.hpp" | |
struct FDoomsweeperData : public FragmentData { | |
QtObjPtr<QStackedLayout> stackedLayout; | |
struct { | |
QtObjPtr<RaycastWidget> wgtRaycast; | |
std::unique_ptr<doomsweeper::GameLogic> gameLogic; | |
} raycastPage; | |
struct { | |
QtObjPtr<QVBoxLayout> vbox; | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QLabel> wgtLabel; | |
QtObjPtr<QTableWidget> wgtTable; | |
QtObjPtr<QPushButton> wgtContinue; | |
} highScorePage; | |
}; | |
class FDoomsweeper : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FDoomsweeper(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FDoomsweeper() override; | |
private slots: | |
void onContinueClick(); | |
void onTableEdit(); | |
private: | |
void setupRaycastPage(); | |
void setupHighScorePage(); | |
bool waitForInit(); | |
FDoomsweeperData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
std::future<void> m_initFuture; | |
EventHandle m_hSetup; | |
EventHandle m_hLevelComplete; | |
std::string m_playerName; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DOOMSWEEPER_GAME_EVENTS_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DOOMSWEEPER_GAME_EVENTS_HPP__ | |
#include "raycast/component.hpp" | |
#include "raycast/game_event.hpp" | |
namespace doomsweeper { | |
struct EPlayerEnterCellInner : public GameEvent { | |
explicit EPlayerEnterCellInner(entityId_t cellId) | |
: GameEvent("player_enter_cell_inner"), | |
cellId(cellId) {} | |
entityId_t cellId; | |
}; | |
struct ECellDoorOpened : public GameEvent { | |
explicit ECellDoorOpened(entityId_t cellId) | |
: GameEvent("cell_door_opened"), | |
cellId(cellId) {} | |
entityId_t cellId; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MINESWEEPER_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MINESWEEPER_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FMinesweeperSpec : public FragmentSpec { | |
FMinesweeperSpec() | |
: FragmentSpec("FMinesweeper", {}) {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MINESWEEPER_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MINESWEEPER_HPP__ | |
#include <array> | |
#include <set> | |
#include <QWidget> | |
#include <QGridLayout> | |
#include <QPushButton> | |
#include <QLabel> | |
#include <QButtonGroup> | |
#include <QStackedLayout> | |
#include <QMouseEvent> | |
#include "fragment.hpp" | |
#include "event_system.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/events.hpp" | |
class GoodButton : public QPushButton { | |
Q_OBJECT | |
public: | |
GoodButton() | |
: QPushButton() {} | |
signals: | |
void rightClicked(QAbstractButton*); | |
protected: | |
void mousePressEvent(QMouseEvent* event) override { | |
if (event->button() == Qt::RightButton) { | |
emit rightClicked(this); | |
} | |
QPushButton::mousePressEvent(event); | |
} | |
}; | |
class GoodButtonGroup : public QButtonGroup { | |
Q_OBJECT | |
public: | |
GoodButtonGroup() | |
: QButtonGroup() {} | |
void addGoodButton(GoodButton* button, int id = -1) { | |
QButtonGroup::addButton(button, id); | |
connect(button, SIGNAL(rightClicked(QAbstractButton*)), this, | |
SLOT(onRightClick(QAbstractButton*))); | |
} | |
signals: | |
void rightClicked(int); | |
private slots: | |
void onRightClick(QAbstractButton* btn) { | |
emit rightClicked(id(btn)); | |
} | |
}; | |
struct IconSet { | |
QIcon mine; | |
QIcon noMine; | |
QIcon flag; | |
QIcon player; | |
}; | |
class MinesweeperCell : public QWidget { | |
Q_OBJECT | |
public: | |
MinesweeperCell(int row, int col, const IconSet& icons); | |
const int row; | |
const int col; | |
int value() const { | |
return m_value; | |
} | |
void setValue(int val); | |
GoodButton& button() const { | |
return *m_button; | |
} | |
bool hidden() const; | |
void setHidden(bool hidden); | |
bool flagged() const { | |
return m_flagged; | |
} | |
void setFlagged(bool flagged); | |
void onPlayerChangeCell(int row, int col); | |
void onPlayerClick(); | |
private: | |
void render(); | |
const IconSet& m_icons; | |
int m_value; | |
bool m_flagged = false; | |
bool m_exploded = false; | |
QPixmap m_pixmap; | |
QtObjPtr<QStackedLayout> m_stackedLayout; | |
QtObjPtr<QLabel> m_label; | |
QtObjPtr<GoodButton> m_button; | |
bool m_hasPlayer = false; | |
}; | |
struct FMinesweeperData : public FragmentData {}; | |
class FMinesweeper : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FMinesweeper(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FMinesweeper() override; | |
private slots: | |
void onBtnClick(int id); | |
void onBtnRightClick(int id); | |
private: | |
void constructLoadingPage(); | |
void constructMainPage(); | |
std::set<doomsweeper::Coord> placeMines(); | |
void setNumbers(); | |
std::set<MinesweeperCell*> getNeighbours(const MinesweeperCell& cell) const; | |
void clearNeighbours_r(const MinesweeperCell& cell, std::set<const MinesweeperCell*>& visited); | |
void onInnerCellEntered(const Event& e_); | |
FMinesweeperData m_data; | |
QtObjPtr<QStackedLayout> m_stackedLayout; | |
struct { | |
QtObjPtr<QLabel> widget; | |
} m_loadingPage; | |
struct { | |
QtObjPtr<QWidget> widget; | |
QtObjPtr<QGridLayout> grid; | |
std::array<std::array<QtObjPtr<MinesweeperCell>, 8>, 8> cells; | |
QtObjPtr<GoodButtonGroup> buttonGroup; | |
} m_mainPage; | |
IconSet m_icons; | |
EventHandle m_hInnerCellEntered; | |
EventHandle m_hStart; | |
bool m_dead = false; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_MINESWEEPER_EVENTS_HPP__ | |
#define __PROCALC_FRAGMENTS_F_MINESWEEPER_EVENTS_HPP__ | |
#include <set> | |
#include <ostream> | |
#include <vector> | |
#include "event.hpp" | |
namespace doomsweeper { | |
struct Coord { | |
int row; | |
int col; | |
}; | |
inline std::ostream& operator<<(std::ostream& out, const Coord& coord) { | |
out << "(" << coord.row << ", " << coord.col << ")"; | |
return out; | |
} | |
inline bool operator==(const Coord& lhs, const Coord& rhs) { | |
return lhs.row == rhs.row && lhs.col == rhs.col; | |
} | |
inline bool operator!=(const Coord& lhs, const Coord& rhs) { | |
return !(lhs == rhs); | |
} | |
inline bool operator<(const Coord& lhs, const Coord& rhs) { | |
if (lhs.row == rhs.row) { | |
return lhs.col < rhs.col; | |
} | |
else { | |
return lhs.row < rhs.row; | |
} | |
} | |
struct MinesweeperSetupEvent : public Event { | |
MinesweeperSetupEvent(const std::set<Coord>& coords) | |
: Event("doomsweeper/minesweeperSetupComplete"), | |
mineCoords(coords) {} | |
std::set<Coord> mineCoords; | |
}; | |
struct CellEnteredEvent : public Event { | |
CellEnteredEvent(const Coord& coords) | |
: Event("doomsweeper/cellEntered"), | |
coords(coords) {} | |
Coord coords; | |
}; | |
struct InnerCellEnteredEvent : public Event { | |
InnerCellEnteredEvent(const Coord& coords) | |
: Event("doomsweeper/innerCellEntered"), | |
coords(coords) {} | |
Coord coords; | |
}; | |
struct CommandsGeneratedEvent : public Event { | |
CommandsGeneratedEvent(const std::vector<std::vector<std::string>>& commands) | |
: Event("doomsweeper/commandsGenerated"), | |
commands(commands) {} | |
std::vector<std::vector<std::string>> commands; | |
}; | |
} | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_LOGIN_SCREEN_HPP__ | |
#define __PROCALC_FRAGMENTS_F_LOGIN_SCREEN_HPP__ | |
#include <QLabel> | |
#include <QTimer> | |
#include <QImage> | |
#include <QLineEdit> | |
#include <QMargins> | |
#include "fragment.hpp" | |
#include "event_system.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FLoginScreenData : public FragmentData { | |
QtObjPtr<QLineEdit> wgtUser; | |
QtObjPtr<QLineEdit> wgtPassword; | |
std::string password; | |
}; | |
struct PasswordGeneratedEvent : public Event { | |
PasswordGeneratedEvent(const std::string& password) | |
: Event("passwordGeneratedEvent"), | |
password(password) {} | |
std::string password; | |
}; | |
class FLoginScreen : public QLabel, public Fragment { | |
Q_OBJECT | |
public: | |
FLoginScreen(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FLoginScreen() override; | |
private slots: | |
void onLoginAttempt(); | |
private: | |
FLoginScreenData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
EventHandle m_hPwdGen; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_LOGIN_SCREEN_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_LOGIN_SCREEN_SPEC_HPP__ | |
#include <QString> | |
#include "fragment_spec.hpp" | |
struct FLoginScreenSpec : public FragmentSpec { | |
FLoginScreenSpec() | |
: FragmentSpec("FLoginScreen", {}) {} | |
QString backgroundImage; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DESKTOP_DESKTOP_ICON_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DESKTOP_DESKTOP_ICON_HPP__ | |
#include <string> | |
#include <QWidget> | |
#include <QPushButton> | |
#include <QVBoxLayout> | |
#include <QLabel> | |
#include "qt_obj_ptr.hpp" | |
class DesktopIcon : public QWidget { | |
Q_OBJECT | |
public: | |
DesktopIcon(const std::string& name, const std::string& image, const std::string& text); | |
virtual ~DesktopIcon() override; | |
QtObjPtr<QPushButton> wgtButton; | |
QtObjPtr<QLabel> wgtText; | |
signals: | |
void activated(const std::string& name); | |
private slots: | |
void onButtonClick(); | |
private: | |
std::string m_name; | |
QtObjPtr<QVBoxLayout> m_vbox; | |
long long m_lastClick = 0; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DESKTOP_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DESKTOP_SPEC_HPP__ | |
#include <string> | |
#include <vector> | |
#include "fragment_spec.hpp" | |
#include "fragments/f_main/f_desktop/f_server_room_init/f_server_room_init_spec.hpp" | |
struct FDesktopSpec : public FragmentSpec { | |
struct Icon { | |
std::string image; | |
std::string text; | |
std::string eventName; | |
}; | |
FDesktopSpec() | |
: FragmentSpec("FDesktop", { | |
&serverRoomInitSpec | |
}) {} | |
FServerRoomInitSpec serverRoomInitSpec; | |
std::vector<Icon> icons; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_DESKTOP_HPP__ | |
#define __PROCALC_FRAGMENTS_F_DESKTOP_HPP__ | |
#include <vector> | |
#include <QWidget> | |
#include <QGridLayout> | |
#include <QMargins> | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
#include "fragments/f_main/f_desktop/desktop_icon.hpp" | |
struct FDesktopData : public FragmentData { | |
QtObjPtr<QGridLayout> grid; | |
std::vector<QtObjPtr<DesktopIcon>> icons; | |
}; | |
class FDesktop : public QWidget, public Fragment { | |
Q_OBJECT | |
public: | |
FDesktop(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FDesktop() override; | |
private slots: | |
void onIconActivate(const std::string& name); | |
private: | |
FDesktopData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SERVER_ROOM_INIT_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SERVER_ROOM_INIT_HPP__ | |
#include "fragment.hpp" | |
#include "event_system.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FServerRoomInitData : public FragmentData {}; | |
class FServerRoomInit : public Fragment { | |
public: | |
FServerRoomInit(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FServerRoomInit() override; | |
private: | |
FServerRoomInitData m_data; | |
EventHandle m_hLaunch; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_SERVER_ROOM_INIT_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_SERVER_ROOM_INIT_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FServerRoomInitSpec : public FragmentSpec { | |
FServerRoomInitSpec() | |
: FragmentSpec("FServerRoomInit", {}) {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_GLITCH_HPP__ | |
#define __PROCALC_FRAGMENTS_F_GLITCH_HPP__ | |
#include <random> | |
#include <QLabel> | |
#include <QImage> | |
#include <QTimer> | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FGlitchData : public FragmentData {}; | |
class FGlitch : public QLabel, public Fragment { | |
Q_OBJECT | |
public: | |
FGlitch(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FGlitch() override; | |
public slots: | |
void tick(); | |
private: | |
FGlitchData m_data; | |
double m_glitchFreqMin; | |
double m_glitchFreqMax; | |
double m_glitchDuration; | |
std::unique_ptr<QImage> m_glitchBuffer; | |
QtObjPtr<QTimer> m_glitchTimer; | |
std::mt19937 m_randEngine; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_GLITCH_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_GLITCH_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FGlitchSpec : public FragmentSpec { | |
FGlitchSpec() | |
: FragmentSpec("FGlitch", {}) {} | |
double glitchFreqMin = 0.1; | |
double glitchFreqMax = 2.0; | |
double glitchDuration = 0.1; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_TETROMINOS_HPP__ | |
#define __PROCALC_FRAGMENTS_F_TETROMINOS_HPP__ | |
#include <list> | |
#include <array> | |
#include <QLabel> | |
#include <QTimer> | |
#include <QImage> | |
#include <QPolygon> | |
#include "fragment.hpp" | |
#include "event_system.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct FTetrominosData : public FragmentData { | |
QtObjPtr<QTimer> timer; | |
}; | |
class FTetrominos : public QLabel, public Fragment { | |
Q_OBJECT | |
public: | |
FTetrominos(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FTetrominos() override; | |
private slots: | |
void tick(); | |
private: | |
struct Tetromino { | |
enum kind_t { | |
I = 0, | |
J = 1, | |
L = 2, | |
O = 3, | |
S = 4, | |
T = 5, | |
Z = 6 | |
}; | |
kind_t kind; | |
double x; | |
double y; | |
double a; | |
double dy; | |
double da; | |
std::array<QPolygon, 4> blocks; | |
QColor colour; | |
}; | |
FTetrominosData m_data; | |
std::list<Tetromino> m_tetrominos; | |
std::unique_ptr<QImage> m_buffer; | |
EventHandle m_hIncTetroRain; | |
void constructTetrominos(double speedMultiplier, double percentageFill); | |
void moveTetrominos(); | |
void drawTetrominos(QImage& buffer); | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_TETROMINOS_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_TETROMINOS_SPEC_HPP__ | |
#include "fragment_spec.hpp" | |
struct FTetrominosSpec : public FragmentSpec { | |
FTetrominosSpec() | |
: FragmentSpec("FTetrominos", {}) {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_WIDGET_FLAG_DATA_HPP__ | |
#define __PROCALC_FRAGMENTS_WIDGET_FLAG_DATA_HPP__ | |
#include <QBoxLayout> | |
#include "fragment.hpp" | |
#include "qt_obj_ptr.hpp" | |
struct WidgetFragData : public FragmentData { | |
WidgetFragData(QtObjPtr<QBoxLayout> box) | |
: FragmentData(), | |
box(std::move(box)) {} | |
QtObjPtr<QBoxLayout> box; | |
virtual ~WidgetFragData() override {} | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_NORMAL_CALC_TRIGGER_HPP__ | |
#define __PROCALC_FRAGMENTS_F_NORMAL_CALC_TRIGGER_HPP__ | |
#include <QObject> | |
#include "fragment.hpp" | |
struct FNormalCalcTriggerData : public FragmentData {}; | |
class FNormalCalcTrigger : public QObject, public Fragment { | |
Q_OBJECT | |
public: | |
FNormalCalcTrigger(Fragment& parent, FragmentData& parentData, | |
const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FNormalCalcTrigger() override; | |
private slots: | |
void onButtonClick(int id); | |
private: | |
FNormalCalcTriggerData m_data; | |
QColor m_targetWindowColour; | |
QColor m_targetDisplayColour; | |
QString m_symbols; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_NORMAL_CALC_TRIGGER_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_NORMAL_CALC_TRIGGER_SPEC_HPP__ | |
#include <QColor> | |
#include "fragment_spec.hpp" | |
struct FNormalCalcTriggerSpec : public FragmentSpec { | |
FNormalCalcTriggerSpec() | |
: FragmentSpec("FNormalCalcTrigger", {}) {} | |
int stateId; | |
QColor targetWindowColour; | |
QColor targetDisplayColour; | |
QString symbols = "1234567890.+-/*=C"; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CALCULATOR_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CALCULATOR_HPP__ | |
#include <QMargins> | |
#include "fragment.hpp" | |
#include "calculator_widget.hpp" | |
#include "qt_obj_ptr.hpp" | |
class QMainWindow; | |
struct FCalculatorData : public FragmentData { | |
QtObjPtr<CalculatorWidget> wgtCalculator; | |
}; | |
class FCalculator : public Fragment { | |
public: | |
FCalculator(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FCalculator() override; | |
private: | |
FCalculatorData m_data; | |
struct { | |
int spacing; | |
QMargins margins; | |
} m_origParentState; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_CALCULATOR_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_CALCULATOR_SPEC_HPP__ | |
#include <QColor> | |
#include "fragment_spec.hpp" | |
#include "fragments/relocatable/f_calculator/f_normal_calc_trigger/f_normal_calc_trigger_spec.hpp" | |
#include "fragments/relocatable/f_calculator/f_partial_calc/f_partial_calc_spec.hpp" | |
struct FCalculatorSpec : public FragmentSpec { | |
FCalculatorSpec() | |
: FragmentSpec("FCalculator", { | |
&normalCalcTriggerSpec, | |
&partialCalcSpec | |
}) {} | |
FNormalCalcTriggerSpec normalCalcTriggerSpec; | |
FPartialCalcSpec partialCalcSpec; | |
QColor displayColour = QColor(255, 255, 255); | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_PARTIAL_CALC_SPEC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_PARTIAL_CALC_SPEC_HPP__ | |
#include <QColor> | |
#include "fragment_spec.hpp" | |
struct FPartialCalcSpec : public FragmentSpec { | |
FPartialCalcSpec() | |
: FragmentSpec("FPartialCalc", {}) {} | |
int stateId; | |
QColor targetWindowColour; | |
QColor targetDisplayColour; | |
QString symbols = "1234567890.+-/*=C"; | |
}; | |
#endif | |
#ifndef __PROCALC_FRAGMENTS_F_PARTIAL_CALC_HPP__ | |
#define __PROCALC_FRAGMENTS_F_PARTIAL_CALC_HPP__ | |
#include <set> | |
#include <QObject> | |
#include "fragment.hpp" | |
#include "event_system.hpp" | |
#include "button_grid.hpp" | |
struct FPartialCalcData : public FragmentData {}; | |
class DialogClosedEvent; | |
class FPartialCalc : public QObject, public Fragment { | |
Q_OBJECT | |
public: | |
FPartialCalc(Fragment& parent, FragmentData& parentData, const CommonFragData& commonData); | |
virtual void reload(const FragmentSpec& spec) override; | |
virtual void cleanUp() override; | |
virtual ~FPartialCalc() override; | |
private slots: | |
void onButtonClick(int id); | |
void onButtonPress(int id); | |
private: | |
void toggleFeatures(const std::set<buttonId_t>& features); | |
FPartialCalcData m_data; | |
EventHandle m_hSetupComplete; | |
EventHandle m_hDialogClosed; | |
}; | |
#endif | |
#ifndef __PROCALC_EXPLODING_BUTTON_HPP__ | |
#define __PROCALC_EXPLODING_BUTTON_HPP__ | |
#include <QPushButton> | |
#include <QLabel> | |
#include <QImage> | |
#include "qt_obj_ptr.hpp" | |
class AppConfig; | |
class UpdateLoop; | |
class ExplodingButton : public QPushButton { | |
Q_OBJECT | |
public: | |
ExplodingButton(QWidget* parent, const QString& caption, const AppConfig& appConfig, | |
UpdateLoop& updateLoop); | |
~ExplodingButton() override; | |
private slots: | |
void onClick(); | |
private: | |
UpdateLoop& m_updateLoop; | |
QtObjPtr<QLabel> m_wgtLabel; | |
std::unique_ptr<QImage> m_spriteSheet; | |
}; | |
#endif | |
#include <fstream> | |
#include <tinyxml2.h> | |
#include <QStandardPaths> | |
#include <QDir> | |
#include <QFontDatabase> | |
#include <QCoreApplication> | |
#include <QApplication> | |
#include "app_config.hpp" | |
#include "utils.hpp" | |
using std::ifstream; | |
using std::string; | |
using CommandLineArgs = AppConfig::CommandLineArgs; | |
using tinyxml2::XMLDocument; | |
using tinyxml2::XMLElement; | |
//=========================================== | |
// convert | |
//=========================================== | |
template<typename T> | |
T convert(const string&) { | |
T t; | |
return t; | |
} | |
template<> | |
double convert<double>(const string& s) { | |
return std::stod(s); | |
} | |
template<> | |
int convert<int>(const string& s) { | |
return std::stoi(s); | |
} | |
//=========================================== | |
//=========================================== | |
// getArg | |
//=========================================== | |
template<typename T> | |
T getArg(const CommandLineArgs& args, unsigned int idx, const T& defaultVal) { | |
if (idx < args.size()) { | |
return convert<T>(args[idx]); | |
} | |
else { | |
return defaultVal; | |
} | |
} | |
//=========================================== | |
// AppConfig::AppConfig | |
//=========================================== | |
AppConfig::AppConfig(int argc, char** argv) { | |
for (int i = 1; i < argc; ++i) { | |
this->args.push_back(argv[i]); | |
} | |
readVersionFile(); | |
loadState(); | |
if (this->args.size() > 0) { | |
#ifdef DEBUG | |
this->stateId = getIntArg(0, 0); | |
#endif | |
} | |
QFontDatabase::addApplicationFont(dataPath("common/fonts/DejaVuSans.ttf").c_str()); | |
QFontDatabase::addApplicationFont(dataPath("common/fonts/DejaVuSansMono.ttf").c_str()); | |
this->normalFont = QFont{"DejaVu Sans"}; | |
this->monoFont = QFont{"DejaVu Sans Mono"}; | |
this->normalFont.setPixelSize(14); | |
QApplication::setFont(this->normalFont); | |
} | |
//=========================================== | |
// AppConfig::loadState | |
//=========================================== | |
void AppConfig::loadState() { | |
string filePath = saveDataPath("procalc.dat"); | |
XMLDocument doc; | |
doc.LoadFile(filePath.c_str()); | |
if (!doc.Error()) { | |
XMLElement* root = doc.FirstChildElement("config"); | |
XMLElement* e = root->FirstChildElement(); | |
while (e != nullptr) { | |
string tagName(e->Name()); | |
m_params[tagName] = e->GetText(); | |
DBG_PRINT("Loaded param " << tagName << "=" << e->GetText() << "\n"); | |
e = e->NextSiblingElement(); | |
} | |
this->stateId = convert<int>(GET_VALUE(m_params, "state-id")); | |
m_params.erase("state-id"); | |
} | |
else { | |
DBG_PRINT("Could not load procalc.dat\n"); | |
} | |
} | |
//=========================================== | |
// AppConfig::persistState | |
//=========================================== | |
void AppConfig::persistState() { | |
DBG_PRINT("Persisting state id " << this->stateId << "\n"); | |
QDir rootDir{"/"}; | |
rootDir.mkpath(saveDataPath("").c_str()); | |
string filePath = saveDataPath("procalc.dat"); | |
XMLDocument doc; | |
XMLElement* root = doc.NewElement("config"); | |
doc.InsertEndChild(root); | |
XMLElement* e = doc.NewElement("state-id"); | |
e->SetText(this->stateId); | |
root->InsertEndChild(e); | |
for (auto it = m_params.begin(); it != m_params.end(); ++it) { | |
auto& name = it->first; | |
auto& value = it->second; | |
DBG_PRINT("Persisting param " << name << "=" << value << "\n"); | |
XMLElement* e = doc.NewElement(name.c_str()); | |
e->SetText(value.c_str()); | |
root->InsertEndChild(e); | |
} | |
doc.SaveFile(filePath.c_str()); | |
} | |
//=========================================== | |
// AppConfig::getParam | |
//=========================================== | |
const string& AppConfig::getParam(const string& name) const { | |
return GET_VALUE(m_params, name); | |
} | |
//=========================================== | |
// AppConfig::setParam | |
//=========================================== | |
void AppConfig::setParam(const string& name, const string& value) { | |
m_params[name] = value; | |
} | |
//=========================================== | |
// AppConfig::getDoubleArg | |
//=========================================== | |
double AppConfig::getDoubleArg(unsigned int idx, double defaultVal) const { | |
return getArg<double>(this->args, idx, defaultVal); | |
} | |
//=========================================== | |
// AppConfig::getIntArg | |
//=========================================== | |
int AppConfig::getIntArg( unsigned int idx, int defaultVal) const { | |
return getArg<int>(this->args, idx, defaultVal); | |
} | |
//=========================================== | |
// AppConfig::getStringArg | |
//=========================================== | |
string AppConfig::getStringArg(unsigned int idx, const string& defaultVal) const { | |
return getArg<string>(this->args, idx, defaultVal); | |
} | |
//=========================================== | |
// AppConfig::readVersionFile | |
//=========================================== | |
void AppConfig::readVersionFile() { | |
string path = versionFilePath(); | |
ifstream fin(path); | |
if (fin.good()) { | |
fin >> this->version; | |
} | |
fin.close(); | |
} | |
//=========================================== | |
// AppConfig::versionFilePath | |
//=========================================== | |
string AppConfig::versionFilePath() const { | |
// Windows | |
#ifdef WIN32 | |
return QCoreApplication::applicationDirPath().toStdString() + "/VERSION"; | |
// OS X | |
#elif defined(__APPLE__) | |
return QCoreApplication::applicationDirPath().toStdString() + "/../Resources/VERSION"; | |
// Linux | |
#else | |
#ifdef DEBUG | |
return QCoreApplication::applicationDirPath().toStdString() + "/VERSION"; | |
#else | |
// Whether the installation is global or relocatable, the usr dir should be one level above | |
// the bin directory | |
string usrDir = QCoreApplication::applicationDirPath().toStdString() + "/.."; | |
return usrDir + "/share/procalc/VERSION"; | |
#endif | |
#endif | |
} | |
//=========================================== | |
// AppConfig::dataPath | |
//=========================================== | |
string AppConfig::dataPath(const string& relPath) const { | |
// Windows | |
#ifdef WIN32 | |
return QCoreApplication::applicationDirPath().toStdString() + "/data/" + relPath; | |
// OS X | |
#elif defined(__APPLE__) | |
return QCoreApplication::applicationDirPath().toStdString() + "/../Resources/data/" + relPath; | |
// Linux | |
#else | |
#ifdef DEBUG | |
return QCoreApplication::applicationDirPath().toStdString() + "/data/" + relPath; | |
#else | |
// Whether the installation is global or relocatable, the usr dir should be one level above | |
// the bin directory | |
string usrDir = QCoreApplication::applicationDirPath().toStdString() + "/.."; | |
return usrDir + "/share/procalc/data/" + relPath; | |
#endif | |
#endif | |
} | |
//=========================================== | |
// AppConfig::saveDataPath | |
//=========================================== | |
string AppConfig::saveDataPath(const string& relPath) const { | |
#ifdef DEBUG | |
return QCoreApplication::applicationDirPath().toStdString() + "/" + relPath; | |
#else | |
static string dir; | |
if (dir.length() == 0) { | |
auto paths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); | |
if (!paths.empty()) { | |
dir = paths.first().toStdString(); | |
} | |
} | |
return dir + "/" + relPath; | |
#endif | |
} | |
#include <cassert> | |
#include "fragment.hpp" | |
#include "fragment_spec.hpp" | |
#include "fragment_factory.hpp" | |
#include "utils.hpp" | |
using std::string; | |
//=========================================== | |
// FragmentData::~FragmentData | |
//=========================================== | |
FragmentData::~FragmentData() {} | |
//=========================================== | |
// Fragment::Fragment | |
//=========================================== | |
Fragment::Fragment(const string& name, Fragment& parent, FragmentData& parentData, | |
FragmentData& ownData, const CommonFragData& commonData) | |
: commonData(commonData), | |
m_name(name), | |
m_parent(&parent), | |
m_parentData(&parentData), | |
m_ownData(ownData) {} | |
//=========================================== | |
// Fragment::Fragment | |
//=========================================== | |
Fragment::Fragment(const string& name, FragmentData& ownData, const CommonFragData& commonData) | |
: commonData(commonData), | |
m_name(name), | |
m_parent(nullptr), | |
m_parentData(nullptr), | |
m_ownData(ownData) {} | |
//=========================================== | |
// Fragment::name | |
//=========================================== | |
const string& Fragment::name() const { | |
return m_name; | |
} | |
//=========================================== | |
// Fragment::rebuild | |
//=========================================== | |
void Fragment::rebuild(const FragmentSpec& spec, bool hardReset) { | |
// Remove unused child fragments, recursively triggering their cleanUp methods | |
// | |
for (auto it = spec.specs().begin(); it != spec.specs().end(); ++it) { | |
const string& chName = it->first; | |
const FragmentSpec& chSpec = *it->second; | |
if (!chSpec.isEnabled() || hardReset) { | |
auto jt = m_children.find(chName); | |
if (jt != m_children.end()) { | |
Fragment& chFrag = *jt->second; | |
chFrag.detach(); | |
m_children.erase(jt); | |
} | |
} | |
} | |
// Call reload before constructing children | |
// | |
reload(spec); | |
// Construct any newly enabled child fragments | |
// | |
for (auto it = spec.specs().begin(); it != spec.specs().end(); ++it) { | |
const string& chName = it->first; | |
const FragmentSpec& chSpec = *it->second; | |
if (chSpec.isEnabled()) { | |
if (m_children.find(chName) == m_children.end()) { | |
Fragment* frag = constructFragment(chSpec.type(), *this, m_ownData, commonData); | |
m_children.insert(std::make_pair(chName, pFragment_t(frag))); | |
} | |
} | |
} | |
// Rebuild children | |
// | |
for (auto it = m_children.begin(); it != m_children.end(); ++it) { | |
const string& name = it->first; | |
Fragment& frag = *it->second; | |
frag.rebuild(spec.spec(name), hardReset); | |
} | |
} | |
//=========================================== | |
// Fragment::detach | |
//=========================================== | |
void Fragment::detach() { | |
for (auto it = m_children.begin(); it != m_children.end(); ++it) { | |
Fragment& frag = *it->second; | |
frag.detach(); | |
} | |
cleanUp(); | |
} | |
//=========================================== | |
// Fragment::~Fragment | |
//=========================================== | |
Fragment::~Fragment() {} | |
#include <string> | |
#include <QButtonGroup> | |
#include <QPushButton> | |
#include "button_grid.hpp" | |
#include "utils.hpp" | |
using std::string; | |
//=========================================== | |
// makeButton | |
//=========================================== | |
static QtObjPtr<QPushButton> makeButton(QWidget* parent, const QString& text) { | |
auto btn = makeQtObjPtr<QPushButton>(text, parent); | |
QSizePolicy sp = btn->sizePolicy(); | |
sp.setRetainSizeWhenHidden(true); | |
btn->setMaximumHeight(60); | |
btn->setSizePolicy(sp); | |
return btn; | |
} | |
//=========================================== | |
// ButtonGrid::ButtonGrid | |
//=========================================== | |
ButtonGrid::ButtonGrid(QWidget* parent) | |
: QWidget(parent) { | |
grid = makeQtObjPtr<QGridLayout>(); | |
grid->setSpacing(1); | |
setLayout(grid.get()); | |
auto btn0 = makeButton(this, "0"); | |
auto btn1 = makeButton(this, "1"); | |
auto btn2 = makeButton(this, "2"); | |
auto btn3 = makeButton(this, "3"); | |
auto btn4 = makeButton(this, "4"); | |
auto btn5 = makeButton(this, "5"); | |
auto btn6 = makeButton(this, "6"); | |
auto btn7 = makeButton(this, "7"); | |
auto btn8 = makeButton(this, "8"); | |
auto btn9 = makeButton(this, "9"); | |
auto btnPlus = makeButton(this, "+"); | |
auto btnMinus = makeButton(this, "-"); | |
auto btnTimes = makeButton(this, "*"); | |
auto btnDivide = makeButton(this, "/"); | |
auto btnPoint = makeButton(this, "."); | |
auto btnClear = makeButton(this, "C"); | |
auto btnEquals = makeButton(this, "="); | |
buttonGroup = makeQtObjPtr<QButtonGroup>(); | |
buttonGroup->addButton(btn0.get(), BTN_ZERO); | |
buttonGroup->addButton(btn1.get(), BTN_ONE); | |
buttonGroup->addButton(btn2.get(), BTN_TWO); | |
buttonGroup->addButton(btn3.get(), BTN_THREE); | |
buttonGroup->addButton(btn4.get(), BTN_FOUR); | |
buttonGroup->addButton(btn5.get(), BTN_FIVE); | |
buttonGroup->addButton(btn6.get(), BTN_SIX); | |
buttonGroup->addButton(btn7.get(), BTN_SEVEN); | |
buttonGroup->addButton(btn8.get(), BTN_EIGHT); | |
buttonGroup->addButton(btn9.get(), BTN_NINE); | |
buttonGroup->addButton(btnPlus.get(), BTN_PLUS); | |
buttonGroup->addButton(btnMinus.get(), BTN_MINUS); | |
buttonGroup->addButton(btnTimes.get(), BTN_TIMES); | |
buttonGroup->addButton(btnDivide.get(), BTN_DIVIDE); | |
buttonGroup->addButton(btnPoint.get(), BTN_POINT); | |
buttonGroup->addButton(btnClear.get(), BTN_CLEAR); | |
buttonGroup->addButton(btnEquals.get(), BTN_EQUALS); | |
grid->addWidget(btnClear.get(), 0, 0); | |
grid->addWidget(btn0.get(), 4, 0); | |
grid->addWidget(btnPoint.get(), 4, 1); | |
grid->addWidget(btnEquals.get(), 4, 2); | |
grid->addWidget(btn1.get(), 3, 0); | |
grid->addWidget(btn2.get(), 3, 1); | |
grid->addWidget(btn3.get(), 3, 2); | |
grid->addWidget(btn4.get(), 2, 0); | |
grid->addWidget(btn5.get(), 2, 1); | |
grid->addWidget(btn6.get(), 2, 2); | |
grid->addWidget(btn7.get(), 1, 0); | |
grid->addWidget(btn8.get(), 1, 1); | |
grid->addWidget(btn9.get(), 1, 2); | |
grid->addWidget(btnPlus.get(), 4, 3); | |
grid->addWidget(btnMinus.get(), 3, 3); | |
grid->addWidget(btnTimes.get(), 2, 3); | |
grid->addWidget(btnDivide.get(), 1, 3); | |
buttons.push_back(std::move(btn0)); | |
buttons.push_back(std::move(btn1)); | |
buttons.push_back(std::move(btn2)); | |
buttons.push_back(std::move(btn3)); | |
buttons.push_back(std::move(btn4)); | |
buttons.push_back(std::move(btn5)); | |
buttons.push_back(std::move(btn6)); | |
buttons.push_back(std::move(btn7)); | |
buttons.push_back(std::move(btn8)); | |
buttons.push_back(std::move(btn9)); | |
buttons.push_back(std::move(btnPlus)); | |
buttons.push_back(std::move(btnMinus)); | |
buttons.push_back(std::move(btnTimes)); | |
buttons.push_back(std::move(btnDivide)); | |
buttons.push_back(std::move(btnPoint)); | |
buttons.push_back(std::move(btnClear)); | |
buttons.push_back(std::move(btnEquals)); | |
connect(buttonGroup.get(), SIGNAL(buttonPressed(int)), this, SLOT(onBtnPress(int))); | |
connect(buttonGroup.get(), SIGNAL(buttonClicked(int)), this, SLOT(onBtnClick(int))); | |
} | |
//=========================================== | |
// ButtonGrid::onBtnClick | |
//=========================================== | |
void ButtonGrid::onBtnClick(int id) { | |
emit buttonClicked(id); | |
} | |
//=========================================== | |
// ButtonGrid::onBtnPress | |
//=========================================== | |
void ButtonGrid::onBtnPress(int id) { | |
emit buttonPressed(id); | |
} | |
//=========================================== | |
// ButtonGrid::~ButtonGrid | |
//=========================================== | |
ButtonGrid::~ButtonGrid() { | |
DBG_PRINT("ButtonGrid::~ButtonGrid()\n"); | |
} | |
#include <cmath> | |
#include <QMouseEvent> | |
#include "evasive_button.hpp" | |
#include "utils.hpp" | |
static const double FRAME_RATE = 20.0; | |
static const double RADIUS = 80.0; | |
//=========================================== | |
// distance | |
//=========================================== | |
inline static double distance(const QPoint& a, const QPoint& b) { | |
return sqrt(pow(a.x() - b.x(), 2) + pow(a.y() - b.y(), 2)); | |
} | |
//=========================================== | |
// length | |
//=========================================== | |
inline static double length(const QPoint& p) { | |
return sqrt(p.x() * p.x() + p.y() * p.y()); | |
} | |
//=========================================== | |
// normalise | |
//=========================================== | |
inline static QPointF normalise(const QPoint& p) { | |
return QPointF(p) / length(p); | |
} | |
//=========================================== | |
// clamp | |
//=========================================== | |
inline static double clamp(double x, double min, double max) { | |
if (x < min) { | |
return min; | |
} | |
if (x > max) { | |
return max; | |
} | |
return x; | |
} | |
//=========================================== | |
// EvasiveButton::EvasiveButton | |
//=========================================== | |
EvasiveButton::EvasiveButton(const QString& caption) | |
: QPushButton(caption), | |
m_active(false) { | |
setMouseTracking(true); | |
setFocusPolicy(Qt::NoFocus); | |
m_timer = makeQtObjPtr<QTimer>(this); | |
connect(m_timer.get(), SIGNAL(timeout()), this, SLOT(tick())); | |
reset(); | |
} | |
//=========================================== | |
// EvasiveButton::reset | |
//=========================================== | |
void EvasiveButton::reset() { | |
m_originalPos = pos(); | |
} | |
//=========================================== | |
// EvasiveButton::cursorInRange | |
//=========================================== | |
bool EvasiveButton::cursorInRange(QPoint cursor) const { | |
return distance(cursor, rect().center()) <= RADIUS; | |
} | |
//=========================================== | |
// EvasiveButton::onMouseMove | |
//=========================================== | |
void EvasiveButton::onMouseMove() { | |
QPoint cursor = mapFromGlobal(QCursor::pos()); | |
if (cursorInRange(cursor) || pos() != m_originalPos) { | |
if (!m_timer->isActive()) { | |
m_timer->start(1000.0 / FRAME_RATE); | |
} | |
} | |
} | |
//=========================================== | |
// EvasiveButton::tick | |
//=========================================== | |
void EvasiveButton::tick() { | |
QPoint cursorPos = mapFromGlobal(QCursor::pos()); | |
QPoint btnPos = pos(); | |
QPoint btnCentre = rect().center(); | |
// The button wants to get to m_originalPos and avoid cursorPos | |
double curDist = distance(cursorPos, btnCentre); | |
QPointF A; | |
QPointF B; | |
if (curDist >= 1.0) { | |
double maxSpeed = 700.0; // Pixels per second | |
QPointF v = normalise(btnCentre - cursorPos); | |
double speed = maxSpeed * clamp(1.0 - (curDist / RADIUS), 0, 1); | |
double delta = speed / FRAME_RATE; | |
A = delta * v; | |
} | |
double minReturnSpeed = 100.0; | |
double dist = distance(m_originalPos, btnPos); | |
if (dist >= 1.0) { | |
double speedAtRadius = 200.0; | |
QPointF v = normalise(m_originalPos - btnPos); | |
double speed = clamp(speedAtRadius * dist / RADIUS, minReturnSpeed, 100 * speedAtRadius); | |
double delta = speed / FRAME_RATE; | |
B = delta * v; | |
} | |
move(btnPos + (A + B).toPoint()); | |
if (distance(pos(), m_originalPos) <= minReturnSpeed / FRAME_RATE) { | |
move(m_originalPos); | |
m_timer->stop(); | |
} | |
} | |
//=========================================== | |
// EvasiveButton::mouseMoveEvent | |
//=========================================== | |
void EvasiveButton::mouseMoveEvent(QMouseEvent*) { | |
onMouseMove(); | |
} | |
//=========================================== | |
// EvasiveButton::~EvasiveButton | |
//=========================================== | |
EvasiveButton::~EvasiveButton() {} | |
#include "platform.hpp" | |
#ifdef WIN32 | |
#include <Windows.h> | |
#else | |
#include <unistd.h> | |
#endif | |
#ifdef WIN32 | |
void sleepThread(long millis) { | |
Sleep(millis); | |
} | |
#else | |
void sleepThread(long millis) { | |
usleep(millis * 1000); | |
} | |
#endif | |
#include "f_main_spec_factory.hpp" | |
#include "exception.hpp" | |
#include "app_config.hpp" | |
#include "fragments/f_main/f_main_spec.hpp" | |
#include "state_ids.hpp" | |
#include "states/st_normal_calc.hpp" | |
#include "states/st_danger_infinity.hpp" | |
#include "states/st_shuffled_keys.hpp" | |
#include "states/st_are_you_sure.hpp" | |
#include "states/st_its_raining_tetrominos.hpp" | |
#include "states/st_making_progress.hpp" | |
#include "states/st_youve_got_mail.hpp" | |
#include "states/st_going_in_circles.hpp" | |
#include "states/st_doomsweeper.hpp" | |
#include "states/st_t_minus_two_minutes.hpp" | |
#include "states/st_back_to_normal.hpp" | |
#include "states/st_test.hpp" | |
FMainSpec* makeFMainSpec(const AppConfig& appConfig) { | |
switch (appConfig.stateId) { | |
case ST_NORMAL_CALCULATOR_0: | |
case ST_NORMAL_CALCULATOR_1: | |
case ST_NORMAL_CALCULATOR_2: | |
case ST_NORMAL_CALCULATOR_3: | |
case ST_NORMAL_CALCULATOR_4: | |
case ST_NORMAL_CALCULATOR_5: | |
case ST_NORMAL_CALCULATOR_6: | |
case ST_NORMAL_CALCULATOR_7: | |
case ST_NORMAL_CALCULATOR_8: | |
case ST_NORMAL_CALCULATOR_9: | |
return st_normal_calc::makeFMainSpec(appConfig); | |
case ST_DANGER_INFINITY: | |
return st_danger_infinity::makeFMainSpec(appConfig); | |
case ST_SHUFFLED_KEYS: | |
return st_shuffled_keys::makeFMainSpec(appConfig); | |
case ST_ARE_YOU_SURE: | |
return st_are_you_sure::makeFMainSpec(appConfig); | |
case ST_ITS_RAINING_TETROMINOS: | |
return st_its_raining_tetrominos::makeFMainSpec(appConfig); | |
case ST_MAKING_PROGRESS: | |
return st_making_progress::makeFMainSpec(appConfig); | |
case ST_YOUVE_GOT_MAIL: | |
return st_youve_got_mail::makeFMainSpec(appConfig); | |
case ST_GOING_IN_CIRCLES: | |
return st_going_in_circles::makeFMainSpec(appConfig); | |
case ST_DOOMSWEEPER: | |
return st_doomsweeper::makeFMainSpec(appConfig); | |
case ST_T_MINUS_TWO_MINUTES: | |
return st_t_minus_two_minutes::makeFMainSpec(appConfig); | |
case ST_BACK_TO_NORMAL: | |
return st_back_to_normal::makeFMainSpec(appConfig); | |
case ST_TEST: | |
return st_test::makeFMainSpec(appConfig); | |
default: | |
EXCEPTION("Unrecognised state id"); | |
} | |
} | |
#include <cassert> | |
#include "strings.hpp" | |
//=========================================== | |
// ucs4ToUtf8 | |
//=========================================== | |
utf8string_t ucs4ToUtf8(const ucs4string_t& ucs) { | |
utf8string_t utf; | |
for (unsigned int i = 0; i < ucs.length(); ++i) { | |
uint32_t c = ucs.data()[i]; | |
if (c < 0x80) { | |
utf.push_back(c); | |
} | |
else if (c < 0x800) { | |
utf.push_back(0b11000000 + ((c & 0b11111000000) >> 6)); | |
utf.push_back(0b10000000 + (c & 0b111111)); | |
} | |
else if (c < 0x10000) { | |
utf.push_back(0b11100000 + ((c & 0b1111000000000000) >> 12)); | |
utf.push_back(0b10000000 + ((c & 0b111111000000) >> 6)); | |
utf.push_back(0b10000000 + (c & 0b111111)); | |
} | |
else if (c < 0x200000) { | |
utf.push_back(0b11110000 + ((c & 0b111000000000000000000) >> 18)); | |
utf.push_back(0b10000000 + ((c & 0b111111000000000000) >> 12)); | |
utf.push_back(0b10000000 + ((c & 0b111111000000) >> 6)); | |
utf.push_back(0b10000000 + (c & 0b111111)); | |
} | |
else if (c < 0x4000000) { | |
utf.push_back(0b11111000 + ((c & 0b11000000000000000000000000) >> 24)); | |
utf.push_back(0b10000000 + ((c & 0b111111000000000000000000) >> 18)); | |
utf.push_back(0b10000000 + ((c & 0b111111000000000000) >> 12)); | |
utf.push_back(0b10000000 + ((c & 0b111111000000) >> 6)); | |
utf.push_back(0b10000000 + (c & 0b111111)); | |
} | |
} | |
return utf; | |
} | |
//=========================================== | |
// utf8ToUcs4 | |
//=========================================== | |
ucs4string_t utf8ToUcs4(const utf8string_t& utf) { | |
ucs4string_t ucs; | |
uint32_t val = 0; | |
for (unsigned int i = 0; i < utf.length(); ++i) { | |
uint8_t c = utf.data()[i]; | |
if (c >> 7 == 0b0) { | |
if (i > 0) { | |
ucs.push_back(val); | |
} | |
val = c; | |
if (i + 1 == utf.length()) { | |
ucs.push_back(val); | |
} | |
} | |
else if (c >> 6 == 0b10) { | |
val = val << 6; | |
val += c & 0b111111; | |
if (i + 1 == utf.length()) { | |
ucs.push_back(val); | |
} | |
} | |
else if (c >> 5 == 0b110) { | |
if (i > 0) { | |
ucs.push_back(val); | |
} | |
val = c & 0b11111; | |
} | |
else if (c >> 4 == 0b1110) { | |
if (i > 0) { | |
ucs.push_back(val); | |
} | |
val = c & 0b1111; | |
} | |
else if (c >> 3 == 0b11110) { | |
if (i > 0) { | |
ucs.push_back(val); | |
} | |
val = c & 0b111; | |
} | |
else if (c >> 2 == 0b111110) { | |
if (i > 0) { | |
ucs.push_back(val); | |
} | |
val = c & 0b11; | |
} | |
else if (c >> 1 == 0b1111110) { | |
if (i > 0) { | |
ucs.push_back(val); | |
} | |
val = c & 0b1; | |
} | |
else { | |
assert(false); | |
} | |
} | |
return ucs; | |
} | |
#include <cassert> | |
#include <QEvent> | |
#include <QCoreApplication> | |
#include "event_system.hpp" | |
#include "utils.hpp" | |
using std::string; | |
using std::map; | |
using std::vector; | |
static int CUSTOM_EVENT_TYPE = QEvent::registerEventType(); | |
struct CustomEvent : public QEvent { | |
CustomEvent(pEvent_t event) | |
: QEvent(static_cast<QEvent::Type>(CUSTOM_EVENT_TYPE)), | |
event(std::move(event)) {} | |
pEvent_t event; | |
}; | |
//=========================================== | |
// EventSystem::event | |
//=========================================== | |
bool EventSystem::event(QEvent* e) { | |
if (e->type() == CUSTOM_EVENT_TYPE) { | |
CustomEvent* event = dynamic_cast<CustomEvent*>(e); | |
assert(event != nullptr); | |
processEvent(*event->event); | |
return true; | |
} | |
return QObject::event(e); | |
} | |
//=========================================== | |
// EventSystem::listen | |
//=========================================== | |
EventSystem::Handle EventSystem::listen(const string& name, handlerFunc_t fn) { | |
static int nextId = 0; | |
if (m_processingEvent) { | |
m_pendingAddition[name][nextId] = fn; | |
} | |
else { | |
m_handlers[name][nextId] = fn; | |
} | |
return Handle{shared_from_this(), nextId++}; | |
} | |
//=========================================== | |
// EventSystem::forget_ | |
//=========================================== | |
void EventSystem::forget_(int id) { | |
for (auto it = m_handlers.begin(); it != m_handlers.end(); ++it) { | |
map<int, handlerFunc_t>& fns = it->second; | |
fns.erase(id); | |
} | |
} | |
//=========================================== | |
// EventSystem::forget | |
//=========================================== | |
void EventSystem::forget(Handle& handle) { | |
if (handle.id == -1) { | |
return; | |
} | |
if (m_processingEvent) { | |
m_pendingForget.insert(handle.id); | |
} | |
else { | |
forget_(handle.id); | |
} | |
handle.id = -1; | |
} | |
//=========================================== | |
// EventSystem::addPending | |
//=========================================== | |
void EventSystem::addPending() { | |
for (auto it = m_pendingAddition.begin(); it != m_pendingAddition.end(); ++it) { | |
string name = it->first; | |
auto handlersById = it->second; | |
for (auto jt = handlersById.begin(); jt != handlersById.end(); ++jt) { | |
int id = jt->first; | |
auto handlerFn = jt->second; | |
m_handlers[name][id] = handlerFn; | |
} | |
} | |
m_pendingAddition.clear(); | |
} | |
//=========================================== | |
// EventSystem::forgetPending | |
//=========================================== | |
void EventSystem::forgetPending() { | |
for (auto it = m_pendingForget.begin(); it != m_pendingForget.end(); ++it) { | |
forget_(*it); | |
} | |
m_pendingForget.clear(); | |
} | |
//=========================================== | |
// EventSystem::processingStart | |
//=========================================== | |
void EventSystem::processingStart() { | |
m_processingEvent = true; | |
} | |
//=========================================== | |
// EventSystem::processingEnd | |
//=========================================== | |
void EventSystem::processingEnd() { | |
forgetPending(); | |
addPending(); | |
m_processingEvent = false; | |
} | |
//=========================================== | |
// EventSystem::processEvent_ | |
//=========================================== | |
void EventSystem::processEvent_(const string& name, const Event& event) { | |
DBG_PRINT("Calling handlers for: " << name << "\n"); | |
auto it = m_handlers.find(name); | |
if (it != m_handlers.end()) { | |
const map<int, handlerFunc_t>& fns = it->second; | |
for (auto jt = fns.begin(); jt != fns.end(); ++jt) { | |
jt->second(event); | |
} | |
} | |
} | |
//=========================================== | |
// EventSystem::processEvent | |
//=========================================== | |
void EventSystem::processEvent(const Event& event) { | |
processingStart(); | |
vector<string> v = splitString(event.name, '.'); | |
string name; | |
processEvent_(name, event); | |
for (auto it = v.begin(); it != v.end(); ++it) { | |
if (name.length() > 0) { | |
name.append("."); | |
} | |
name.append(*it); | |
processEvent_(name, event); | |
} | |
processingEnd(); | |
} | |
//=========================================== | |
// EventSystem::fire | |
//=========================================== | |
void EventSystem::fire(pEvent_t event) { | |
DBG_PRINT("Event: " << event->name << "\n"); | |
QCoreApplication::postEvent(this, new CustomEvent(std::move(event))); | |
} | |
#include <cmath> | |
#include <QPixmap> | |
#include <QPainter> | |
#include "exploding_button.hpp" | |
#include "utils.hpp" | |
#include "update_loop.hpp" | |
#include "app_config.hpp" | |
//=========================================== | |
// ExplodingButton::ExplodingButton | |
//=========================================== | |
ExplodingButton::ExplodingButton(QWidget* parent, const QString& caption, | |
const AppConfig& appConfig, UpdateLoop& updateLoop) | |
: QPushButton(caption, parent), | |
m_updateLoop(updateLoop) { | |
m_spriteSheet.reset(new QImage(appConfig.dataPath("common/images/explosion.png").c_str())); | |
m_wgtLabel = makeQtObjPtr<QLabel>(parent); | |
m_wgtLabel->setFixedSize(100, 100); | |
m_wgtLabel->hide(); | |
connect(this, SIGNAL(clicked()), this, SLOT(onClick())); | |
} | |
//=========================================== | |
// ExplodingButton::onClick | |
//=========================================== | |
void ExplodingButton::onClick() { | |
QPoint A = geometry().center(); | |
QPoint B = m_wgtLabel->geometry().center(); | |
m_wgtLabel->move(m_wgtLabel->pos() + (A - B)); | |
m_wgtLabel->show(); | |
int nFrames = 5; | |
int ticks = 10; | |
int frameW = m_spriteSheet->width() / nFrames; | |
int frameH = m_spriteSheet->height(); | |
double ticksPerFrame = static_cast<double>(ticks) / nFrames; | |
QPixmap pixmap(100, 100); | |
hide(); | |
int f = 0; | |
m_updateLoop.add([=]() mutable { | |
pixmap.fill(QColor(0, 0, 0, 0)); | |
QPainter painter; | |
painter.begin(&pixmap); | |
int i = static_cast<int>(static_cast<double>(f) / ticksPerFrame); | |
int frameX = i * frameW; | |
QRect trg(QPoint(0, 0), m_wgtLabel->size()); | |
QRect src(frameX, 0, frameW, frameH); | |
painter.drawImage(trg, *m_spriteSheet, src); | |
m_wgtLabel->setPixmap(pixmap); | |
return ++f < ticks; | |
}, [this]() { | |
m_wgtLabel->hide(); | |
}); | |
} | |
//=========================================== | |
// ExplodingButton::~ExplodingButton | |
//=========================================== | |
ExplodingButton::~ExplodingButton() {} | |
#include "fragment_spec.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FragmentSpec::FragmentSpec | |
//=========================================== | |
FragmentSpec::FragmentSpec(const std::string& type, std::vector<const FragmentSpec*> specs) | |
: m_enabled(false), | |
m_type(type), | |
m_id(0), | |
m_tmpChildren(specs) {} | |
//=========================================== | |
// FragmentSpec::FragmentSpec | |
//=========================================== | |
FragmentSpec::FragmentSpec(const std::string& type, int id, std::vector<const FragmentSpec*> specs) | |
: m_enabled(false), | |
m_type(type), | |
m_id(id), | |
m_tmpChildren(specs) {} | |
//=========================================== | |
// FragmentSpec::setEnabled | |
//=========================================== | |
void FragmentSpec::setEnabled(bool b) { | |
m_enabled = b; | |
} | |
//=========================================== | |
// FragmentSpec::isEnabled | |
//=========================================== | |
bool FragmentSpec::isEnabled() const { | |
return m_enabled; | |
} | |
//=========================================== | |
// FragmentSpec::specs | |
//=========================================== | |
const std::map<std::string, const FragmentSpec*>& FragmentSpec::specs() const { | |
populateChildrenMap(); | |
return m_children; | |
} | |
//=========================================== | |
// FragmentSpec::populateChildrenMap | |
//=========================================== | |
void FragmentSpec::populateChildrenMap() const { | |
if (m_tmpChildren.size() > 0) { | |
for (auto it = m_tmpChildren.begin(); it != m_tmpChildren.end(); ++it) { | |
m_children.insert({(*it)->name(), *it}); | |
} | |
m_tmpChildren.clear(); | |
} | |
} | |
//=========================================== | |
// FragmentSpec::spec | |
//=========================================== | |
const FragmentSpec& FragmentSpec::spec(const std::string& name) const { | |
populateChildrenMap(); | |
return *m_children.at(name); | |
} | |
//=========================================== | |
// FragmentSpec::type | |
//=========================================== | |
const std::string& FragmentSpec::type() const { | |
return m_type; | |
} | |
//=========================================== | |
// FragmentSpec::name | |
// | |
// Type name with id appended | |
//=========================================== | |
std::string FragmentSpec::name() const { | |
return m_type + std::to_string(m_id); | |
} | |
//=========================================== | |
// FragmentSpec::~FragmentSpec | |
//=========================================== | |
FragmentSpec::~FragmentSpec() {} | |
#include <vector> | |
#include <sstream> | |
#include "raycast/player.hpp" | |
#include "raycast/camera.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/behaviour_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/c_player_behaviour.hpp" | |
using std::vector; | |
using std::string; | |
using std::stringstream; | |
const double FOREHEAD_SIZE = 15.0; | |
const double COLLISION_RADIUS = 10.0; | |
const double HUD_H = 1.0; | |
const double INVENTORY_H = 1.2; | |
const double V_MARGIN = 0.15; | |
//=========================================== | |
// Player::Player | |
//=========================================== | |
Player::Player(EntityManager& entityManager, AudioService& audioService, TimeService& timeService, | |
const parser::Object& obj, entityId_t parentId, const Matrix& parentTransform) | |
: m_entityManager(entityManager), | |
m_audioService(audioService), | |
m_timeService(timeService), | |
m_shootTimer(0.5) { | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
auto& animationSystem = m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
auto& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
auto& behaviourSystem = m_entityManager.system<BehaviourSystem>(ComponentKind::C_BEHAVIOUR); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
auto& inventorySystem = m_entityManager.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
constructPlayer(obj, parentId, parentTransform, spatialSystem, renderSystem, animationSystem, | |
damageSystem, behaviourSystem, eventHandlerSystem); | |
constructInventory(renderSystem, inventorySystem, eventHandlerSystem, damageSystem); | |
setupHudShowHide(renderSystem, eventHandlerSystem); | |
} | |
//=========================================== | |
// makeTween | |
//=========================================== | |
static void makeTween(RenderSystem& renderSystem, TimeService& timeService, const string& name, | |
double duration, entityId_t overlayId, double fromY, double toY) { | |
if (renderSystem.hasComponent(overlayId)) { | |
auto& overlay = dynamic_cast<COverlay&>(renderSystem.getComponent(overlayId)); | |
double s = toY - fromY; | |
double ds = s / (duration * timeService.frameRate); | |
Tween tween{ | |
[=, &overlay](long, double, double) -> bool { | |
overlay.pos.y += ds; | |
if (fabs(overlay.pos.y - fromY) >= fabs(s)) { | |
overlay.pos.y = toY; | |
return false; | |
} | |
return true; | |
}, | |
[](long, double, double) {}}; | |
timeService.addTween(tween, name); | |
} | |
} | |
//=========================================== | |
// Player::setupHudShowHide | |
//=========================================== | |
void Player::setupHudShowHide(RenderSystem& renderSystem, EventHandlerSystem& eventHandlerSystem) { | |
// Transition time | |
const double T = 0.3; | |
const Size& viewport = renderSystem.rg.viewport; | |
CEventHandler& events = eventHandlerSystem.getComponent(this->body); | |
events.broadcastedEventHandlers.push_back(EventHandler{"mouse_captured", | |
[=, &renderSystem, &viewport] (const GameEvent&) { | |
m_timeService.removeTween("gunSlideOut"); | |
makeTween(renderSystem, m_timeService, "gunSlideIn", T, this->sprite, -4.0, 0.0); | |
m_timeService.removeTween("ammoSlideOut"); | |
makeTween(renderSystem, m_timeService, "ammoSlideId", T, m_ammoId, viewport.y + V_MARGIN, | |
viewport.y - HUD_H + V_MARGIN); | |
m_timeService.removeTween("healthSlideOut"); | |
makeTween(renderSystem, m_timeService, "healthSlideIn", T, m_healthId, viewport.y + V_MARGIN, | |
viewport.y - HUD_H + V_MARGIN); | |
m_timeService.removeTween("itemsSlideOut"); | |
makeTween(renderSystem, m_timeService, "itemsSlideIn", T, m_itemsId, -INVENTORY_H, 0.0); | |
m_timeService.removeTween("hudBgSlideOut"); | |
makeTween(renderSystem, m_timeService, "hudBgSlideIn", T, m_hudBgId, viewport.y, | |
viewport.y - HUD_H); | |
if (renderSystem.hasComponent(this->crosshair)) { | |
auto& crosshair = dynamic_cast<CImageOverlay&>(renderSystem.getComponent(this->crosshair)); | |
crosshair.pos = viewport / 2 - Vec2f(0.5, 0.5) / 2; | |
} | |
}}); | |
events.broadcastedEventHandlers.push_back(EventHandler{"mouse_uncaptured", | |
[=, &renderSystem, &viewport](const GameEvent&) { | |
m_timeService.removeTween("gunSlideIn"); | |
makeTween(renderSystem, m_timeService, "gunSlideOut", T, this->sprite, 0.0, -4.0); | |
m_timeService.removeTween("ammoSlideIn"); | |
makeTween(renderSystem, m_timeService, "ammoSlideOut", T, m_ammoId, | |
viewport.y - HUD_H + V_MARGIN, viewport.y + V_MARGIN); | |
m_timeService.removeTween("healthSlideIn"); | |
makeTween(renderSystem, m_timeService, "healthSlideOut", T, m_healthId, | |
viewport.y - HUD_H + V_MARGIN, viewport.y + V_MARGIN); | |
m_timeService.removeTween("itemsSlideIn"); | |
makeTween(renderSystem, m_timeService, "itemsSlideOut", T, m_itemsId, 0.0, -INVENTORY_H); | |
m_timeService.removeTween("hudBgSlideIn"); | |
makeTween(renderSystem, m_timeService, "hudBgSlideOut", T, m_hudBgId, viewport.y - HUD_H, | |
viewport.y); | |
if (renderSystem.hasComponent(this->crosshair)) { | |
auto& crosshair = dynamic_cast<CImageOverlay&>(renderSystem.getComponent(this->crosshair)); | |
crosshair.pos = Point(10, 10); | |
} | |
}}); | |
} | |
//=========================================== | |
// Player::constructPlayer | |
//=========================================== | |
void Player::constructPlayer(const parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform, SpatialSystem& spatialSystem, RenderSystem& renderSystem, | |
AnimationSystem& animationSystem, DamageSystem& damageSystem, BehaviourSystem& behaviourSystem, | |
EventHandlerSystem& eventHandlerSystem) { | |
double tallness = std::stod(getValue(obj.dict, "tallness")); | |
this->body = Component::getIdFromString("player"); | |
CZone& zone = dynamic_cast<CZone&>(spatialSystem.getComponent(parentId)); | |
Matrix m = parentTransform * obj.groupTransform * obj.pathTransform | |
* transformFromTriangle(obj.path); | |
CVRect* b = new CVRect(body, zone.entityId(), Size(0, 0)); | |
b->setTransform(m); | |
b->zone = &zone; | |
b->size.x = 60.0; | |
b->size.y = tallness + FOREHEAD_SIZE; | |
spatialSystem.addComponent(pCSpatial_t(b)); | |
m_camera.reset(new Camera(renderSystem.rg.viewport.x, DEG_TO_RAD(60), DEG_TO_RAD(50), *b, | |
tallness - FOREHEAD_SIZE + zone.floorHeight)); | |
this->sprite = Component::getNextId(); | |
this->crosshair = Component::getNextId(); | |
CSprite* bodySprite = new CSprite(this->body, parentId, "player"); | |
renderSystem.addComponent(pComponent_t(bodySprite)); | |
// Number of frames in sprite sheet | |
const int W = 8; | |
const int H = 14; | |
CAnimation* bodyAnims = new CAnimation(this->body); | |
vector<AnimationFrame> runFrames = constructFrames(W, H, | |
{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }); | |
bodyAnims->addAnimation(pAnimation_t(new Animation("run", m_timeService.frameRate, 1.0, | |
runFrames))); | |
vector<AnimationFrame> shootFrames = constructFrames(W, H, { 1, 0 }); | |
bodyAnims->addAnimation(pAnimation_t(new Animation("shoot", m_timeService.frameRate, 1.0, | |
shootFrames))); | |
vector<AnimationFrame> idleFrames = constructFrames(W, H, { 0 }); | |
bodyAnims->addAnimation(pAnimation_t(new Animation("idle", m_timeService.frameRate, 1.0, | |
idleFrames))); | |
animationSystem.addComponent(pComponent_t(bodyAnims)); | |
animationSystem.playAnimation(this->body, "idle", true); | |
CDamage* damage = new CDamage(this->body, 10, 10); | |
damageSystem.addComponent(pCDamage_t(damage)); | |
CPlayerBehaviour* behaviour = new CPlayerBehaviour(this->body, m_entityManager, m_timeService); | |
behaviourSystem.addComponent(pComponent_t(behaviour)); | |
const Size& viewport = renderSystem.rg.viewport; | |
Size sz(0.5, 0.5); | |
CImageOverlay* crosshair = new CImageOverlay(this->crosshair, "crosshair", Point(10, 10), sz); | |
renderSystem.addComponent(pCRender_t(crosshair)); | |
CImageOverlay* gunSprite = new CImageOverlay(this->sprite, "gun", Point(viewport.x * 0.5, -4), | |
Size(4, 4)); | |
gunSprite->texRect = QRectF(0, 0, 0.25, 1); | |
renderSystem.addComponent(pCRender_t(gunSprite)); | |
CAnimation* shoot = new CAnimation(this->sprite); | |
shoot->addAnimation(pAnimation_t(new Animation("shoot", m_timeService.frameRate, 0.4, { | |
AnimationFrame{{ | |
QRectF(0.75, 0, 0.25, 1) | |
}}, | |
AnimationFrame{{ | |
QRectF(0.5, 0, 0.25, 1) | |
}}, | |
AnimationFrame{{ | |
QRectF(0.25, 0, 0.25, 1) | |
}}, | |
AnimationFrame{{ | |
QRectF(0, 0, 0.25, 1) | |
}} | |
}))); | |
shoot->addAnimation(pAnimation_t(new Animation("shoot_no_ammo", m_timeService.frameRate, 0.2, { | |
AnimationFrame{{ | |
QRectF(0.25, 0, 0.25, 1) | |
}}, | |
AnimationFrame{{ | |
QRectF(0, 0, 0.25, 1) | |
}} | |
}))); | |
animationSystem.addComponent(pCAnimation_t(shoot)); | |
CEventHandler* events = new CEventHandler(this->body); | |
events->targetedEventHandlers.push_back(EventHandler{"player_move", | |
[=, &animationSystem](const GameEvent&) { | |
if (animationSystem.animationState(this->body, "run") == AnimState::STOPPED) { | |
animationSystem.playAnimation(this->body, "run", false); | |
} | |
}}); | |
events->targetedEventHandlers.push_back(EventHandler{"animation_finished", | |
[=, &animationSystem](const GameEvent& e_) { | |
const EAnimationFinished& e = dynamic_cast<const EAnimationFinished&>(e_); | |
if (e.animName == "run") { | |
animationSystem.playAnimation(this->body, "idle", true); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
} | |
//=========================================== | |
// Player::constructInventory | |
//=========================================== | |
void Player::constructInventory(RenderSystem& renderSystem, InventorySystem& inventorySystem, | |
EventHandlerSystem& eventHandlerSystem, DamageSystem& damageSystem) { | |
const RenderGraph& rg = renderSystem.rg; | |
const Size& viewport = rg.viewport; | |
CCollector* inventory = new CCollector(this->body); | |
inventory->buckets["ammo"] = pBucket_t(new CounterBucket(50)); | |
inventory->buckets["item"] = pBucket_t(new ItemBucket(5)); | |
inventorySystem.addComponent(pComponent_t(inventory)); | |
const double INVENTORY_W = 6.0; | |
double itemsDisplayAspectRatio = INVENTORY_W / INVENTORY_H; | |
double itemsDisplayH_px = INVENTORY_H * rg.hWorldUnit_px; | |
double itemsDisplayW_px = itemsDisplayH_px * itemsDisplayAspectRatio; | |
QImage imgItems(itemsDisplayW_px, itemsDisplayH_px, QImage::Format_ARGB32); | |
imgItems.fill(Qt::GlobalColor::transparent); | |
renderSystem.rg.textures["items_display"] = Texture{imgItems, Size(0, 0)}; | |
m_ammoId = Component::getNextId(); | |
m_healthId = Component::getNextId(); | |
m_itemsId = Component::getNextId(); | |
// Start everything off-screen | |
// | |
CImageOverlay* itemsDisplay = new CImageOverlay(m_itemsId, "items_display", | |
Point(0, -INVENTORY_H), Size(INVENTORY_W, INVENTORY_H), 1); | |
renderSystem.addComponent(pComponent_t(itemsDisplay)); | |
double hMargin = 0.15; | |
CTextOverlay* ammoCounter = new CTextOverlay(m_ammoId, "AMMO 0/50", | |
Point(hMargin, viewport.y + V_MARGIN), HUD_H - 2.0 * V_MARGIN, Qt::green, 2); | |
renderSystem.addComponent(pComponent_t(ammoCounter)); | |
CTextOverlay* healthCounter = new CTextOverlay(m_healthId, "HEALTH 10/10", | |
Point(0, viewport.y + V_MARGIN), HUD_H - 2.0 * V_MARGIN, Qt::red, 2); | |
healthCounter->pos.x = viewport.x - renderSystem.textOverlayWidth(*healthCounter) - hMargin; | |
renderSystem.addComponent(pComponent_t(healthCounter)); | |
CEventHandler& events = eventHandlerSystem.getComponent(this->body); | |
events.targetedEventHandlers.push_back(EventHandler{"bucket_count_change", | |
[=, &damageSystem](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EBucketCountChange&>(e_); | |
if (e.collectableType == "ammo") { | |
stringstream ss; | |
ss << "AMMO " << e.bucket.count << "/" << e.bucket.capacity; | |
ammoCounter->text = ss.str(); | |
if (e.bucket.count > e.prevCount) { | |
m_audioService.playSound("ammo_collect"); | |
} | |
} | |
}}); | |
events.targetedEventHandlers.push_back(EventHandler{"collectable_encountered", | |
[this, &damageSystem](const GameEvent& e_) { | |
auto& e = dynamic_cast<const ECollectableEncountered&>(e_); | |
if (e.item.collectableType == "health_pack") { | |
if (damageSystem.getHealth(this->body) < damageSystem.getMaxHealth(this->body)) { | |
DBG_PRINT("Health pack collected\n"); | |
m_audioService.playSound("health_pack_collect"); | |
damageSystem.damageEntity(this->body, -e.item.value); | |
m_entityManager.deleteEntity(e.item.entityId()); | |
} | |
} | |
}}); | |
events.targetedEventHandlers.push_back(EventHandler{"bucket_items_change", | |
[=, &renderSystem, &rg](const GameEvent& e_) { | |
const EBucketItemsChange& e = dynamic_cast<const EBucketItemsChange&>(e_); | |
if (e.collectableType == "item") { | |
QImage& target = renderSystem.rg.textures["items_display"].image; | |
target.fill(Qt::GlobalColor::transparent); | |
QPainter painter; | |
painter.begin(&target); | |
int i = 0; | |
for (auto it = e.bucket.items.rbegin(); it != e.bucket.items.rend(); ++it) { | |
entityId_t id = it->second; | |
const CRender& c = dynamic_cast<const CRender&>(renderSystem.getComponent(id)); | |
if (c.kind == CRenderKind::SPRITE) { | |
const CSprite& sprite = dynamic_cast<const CSprite&>(c); | |
const QImage& img = renderSystem.rg.textures.at(sprite.texture).image; | |
double slotH = itemsDisplayW_px; | |
double slotW = itemsDisplayW_px / e.bucket.capacity; | |
double slotX = slotW * i; | |
double slotY = 0; | |
double vMargin = V_MARGIN * rg.hWorldUnit_px; | |
double hMargin = 0.1 * rg.hWorldUnit_px; | |
double aspectRatio = static_cast<double>(img.width()) / img.height(); | |
double maxH = slotH - vMargin * 2.0; | |
double maxW = slotW - hMargin * 2.0; | |
double h = maxH; | |
double w = h * aspectRatio; | |
double s = smallest(maxH / h, maxW / w); | |
h *= s; | |
w *= s; | |
QRect srcRect(0, 0, img.width(), img.height()); | |
QRect trgRect(slotX + hMargin, slotY + vMargin, w, h); | |
painter.setBrush(QColor(0, 0, 0, 100)); | |
painter.setPen(Qt::NoPen); | |
painter.drawRect(slotX, slotY, slotW, slotH); | |
painter.drawImage(trgRect, img, srcRect); | |
} | |
++i; | |
} | |
painter.end(); | |
if (static_cast<int>(e.bucket.items.size()) > e.prevCount) { | |
m_audioService.playSound("item_collect"); | |
} | |
} | |
}}); | |
events.targetedEventHandlers.push_back(EventHandler{"entity_damaged", | |
[=, &damageSystem](const GameEvent&) { | |
const CDamage& damage = dynamic_cast<const CDamage&>(damageSystem.getComponent(this->body)); | |
stringstream ss; | |
ss << "HEALTH " << damage.health << "/" << damage.maxHealth; | |
healthCounter->text = ss.str(); | |
}}); | |
m_hudBgId = Component::getNextId(); | |
CColourOverlay* bg = new CColourOverlay(m_hudBgId, QColor(0, 0, 0, 100), Point(0, viewport.y), | |
Size(viewport.x, HUD_H), 1); | |
renderSystem.addComponent(pComponent_t(bg)); | |
} | |
//=========================================== | |
// Player::region | |
//=========================================== | |
entityId_t Player::region() const { | |
return getBody().zone->entityId(); | |
} | |
//=========================================== | |
// Player::aboveGround | |
//=========================================== | |
bool Player::aboveGround() const { | |
return feetHeight() - 0.1 > getBody().zone->floorHeight; | |
} | |
//=========================================== | |
// Player::belowGround | |
//=========================================== | |
bool Player::belowGround() const { | |
return feetHeight() + 0.1 < getBody().zone->floorHeight; | |
} | |
//=========================================== | |
// Player::feetHeight | |
//=========================================== | |
double Player::feetHeight() const { | |
return eyeHeight() + FOREHEAD_SIZE - getTallness(); | |
} | |
//=========================================== | |
// Player::headHeight | |
//=========================================== | |
double Player::headHeight() const { | |
return eyeHeight() + FOREHEAD_SIZE; | |
} | |
//=========================================== | |
// Player::eyeHeight | |
//=========================================== | |
double Player::eyeHeight() const { | |
return m_camera->height; | |
} | |
//=========================================== | |
// Player::getTallness | |
//=========================================== | |
double Player::getTallness() const { | |
return getBody().size.y; | |
} | |
//=========================================== | |
// Player::changeTallness | |
//=========================================== | |
void Player::changeTallness(double delta) { | |
getBody().size.y += delta; | |
m_camera->height += delta; | |
} | |
//=========================================== | |
// Player::setFeetHeight | |
//=========================================== | |
void Player::setFeetHeight(double h) { | |
m_camera->height = h + getTallness() - FOREHEAD_SIZE; | |
} | |
//=========================================== | |
// Player::setEyeHeight | |
//=========================================== | |
void Player::setEyeHeight(double h) { | |
m_camera->height = h; | |
} | |
//=========================================== | |
// Player::changeHeight | |
//=========================================== | |
void Player::changeHeight(const CZone& zone, double deltaH) { | |
// If applying this delta puts the player's feet through the floor | |
if (feetHeight() + deltaH < zone.floorHeight) { | |
// Only permit positive delta | |
if (deltaH <= 0) { | |
// Reset to floor height | |
setFeetHeight(zone.floorHeight); | |
return; | |
} | |
} | |
// If applying this delta puts the player's head through the ceiling | |
else if (zone.hasCeiling && (headHeight() + deltaH > zone.ceilingHeight)) { | |
// Only permit negative delta | |
if (deltaH >= 0) { | |
vVelocity = 0; | |
return; | |
} | |
} | |
m_camera->height += deltaH; | |
} | |
//=========================================== | |
// Player::pos | |
//=========================================== | |
const Point& Player::pos() const { | |
return getBody().pos; | |
} | |
//=========================================== | |
// Player::dir | |
//=========================================== | |
Vec2f Player::dir() const { | |
return Vec2f(cos(getBody().angle), sin(getBody().angle)); | |
} | |
//=========================================== | |
// Player::getBody | |
//=========================================== | |
CVRect& Player::getBody() const { | |
return m_entityManager.getComponent<CVRect>(body, ComponentKind::C_SPATIAL); | |
} | |
//=========================================== | |
// Player::setPosition | |
//=========================================== | |
void Player::setPosition(const Point& pos) { | |
getBody().pos = pos; | |
} | |
//=========================================== | |
// Player::hRotate | |
//=========================================== | |
void Player::hRotate(double da) { | |
getBody().angle += da; | |
} | |
//=========================================== | |
// Player::vRotate | |
//=========================================== | |
void Player::vRotate(double da) { | |
if (fabs(m_camera->vAngle + da) <= DEG_TO_RAD(20)) { | |
m_camera->vAngle += da; | |
} | |
} | |
//=========================================== | |
// Player::shoot | |
//=========================================== | |
void Player::shoot() { | |
if (m_shootTimer.ready()) { | |
InventorySystem& inventorySystem = m_entityManager | |
.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
AnimationSystem& animationSystem = m_entityManager | |
.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
if (inventorySystem.getBucketValue(body, "ammo") > 0) { | |
DamageSystem& damageSystem = m_entityManager | |
.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
animationSystem.playAnimation(sprite, "shoot", false); | |
animationSystem.playAnimation(body, "shoot", false); | |
m_audioService.playSound("pistol_shoot"); | |
inventorySystem.subtractFromBucket(body, "ammo", 1); | |
damageSystem.damageAtIntersection(Vec2f(1, 0), 0, 1); | |
} | |
else { | |
animationSystem.playAnimation(sprite, "shoot_no_ammo", false); | |
m_audioService.playSound("click"); | |
} | |
} | |
} | |
//=========================================== | |
// Player::camera | |
//=========================================== | |
const Camera& Player::camera() const { | |
return *m_camera; | |
} | |
#include <list> | |
#include <cassert> | |
#include "raycast/geometry_factory.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/time_service.hpp" | |
#include "utils.hpp" | |
using std::stringstream; | |
using std::unique_ptr; | |
using std::function; | |
using std::string; | |
using std::list; | |
using std::map; | |
using std::set; | |
using std::vector; | |
const double SNAP_DISTANCE = 1.0; | |
//=========================================== | |
// snapEndpoint | |
//=========================================== | |
static void snapEndpoint(map<Point, bool>& endpoints, Point& pt) { | |
for (auto it = endpoints.begin(); it != endpoints.end(); ++it) { | |
if (distance(pt, it->first) <= SNAP_DISTANCE) { | |
pt = it->first; | |
it->second = true; | |
return; | |
} | |
} | |
endpoints[pt] = false; | |
}; | |
//=========================================== | |
// GeometryFactory::constructPath | |
//=========================================== | |
bool GeometryFactory::constructPath(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
Matrix m = parentTransform * obj.groupTransform * obj.pathTransform; | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
CPath* path = new CPath(entityId, parentId); | |
for (unsigned int i = 0; i < obj.path.points.size(); ++i) { | |
path->points.push_back(m * obj.path.points[i]); | |
} | |
spatialSystem.addComponent(pComponent_t(path)); | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructWallDecal | |
//=========================================== | |
bool GeometryFactory::constructWallDecal(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
auto& renderSystem = m_entityManager.system<RenderSystem&>(ComponentKind::C_RENDER); | |
auto& focusSystem = m_entityManager.system<FocusSystem&>(ComponentKind::C_FOCUS); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
const CEdge& edge = dynamic_cast<const CEdge&>(spatialSystem.getComponent(parentId)); | |
const LineSegment& wall = edge.lseg; | |
LineSegment lseg(obj.path.points[0], obj.path.points[1]); | |
lseg = transform(lseg, parentTransform * obj.groupTransform * obj.pathTransform); | |
Point A = lseg.A; | |
Point B = lseg.B; | |
double a_ = distance(wall.A, A); | |
double b_ = distance(wall.A, B); | |
double a = smallest(a_, b_); | |
double b = largest(a_, b_); | |
double w = b - a; | |
if (distanceFromLine(wall.line(), A) > SNAP_DISTANCE | |
|| distanceFromLine(wall.line(), B) > SNAP_DISTANCE) { | |
return false; | |
} | |
double delta = SNAP_DISTANCE; | |
if (!(isBetween(A.x, wall.A.x, wall.B.x, delta) | |
&& isBetween(A.y, wall.A.y, wall.B.y, delta) | |
&& isBetween(B.x, wall.A.x, wall.B.x, delta) | |
&& isBetween(B.y, wall.A.y, wall.B.y, delta))) { | |
return false; | |
} | |
CZone& zone = dynamic_cast<CZone&>(spatialSystem.getComponent(edge.parentId)); | |
double r = std::stod(getValue(obj.dict, "aspect_ratio")); | |
Size size(w, w / r); | |
double y = std::stod(getValue(obj.dict, "y")); | |
Point pos(a, y); | |
int zIndex = std::stoi(getValue(obj.dict, "z_index", "1")); | |
string texture = getValue(obj.dict, "texture", "default"); | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
CVRect* vRect = new CVRect(entityId, parentId, size); | |
vRect->pos = pos; | |
vRect->zone = &zone; | |
spatialSystem.addComponent(pComponent_t(vRect)); | |
CWallDecal* decal = new CWallDecal(entityId, parentId); | |
decal->texture = texture; | |
decal->zIndex = zIndex; | |
renderSystem.addComponent(pComponent_t(decal)); | |
string hoverText = getValue(obj.dict, "hover_text", ""); | |
string captionText = getValue(obj.dict, "caption_text", ""); | |
if (hoverText != "" || captionText != "") { | |
CFocus* focus = new CFocus(entityId); | |
focus->hoverText = hoverText; | |
focus->captionText = captionText; | |
focusSystem.addComponent(pComponent_t(focus)); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->targetedEventHandlers.push_back(EventHandler{"player_activate_entity", | |
[=, &focusSystem](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EPlayerActivateEntity&>(e_); | |
if (e.lookingAt.count(entityId)) { | |
focusSystem.showCaption(entityId); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
} | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructWalls | |
//=========================================== | |
bool GeometryFactory::constructWalls(parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
CZone& zone = dynamic_cast<CZone&>(spatialSystem.getComponent(parentId)); | |
CRegion& region = dynamic_cast<CRegion&>(renderSystem.getComponent(parentId)); | |
Matrix m = parentTransform * obj.groupTransform * obj.pathTransform; | |
list<CHardEdge*> edges; | |
for (unsigned int i = 0; i < obj.path.points.size(); ++i) { | |
int j = i - 1; | |
if (i == 0) { | |
if (obj.path.closed) { | |
j = static_cast<int>(obj.path.points.size()) - 1; | |
} | |
else { | |
continue; | |
} | |
} | |
entityId_t entityId = Component::getNextId(); | |
CHardEdge* edge = new CHardEdge(entityId, zone.entityId()); | |
edge->lseg.A = obj.path.points[j]; | |
edge->lseg.B = obj.path.points[i]; | |
edge->lseg = transform(edge->lseg, m); | |
edge->zone = &zone; | |
edges.push_back(edge); | |
snapEndpoint(m_endpoints, edge->lseg.A); | |
snapEndpoint(m_endpoints, edge->lseg.B); | |
spatialSystem.addComponent(pComponent_t(edge)); | |
CWall* wall = new CWall(entityId, region.entityId()); | |
wall->region = ®ion; | |
wall->texture = getValue(obj.dict, "texture", "default"); | |
renderSystem.addComponent(pComponent_t(wall)); | |
for (auto it = obj.children.begin(); it != obj.children.end(); ++it) { | |
m_rootFactory.constructObject((*it)->type, -1, **it, entityId, | |
parentTransform * obj.groupTransform); | |
} | |
} | |
if (edges.size() == 0) { | |
return false; | |
} | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructFloorDecal | |
//=========================================== | |
bool GeometryFactory::constructFloorDecal(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
string texName = getValue(obj.dict, "texture", "default"); | |
if (obj.path.points.size() != 4) { | |
EXCEPTION("Floor decal path must have exactly 4 points"); | |
} | |
Point pos = obj.path.points[0]; | |
Size size = obj.path.points[2] - obj.path.points[0]; | |
assert(size.x > 0); | |
assert(size.y > 0); | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
Matrix m = parentTransform * obj.groupTransform * obj.pathTransform * Matrix(0, pos); | |
CHRect* hRect = new CHRect(entityId, parentId); | |
hRect->size = size; | |
hRect->transform = m.inverse(); | |
spatialSystem.addComponent(pComponent_t(hRect)); | |
CFloorDecal* decal = new CFloorDecal(entityId, parentId); | |
decal->texture = texName; | |
renderSystem.addComponent(pComponent_t(decal)); | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructPortal | |
//=========================================== | |
bool GeometryFactory::constructPortal(parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
if (obj.path.points.size() != 2) { | |
EXCEPTION("Portal must contain 1 line segment"); | |
} | |
entityId_t joinId = Component::getIdFromString(getValue(obj.dict, "pair_name")); | |
entityId_t entityId = Component::getNextId(); | |
CSoftEdge* edge = new CSoftEdge(entityId, parentId, joinId); | |
edge->lseg.A = obj.path.points[0]; | |
edge->lseg.B = obj.path.points[1]; | |
edge->lseg = transform(edge->lseg, parentTransform * obj.groupTransform * obj.pathTransform); | |
edge->isPortal = true; | |
snapEndpoint(m_endpoints, edge->lseg.A); | |
snapEndpoint(m_endpoints, edge->lseg.B); | |
spatialSystem.addComponent(pComponent_t(edge)); | |
CJoin* boundary = new CJoin(entityId, parentId, Component::getNextId()); | |
boundary->topTexture = getValue(obj.dict, "top_texture", "default"); | |
boundary->bottomTexture = getValue(obj.dict, "bottom_texture", "default"); | |
renderSystem.addComponent(pComponent_t(boundary)); | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructBoundaries | |
//=========================================== | |
bool GeometryFactory::constructBoundaries(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
list<CSoftEdge*> edges; | |
if (obj.path.points.size() > 2 && entityId != -1) { | |
EXCEPTION("Cannot specify entityId for multiple joins"); | |
} | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
for (unsigned int i = 0; i < obj.path.points.size(); ++i) { | |
int j = i - 1; | |
if (i == 0) { | |
if (obj.path.closed) { | |
j = static_cast<int>(obj.path.points.size()) - 1; | |
} | |
else { | |
continue; | |
} | |
} | |
CSoftEdge* edge = new CSoftEdge(entityId, parentId, Component::getNextId()); | |
edge->lseg.A = obj.path.points[j]; | |
edge->lseg.B = obj.path.points[i]; | |
edge->lseg = transform(edge->lseg, parentTransform * obj.groupTransform * obj.pathTransform); | |
snapEndpoint(m_endpoints, edge->lseg.A); | |
snapEndpoint(m_endpoints, edge->lseg.B); | |
edges.push_back(edge); | |
spatialSystem.addComponent(pComponent_t(edge)); | |
CJoin* boundary = new CJoin(entityId, parentId, Component::getNextId()); | |
boundary->topTexture = getValue(obj.dict, "top_texture", "default"); | |
boundary->bottomTexture = getValue(obj.dict, "bottom_texture", "default"); | |
renderSystem.addComponent(pComponent_t(boundary)); | |
for (auto it = obj.children.begin(); it != obj.children.end(); ++it) { | |
m_rootFactory.constructObject((*it)->type, -1, **it, entityId, | |
parentTransform * obj.groupTransform); | |
} | |
entityId = Component::getNextId(); | |
} | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructRegion_r | |
//=========================================== | |
bool GeometryFactory::constructRegion_r(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderGraph& rg = renderSystem.rg; | |
CZone* parentZone = parentId == -1 ? nullptr | |
: dynamic_cast<CZone*>(&spatialSystem.getComponent(parentId)); | |
CRegion* parentRegion = parentId == -1 ? nullptr | |
: dynamic_cast<CRegion*>(&renderSystem.getComponent(parentId)); | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
CZone* zone = new CZone(entityId, parentId); | |
zone->parent = parentZone; | |
CRegion* region = new CRegion(entityId, parentId); | |
try { | |
spatialSystem.addComponent(pComponent_t(zone)); | |
renderSystem.addComponent(pComponent_t(region)); | |
if (obj.path.points.size() > 0) { | |
EXCEPTION("Region has unexpected path"); | |
} | |
if (contains<string>(obj.dict, "has_ceiling")) { | |
string s = getValue(obj.dict, "has_ceiling"); | |
if (s == "true") { | |
region->hasCeiling = true; | |
zone->hasCeiling = true; | |
} | |
else if (s == "false") { | |
region->hasCeiling = false; | |
zone->hasCeiling = false; | |
} | |
else { | |
EXCEPTION("has_ceiling must be either 'true' or 'false'"); | |
} | |
} | |
else { | |
if (parentZone != nullptr) { | |
region->hasCeiling = parentZone->hasCeiling; | |
zone->hasCeiling = parentZone->hasCeiling; | |
} | |
} | |
double parentFloorHeight = parentZone ? parentZone->floorHeight : 0; | |
double parentCeilingHeight = parentZone ? parentZone->ceilingHeight : 0; | |
if (contains<string>(obj.dict, "floor_height")) { | |
if (contains<string>(obj.dict, "floor_offset")) { | |
EXCEPTION("Region cannot specify both floor_height and floor_offset"); | |
} | |
zone->floorHeight = std::stod(getValue(obj.dict, "floor_height")); | |
} | |
else if (contains<string>(obj.dict, "floor_offset")) { | |
if (contains<string>(obj.dict, "floor_height")) { | |
EXCEPTION("Region cannot specify both floor_height and floor_offset"); | |
} | |
zone->floorHeight = parentFloorHeight + std::stod(getValue(obj.dict, "floor_offset")); | |
} | |
else { | |
if (parentZone != nullptr) { | |
zone->floorHeight = parentZone->floorHeight; | |
} | |
} | |
if (contains<string>(obj.dict, "ceiling_height")) { | |
if (contains<string>(obj.dict, "ceiling_offset")) { | |
EXCEPTION("Region cannot specify both ceiling_height and ceiling_offset"); | |
} | |
// Ceiling height is relative to floor height | |
zone->ceilingHeight = zone->floorHeight + std::stod(getValue(obj.dict, "ceiling_height")); | |
} | |
else if (contains<string>(obj.dict, "ceiling_offset")) { | |
if (contains<string>(obj.dict, "ceiling_height")) { | |
EXCEPTION("Region cannot specify both ceiling_height and ceiling_offset"); | |
} | |
zone->ceilingHeight = std::stod(getValue(obj.dict, "ceiling_offset")) + parentCeilingHeight; | |
} | |
else { | |
if (parentZone != nullptr) { | |
zone->ceilingHeight = parentZone->ceilingHeight; | |
} | |
} | |
string defaultFloorTex = parentRegion ? | |
parentRegion->floorTexture : | |
rg.defaults.floorTexture; | |
string defaultCeilingTex = parentRegion ? | |
parentRegion->ceilingTexture : | |
rg.defaults.ceilingTexture; | |
region->floorTexture = getValue(obj.dict, "floor_texture", defaultFloorTex); | |
region->ceilingTexture = getValue(obj.dict, "ceiling_texture", defaultCeilingTex); | |
for (auto it = obj.children.begin(); it != obj.children.end(); ++it) { | |
parser::Object& child = **it; | |
m_rootFactory.constructObject(child.type, -1, child, entityId, | |
parentTransform * obj.groupTransform); | |
} | |
} | |
catch (Exception& ex) { | |
//delete zone; | |
ex.prepend("Error constructing region; "); | |
throw ex; | |
} | |
catch (const std::exception& ex) { | |
//delete zone; | |
EXCEPTION("Error constructing region; " << ex.what()); | |
} | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructRootRegion | |
//=========================================== | |
bool GeometryFactory::constructRootRegion(parser::Object& obj) { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderGraph& rg = renderSystem.rg; | |
SceneGraph& sg = spatialSystem.sg; | |
if (obj.type != "region") { | |
EXCEPTION("Expected object of type 'region'"); | |
} | |
if (sg.rootZone || rg.rootRegion) { | |
EXCEPTION("Root region already exists"); | |
} | |
m_endpoints.clear(); | |
Matrix m; | |
if (constructRegion_r(-1, obj, -1, m)) { | |
for (auto it = m_endpoints.begin(); it != m_endpoints.end(); ++it) { | |
if (it->second == false) { | |
EXCEPTION("There are unconnected endpoints"); | |
} | |
} | |
if (!sg.player) { | |
EXCEPTION("SpatialSystem must contain the player"); | |
}; | |
spatialSystem.connectZones(); | |
renderSystem.connectRegions(); | |
return true; | |
} | |
else { | |
return false; | |
} | |
} | |
//=========================================== | |
// GeometryFactory::constructVRect | |
//=========================================== | |
bool GeometryFactory::constructVRect(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
CZone& zone = spatialSystem.zone(parentId); | |
double width = std::stod(getValue(obj.dict, "width", "0.0")); | |
double height = std::stod(getValue(obj.dict, "height", "0.0")); | |
double y = std::stod(getValue(obj.dict, "y", "0.0")); | |
CVRect* vRect = new CVRect(entityId, zone.entityId(), Size(0, 0)); | |
Matrix m = transformFromTriangle(obj.path); | |
vRect->setTransform(parentTransform * obj.groupTransform * obj.pathTransform * m); | |
vRect->zone = &zone; | |
vRect->size = Size(width, height); | |
vRect->y = y; | |
spatialSystem.addComponent(pComponent_t(vRect)); | |
return true; | |
} | |
//=========================================== | |
// GeometryFactory::constructRegion | |
//=========================================== | |
bool GeometryFactory::constructRegion(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (parentId == -1) { | |
return constructRootRegion(obj); | |
} | |
else { | |
return constructRegion_r(entityId, obj, parentId, parentTransform); | |
} | |
} | |
//=========================================== | |
// GeometryFactory::GeometryFactory | |
//=========================================== | |
GeometryFactory::GeometryFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
AudioService& audioService, TimeService& timeService) | |
: m_rootFactory(rootFactory), | |
m_entityManager(entityManager), | |
m_audioService(audioService), | |
m_timeService(timeService) {} | |
//=========================================== | |
// GeometryFactory::types | |
//=========================================== | |
const set<string>& GeometryFactory::types() const { | |
static const set<string> types{ | |
"v_rect", | |
"region", | |
"join", | |
"portal", | |
"wall", | |
"wall_decal", | |
"floor_decal", | |
"path" | |
}; | |
return types; | |
} | |
//=========================================== | |
// GeometryFactory::constructObject | |
//=========================================== | |
bool GeometryFactory::constructObject(const string& type, entityId_t entityId, | |
parser::Object& obj, entityId_t parentId, const Matrix& parentTransform) { | |
if (type == "v_rect") { | |
return constructVRect(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "region") { | |
return constructRegion(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "join") { | |
return constructBoundaries(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "portal") { | |
return constructPortal(obj, parentId, parentTransform); | |
} | |
else if (type == "wall") { | |
return constructWalls(obj, parentId, parentTransform); | |
} | |
else if (type == "wall_decal") { | |
return constructWallDecal(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "floor_decal") { | |
return constructFloorDecal(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "path") { | |
return constructPath(entityId, obj, parentId, parentTransform); | |
} | |
return false; | |
} | |
#include <algorithm> | |
#include <vector> | |
#include "raycast/misc_factory.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/behaviour_system.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/spawn_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/c_door_behaviour.hpp" | |
#include "raycast/c_elevator_behaviour.hpp" | |
#include "raycast/c_switch_behaviour.hpp" | |
#include "raycast/c_sound_source_behaviour.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/time_service.hpp" | |
#include "exception.hpp" | |
using std::stringstream; | |
using std::unique_ptr; | |
using std::function; | |
using std::string; | |
using std::list; | |
using std::map; | |
using std::set; | |
using std::vector; | |
//=========================================== | |
// setBoolean | |
//=========================================== | |
static void setBoolean(bool& b, const std::string& s) { | |
if (s == "true") { | |
b = true; | |
} | |
else if (s == "false") { | |
b = false; | |
} | |
} | |
//=========================================== | |
// MiscFactory::MiscFactory | |
//=========================================== | |
MiscFactory::MiscFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
AudioService& audioService, TimeService& timeService) | |
: m_rootFactory(rootFactory), | |
m_entityManager(entityManager), | |
m_audioService(audioService), | |
m_timeService(timeService) {} | |
//=========================================== | |
// MiscFactory::types | |
//=========================================== | |
const set<string>& MiscFactory::types() const { | |
static const set<string> types{ | |
"player", | |
"player_inventory", | |
"door", | |
"switch", | |
"elevator", | |
"spawn_point", | |
"collectable_item", | |
"computer_screen", | |
"water", | |
"sound_source" | |
}; | |
return types; | |
} | |
//=========================================== | |
// MiscFactory::constructCollectableItem | |
//=========================================== | |
bool MiscFactory::constructCollectableItem(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("sprite", entityId, obj, parentId, parentTransform)) { | |
InventorySystem& inventorySystem = | |
m_entityManager.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
CCollectable* collectable = new CCollectable(entityId, "item"); | |
if (!contains<string>(obj.dict, "name")) { | |
EXCEPTION("collectable_item is missing 'name' property"); | |
} | |
collectable->name = obj.dict.at("name"); | |
inventorySystem.addComponent(pComponent_t(collectable)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// MiscFactory::constructPlayer | |
//=========================================== | |
bool MiscFactory::constructPlayer(parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
Player* player = new Player(m_entityManager, m_audioService, m_timeService, obj, parentId, | |
parentTransform); | |
spatialSystem.sg.player.reset(player); | |
return true; | |
} | |
//=========================================== | |
// MiscFactory::constructSpawnPoint | |
//=========================================== | |
bool MiscFactory::constructSpawnPoint(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
SpawnSystem& spawnSystem = m_entityManager.system<SpawnSystem>(ComponentKind::C_SPAWN); | |
CZone& zone = dynamic_cast<CZone&>(spatialSystem.getComponent(parentId)); | |
CVRect* vRect = new CVRect(entityId, zone.entityId(), Size(1, 1)); | |
Matrix m = transformFromTriangle(obj.path); | |
vRect->setTransform(parentTransform * obj.groupTransform * obj.pathTransform * m); | |
vRect->zone = &zone; | |
spatialSystem.addComponent(pComponent_t(vRect)); | |
CSpawnPoint* spawnPoint = new CSpawnPoint(entityId); | |
spawnSystem.addComponent(pComponent_t(spawnPoint)); | |
return true; | |
} | |
//=========================================== | |
// MiscFactory::constructDoor | |
//=========================================== | |
bool MiscFactory::constructDoor(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("region", entityId, obj, parentId, parentTransform)) { | |
auto& behaviourSystem = m_entityManager.system<BehaviourSystem>(ComponentKind::C_BEHAVIOUR); | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
string caption = getValue(obj.dict, "denied_caption", ""); | |
if (caption != "") { | |
CFocus* focus = new CFocus(entityId); | |
focus->captionText = caption; | |
focusSystem.addComponent(pComponent_t(focus)); | |
} | |
CDoorBehaviour* behaviour = new CDoorBehaviour(entityId, m_entityManager, m_timeService, | |
m_audioService); | |
string s = getValue(obj.dict, "player_activated", ""); | |
setBoolean(behaviour->isPlayerActivated, s); | |
s = getValue(obj.dict, "player_activated", ""); | |
setBoolean(behaviour->closeAutomatically, s); | |
behaviour->openOnEvent = getValue(obj.dict, "open_on_event", ""); | |
behaviourSystem.addComponent(pComponent_t(behaviour)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// MiscFactory::constructSwitch | |
//=========================================== | |
bool MiscFactory::constructSwitch(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("wall_decal", entityId, obj, parentId, parentTransform)) { | |
BehaviourSystem& behaviourSystem = | |
m_entityManager.system<BehaviourSystem>(ComponentKind::C_BEHAVIOUR); | |
FocusSystem& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
string strTarget = getValue(obj.dict, "target", ""); | |
entityId_t target = -1; | |
if (strTarget != "") { | |
target = Component::getIdFromString(strTarget); | |
} | |
bool toggleable = getValue(obj.dict, "toggleable", "false") == "true"; | |
double toggleDelay = std::stod(getValue(obj.dict, "toggle_delay", "0.0")); | |
SwitchState initialState = SwitchState::OFF; | |
if (getValue(obj.dict, "initial_state", "") == "on") { | |
initialState = SwitchState::ON; | |
} | |
string message = getValue(obj.dict, "message", ""); | |
CSwitchBehaviour* behaviour = new CSwitchBehaviour(entityId, m_entityManager, m_timeService, | |
message, initialState, toggleable, toggleDelay); | |
behaviour->target = target; | |
behaviour->requiredItemType = getValue(obj.dict, "required_item_type", ""); | |
behaviour->requiredItemName = getValue(obj.dict, "required_item_name", ""); | |
behaviourSystem.addComponent(pComponent_t(behaviour)); | |
string captionText = getValue(obj.dict, "caption", ""); | |
if (captionText != "") { | |
CFocus* focus = new CFocus(entityId); | |
focus->captionText = captionText; | |
focusSystem.addComponent(pComponent_t(focus)); | |
} | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// MiscFactory::constructComputerScreen | |
//=========================================== | |
bool MiscFactory::constructComputerScreen(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("join", entityId, obj, parentId, parentTransform)) { | |
AnimationSystem& animationSystem = | |
m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
// Number of frames in sprite sheet | |
const int W = 1; | |
const int H = 23; | |
CAnimation* anim = new CAnimation(entityId); | |
vector<AnimationFrame> frames = constructFrames(W, H, | |
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 }); | |
anim->addAnimation(pAnimation_t(new Animation("idle", m_timeService.frameRate, 3.0, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "idle", true); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// MiscFactory::constructElevator | |
//=========================================== | |
bool MiscFactory::constructElevator(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("region", entityId, obj, parentId, parentTransform)) { | |
BehaviourSystem& behaviourSystem = | |
m_entityManager.system<BehaviourSystem>(ComponentKind::C_BEHAVIOUR); | |
vector<string> strLevels = splitString(getValue(obj.dict, "levels"), ','); | |
vector<double> levels(strLevels.size()); | |
std::transform(strLevels.begin(), strLevels.end(), levels.begin(), [](const string& s) { | |
return std::stod(s); | |
}); | |
string strInitLevelIdx = getValue(obj.dict, "init_level_idx", "0"); | |
int initLevelIdx = std::stod(strInitLevelIdx); | |
CElevatorBehaviour* behaviour = new CElevatorBehaviour(entityId, m_entityManager, | |
m_audioService, m_timeService.frameRate, levels, initLevelIdx); | |
string strPlayerActivated = getValue(obj.dict, "player_activated", ""); | |
if (strPlayerActivated != "") { | |
behaviour->isPlayerActivated = strPlayerActivated == "true"; | |
} | |
if (contains<string>(obj.dict, "speed")) { | |
double speed = std::stod(obj.dict.at("speed")); | |
behaviour->setSpeed(speed); | |
} | |
behaviourSystem.addComponent(pComponent_t(behaviour)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// MiscFactory::constructWater | |
//=========================================== | |
bool MiscFactory::constructWater(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["floor_texture"] = "water"; | |
if (m_rootFactory.constructObject("region", entityId, obj, parentId, parentTransform)) { | |
auto& animationSystem = m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
CAnimation* anim = new CAnimation(entityId); | |
vector<AnimationFrame> frames = constructFrames(1, 3, { 0, 1, 2 }); | |
anim->addAnimation(pAnimation_t(new Animation("gurgle", m_timeService.frameRate, 1.0, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "gurgle", true); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// MiscFactory::constructSoundSource | |
//=========================================== | |
bool MiscFactory::constructSoundSource(entityId_t entityId, parser::Object& obj, | |
entityId_t, const Matrix& parentTransform) { | |
auto& behaviourSystem = m_entityManager.system<BehaviourSystem>(ComponentKind::C_BEHAVIOUR); | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
string name = GET_VALUE(obj.dict, "sound"); | |
double radius = std::stod(GET_VALUE(obj.dict, "radius")); | |
Matrix m = transformFromTriangle(obj.path); | |
Matrix worldM = parentTransform * obj.groupTransform * obj.pathTransform * m; | |
Point pos{worldM.tx(), worldM.ty()}; | |
CSoundSourceBehaviour* behaviour = new CSoundSourceBehaviour(entityId, name, pos, radius, | |
m_audioService); | |
behaviourSystem.addComponent(pComponent_t(behaviour)); | |
return true; | |
} | |
//=========================================== | |
// MiscFactory::constructObject | |
//=========================================== | |
bool MiscFactory::constructObject(const string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (type == "player") { | |
return constructPlayer(obj, parentId, parentTransform); | |
} | |
else if (type == "door") { | |
return constructDoor(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "switch") { | |
return constructSwitch(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "elevator") { | |
return constructElevator(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "spawn_point") { | |
return constructSpawnPoint(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "collectable_item") { | |
return constructCollectableItem(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "computer_screen") { | |
return constructComputerScreen(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "water") { | |
return constructWater(entityId, obj, parentId, parentTransform); | |
} | |
else if (type == "sound_source") { | |
return constructSoundSource(entityId, obj, parentId, parentTransform); | |
} | |
return false; | |
} | |
#include <cassert> | |
#include <list> | |
#include <set> | |
#include <ostream> | |
#include <QImage> | |
#include "raycast/render_system.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "exception.hpp" | |
using std::string; | |
using std::list; | |
using std::set; | |
using std::vector; | |
using std::array; | |
using std::unique_ptr; | |
using std::ostream; | |
using std::function; | |
using std::make_pair; | |
//=========================================== | |
// operator<< | |
//=========================================== | |
ostream& operator<<(ostream& os, CRenderKind kind) { | |
switch (kind) { | |
case CRenderKind::REGION: os << "REGION"; break; | |
case CRenderKind::WALL: os << "WALL"; break; | |
case CRenderKind::JOIN: os << "JOIN"; break; | |
case CRenderKind::FLOOR_DECAL: os << "FLOOR_DECAL"; break; | |
case CRenderKind::WALL_DECAL: os << "WALL_DECAL"; break; | |
case CRenderKind::SPRITE: os << "SPRITE"; break; | |
case CRenderKind::OVERLAY: os << "OVERLAY"; break; | |
} | |
return os; | |
} | |
//=========================================== | |
// getSoftEdge | |
//=========================================== | |
static inline CSoftEdge& getSoftEdge(const SpatialSystem& spatialSystem, const CBoundary& b) { | |
return dynamic_cast<CSoftEdge&>(spatialSystem.getComponent(b.entityId())); | |
} | |
//=========================================== | |
// forEachConstRegion | |
// | |
// fn can return false to abort the loop | |
//=========================================== | |
static bool forEachConstRegion(const CRegion& region, function<bool(const CRegion&)> fn) { | |
if (fn(region)) { | |
for (auto& child : region.children) { | |
if (!forEachConstRegion(*child, fn)) { | |
break; | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// forEachRegion | |
// | |
// fn can return false to abort the loop | |
//=========================================== | |
static bool forEachRegion(CRegion& region, function<bool(CRegion&)> fn) { | |
if (fn(region)) { | |
for (auto& child : region.children) { | |
if (!forEachRegion(*child, fn)) { | |
break; | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// RenderSystem::RenderSystem | |
//=========================================== | |
RenderSystem::RenderSystem(const AppConfig& appConfig, EntityManager& entityManager, QImage& target) | |
: m_appConfig(appConfig), | |
m_entityManager(entityManager), | |
m_renderer(appConfig, entityManager, target) { | |
rg.viewport.x = 10.0 * 320.0 / 240.0; // TODO: Read from map file | |
rg.viewport.y = 10.0; | |
rg.viewport_px = Size(target.width(), target.height()); | |
rg.hWorldUnit_px = rg.viewport_px.x / rg.viewport.x; | |
rg.vWorldUnit_px = rg.viewport_px.y / rg.viewport.y; | |
} | |
//=========================================== | |
// RenderSystem::textOverlayWidth | |
// | |
// Returns width in world units | |
//=========================================== | |
double RenderSystem::textOverlayWidth(const CTextOverlay& overlay) const { | |
double h = overlay.height * rg.vWorldUnit_px; | |
QFont font = m_appConfig.monoFont; | |
font.setPixelSize(h); | |
QFontMetrics fm{font}; | |
double textW_px = fm.size(Qt::TextSingleLine, overlay.text.c_str()).width(); | |
return textW_px / rg.hWorldUnit_px; | |
} | |
//=========================================== | |
// RenderSystem::centreTextOverlay | |
//=========================================== | |
void RenderSystem::centreTextOverlay(CTextOverlay& overlay) const { | |
double x = 0.5 * (rg.viewport.x - textOverlayWidth(overlay)); | |
overlay.pos.x = x; | |
} | |
//=========================================== | |
// connectSubregions | |
//=========================================== | |
static void connectSubregions(const SpatialSystem& spatialSystem, CRegion& region) { | |
int i = 0; | |
forEachRegion(region, [&](CRegion& r) { | |
for (auto jt = r.boundaries.begin(); jt != r.boundaries.end(); ++jt) { | |
if ((*jt)->kind == CRenderKind::JOIN) { | |
CJoin* je = dynamic_cast<CJoin*>(*jt); | |
assert(je != nullptr); | |
bool hasTwin = false; | |
int j = 0; | |
forEachRegion(region, [&](CRegion& r_) { | |
if (&r_ != &r) { | |
for (auto lt = r_.boundaries.begin(); lt != r_.boundaries.end(); ++lt) { | |
if ((*lt)->kind == CRenderKind::JOIN) { | |
CJoin* other = dynamic_cast<CJoin*>(*lt); | |
entityId_t id1 = getSoftEdge(spatialSystem, *je).joinId; | |
entityId_t id2 = getSoftEdge(spatialSystem, *other).joinId; | |
if (id1 == id2) { | |
hasTwin = true; | |
je->joinId = other->joinId; | |
je->regionA = other->regionA = &r; | |
je->regionB = other->regionB = &r_; | |
je->mergeIn(*other); | |
other->mergeIn(*je); | |
return false; | |
} | |
} | |
} | |
} | |
if (j > i) { | |
return false; | |
} | |
++j; | |
return true; | |
}); | |
if (!hasTwin) { | |
je->regionA = &r; | |
je->regionB = ®ion; | |
} | |
} | |
} | |
++i; | |
return true; | |
}); | |
} | |
//=========================================== | |
// RenderSystem::connectRegions | |
// | |
// Assumes SpatialSystem::connectZones() has already been called | |
//=========================================== | |
void RenderSystem::connectRegions() { | |
const SpatialSystem& spatialSystem = m_entityManager | |
.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
connectSubregions(spatialSystem, *rg.rootRegion); | |
} | |
//=========================================== | |
// RenderSystem::render | |
//=========================================== | |
void RenderSystem::render() { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
const Camera& cam = spatialSystem.sg.player->camera(); | |
m_renderer.renderScene(rg, cam); | |
} | |
//=========================================== | |
// addToRegion | |
//=========================================== | |
static void addToRegion(RenderGraph& rg, CRegion& region, pCRender_t child) { | |
switch (child->kind) { | |
case CRenderKind::REGION: { | |
pCRegion_t ptr(dynamic_cast<CRegion*>(child.release())); | |
region.children.push_back(std::move(ptr)); | |
break; | |
} | |
case CRenderKind::JOIN: | |
case CRenderKind::WALL: { | |
pCBoundary_t ptr(dynamic_cast<CBoundary*>(child.release())); | |
region.boundaries.push_back(ptr.get()); | |
rg.boundaries.push_back(std::move(ptr)); | |
break; | |
} | |
case CRenderKind::FLOOR_DECAL: { | |
pCFloorDecal_t ptr(dynamic_cast<CFloorDecal*>(child.release())); | |
region.floorDecals.push_back(std::move(ptr)); | |
break; | |
} | |
case CRenderKind::SPRITE: { | |
pCSprite_t ptr(dynamic_cast<CSprite*>(child.release())); | |
region.sprites.push_back(std::move(ptr)); | |
break; | |
} | |
default: | |
EXCEPTION("Cannot add component of kind " << child->kind << " to region"); | |
} | |
} | |
//=========================================== | |
// addToWall | |
//=========================================== | |
static void addToWall(CWall& boundary, pCRender_t child) { | |
switch (child->kind) { | |
case CRenderKind::WALL_DECAL: { | |
pCWallDecal_t ptr(dynamic_cast<CWallDecal*>(child.release())); | |
boundary.decals.push_back(std::move(ptr)); | |
break; | |
} | |
default: | |
EXCEPTION("Cannot add component of kind " << child->kind << " to Wall"); | |
} | |
} | |
//=========================================== | |
// addToJoin | |
//=========================================== | |
static void addToJoin(CJoin& boundary, pCRender_t child) { | |
switch (child->kind) { | |
case CRenderKind::WALL_DECAL: { | |
pCWallDecal_t ptr(dynamic_cast<CWallDecal*>(child.release())); | |
boundary.decals.push_back(std::move(ptr)); | |
break; | |
} | |
default: | |
EXCEPTION("Cannot add component of kind " << child->kind << " to Join"); | |
} | |
} | |
//=========================================== | |
// addChildToComponent | |
//=========================================== | |
static void addChildToComponent(RenderGraph& rg, CRender& parent, pCRender_t child) { | |
switch (parent.kind) { | |
case CRenderKind::REGION: | |
addToRegion(rg, dynamic_cast<CRegion&>(parent), std::move(child)); | |
break; | |
case CRenderKind::WALL: | |
addToWall(dynamic_cast<CWall&>(parent), std::move(child)); | |
break; | |
case CRenderKind::JOIN: | |
addToJoin(dynamic_cast<CJoin&>(parent), std::move(child)); | |
break; | |
default: | |
EXCEPTION("Cannot add component of kind " << child->kind << " to component of kind " | |
<< parent.kind); | |
}; | |
} | |
//=========================================== | |
// removeFromRegion | |
//=========================================== | |
static bool removeFromRegion(RenderGraph& rg, CRegion& region, const CRender& child, | |
bool keepAlive) { | |
bool found = false; | |
switch (child.kind) { | |
case CRenderKind::REGION: { | |
auto it = find_if(region.children.begin(), region.children.end(), [&](const pCRegion_t& e) { | |
return e.get() == dynamic_cast<const CRegion*>(&child); | |
}); | |
if (it != region.children.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(region.children, it); | |
found = true; | |
} | |
break; | |
} | |
case CRenderKind::JOIN: | |
case CRenderKind::WALL: { | |
auto it = find_if(region.boundaries.begin(), region.boundaries.end(), | |
[&](const CBoundary* b) { | |
return b == dynamic_cast<const CBoundary*>(&child); | |
}); | |
erase(region.boundaries, it); | |
auto jt = find_if(rg.boundaries.begin(), rg.boundaries.end(), [&](const pCBoundary_t& b) { | |
return b.get() == dynamic_cast<const CBoundary*>(&child); | |
}); | |
if (jt != rg.boundaries.end()) { | |
if (keepAlive) { | |
jt->release(); | |
} | |
erase(rg.boundaries, jt); | |
found = true; | |
} | |
break; | |
} | |
case CRenderKind::FLOOR_DECAL: { | |
auto it = find_if(region.floorDecals.begin(), region.floorDecals.end(), | |
[&](const pCFloorDecal_t& e) { | |
return e.get() == dynamic_cast<const CFloorDecal*>(&child); | |
}); | |
if (it != region.floorDecals.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(region.floorDecals, it); | |
found = true; | |
} | |
break; | |
} | |
case CRenderKind::SPRITE: { | |
auto it = find_if(region.sprites.begin(), region.sprites.end(), [&](const pCSprite_t& e) { | |
return e.get() == dynamic_cast<const CSprite*>(&child); | |
}); | |
if (it != region.sprites.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(region.sprites, it); | |
found = true; | |
} | |
break; | |
} | |
default: | |
EXCEPTION("Cannot add component of kind " << child.kind << " to region"); | |
} | |
return found; | |
} | |
//=========================================== | |
// removeFromWall | |
//=========================================== | |
static bool removeFromWall(CWall& boundary, const CRender& child, bool keepAlive) { | |
bool found = false; | |
switch (child.kind) { | |
case CRenderKind::WALL_DECAL: { | |
auto it = find_if(boundary.decals.begin(), boundary.decals.end(), | |
[&](const pCWallDecal_t& e) { | |
return e.get() == dynamic_cast<const CWallDecal*>(&child); | |
}); | |
if (it != boundary.decals.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(boundary.decals, it); | |
found = true; | |
} | |
break; | |
} | |
default: | |
EXCEPTION("Cannot remove component of kind " << child.kind << " from Wall"); | |
} | |
return found; | |
} | |
//=========================================== | |
// RenderSystem::crossRegions | |
//=========================================== | |
void RenderSystem::crossRegions(RenderGraph& rg, entityId_t entityId, entityId_t oldZone, | |
entityId_t newZone) { | |
auto it = m_components.find(entityId); | |
if (it != m_components.end()) { | |
CRender& c = *it->second; | |
CRegion& oldRegion = dynamic_cast<CRegion&>(getComponent(oldZone)); | |
CRegion& newRegion = dynamic_cast<CRegion&>(getComponent(newZone)); | |
if (removeFromRegion(rg, oldRegion, c, true)) { | |
addChildToComponent(rg, newRegion, pCRender_t(&c)); | |
} | |
} | |
} | |
//=========================================== | |
// RenderSystem::handleEvent | |
//=========================================== | |
void RenderSystem::handleEvent(const GameEvent& event) { | |
if (event.name == "entity_changed_zone") { | |
const EChangedZone& e = dynamic_cast<const EChangedZone&>(event); | |
crossRegions(rg, e.entityId, e.oldZone, e.newZone); | |
} | |
} | |
//=========================================== | |
// removeChildFromComponent | |
//=========================================== | |
static void removeChildFromComponent(RenderGraph& rg, CRender& parent, const CRender& child, | |
bool keepAlive = false) { | |
switch (parent.kind) { | |
case CRenderKind::REGION: | |
removeFromRegion(rg, dynamic_cast<CRegion&>(parent), child, keepAlive); | |
break; | |
case CRenderKind::WALL: | |
removeFromWall(dynamic_cast<CWall&>(parent), child, keepAlive); | |
break; | |
default: | |
EXCEPTION("Cannot remove component of kind " << child.kind << " from component of kind " | |
<< parent.kind); | |
}; | |
} | |
//=========================================== | |
// RenderSystem::hasComponent | |
//=========================================== | |
bool RenderSystem::hasComponent(entityId_t entityId) const { | |
return m_components.find(entityId) != m_components.end(); | |
} | |
//=========================================== | |
// RenderSystem::getComponent | |
//=========================================== | |
CRender& RenderSystem::getComponent(entityId_t entityId) const { | |
return *m_components.at(entityId); | |
} | |
//=========================================== | |
// RenderSystem::addComponent | |
//=========================================== | |
void RenderSystem::addComponent(pComponent_t component) { | |
if (component->kind() != ComponentKind::C_RENDER) { | |
EXCEPTION("Component is not of kind C_RENDER"); | |
} | |
CRender* ptr = dynamic_cast<CRender*>(component.release()); | |
pCRender_t c(ptr); | |
if (c->parentId == -1) { | |
if (c->kind == CRenderKind::REGION) { | |
if (rg.rootRegion) { | |
EXCEPTION("Root region already set"); | |
} | |
pCRegion_t z(dynamic_cast<CRegion*>(c.release())); | |
rg.rootRegion = std::move(z); | |
m_components.clear(); | |
} | |
else if (c->kind == CRenderKind::OVERLAY) { | |
pCOverlay_t z(dynamic_cast<COverlay*>(c.release())); | |
auto it = find_if(rg.overlays.begin(), rg.overlays.end(), [&](const pCOverlay_t& o) { | |
return o->entityId() == z->entityId(); | |
}); | |
if (it == rg.overlays.end()) { | |
rg.overlays.insert(std::move(z)); | |
} | |
} | |
else { | |
EXCEPTION("Component has no parent and is not of type REGION or OVERLAY"); | |
} | |
} | |
else { | |
auto it = m_components.find(c->parentId); | |
if (it == m_components.end()) { | |
EXCEPTION("Could not find parent component with id " << c->parentId); | |
} | |
CRender* parent = it->second; | |
assert(parent->entityId() == c->parentId); | |
m_entityChildren[c->parentId].insert(c->entityId()); | |
addChildToComponent(rg, *parent, std::move(c)); | |
} | |
m_components.insert(make_pair(ptr->entityId(), ptr)); | |
} | |
//=========================================== | |
// RenderSystem::isRoot | |
//=========================================== | |
bool RenderSystem::isRoot(const CRender& c) const { | |
if (c.kind != CRenderKind::REGION) { | |
return false; | |
} | |
if (rg.rootRegion == nullptr) { | |
return false; | |
} | |
const CRegion* ptr = dynamic_cast<const CRegion*>(&c); | |
return ptr == rg.rootRegion.get(); | |
} | |
//=========================================== | |
// RenderSystem::removeEntity_r | |
//=========================================== | |
void RenderSystem::removeEntity_r(entityId_t id) { | |
m_components.erase(id); | |
auto it = m_entityChildren.find(id); | |
if (it != m_entityChildren.end()) { | |
set<entityId_t>& children = it->second; | |
for (auto jt = children.begin(); jt != children.end(); ++jt) { | |
removeEntity_r(*jt); | |
} | |
} | |
m_entityChildren.erase(id); | |
} | |
//=========================================== | |
// RenderSystem::removeEntity | |
//=========================================== | |
void RenderSystem::removeEntity(entityId_t id) { | |
auto it = m_components.find(id); | |
if (it == m_components.end()) { | |
return; | |
} | |
CRender& c = *it->second; | |
auto jt = m_components.find(c.parentId); | |
if (jt != m_components.end()) { | |
CRender& parent = *jt->second; | |
removeChildFromComponent(rg, parent, c); | |
} | |
else { | |
if (c.kind == CRenderKind::REGION) { | |
assert(isRoot(c)); | |
} | |
else if (c.kind == CRenderKind::OVERLAY) { | |
auto it = find_if(rg.overlays.begin(), rg.overlays.end(), [&](const pCOverlay_t& o) { | |
return o->entityId() == id; | |
}); | |
erase(rg.overlays, it); | |
} | |
} | |
removeEntity_r(id); | |
} | |
#include "raycast/c_door_behaviour.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/time_service.hpp" | |
#include "event.hpp" | |
using std::string; | |
//=========================================== | |
// CDoorBehaviour::CDoorBehaviour | |
//=========================================== | |
CDoorBehaviour::CDoorBehaviour(entityId_t entityId, EntityManager& entityManager, | |
TimeService& timeService, AudioService& audioService) | |
: CBehaviour(entityId), | |
m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_audioService(audioService), | |
m_timer(5.0) { | |
CZone& zone = entityManager.getComponent<CZone>(entityId, ComponentKind::C_SPATIAL); | |
m_y0 = zone.floorHeight; | |
m_y1 = zone.ceilingHeight; | |
zone.ceilingHeight = zone.floorHeight + 0.1; | |
} | |
//=========================================== | |
// CDoorBehaviour::setPauseTime | |
//=========================================== | |
void CDoorBehaviour::setPauseTime(double t) { | |
m_timer = Debouncer{t}; | |
} | |
//=========================================== | |
// CDoorBehaviour::playSound | |
//=========================================== | |
void CDoorBehaviour::playSound() { | |
CZone& zone = m_entityManager.getComponent<CZone>(entityId(), ComponentKind::C_SPATIAL); | |
const Point& pos = zone.edges.front()->lseg.A; | |
if (!m_audioService.soundIsPlaying("door", m_soundId)) { | |
m_soundId = m_audioService.playSoundAtPos("door", pos, true); | |
} | |
} | |
//=========================================== | |
// CDoorBehaviour::stopSound | |
//=========================================== | |
void CDoorBehaviour::stopSound() const { | |
m_audioService.stopSound("door", m_soundId); | |
} | |
//=========================================== | |
// CDoorBehaviour::update | |
//=========================================== | |
void CDoorBehaviour::update() { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
Player& player = *spatialSystem.sg.player; | |
CZone& zone = m_entityManager.getComponent<CZone>(entityId(), ComponentKind::C_SPATIAL); | |
double dy = this->speed / m_timeService.frameRate; | |
switch (m_state) { | |
case ST_CLOSED: | |
return; | |
case ST_OPEN: | |
if (closeAutomatically && m_timer.ready()) { | |
m_state = ST_CLOSING; | |
playSound(); | |
m_entityManager.fireEvent(EDoorCloseStart{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorCloseStart{entityId()}); | |
} | |
return; | |
case ST_OPENING: | |
zone.ceilingHeight += dy; | |
if (zone.ceilingHeight + dy >= m_y1) { | |
m_state = ST_OPEN; | |
stopSound(); | |
m_entityManager.fireEvent(EDoorOpenFinish{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorOpenFinish{entityId()}); | |
} | |
else { | |
m_timer.reset(); | |
} | |
break; | |
case ST_CLOSING: | |
zone.ceilingHeight -= dy; | |
if (player.region() == entityId()) { | |
if (zone.ceilingHeight - dy <= player.headHeight()) { | |
m_state = ST_OPENING; | |
m_entityManager.fireEvent(EDoorOpenStart{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorOpenStart{entityId()}); | |
} | |
} | |
else if (zone.ceilingHeight - dy <= m_y0) { | |
m_state = ST_CLOSED; | |
stopSound(); | |
m_entityManager.fireEvent(EDoorCloseFinish{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorCloseFinish{entityId()}); | |
} | |
break; | |
} | |
} | |
//=========================================== | |
// CDoorBehaviour::handleBroadcastedEvent | |
//=========================================== | |
void CDoorBehaviour::handleBroadcastedEvent(const GameEvent& e) { | |
if (e.name == openOnEvent) { | |
if (m_state != ST_OPEN) { | |
m_state = ST_OPENING; | |
playSound(); | |
} | |
} | |
} | |
//=========================================== | |
// CDoorBehaviour::handleTargetedEvent | |
//=========================================== | |
void CDoorBehaviour::handleTargetedEvent(const GameEvent& e) { | |
const std::set<std::string> EVENT_NAMES{ | |
"activate_entity", | |
"player_activate_entity", | |
"switch_activated" | |
}; | |
if (!isPlayerActivated && e.name == "player_activate_entity") { | |
if (m_state != ST_OPEN) { | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
if (focusSystem.hasComponent(entityId())) { | |
focusSystem.showCaption(entityId()); | |
} | |
} | |
return; | |
} | |
if (EVENT_NAMES.count(e.name)) { | |
switch (m_state) { | |
case ST_CLOSED: | |
m_state = ST_OPENING; | |
playSound(); | |
m_entityManager.fireEvent(EDoorOpenStart{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorOpenStart{entityId()}); | |
break; | |
case ST_OPEN: | |
m_state = ST_CLOSING; | |
playSound(); | |
m_entityManager.fireEvent(EDoorCloseStart{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorCloseStart{entityId()}); | |
break; | |
case ST_OPENING: | |
m_state = ST_CLOSING; | |
m_entityManager.fireEvent(EDoorCloseStart{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorCloseStart{entityId()}); | |
break; | |
case ST_CLOSING: | |
m_state = ST_OPENING; | |
m_entityManager.fireEvent(EDoorOpenStart{entityId()}, { entityId() }); | |
m_entityManager.broadcastEvent(EDoorOpenStart{entityId()}); | |
break; | |
} | |
} | |
} | |
#include <cmath> | |
#include <cassert> | |
#include <limits> | |
#include <set> | |
#include <vector> | |
#include <ostream> | |
#include <functional> | |
#include <QFont> | |
#include <QFontMetrics> | |
#include <QPainter> | |
#include <QPaintDevice> | |
#include <QBrush> | |
#include "raycast/renderer.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "exception.hpp" | |
using std::string; | |
using std::list; | |
using std::set; | |
using std::vector; | |
using std::array; | |
using std::unique_ptr; | |
using std::function; | |
using std::for_each; | |
static const double TWO_FIVE_FIVE_RP = 1.0 / 255.0; | |
static inline CVRect& getVRect(const SpatialSystem& spatialSystem, const CWallDecal& d) { | |
return DYNAMIC_CAST<CVRect&>(spatialSystem.getComponent(d.entityId())); | |
} | |
static inline CHRect& getHRect(const SpatialSystem& spatialSystem, const CFloorDecal& d) { | |
return DYNAMIC_CAST<CHRect&>(spatialSystem.getComponent(d.entityId())); | |
} | |
//=========================================== | |
// blend | |
//=========================================== | |
static inline QRgb blend(const QRgb& A, const QRgb& B, double alphaB) { | |
double alphaA = 1.0 - alphaB; | |
return qRgba(alphaA * qRed(A) + alphaB * qRed(B), | |
alphaA * qGreen(A) + alphaB * qGreen(B), | |
alphaA * qBlue(A) + alphaB * qBlue(B), | |
255); | |
} | |
//=========================================== | |
// pixel | |
//=========================================== | |
static inline QRgb pixel(const QImage& img, int x, int y) { | |
return reinterpret_cast<const QRgb*>(img.constScanLine(y))[x]; | |
} | |
//=========================================== | |
// getShade | |
//=========================================== | |
static inline double getShade(double distance) { | |
static const Range r(0, 255); | |
return clipNumber(distance * 0.2, r); | |
} | |
//=========================================== | |
// applyShade | |
//=========================================== | |
static inline QRgb applyShade(const QRgb& c, double distance) { | |
double s = 1.0 - getShade(distance) * TWO_FIVE_FIVE_RP; | |
return qRgba(qRed(c) * s, qGreen(c) * s, qBlue(c) * s, qAlpha(c)); | |
} | |
//=========================================== | |
// Renderer::drawImage | |
//=========================================== | |
void Renderer::drawImage(const QRect& trgRect, const QImage& tex, const QRect& srcRect, | |
double distance) const { | |
int y0 = clipNumber(trgRect.y(), Range(0, m_target.height())); | |
int y1 = clipNumber(trgRect.y() + trgRect.height(), Range(0, m_target.height())); | |
int x0 = clipNumber(trgRect.x(), Range(0, m_target.width())); | |
int x1 = clipNumber(trgRect.x() + trgRect.width(), Range(0, m_target.width())); | |
double trgW_rp = 1.0 / trgRect.width(); | |
double trgH_rp = 1.0 / trgRect.height(); | |
for (int j = y0; j < y1; ++j) { | |
QRgb* pixels = reinterpret_cast<QRgb*>(m_target.scanLine(j)); | |
double y = static_cast<double>(j - trgRect.y()) * trgH_rp; | |
int srcY = srcRect.y() + y * srcRect.height(); | |
for (int i = x0; i < x1; ++i) { | |
double x = static_cast<double>(i - trgRect.x()) * trgW_rp; | |
int srcX = srcRect.x() + x * srcRect.width(); | |
QRgb srcPx = pixel(tex, srcX, srcY); | |
double srcAlpha = qAlpha(srcPx); | |
if (srcAlpha > 0) { | |
pixels[i] = applyShade(blend(pixels[i], srcPx, TWO_FIVE_FIVE_RP * srcAlpha), distance); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// Renderer::constructXWrapper | |
//=========================================== | |
Renderer::XWrapper* Renderer::constructXWrapper(const SpatialSystem& spatialSystem, | |
const RenderSystem& renderSystem, pIntersection_t X) const { | |
Intersection* pX = X.get(); | |
switch (pX->kind) { | |
case CSpatialKind::HARD_EDGE: { | |
WallX* wrapper = new WallX(std::move(X)); | |
wrapper->hardEdge = DYNAMIC_CAST<const CHardEdge*>(&spatialSystem.getComponent(pX->entityId)); | |
wrapper->wall = DYNAMIC_CAST<const CWall*>(&renderSystem.getComponent(pX->entityId)); | |
return wrapper; | |
} | |
case CSpatialKind::SOFT_EDGE: { | |
JoinX* wrapper = new JoinX(std::move(X)); | |
wrapper->softEdge = DYNAMIC_CAST<const CSoftEdge*>(&spatialSystem.getComponent(pX->entityId)); | |
wrapper->join = DYNAMIC_CAST<const CJoin*>(&renderSystem.getComponent(pX->entityId)); | |
return wrapper; | |
} | |
case CSpatialKind::V_RECT: { | |
if (pX->parentKind == CSpatialKind::ZONE) { | |
SpriteX* wrapper = new SpriteX(std::move(X)); | |
wrapper->vRect = DYNAMIC_CAST<const CVRect*>(&spatialSystem.getComponent(pX->entityId)); | |
wrapper->sprite = DYNAMIC_CAST<const CSprite*>(&renderSystem.getComponent(pX->entityId)); | |
return wrapper; | |
} | |
} | |
default: break; | |
} | |
return nullptr; | |
} | |
//=========================================== | |
// projectionPlane | |
//=========================================== | |
static LineSegment projectionPlane(const Camera& cam, const RenderGraph& rg) { | |
LineSegment lseg(Point(cam.F, -rg.viewport.y * 0.5), Point(cam.F, rg.viewport.y * 0.5)); | |
Matrix m(cam.vAngle, Vec2f(0, 0)); | |
return transform(lseg, m); | |
} | |
//=========================================== | |
// Renderer::computeSlice | |
//=========================================== | |
Renderer::Slice Renderer::computeSlice(const LineSegment& rotProjPlane, const LineSegment& wall, | |
double subview0, double subview1, const LineSegment& projRay0, const LineSegment& projRay1, | |
Point& projX0, Point& projX1) const { | |
Slice slice; | |
LineSegment wallRay0(Point(0, 0), wall.A); | |
LineSegment wallRay1(Point(0, 0), wall.B); | |
Line l = LineSegment(Point(wall.A.x, 0), Point(wall.A.x, 1)).line(); | |
double wall_s0 = lineIntersect(projRay0.line(), l).y; | |
double wall_s1 = lineIntersect(projRay1.line(), l).y; | |
auto wallAClip = clipNumber(wall.A.y, Range(wall_s0, wall_s1), slice.sliceBottom_wd); | |
auto wallBClip = clipNumber(wall.B.y, Range(wall_s0, wall_s1), slice.sliceTop_wd); | |
projX0 = lineIntersect(wallRay0.line(), rotProjPlane.line()); | |
projX0 = clipToLineSegment(projX0, rotProjPlane); | |
double projW0 = rotProjPlane.signedDistance(projX0); | |
projX1 = lineIntersect(wallRay1.line(), rotProjPlane.line()); | |
projX1 = clipToLineSegment(projX1, rotProjPlane); | |
double projW1 = rotProjPlane.signedDistance(projX1); | |
if (wallAClip == CLIPPED_TO_BOTTOM) { | |
projW0 = subview0; | |
} | |
if (wallAClip == CLIPPED_TO_TOP) { | |
projW0 = subview1; | |
} | |
if (wallBClip == CLIPPED_TO_BOTTOM) { | |
projW1 = subview0; | |
} | |
if (wallBClip == CLIPPED_TO_TOP) { | |
projW1 = subview1; | |
} | |
slice.projSliceBottom_wd = projW0; | |
slice.projSliceTop_wd = projW1; | |
slice.viewportBottom_wd = subview0; | |
slice.viewportTop_wd = subview1; | |
return slice; | |
} | |
//=========================================== | |
// Renderer::castRay | |
//=========================================== | |
void Renderer::castRay(const RenderGraph& rg, const Camera& cam, const SpatialSystem& spatialSystem, | |
const RenderSystem& renderSystem, const Vec2f& dir, CastResult& result) const { | |
LineSegment rotProjPlane = projectionPlane(cam, rg); | |
vector<pIntersection_t> intersections = spatialSystem.entitiesAlongRay(dir); | |
LineSegment projRay0(Point(0, 0), rotProjPlane.A * 9999.9); | |
LineSegment projRay1(Point(0, 0), rotProjPlane.B * 9999.9); | |
double subview0 = 0; | |
double subview1 = rg.viewport.y; | |
const CZone* zone = &cam.zone(); | |
int last = -1; | |
for (auto it = intersections.begin(); it != intersections.end(); ++it) { | |
if (!renderSystem.hasComponent((*it)->entityId)) { | |
continue; | |
} | |
++last; | |
XWrapper* X = constructXWrapper(spatialSystem, renderSystem, std::move(*it)); | |
if (X == nullptr) { | |
continue; | |
} | |
result.intersections.push_back(pXWrapper_t(X)); | |
if (X->kind == XWrapperKind::WALL) { | |
WallX& wallX = DYNAMIC_CAST<WallX&>(*X); | |
wallX.nearZone = zone; | |
double floorHeight = zone->floorHeight; | |
double ceilingHeight = zone->hasCeiling ? zone->ceilingHeight | |
: wallX.hardEdge->zone->ceilingHeight; | |
double targetHeight = ceilingHeight - floorHeight; | |
const Point& pt = wallX.X->point_rel; | |
LineSegment wall(Point(pt.x, floorHeight - cam.height), | |
Point(pt.x, floorHeight - cam.height + targetHeight)); | |
Point projX0, projX1; | |
wallX.slice = computeSlice(rotProjPlane, wall, subview0, subview1, projRay0, projRay1, | |
projX0, projX1); | |
break; | |
} | |
else if (X->kind == XWrapperKind::JOIN) { | |
JoinX& joinX = DYNAMIC_CAST<JoinX&>(*X); | |
if(!(zone == joinX.softEdge->zoneA || zone == joinX.softEdge->zoneB)) { | |
//DBG_PRINT("Warning: Possibly overlapping regions\n"); | |
} | |
CZone* nextZone = zone == joinX.softEdge->zoneA ? joinX.softEdge->zoneB | |
: joinX.softEdge->zoneA; | |
joinX.nearZone = zone; | |
joinX.farZone = nextZone; | |
const CRegion& region = | |
DYNAMIC_CAST<const CRegion&>(renderSystem.getComponent(zone->entityId())); | |
const CRegion& nextRegion = | |
DYNAMIC_CAST<const CRegion&>(renderSystem.getComponent(nextZone->entityId())); | |
double floorDiff = nextZone->floorHeight - zone->floorHeight; | |
double ceilingDiff = zone->ceilingHeight - nextZone->ceilingHeight; | |
double nextZoneSpan = nextZone->ceilingHeight - nextZone->floorHeight; | |
double bottomWallA = zone->floorHeight - cam.height; | |
double bottomWallB = bottomWallA + floorDiff; | |
double topWallA = bottomWallB + nextZoneSpan; | |
double topWallB = topWallA + ceilingDiff; | |
bool slice1Visible = true; | |
if (floorDiff < 0) { | |
bottomWallB = bottomWallA; | |
} | |
if (!nextRegion.hasCeiling || ceilingDiff < 0) { | |
topWallA = topWallB; | |
if (!region.hasCeiling) { | |
slice1Visible = false; | |
} | |
} | |
// Top and bottom walls cannot overlap | |
topWallA = largest(topWallA, bottomWallB); | |
bottomWallB = smallest(bottomWallB, topWallA); | |
const Point& pt = joinX.X->point_rel; | |
LineSegment bottomWall(Point(pt.x, bottomWallA), Point(pt.x, bottomWallB)); | |
LineSegment topWall(Point(pt.x, topWallA), Point(pt.x, topWallB)); | |
Point bProjX0, bProjX1, tProjX0, tProjX1; | |
joinX.slice0 = computeSlice(rotProjPlane, bottomWall, subview0, subview1, projRay0, projRay1, | |
bProjX0, bProjX1); | |
joinX.slice1 = computeSlice(rotProjPlane, topWall, subview0, subview1, projRay0, projRay1, | |
tProjX0, tProjX1); | |
joinX.slice1.visible = slice1Visible; | |
if (joinX.slice0.projSliceTop_wd > subview0) { | |
subview0 = joinX.slice0.projSliceTop_wd; | |
projRay0 = LineSegment(Point(0, 0), bProjX1 * 9999.9); | |
} | |
if (region.hasCeiling && joinX.slice1.projSliceBottom_wd < subview1) { | |
subview1 = joinX.slice1.projSliceBottom_wd; | |
projRay1 = LineSegment(Point(0, 0), tProjX0 * 9999.9); | |
} | |
zone = nextZone; | |
} | |
else if (X->kind == XWrapperKind::SPRITE) { | |
SpriteX& spriteX = DYNAMIC_CAST<SpriteX&>(*X); | |
double floorHeight = spriteX.vRect->zone->floorHeight; | |
const Point& pt = spriteX.X->point_rel; | |
LineSegment sprite(Point(pt.x, floorHeight + spriteX.vRect->y - cam.height), | |
Point(pt.x, floorHeight + spriteX.vRect->y - cam.height + spriteX.vRect->size.y)); | |
Point projX0, projX1; | |
spriteX.slice = computeSlice(rotProjPlane, sprite, subview0, subview1, projRay0, projRay1, | |
projX0, projX1); | |
} | |
if (subview1 <= subview0) { | |
break; | |
} | |
} | |
if (intersections.size() > 0) { | |
intersections.resize(last + 1); | |
} | |
} | |
//=========================================== | |
// Renderer::drawSkySlice | |
//=========================================== | |
void Renderer::drawSkySlice(const RenderGraph& rg, const Camera& cam, const ScreenSlice& slice, | |
int screenX_px) const { | |
const Texture& skyTex = rg.textures.at("sky"); | |
Size tileSz_px(skyTex.image.rect().width(), skyTex.image.rect().height()); | |
int W_px = m_target.rect().width(); | |
int H_px = m_target.rect().height(); | |
double hPxAngle = cam.hFov / W_px; | |
double vPxAngle = cam.vFov / H_px; | |
double hAngle = normaliseAngle(cam.angle() - 0.5 * cam.hFov + hPxAngle * screenX_px); | |
double s = hAngle / (2.0 * PI); | |
assert(isBetween(s, 0.0, 1.0)); | |
int x = tileSz_px.x * s; | |
double maxPitch = DEG_TO_RAD(PLAYER_MAX_PITCH); | |
double minVAngle = -0.5 * cam.vFov - maxPitch; | |
double maxVAngle = 0.5 * cam.vFov + maxPitch; | |
double vAngleRange = maxVAngle - minVAngle; | |
double viewportBottomAngle = cam.vAngle - 0.5 * cam.vFov; | |
for (int j = slice.viewportTop_px; j <= slice.sliceTop_px; ++j) { | |
double vAngle = viewportBottomAngle + (H_px - j) * vPxAngle; | |
double s = 1.0 - normaliseAngle(vAngle - minVAngle) / vAngleRange; | |
assert(isBetween(s, 0.0, 1.0)); | |
assert(isBetween(screenX_px, 0, m_target.width() - 1) && isBetween(j, 0, | |
m_target.height() - 1)); | |
QRgb* pixels = reinterpret_cast<QRgb*>(m_target.scanLine(j)); | |
pixels[screenX_px] = pixel(skyTex.image, x, s * tileSz_px.y); | |
} | |
} | |
//=========================================== | |
// worldPointToFloorTexel | |
//=========================================== | |
inline static Point worldPointToFloorTexel(const Point& p, const Size& tileSz_wd_rp, | |
const QRectF& frameRect_tx) { | |
double nx = p.x * tileSz_wd_rp.x; | |
double ny = p.y * tileSz_wd_rp.y; | |
if (nx < 0 || std::isinf(nx) || std::isnan(nx)) { | |
nx = 0; | |
} | |
if (ny < 0 || std::isinf(ny) || std::isnan(ny)) { | |
ny = 0; | |
} | |
return Point(frameRect_tx.x() + (nx - static_cast<int>(nx)) * frameRect_tx.width(), | |
frameRect_tx.y() + (ny - static_cast<int>(ny)) * frameRect_tx.height()); | |
} | |
//=========================================== | |
// Renderer::drawCeilingSlice | |
//=========================================== | |
void Renderer::drawCeilingSlice(const RenderGraph& rg, const Camera& cam, const Intersection& X, | |
const CRegion& region, double ceilingHeight, const ScreenSlice& slice, int screenX_px, | |
double projX_wd) const { | |
double screenH_px = rg.viewport.y * rg.vWorldUnit_px; // TODO: Pre-compute this | |
const Texture& ceilingTex = rg.textures.at(region.ceilingTexture); | |
Size texSz_tx(ceilingTex.image.rect().width(), ceilingTex.image.rect().height()); | |
const QRectF& frameRect = region.ceilingTexRect; | |
QRectF frameRect_tx(frameRect.x() * texSz_tx.x, frameRect.y() * texSz_tx.y, | |
frameRect.width() * texSz_tx.x, frameRect.height() * texSz_tx.y); | |
double hAngle = fastATan(projX_wd / cam.F); | |
LineSegment ray(X.viewPoint, X.point_wld); | |
Size tileSz_wd_rp(1.0 / ceilingTex.size_wd.x, 1.0 / ceilingTex.size_wd.y); | |
double vWorldUnit_px_rp = 1.0 / rg.vWorldUnit_px; | |
double F_rp = 1.0 / cam.F; | |
double rayLen = ray.length(); | |
double rayLen_rp = 1.0 / rayLen; | |
double cosHAngle_rp = 1.0 / cos(hAngle); | |
for (int j = slice.sliceTop_px; j >= slice.viewportTop_px; --j) { | |
QRgb* pixels = reinterpret_cast<QRgb*>(m_target.scanLine(j)); | |
double projY_wd = (screenH_px * 0.5 - j) * vWorldUnit_px_rp; | |
double vAngle = fastATan(projY_wd * F_rp) + cam.vAngle; | |
double d_ = (ceilingHeight - cam.height) * fastTan_rp(vAngle); | |
double d = d_ * cosHAngle_rp; | |
if (!isBetween(d, 0, rayLen)) { | |
d = rayLen; | |
} | |
double s = d * rayLen_rp; | |
Point p(ray.A.x + (ray.B.x - ray.A.x) * s, ray.A.y + (ray.B.y - ray.A.y) * s); | |
Point texel = worldPointToFloorTexel(p, tileSz_wd_rp, frameRect_tx); | |
pixels[screenX_px] = applyShade(pixel(ceilingTex.image, texel.x, texel.y), d); | |
} | |
} | |
//=========================================== | |
// getFloorDecal | |
// | |
// x is set to the point inside decal space | |
//=========================================== | |
static const CFloorDecal* getFloorDecal(const SpatialSystem& spatialSystem, const CRegion& region, | |
const Point& pt, Point& x) { | |
for (auto it = region.floorDecals.begin(); it != region.floorDecals.end(); ++it) { | |
const CFloorDecal& decal = **it; | |
const CHRect& hRect = getHRect(spatialSystem, decal); | |
x = hRect.transform * pt; | |
if (isBetween(x.x, 0, hRect.size.x) && isBetween(x.y, 0, hRect.size.y)) { | |
return &decal; | |
} | |
} | |
return nullptr; | |
} | |
//=========================================== | |
// Renderer::drawFloorSlice | |
//=========================================== | |
void Renderer::drawFloorSlice(const RenderGraph& rg, const Camera& cam, | |
const SpatialSystem& spatialSystem, const Intersection& X, const CRegion& region, | |
double floorHeight, const ScreenSlice& slice, int screenX_px, double projX_wd) const { | |
double screenH_px = rg.viewport.y * rg.vWorldUnit_px; | |
const Texture& floorTex = rg.textures.at(region.floorTexture); | |
Size texSz_tx(floorTex.image.rect().width(), floorTex.image.rect().height()); | |
const QRectF& frameRect = region.floorTexRect; | |
QRectF frameRect_tx(frameRect.x() * texSz_tx.x, frameRect.y() * texSz_tx.y, | |
frameRect.width() * texSz_tx.x, frameRect.height() * texSz_tx.y); | |
Size tileSz_wd_rp(1.0 / floorTex.size_wd.x, 1.0 / floorTex.size_wd.y); | |
double hAngle = fastATan(projX_wd / cam.F); | |
LineSegment ray(X.viewPoint, X.point_wld); | |
double vWorldUnit_px_rp = 1.0 / rg.vWorldUnit_px; // TODO: Store in Renderer instance? | |
double F_rp = 1.0 / cam.F; | |
double rayLen = ray.length(); | |
double rayLen_rp = 1.0 / rayLen; | |
double cosHAngle_rp = 1.0 / cos(hAngle); | |
for (int j = slice.sliceBottom_px; j <= slice.viewportBottom_px; ++j) { | |
QRgb* pixels = reinterpret_cast<QRgb*>(m_target.scanLine(j)); | |
double projY_wd = (j - screenH_px * 0.5) * vWorldUnit_px_rp; | |
double vAngle = fastATan(projY_wd * F_rp) - cam.vAngle; | |
double d_ = (cam.height - floorHeight) * fastTan_rp(vAngle); | |
double d = d_ * cosHAngle_rp; | |
if (!isBetween(d, 0, rayLen)) { | |
d = rayLen; | |
} | |
double s = d * rayLen_rp; | |
Point p(ray.A.x + (ray.B.x - ray.A.x) * s, ray.A.y + (ray.B.y - ray.A.y) * s); | |
Point decalPt; | |
const CFloorDecal* decal = getFloorDecal(spatialSystem, region, p, decalPt); | |
Point floorUv = worldPointToFloorTexel(p, tileSz_wd_rp, frameRect_tx); | |
QRgb& destPixel = pixels[screenX_px]; | |
if (decal != nullptr) { | |
destPixel = pixel(floorTex.image, floorUv.x, floorUv.y); | |
const CHRect& hRect = getHRect(spatialSystem, *decal); | |
// TODO: Use frameRect to permit animations | |
const Texture& decalTex = rg.textures.at(decal->texture); | |
Size texSz_tx(decalTex.image.rect().width(), decalTex.image.rect().height()); | |
Point decalUv(texSz_tx.x * decalPt.x / hRect.size.x, texSz_tx.y * decalPt.y / hRect.size.y); | |
QRgb decalTexel = pixel(decalTex.image, decalUv.x, decalUv.y); | |
QRgb decalAlpha = qAlpha(decalTexel); | |
destPixel = applyShade(blend(destPixel, decalTexel, TWO_FIVE_FIVE_RP * decalAlpha), d); | |
} | |
else { | |
destPixel = applyShade(pixel(floorTex.image, floorUv.x, floorUv.y), d); | |
} | |
} | |
} | |
//=========================================== | |
// Renderer::sampleSpriteTexture | |
//=========================================== | |
QRect Renderer::sampleSpriteTexture(const Camera& cam, const QRect& rect, const SpriteX& X, | |
const Size& size_wd, double y_wd) const { | |
double H_tx = rect.height(); | |
double W_tx = rect.width(); | |
double hWorldUnit_tx = W_tx / size_wd.x; | |
double texW_wd = W_tx / hWorldUnit_tx; | |
double n = X.X->distanceAlongTarget / texW_wd; | |
double x = (n - floor(n)) * texW_wd; | |
double texBottom_tx = H_tx - ((cam.height + X.slice.sliceBottom_wd - y_wd) / size_wd.y) * H_tx; | |
double texTop_tx = H_tx - ((cam.height + X.slice.sliceTop_wd - y_wd) / size_wd.y) * H_tx; | |
int i = x * hWorldUnit_tx; | |
return QRect(rect.x() + i, rect.y() + texTop_tx, 1, texBottom_tx - texTop_tx); | |
} | |
//=========================================== | |
// Renderer::sampleWallTexture | |
//=========================================== | |
void Renderer::sampleWallTexture(const RenderGraph& rg, const Camera& cam, double screenX_px, | |
double texAnchor_wd, double distanceAlongTarget, const Slice& slice, const Size& texSz_tx, | |
const QRectF& frameRect, const Size& tileSz_wd, vector<QRect>& trgRects, | |
vector<QRect>& srcRects) const { | |
double projSliceH_wd = slice.projSliceTop_wd - slice.projSliceBottom_wd; | |
double sliceH_wd = slice.sliceTop_wd - slice.sliceBottom_wd; | |
double sliceToProjScale = projSliceH_wd / sliceH_wd; | |
double projTexH_wd = tileSz_wd.y * sliceToProjScale; | |
QRectF frameRect_tx(frameRect.x() * texSz_tx.x, frameRect.y() * texSz_tx.y, | |
frameRect.width() * texSz_tx.x, frameRect.height() * texSz_tx.y); | |
// World space | |
double sliceBottom_wd = slice.sliceBottom_wd + cam.height; | |
double sliceTop_wd = slice.sliceTop_wd + cam.height; | |
// World space (not camera space) | |
auto fnSliceToProjY = [&](double y) -> double { | |
return slice.projSliceBottom_wd + (y - sliceBottom_wd) * sliceToProjScale; | |
}; | |
double hWorldUnit_tx = frameRect_tx.width() / tileSz_wd.x; | |
double vWorldUnit_tx = frameRect_tx.height() / tileSz_wd.y; | |
double nx = distanceAlongTarget / tileSz_wd.x; | |
double x = (nx - floor(nx)) * tileSz_wd.x; | |
// Relative to tex anchor | |
double texY0 = floor((sliceBottom_wd - texAnchor_wd) / tileSz_wd.y) * tileSz_wd.y; | |
double texY1 = ceil((sliceTop_wd - texAnchor_wd) / tileSz_wd.y) * tileSz_wd.y; | |
// World space | |
double y0 = texY0 + texAnchor_wd; | |
double y1 = texY1 + texAnchor_wd; | |
double bottomOffset_wd = sliceBottom_wd - y0; | |
double topOffset_wd = y1 - sliceTop_wd; | |
// Ensure offsets are texel-aligned | |
bottomOffset_wd = floor(bottomOffset_wd * vWorldUnit_tx) / vWorldUnit_tx; | |
topOffset_wd = floor(topOffset_wd * vWorldUnit_tx) / vWorldUnit_tx; | |
int j0 = floor(texY0 / tileSz_wd.y); | |
int j1 = ceil(texY1 / tileSz_wd.y); | |
for (int j = j0; j < j1; ++j) { | |
double srcX = frameRect_tx.x() + x * hWorldUnit_tx; | |
double srcY = frameRect_tx.y(); | |
double srcW = 1; | |
double srcH = frameRect_tx.height(); | |
double y = texAnchor_wd + j * tileSz_wd.y; | |
double trgX = screenX_px; | |
double trgY = projToScreenY(rg, fnSliceToProjY(y) + projTexH_wd); | |
double trgW = 1; | |
double trgH = projTexH_wd * rg.vWorldUnit_px; | |
// Note that screen y-axis is inverted | |
if (j == j0) { | |
srcH -= bottomOffset_wd * vWorldUnit_tx; | |
trgH -= bottomOffset_wd * sliceToProjScale * rg.vWorldUnit_px; | |
} | |
if (j + 1 == j1) { | |
double srcDy = topOffset_wd * vWorldUnit_tx; | |
srcY += srcDy; | |
srcH -= srcDy; | |
double trgDy = topOffset_wd * sliceToProjScale * rg.vWorldUnit_px; | |
trgY += trgDy; | |
trgH -= trgDy; | |
} | |
srcRects.push_back(QRect(srcX, srcY, srcW, srcH)); | |
trgRects.push_back(QRect(trgX, trgY, trgW, ceil(trgH))); | |
} | |
} | |
//=========================================== | |
// Renderer::drawSlice | |
//=========================================== | |
Renderer::ScreenSlice Renderer::drawSlice(const RenderGraph& rg, const Camera& cam, | |
const Intersection& X, const Slice& slice, const string& texture, const QRectF& frameRect, | |
double screenX_px, double targetH_wd) const { | |
const Texture& wallTex = GET_VALUE(rg.textures, texture); | |
const QRectF& rect = wallTex.image.rect(); | |
Size texSz_tx(rect.width(), rect.height()); | |
double screenSliceBottom_px = | |
floor(rg.viewport_px.y - slice.projSliceBottom_wd * rg.vWorldUnit_px); | |
double screenSliceTop_px = ceil(rg.viewport_px.y - slice.projSliceTop_wd * rg.vWorldUnit_px); | |
if (screenSliceBottom_px - screenSliceTop_px > 0) { | |
vector<QRect> srcRects; | |
vector<QRect> trgRects; | |
sampleWallTexture(rg, cam, screenX_px, targetH_wd, X.distanceAlongTarget, slice, texSz_tx, | |
frameRect, wallTex.size_wd, trgRects, srcRects); | |
assert(srcRects.size() == trgRects.size()); | |
for (unsigned int i = 0; i < srcRects.size(); ++i) { | |
drawImage(trgRects[i], wallTex.image, srcRects[i], X.distanceFromOrigin); | |
} | |
} | |
double viewportBottom_px = ceil((rg.viewport.y - slice.viewportBottom_wd) * rg.vWorldUnit_px); | |
double viewportTop_px = floor((rg.viewport.y - slice.viewportTop_wd) * rg.vWorldUnit_px); | |
return ScreenSlice{ | |
static_cast<int>(clipNumber(screenSliceBottom_px, Range(0, m_target.height() - 1))), | |
static_cast<int>(clipNumber(screenSliceTop_px, Range(0, m_target.height() - 1))), | |
static_cast<int>(clipNumber(viewportBottom_px, Range(0, m_target.height() - 1))), | |
static_cast<int>(clipNumber(viewportTop_px, Range(0, m_target.height() - 1)))}; | |
} | |
//=========================================== | |
// Renderer::drawSprite | |
//=========================================== | |
void Renderer::drawSprite(const RenderGraph& rg, const Camera& camera, const SpriteX& X, | |
double screenX_px) const { | |
const CSprite& sprite = *X.sprite; | |
const CVRect& vRect = *X.vRect; | |
const Slice& slice = X.slice; | |
const Texture& tex = rg.textures.at(sprite.texture); | |
const QRectF& uv = sprite.getView(vRect, X.X->viewPoint); | |
QRect r = tex.image.rect(); | |
QRect frame(r.width() * uv.x(), r.height() * uv.y(), r.width() * uv.width(), | |
r.height() * uv.height()); | |
int screenSliceBottom_px = rg.viewport_px.y - slice.projSliceBottom_wd * rg.vWorldUnit_px; | |
int screenSliceTop_px = rg.viewport_px.y - slice.projSliceTop_wd * rg.vWorldUnit_px; | |
const CZone& zone = *vRect.zone; | |
QRect srcRect = sampleSpriteTexture(camera, frame, X, vRect.size, | |
zone.floorHeight + vRect.y); | |
QRect trgRect(screenX_px, screenSliceTop_px, 1, screenSliceBottom_px - screenSliceTop_px); | |
drawImage(trgRect, tex.image, srcRect, X.X->distanceFromOrigin); | |
} | |
//=========================================== | |
// Renderer::drawWallDecal | |
//=========================================== | |
void Renderer::drawWallDecal(const RenderGraph& rg, const Camera& cam, | |
const SpatialSystem& spatialSystem, const CWallDecal& decal, const Intersection& X, | |
const Slice& slice, const CZone& zone, int screenX_px) const { | |
const CVRect& vRect = getVRect(spatialSystem, decal); | |
const Texture& decalTex = rg.textures.at(decal.texture); | |
int texX_px = decal.texRect.x() * decalTex.image.width(); | |
int texY_px = decal.texRect.y() * decalTex.image.height(); | |
int texW_px = decal.texRect.width() * decalTex.image.width(); | |
int texH_px = decal.texRect.height() * decalTex.image.height(); | |
double projSliceH_wd = slice.projSliceTop_wd - slice.projSliceBottom_wd; | |
double sliceH_wd = slice.sliceTop_wd - slice.sliceBottom_wd; | |
double sliceToProjScale = projSliceH_wd / sliceH_wd; | |
// World space | |
double sliceBottom_wd = slice.sliceBottom_wd + cam.height; | |
// World space (not camera space) | |
auto fnSliceToProjY = [&](double y) { | |
return slice.projSliceBottom_wd + (y - sliceBottom_wd) * sliceToProjScale; | |
}; | |
double floorH = zone.floorHeight; | |
double y0 = floorH + vRect.pos.y; | |
double y1 = floorH + vRect.pos.y + vRect.size.y; | |
int y0_px = projToScreenY(rg, fnSliceToProjY(y0)); | |
int y1_px = projToScreenY(rg, fnSliceToProjY(y1)); | |
double x = X.distanceAlongTarget - vRect.pos.x; | |
int i = texX_px + texW_px * x / vRect.size.x; | |
QRect srcRect(i, texY_px, 1, texH_px); | |
QRect trgRect(screenX_px, y1_px, 1, y0_px - y1_px); | |
drawImage(trgRect, decalTex.image, srcRect, X.distanceFromOrigin); | |
} | |
//=========================================== | |
// Renderer::drawColourOverlay | |
//=========================================== | |
void Renderer::drawColourOverlay(const RenderGraph& rg, QPainter& painter, | |
const CColourOverlay& overlay) const { | |
double x = (overlay.pos.x / rg.viewport.x) * rg.viewport_px.x; | |
double y = (1.0 - overlay.pos.y / rg.viewport.y) * rg.viewport_px.y; | |
double w = (overlay.size.x / rg.viewport.x) * rg.viewport_px.x; | |
double h = (overlay.size.y / rg.viewport.y) * rg.viewport_px.y; | |
painter.setPen(Qt::NoPen); | |
painter.setBrush(overlay.colour); | |
painter.drawRect(x, y - h, w, h); | |
} | |
//=========================================== | |
// Renderer::drawImageOverlay | |
//=========================================== | |
void Renderer::drawImageOverlay(const RenderGraph& rg, QPainter& painter, | |
const CImageOverlay& overlay) const { | |
double x = (overlay.pos.x / rg.viewport.x) * rg.viewport_px.x; | |
double y = (1.0 - overlay.pos.y / rg.viewport.y) * rg.viewport_px.y; | |
double w = (overlay.size.x / rg.viewport.x) * rg.viewport_px.x; | |
double h = (overlay.size.y / rg.viewport.y) * rg.viewport_px.y; | |
const Texture& tex = rg.textures.at(overlay.texture); | |
double sx = overlay.texRect.x() * tex.image.rect().width(); | |
double sy = overlay.texRect.y() * tex.image.rect().height(); | |
double sw = overlay.texRect.width() * tex.image.rect().width(); | |
double sh = overlay.texRect.height() * tex.image.rect().height(); | |
QRect srcRect(sx, sy, sw, sh); | |
QRect trgRect(x, y - h, w, h); | |
painter.drawImage(trgRect, tex.image, srcRect); | |
} | |
//=========================================== | |
// Renderer::drawTextOverlay | |
//=========================================== | |
void Renderer::drawTextOverlay(const RenderGraph& rg, QPainter& painter, | |
const CTextOverlay& overlay) const { | |
double x = overlay.pos.x * rg.hWorldUnit_px; | |
double y = (rg.viewport.y - overlay.pos.y) * rg.vWorldUnit_px; | |
double h = overlay.height * rg.vWorldUnit_px; | |
QFont font = m_appConfig.monoFont; | |
font.setPixelSize(h); | |
QFontMetrics fm{font}; | |
y -= fm.descent(); | |
painter.setFont(font); | |
painter.setPen(overlay.colour); | |
painter.drawText(x, y, overlay.text.c_str()); | |
} | |
//=========================================== | |
// Renderer::Renderer | |
//=========================================== | |
Renderer::Renderer(const AppConfig& appConfig, EntityManager& entityManager, QImage& target) | |
: m_appConfig(appConfig), | |
m_entityManager(entityManager), | |
m_target(target) { | |
for (unsigned int i = 0; i < m_tanMap_rp.size(); ++i) { | |
m_tanMap_rp[i] = 1.0 / tan(2.0 * PI * static_cast<double>(i) | |
/ static_cast<double>(m_tanMap_rp.size())); | |
} | |
double dx = (ATAN_MAX - ATAN_MIN) / static_cast<double>(m_atanMap.size()); | |
for (unsigned int i = 0; i < m_atanMap.size(); ++i) { | |
m_atanMap[i] = atan(ATAN_MIN + dx * static_cast<double>(i)); | |
} | |
m_numWorkerThreads = std::thread::hardware_concurrency() - 1; | |
if (m_numWorkerThreads < 0) { | |
m_numWorkerThreads = 0; | |
} | |
m_threads = vector<std::thread>(m_numWorkerThreads); | |
} | |
//=========================================== | |
// Renderer::getWallDecals | |
//=========================================== | |
static vector<CWallDecal*> getWallDecals(const SpatialSystem& spatialSystem, const CBoundary& wall, | |
double x) { | |
vector<CWallDecal*> decals; | |
decals.reserve(wall.decals.size()); | |
for (auto it = wall.decals.begin(); it != wall.decals.end(); ++it) { | |
CWallDecal* decal = it->get(); | |
const CVRect& vRect = getVRect(spatialSystem, *decal); | |
double x0 = vRect.pos.x; | |
double x1 = vRect.pos.x + vRect.size.x; | |
if (isBetween(x, x0, x1)) { | |
decals.push_back(decal); | |
} | |
} | |
std::sort(decals.begin(), decals.end(), [](const CWallDecal* a, const CWallDecal* b) { | |
return a->zIndex < b->zIndex; | |
}); | |
return decals; | |
} | |
//=========================================== | |
// Renderer::renderColumns | |
//=========================================== | |
void Renderer::renderColumns(const RenderGraph& rg, const Camera& cam, | |
const SpatialSystem& spatialSystem, const RenderSystem& renderSystem, int from, int to) const { | |
CastResult prev; | |
for (int screenX_px = from; screenX_px < to; ++screenX_px) { | |
double projX_wd = static_cast<double>(screenX_px - rg.viewport_px.x / 2) / rg.hWorldUnit_px; | |
Vec2f ray(cam.F, projX_wd); | |
CastResult result; | |
castRay(rg, cam, spatialSystem, renderSystem, ray, result); | |
// Hack to fill gaps where regions don't connect properly | |
if (result.intersections.size() == 0) { | |
result = std::move(prev); | |
} | |
for (auto it = result.intersections.rbegin(); it != result.intersections.rend(); ++it) { | |
XWrapper& X = **it; | |
if (X.kind == XWrapperKind::WALL) { | |
const WallX& wallX = DYNAMIC_CAST<const WallX&>(X); | |
const CZone& zone = *wallX.nearZone; | |
const CRegion& region = DYNAMIC_CAST<const CRegion&>(renderSystem | |
.getComponent(wallX.nearZone->entityId())); | |
ScreenSlice slice = drawSlice(rg, cam, *wallX.X, wallX.slice, wallX.wall->texture, | |
wallX.wall->texRect, screenX_px); | |
vector<CWallDecal*> decals = getWallDecals(spatialSystem, *wallX.wall, | |
wallX.X->distanceAlongTarget); | |
for (auto decal : decals) { | |
drawWallDecal(rg, cam, spatialSystem, *decal, *wallX.X, wallX.slice, zone, screenX_px); | |
} | |
drawFloorSlice(rg, cam, spatialSystem, *wallX.X, region, zone.floorHeight, slice, | |
screenX_px, projX_wd); | |
if (region.hasCeiling) { | |
drawCeilingSlice(rg, cam, *wallX.X, region, zone.ceilingHeight, slice, screenX_px, | |
projX_wd); | |
} | |
else { | |
drawSkySlice(rg, cam, slice, screenX_px); | |
} | |
} | |
else if (X.kind == XWrapperKind::JOIN) { | |
const JoinX& joinX = DYNAMIC_CAST<const JoinX&>(X); | |
ScreenSlice slice0 = drawSlice(rg, cam, *joinX.X, joinX.slice0, joinX.join->bottomTexture, | |
joinX.join->bottomTexRect, screenX_px, joinX.farZone->floorHeight); | |
ScreenSlice slice1; | |
if (joinX.slice1.visible) { | |
slice1 = drawSlice(rg, cam, *joinX.X, joinX.slice1, joinX.join->topTexture, | |
joinX.join->topTexRect, screenX_px, joinX.farZone->ceilingHeight); | |
} | |
const CZone& parentZone = *joinX.softEdge->zoneA; | |
const CZone& zone = *joinX.nearZone; | |
const CRegion& region = DYNAMIC_CAST<const CRegion&>(renderSystem | |
.getComponent(joinX.nearZone->entityId())); | |
vector<CWallDecal*> decals = getWallDecals(spatialSystem, *joinX.join, | |
joinX.X->distanceAlongTarget); | |
Slice slice = joinX.slice0; | |
if (joinX.slice1.visible) { | |
slice.sliceTop_wd = joinX.slice1.sliceTop_wd; | |
slice.projSliceTop_wd = joinX.slice1.projSliceTop_wd; | |
slice.viewportTop_wd = joinX.slice1.viewportTop_wd; | |
} | |
for (auto decal : decals) { | |
drawWallDecal(rg, cam, spatialSystem, *decal, *joinX.X, slice, parentZone, screenX_px); | |
} | |
drawFloorSlice(rg, cam, spatialSystem, *joinX.X, region, zone.floorHeight, slice0, | |
screenX_px, projX_wd); | |
if (joinX.slice1.visible) { | |
if (region.hasCeiling) { | |
drawCeilingSlice(rg, cam, *joinX.X, region, zone.ceilingHeight, slice1, screenX_px, | |
projX_wd); | |
} | |
else { | |
drawSkySlice(rg, cam, slice1, screenX_px); | |
} | |
} | |
} | |
else if (X.kind == XWrapperKind::SPRITE) { | |
const SpriteX& spriteX = DYNAMIC_CAST<const SpriteX&>(X); | |
drawSprite(rg, cam, spriteX, screenX_px); | |
} | |
} | |
prev = std::move(result); | |
} | |
} | |
//=========================================== | |
// Renderer::renderScene | |
//=========================================== | |
void Renderer::renderScene(const RenderGraph& rg, const Camera& cam) { | |
const SpatialSystem& spatialSystem = m_entityManager | |
.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
const RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
const int W = rg.viewport_px.x; | |
#ifdef SINGLE_THREAD | |
renderColumns(rg, cam, spatialSystem, renderSystem, 0, W); | |
#else | |
if (m_numWorkerThreads == 0) { | |
renderColumns(rg, cam, spatialSystem, renderSystem, 0, W); | |
} | |
else { | |
int perThread = W / (m_numWorkerThreads + 1); | |
int remainder = W % (m_numWorkerThreads + 1); | |
for (int i = 0; i < m_numWorkerThreads; ++i) { | |
int from = i * perThread; | |
int to = from + perThread; | |
m_threads[i] = std::thread{&Renderer::renderColumns, this, std::ref(rg), std::ref(cam), | |
std::ref(spatialSystem), std::ref(renderSystem), from, to}; | |
} | |
int from = m_numWorkerThreads * perThread; | |
int to = from + perThread + remainder; | |
renderColumns(rg, cam, spatialSystem, renderSystem, from, to); | |
for (auto& t : m_threads) { | |
t.join(); | |
} | |
} | |
#endif | |
QPainter painter; | |
painter.begin(&m_target); | |
for (auto it = rg.overlays.begin(); it != rg.overlays.end(); ++it) { | |
const COverlay& overlay = **it; | |
switch (overlay.kind) { | |
case COverlayKind::IMAGE: | |
drawImageOverlay(rg, painter, DYNAMIC_CAST<const CImageOverlay&>(overlay)); | |
break; | |
case COverlayKind::TEXT: | |
drawTextOverlay(rg, painter, DYNAMIC_CAST<const CTextOverlay&>(overlay)); | |
break; | |
case COverlayKind::COLOUR: | |
drawColourOverlay(rg, painter, DYNAMIC_CAST<const CColourOverlay&>(overlay)); | |
break; | |
} | |
} | |
painter.end(); | |
} | |
#include "event.hpp" | |
#include "exception.hpp" | |
#include "raycast/behaviour_system.hpp" | |
#include "utils.hpp" | |
using std::set; | |
//=========================================== | |
// BehaviourSystem::update | |
//=========================================== | |
void BehaviourSystem::update() { | |
for (auto it = m_components.begin(); it != m_components.end(); ++it) { | |
it->second->update(); | |
} | |
} | |
//=========================================== | |
// BehaviourSystem::handleEvent | |
//=========================================== | |
void BehaviourSystem::handleEvent(const GameEvent& event, const set<entityId_t>& entities) { | |
for (auto it = m_components.begin(); it != m_components.end(); ++it) { | |
if (entities.count(it->first)) { | |
CBehaviour& c = *it->second; | |
c.handleTargetedEvent(event); | |
} | |
} | |
} | |
//=========================================== | |
// BehaviourSystem::handleEvent | |
//=========================================== | |
void BehaviourSystem::handleEvent(const GameEvent& event) { | |
for (auto it = m_components.begin(); it != m_components.end(); ++it) { | |
CBehaviour& c = *it->second; | |
c.handleBroadcastedEvent(event); | |
} | |
} | |
//=========================================== | |
// BehaviourSystem::hasComponent | |
//=========================================== | |
bool BehaviourSystem::hasComponent(entityId_t entityId) const { | |
return m_components.find(entityId) != m_components.end(); | |
} | |
//=========================================== | |
// BehaviourSystem::getComponent | |
//=========================================== | |
CBehaviour& BehaviourSystem::getComponent(entityId_t entityId) const { | |
return *m_components.at(entityId); | |
} | |
//=========================================== | |
// BehaviourSystem::addComponent | |
//=========================================== | |
void BehaviourSystem::addComponent(pComponent_t component) { | |
if (component->kind() != ComponentKind::C_BEHAVIOUR) { | |
EXCEPTION("Component is not of type CBehaviour"); | |
} | |
CBehaviour* p = dynamic_cast<CBehaviour*>(component.release()); | |
m_components.insert(std::make_pair(p->entityId(), pCBehaviour_t(p))); | |
} | |
//=========================================== | |
// BehaviourSystem::removeEntity | |
//=========================================== | |
void BehaviourSystem::removeEntity(entityId_t entityId) { | |
m_components.erase(entityId); | |
} | |
#include <cassert> | |
#include <vector> | |
#include <random> | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/agent_system.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "utils.hpp" | |
#include "exception.hpp" | |
using std::vector; | |
using std::function; | |
static const double AGENT_SPEED = 120.0; | |
static const double REACTION_SPEED = 0.25; | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// indexOfClosestPoint | |
//=========================================== | |
static int indexOfClosestPoint(const Point& point, const vector<Point>& points) { | |
int idx = 0; | |
double closest = 1000000; | |
for (unsigned int i = 0; i < points.size(); ++i) { | |
double d = distance(point, points[i]); | |
if (d < closest) { | |
closest = d; | |
idx = i; | |
} | |
} | |
return idx; | |
} | |
//=========================================== | |
// CAgent::setAnimation | |
//=========================================== | |
void CAgent::setAnimation(EntityManager& entityManager) { | |
AnimationSystem& animationSystem = | |
entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
switch (m_state) { | |
case ST_STATIONARY: | |
animationSystem.playAnimation(entityId(), "idle", true); | |
break; | |
case ST_SHOOTING: | |
animationSystem.playAnimation(entityId(), "shoot", false); | |
break; | |
case ST_CHASING_OBJECT: | |
case ST_ON_FIXED_PATH: | |
animationSystem.playAnimation(entityId(), "run", true); | |
break; | |
} | |
} | |
//=========================================== | |
// CAgent::setState | |
//=========================================== | |
void CAgent::setState(EntityManager& entityManager, state_t state) { | |
if (m_state != state) { | |
m_state = state; | |
setAnimation(entityManager); | |
} | |
} | |
//=========================================== | |
// CAgent::hasLineOfSight | |
//=========================================== | |
bool CAgent::hasLineOfSight(const SpatialSystem& spatialSystem, Matrix& m, Vec2f& ray, | |
double& hAngle, double& vAngle, double& height) const { | |
CVRect& body = dynamic_cast<CVRect&>(spatialSystem.getComponent(entityId())); | |
const Player& player = *spatialSystem.sg.player; | |
const Point& target_wld = player.pos(); | |
double targetHeight = player.feetHeight() + 0.5 * player.getTallness(); | |
Vec2f v = target_wld - body.pos; | |
hAngle = atan2(v.y, v.x); | |
Matrix t(hAngle, body.pos); | |
m = t.inverse(); | |
Point target_rel = m * target_wld; | |
ray = normalise(target_rel); | |
height = body.zone->floorHeight + body.size.y * 0.5; | |
vAngle = atan2(targetHeight - height, length(target_rel)); | |
if (fabs(vAngle) > DEG_TO_RAD(PLAYER_MAX_PITCH)) { | |
return false; | |
} | |
vector<pIntersection_t> intersections = spatialSystem.entitiesAlong3dRay(*body.zone, ray * 0.1, | |
height, ray, vAngle, m); | |
if (intersections.size() > 0) { | |
entityId_t id = intersections.front()->entityId; | |
if (id == player.body) { | |
return true; | |
} | |
} | |
return false; | |
} | |
//=========================================== | |
// CAgent::CAgent | |
//=========================================== | |
CAgent::CAgent(entityId_t entityId) | |
: Component(entityId, ComponentKind::C_AGENT) { | |
m_gunfireTiming.reset(new TRandomIntervals(400, 4000)); | |
} | |
//=========================================== | |
// CAgent::startPatrol | |
//=========================================== | |
void CAgent::startPatrol(EntityManager& entityManager) { | |
if (patrolPath != -1) { | |
const CPath& path = entityManager.getComponent<CPath>(patrolPath, ComponentKind::C_SPATIAL); | |
m_path = path.points; | |
m_pathClosed = true; | |
setState(entityManager, ST_ON_FIXED_PATH); | |
m_waypointIdx = -1; | |
} | |
else { | |
setState(entityManager, ST_STATIONARY); | |
} | |
} | |
//=========================================== | |
// CAgent::startChase | |
//=========================================== | |
void CAgent::startChase(EntityManager& entityManager) { | |
SpatialSystem& spatialSystem = entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
m_waypointIdx = -1; | |
setState(entityManager, ST_CHASING_OBJECT); | |
m_targetObject = spatialSystem.sg.player->body; | |
} | |
//=========================================== | |
// CAgent::followPath | |
//=========================================== | |
void CAgent::followPath(EntityManager& entityManager, TimeService& timeService) { | |
SpatialSystem& spatialSystem = entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
CVRect& body = dynamic_cast<CVRect&>(spatialSystem.getComponent(entityId())); | |
if (m_waypointIdx == -1) { | |
m_waypointIdx = indexOfClosestPoint(body.pos, m_path); | |
} | |
const Point& target = m_path[m_waypointIdx]; | |
double speed = AGENT_SPEED / timeService.frameRate; | |
Vec2f v = normalise(target - body.pos) * speed; | |
body.angle = atan2(v.y, v.x); | |
spatialSystem.moveEntity(entityId(), v); | |
if (distance(body.pos, target) < 10) { | |
if (m_pathClosed) { | |
m_waypointIdx = (m_waypointIdx + 1) % m_path.size(); | |
} | |
else { | |
setState(entityManager, ST_STATIONARY); | |
if (m_onFinish) { | |
m_onFinish(*this); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// CAgent::attemptShot | |
//=========================================== | |
void CAgent::attemptShot(EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService) { | |
AgentSystem& agentSystem = entityManager.system<AgentSystem>(ComponentKind::C_AGENT); | |
SpatialSystem& spatialSystem = entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
DamageSystem& damageSystem = entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
CVRect& body = dynamic_cast<CVRect&>(spatialSystem.getComponent(entityId())); | |
m_gunfireTiming->doIfReady([&]() { | |
double hAngle = 0; | |
double vAngle = 0; | |
Matrix m; | |
Vec2f ray; | |
double height = 0; | |
if (hasLineOfSight(spatialSystem, m, ray, hAngle, vAngle, height)) { | |
body.angle = hAngle; | |
entityId_t id = entityId(); | |
state_t prevState = m_state; | |
setState(entityManager, ST_SHOOTING); | |
timeService.onTimeout([&agentSystem, &entityManager, id, prevState, this]() { | |
// If the entity still exists, our pointer is still good | |
if (agentSystem.hasComponent(id)) { | |
setState(entityManager, prevState); | |
} | |
}, 1.0); | |
// Move ray randomly to simulate inaccurate aim | |
// | |
std::normal_distribution<double> dist(0, DEG_TO_RAD(1.4)); | |
double vDa = dist(randEngine); | |
double hDa = dist(randEngine); | |
Matrix rot(hDa, Vec2f(0, 0)); | |
ray = rot * ray; | |
timeService.onTimeout([=, &agentSystem, &damageSystem, &audioService, &body]() { | |
if (agentSystem.hasComponent(id)) { | |
damageSystem.damageAtIntersection(*body.zone, ray * 0.1, height, ray, vAngle + vDa, m, 1); | |
audioService.playSoundAtPos("shotgun_shoot", body.pos); | |
} | |
}, REACTION_SPEED); | |
} | |
}); | |
} | |
//=========================================== | |
// CAgent::navigateTo | |
//=========================================== | |
void CAgent::navigateTo(EntityManager& entityManager, const Point& p, | |
function<void(CAgent&)> onFinish) { | |
SpatialSystem& spatialSystem = entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
AnimationSystem& animationSystem = | |
entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
const CVRect& body = dynamic_cast<const CVRect&>(spatialSystem.getComponent(entityId())); | |
m_path = spatialSystem.shortestPath(body.pos, p, 10); | |
m_pathClosed = false; | |
m_waypointIdx = -1; | |
setState(entityManager, ST_ON_FIXED_PATH); | |
m_onFinish = onFinish; | |
animationSystem.playAnimation(entityId(), "run", true); | |
} | |
//=========================================== | |
// CAgent::update | |
//=========================================== | |
void CAgent::update(EntityManager& entityManager, TimeService& timeService, | |
AudioService& audioService) { | |
SpatialSystem& spatialSystem = entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
const Player& player = *spatialSystem.sg.player; | |
switch (m_state) { | |
case ST_STATIONARY: | |
break; | |
case ST_CHASING_OBJECT: | |
if (player.alive) { | |
assert(m_targetObject != -1); | |
m_path = spatialSystem.shortestPath(entityId(), m_targetObject, 10); | |
m_pathClosed = false; | |
} | |
case ST_ON_FIXED_PATH: | |
assert(m_path.size() > 0); | |
followPath(entityManager, timeService); | |
break; | |
case ST_SHOOTING: | |
break; | |
} | |
if (isHostile && player.alive) { | |
attemptShot(entityManager, timeService, audioService); | |
} | |
} | |
//=========================================== | |
// CAgent::handleEvent | |
//=========================================== | |
void CAgent::handleEvent(const GameEvent& event, EntityManager& entityManager) { | |
if (event.name == stPatrollingTrigger) { | |
startPatrol(entityManager); | |
} | |
else if (event.name == stChasingTrigger && m_state == ST_STATIONARY) { | |
startChase(entityManager); | |
} | |
else if (event.name == "entityDamaged") { | |
const EEntityDamaged& e = dynamic_cast<const EEntityDamaged&>(event); | |
if (e.entityId == entityId()) { | |
onDamage(entityManager); | |
} | |
} | |
} | |
//=========================================== | |
// CAgent::onDamage | |
//=========================================== | |
void CAgent::onDamage(EntityManager&) { | |
// TODO: Chase player? | |
} | |
//=========================================== | |
// AgentSystem::update | |
//=========================================== | |
void AgentSystem::update() { | |
for (auto& c : m_components) { | |
c.second->update(m_entityManager, m_timeService, m_audioService); | |
} | |
} | |
//=========================================== | |
// AgentSystem::handleEvent | |
//=========================================== | |
void AgentSystem::handleEvent(const GameEvent& event) { | |
for (auto& c : m_components) { | |
c.second->handleEvent(event, m_entityManager); | |
} | |
} | |
//=========================================== | |
// AgentSystem::navigateTo | |
//=========================================== | |
void AgentSystem::navigateTo(entityId_t entityId, const Point& point) { | |
DBG_PRINT("Entity " << entityId << " navigating to " << point << "\n"); | |
m_components.at(entityId)->navigateTo(m_entityManager, point, [this](CAgent& agent) { | |
agent.startPatrol(m_entityManager); | |
}); | |
} | |
//=========================================== | |
// AgentSystem::hasComponent | |
//=========================================== | |
bool AgentSystem::hasComponent(entityId_t entityId) const { | |
return m_components.find(entityId) != m_components.end(); | |
} | |
//=========================================== | |
// AgentSystem::getComponent | |
//=========================================== | |
CAgent& AgentSystem::getComponent(entityId_t entityId) const { | |
return *m_components.at(entityId); | |
} | |
//=========================================== | |
// AgentSystem::addComponent | |
//=========================================== | |
void AgentSystem::addComponent(pComponent_t component) { | |
if (component->kind() != ComponentKind::C_AGENT) { | |
EXCEPTION("Component is not of type CAgent"); | |
} | |
CAgent* p = dynamic_cast<CAgent*>(component.release()); | |
m_components.insert(std::make_pair(p->entityId(), pCAgent_t(p))); | |
} | |
//=========================================== | |
// AgentSystem::removeEntity | |
//=========================================== | |
void AgentSystem::removeEntity(entityId_t entityId) { | |
m_components.erase(entityId); | |
} | |
#include <list> | |
#include <regex> | |
#include <cassert> | |
#include <QMessageBox> | |
#include <QMenuBar> | |
#include <QPainter> | |
#include <QBrush> | |
#include <QPaintEvent> | |
#include "event_system.hpp" | |
#include "raycast/raycast_widget.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/behaviour_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/spawn_system.hpp" | |
#include "raycast/agent_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/misc_factory.hpp" | |
#include "raycast/sprite_factory.hpp" | |
#include "raycast/geometry_factory.hpp" | |
#include "raycast/game_event.hpp" | |
#include "app_config.hpp" | |
#ifdef DEBUG | |
namespace chrono = std::chrono; | |
#endif | |
using std::string; | |
using std::list; | |
const double PLAYER_SPEED = 350.0; | |
//=========================================== | |
// RaycastWidget::RaycastWidget | |
//=========================================== | |
RaycastWidget::RaycastWidget(const AppConfig& appConfig, EventSystem& eventSystem, int width, | |
int height, int frameRate) | |
: QWidget(nullptr), | |
m_appConfig(appConfig), | |
m_eventSystem(eventSystem), | |
m_timeService(frameRate), | |
m_audioService(m_entityManager, m_timeService), | |
m_width(width), | |
m_height(height), | |
m_frameRate(frameRate) {} | |
//=========================================== | |
// RaycastWidget::loadTextures | |
//=========================================== | |
void RaycastWidget::loadTextures(RenderGraph& rg, const parser::Object& obj) { | |
for (auto it = obj.dict.begin(); it != obj.dict.end(); ++it) { | |
string name = it->first; | |
string details = it->second; | |
Size sz(100, 100); | |
std::regex rx("([a-zA-Z0-9_\\.\\/]+)(?:,(\\d+),(\\d+))?"); | |
std::smatch m; | |
std::regex_match(details, m, rx); | |
if (m.size() == 0) { | |
EXCEPTION("Error parsing texture description for texture with name '" << name << "'"); | |
} | |
string path = m.str(1); | |
if (!m.str(2).empty()) { | |
sz.x = std::stod(m.str(2)); | |
} | |
if (!m.str(3).empty()) { | |
sz.y = std::stod(m.str(3)); | |
} | |
rg.textures[name] = Texture{QImage(m_appConfig.dataPath(path).c_str()), sz}; | |
} | |
} | |
//=========================================== | |
// RaycastWidget::loadMusicAssets | |
//=========================================== | |
void RaycastWidget::loadMusicAssets(const parser::Object& obj) { | |
for (auto it = obj.dict.begin(); it != obj.dict.end(); ++it) { | |
m_audioService.addMusicTrack(it->first, m_appConfig.dataPath(it->second)); | |
} | |
} | |
//=========================================== | |
// RaycastWidget::loadSoundAssets | |
//=========================================== | |
void RaycastWidget::loadSoundAssets(const parser::Object& obj) { | |
for (auto it = obj.dict.begin(); it != obj.dict.end(); ++it) { | |
m_audioService.addSound(it->first, m_appConfig.dataPath(it->second)); | |
} | |
} | |
//=========================================== | |
// RaycastWidget::configureAudioService | |
//=========================================== | |
void RaycastWidget::configureAudioService(const parser::Object& obj) { | |
parser::Object* pObj = firstObjectOfType(obj.children, "music_assets"); | |
if (pObj != nullptr) { | |
loadMusicAssets(*pObj); | |
} | |
pObj = firstObjectOfType(obj.children, "sound_assets"); | |
if (pObj != nullptr) { | |
loadSoundAssets(*pObj); | |
} | |
string musicTrack = getValue(obj.dict, "music_track", ""); | |
string strMusicVolume = getValue(obj.dict, "music_volume", "1.0"); | |
double musicVolume = std::stod(strMusicVolume); | |
bool loop = getValue(obj.dict, "loop", "true") == "true"; | |
if (musicTrack.length() > 0) { | |
m_audioService.playMusic(musicTrack, loop); | |
m_audioService.setMusicVolume(musicVolume); | |
} | |
} | |
//=========================================== | |
// RaycastWidget::configure | |
//=========================================== | |
void RaycastWidget::configure(RenderGraph& rg, const parser::Object& config) { | |
parser::Object* pObj = firstObjectOfType(config.children, "texture_assets"); | |
if (pObj != nullptr) { | |
loadTextures(rg, *pObj); | |
} | |
pObj = firstObjectOfType(config.children, "audio_config"); | |
if (pObj != nullptr) { | |
configureAudioService(*pObj); | |
} | |
} | |
//=========================================== | |
// RaycastWidget::loadMap | |
//=========================================== | |
void RaycastWidget::loadMap(const string& mapFilePath) { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem&>(ComponentKind::C_RENDER); | |
RenderGraph& rg = renderSystem.rg; | |
list<parser::pObject_t> objects; | |
parser::parse(mapFilePath, objects); | |
// A config object and the root region | |
assert(objects.size() == 2); | |
parser::Object& config = *firstObjectOfType(objects, "config"); | |
parser::Object& rootRegion = *firstObjectOfType(objects, "region"); | |
configure(rg, config); | |
m_rootFactory.constructObject("region", -1, rootRegion, -1, Matrix()); | |
} | |
//=========================================== | |
// RaycastWidget::initialise | |
//=========================================== | |
void RaycastWidget::initialise(const string& mapFile) { | |
m_rootFactory.addFactory(pGameObjectFactory_t(new MiscFactory(m_rootFactory, m_entityManager, | |
m_audioService, m_timeService))); | |
m_rootFactory.addFactory(pGameObjectFactory_t(new SpriteFactory(m_rootFactory, m_entityManager, | |
m_audioService, m_timeService))); | |
m_rootFactory.addFactory(pGameObjectFactory_t(new GeometryFactory(m_rootFactory, | |
m_entityManager, m_audioService, m_timeService))); | |
setMouseTracking(true); | |
m_defaultCursor = cursor().shape(); | |
m_cursorCaptured = false; | |
m_mouseBtnState = false; | |
m_buffer = QImage(m_width, m_height, QImage::Format_ARGB32); | |
setFocus(); | |
BehaviourSystem* behaviourSystem = new BehaviourSystem; | |
m_entityManager.addSystem(ComponentKind::C_BEHAVIOUR, pSystem_t(behaviourSystem)); | |
RenderSystem* renderSystem = new RenderSystem(m_appConfig, m_entityManager, m_buffer); | |
m_entityManager.addSystem(ComponentKind::C_RENDER, pSystem_t(renderSystem)); | |
SpatialSystem* spatialSystem = new SpatialSystem(m_entityManager, m_timeService, m_frameRate); | |
m_entityManager.addSystem(ComponentKind::C_SPATIAL, pSystem_t(spatialSystem)); | |
AnimationSystem* animationSystem = new AnimationSystem(m_entityManager); | |
m_entityManager.addSystem(ComponentKind::C_ANIMATION, pSystem_t(animationSystem)); | |
InventorySystem* inventorySystem = new InventorySystem(m_entityManager); | |
m_entityManager.addSystem(ComponentKind::C_INVENTORY, pSystem_t(inventorySystem)); | |
EventHandlerSystem* eventHandlerSystem = new EventHandlerSystem; | |
m_entityManager.addSystem(ComponentKind::C_EVENT_HANDLER, pSystem_t(eventHandlerSystem)); | |
DamageSystem* damageSystem = new DamageSystem(m_entityManager); | |
m_entityManager.addSystem(ComponentKind::C_DAMAGE, pSystem_t(damageSystem)); | |
SpawnSystem* spawnSystem = new SpawnSystem(m_entityManager, m_rootFactory, m_timeService); | |
m_entityManager.addSystem(ComponentKind::C_SPAWN, pSystem_t(spawnSystem)); | |
AgentSystem* agentSystem = new AgentSystem(m_entityManager, m_timeService, m_audioService); | |
m_entityManager.addSystem(ComponentKind::C_AGENT, pSystem_t(agentSystem)); | |
FocusSystem* focusSystem = new FocusSystem(m_appConfig, m_entityManager, m_timeService); | |
m_entityManager.addSystem(ComponentKind::C_FOCUS, pSystem_t(focusSystem)); | |
m_audioService.initialise(); | |
loadMap(mapFile); | |
m_timer = makeQtObjPtr<QTimer>(this); | |
connect(m_timer.get(), SIGNAL(timeout()), this, SLOT(tick())); | |
QPainter painter; | |
painter.begin(&m_buffer); | |
int h = 20; | |
QFont font; | |
font.setPixelSize(h); | |
m_buffer.fill(Qt::black); | |
painter.setFont(font); | |
painter.setPen(Qt::white); | |
painter.drawText((m_width - 100) / 2, (m_height - h) / 2, "Loading..."); | |
painter.end(); | |
m_playerImmobilised = false; | |
m_entityId = Component::getNextId(); | |
CEventHandler* events = new CEventHandler(m_entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"immobilise_player", | |
[this](const GameEvent&) { | |
m_playerImmobilised = true; | |
uncaptureCursor(); | |
}}); | |
eventHandlerSystem->addComponent(pComponent_t(events)); | |
} | |
//=========================================== | |
// RaycastWidget::start | |
//=========================================== | |
void RaycastWidget::start() { | |
m_eventSystem.fire(pEvent_t{new Event{"raycast/start"}}); | |
m_timer->start(1000 / m_frameRate); | |
} | |
//=========================================== | |
// RaycastWidget::paintEvent | |
//=========================================== | |
void RaycastWidget::paintEvent(QPaintEvent*) { | |
if (m_timer->isActive()) { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
renderSystem.render(); | |
} | |
QPainter painter; | |
painter.begin(this); | |
painter.drawImage(rect(), m_buffer); | |
painter.end(); | |
} | |
//=========================================== | |
// RaycastWidget::keyPressEvent | |
//=========================================== | |
void RaycastWidget::keyPressEvent(QKeyEvent* event) { | |
m_keyStates[event->key()] = true; | |
if (!m_timer->isActive()) { | |
return; | |
} | |
m_entityManager.broadcastEvent(EKeyPressed{event->key()}); | |
if (event->key() == Qt::Key_F) { | |
DBG_PRINT("Frame rate = " << m_measuredFrameRate << "\n"); | |
} | |
else if (event->key() == Qt::Key_Escape) { | |
uncaptureCursor(); | |
} | |
} | |
//=========================================== | |
// RaycastWidget::uncaptureCursor | |
//=========================================== | |
void RaycastWidget::uncaptureCursor() { | |
m_cursorCaptured = false; | |
setCursor(m_defaultCursor); | |
m_entityManager.broadcastEvent(EMouseUncaptured{}); | |
} | |
//=========================================== | |
// RaycastWidget::keyReleaseEvent | |
//=========================================== | |
void RaycastWidget::keyReleaseEvent(QKeyEvent* event) { | |
m_keyStates[event->key()] = false; | |
} | |
//=========================================== | |
// RaycastWidget::mousePressEvent | |
//=========================================== | |
void RaycastWidget::mousePressEvent(QMouseEvent* event) { | |
if (m_cursorCaptured && event->button() == Qt::LeftButton) { | |
m_mouseBtnState = true; | |
} | |
if (!m_timer->isActive() || m_playerImmobilised) { | |
return; | |
} | |
if (m_cursorCaptured == false) { | |
Point centre(width() / 2, height() / 2); | |
QCursor::setPos(mapToGlobal(QPoint(centre.x, centre.y))); | |
m_cursor = centre; | |
setCursor(Qt::BlankCursor); | |
m_entityManager.broadcastEvent(EMouseCaptured{}); | |
m_cursorCaptured = true; | |
} | |
} | |
//=========================================== | |
// RaycastWidget::mouseReleaseEvent | |
//=========================================== | |
void RaycastWidget::mouseReleaseEvent(QMouseEvent* event) { | |
if (event->button() == Qt::LeftButton) { | |
m_mouseBtnState = false; | |
} | |
} | |
//=========================================== | |
// RaycastWidget::mouseMoveEvent | |
//=========================================== | |
void RaycastWidget::mouseMoveEvent(QMouseEvent* event) { | |
m_cursor.x = event->x(); | |
m_cursor.y = event->y(); | |
} | |
//=========================================== | |
// RaycastWidget::tick | |
//=========================================== | |
void RaycastWidget::tick() { | |
#ifdef DEBUG | |
if (m_frame % 10 == 0) { | |
chrono::high_resolution_clock::time_point t_ = chrono::high_resolution_clock::now(); | |
chrono::duration<double> span = chrono::duration_cast<chrono::duration<double>>(t_ - m_t); | |
m_measuredFrameRate = 10.0 / span.count(); | |
m_t = t_; | |
} | |
++m_frame; | |
#endif | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
m_entityManager.purgeEntities(); | |
m_entityManager.update(); | |
m_timeService.update(); | |
if (spatialSystem.sg.player->alive && !m_playerImmobilised) { | |
if (m_keyStates[Qt::Key_E]) { | |
spatialSystem.jump(); | |
} | |
if (m_keyStates[Qt::Key_Space]) { | |
GameEvent e("player_activate"); | |
spatialSystem.handleEvent(e); | |
m_keyStates[Qt::Key_Space] = false; | |
} | |
Vec2f v; // The vector is in camera space | |
if (m_keyStates[Qt::Key_A]) { | |
v.y -= 1; | |
} | |
if (m_keyStates[Qt::Key_D]) { | |
v.y += 1; | |
} | |
if (m_keyStates[Qt::Key_W] || m_keyStates[Qt::Key_Up]) { | |
v.x += 1; | |
} | |
if (m_keyStates[Qt::Key_S] || m_keyStates[Qt::Key_Down]) { | |
v.x -= 1; | |
} | |
if (v.x != 0 || v.y != 0) { | |
double ds = PLAYER_SPEED / m_frameRate; | |
spatialSystem.movePlayer(normalise(v) * ds); | |
} | |
if (m_keyStates[Qt::Key_Left]) { | |
spatialSystem.hRotateCamera(-(1.2 / m_frameRate) * PI); | |
} | |
if (m_keyStates[Qt::Key_Right]) { | |
spatialSystem.hRotateCamera((1.2 / m_frameRate) * PI); | |
} | |
if (m_cursorCaptured) { | |
setFocus(); | |
Point centre(width() / 2, height() / 2); | |
// Y-axis is top to bottom | |
Point v(m_cursor.x - centre.x, centre.y - m_cursor.y); | |
if (v.x != 0 || v.y != 0) { | |
QCursor::setPos(mapToGlobal(QPoint(centre.x, centre.y))); | |
m_cursor = centre; | |
} | |
if (fabs(v.x) > 0) { | |
double da = 0.0006 * PI * v.x; | |
spatialSystem.hRotateCamera(da); | |
} | |
if (fabs(v.y) > 0) { | |
double da = 0.0006 * PI * v.y; | |
spatialSystem.vRotateCamera(da); | |
} | |
if (m_mouseBtnState == true) { | |
spatialSystem.sg.player->shoot(); | |
m_mouseBtnState = false; | |
} | |
} | |
} | |
update(); | |
} | |
//=========================================== | |
// RaycastWidget::~RaycastWidget | |
//=========================================== | |
RaycastWidget::~RaycastWidget() { | |
DBG_PRINT("RaycastWidget::~RaycastWidget\n"); | |
} | |
#include <cassert> | |
#include <algorithm> | |
#include "raycast/geometry.hpp" | |
#include "utils.hpp" | |
#ifdef DEBUG | |
using std::ostream; | |
ostream& operator<<(ostream& os, const Point& pt) { | |
os << "(" << pt.x << ", " << pt.y << ")"; | |
return os; | |
} | |
ostream& operator<<(ostream& os, const Vec3f& pt) { | |
os << "(" << pt.x << ", " << pt.y << ", " << pt.z << ")"; | |
return os; | |
} | |
ostream& operator<<(ostream& os, const LineSegment& lseg) { | |
os << "LineSegment " << lseg.A << ", " << lseg.B; | |
return os; | |
} | |
ostream& operator<<(ostream& os, const Line& line) { | |
os << "Line " << line.a << "x + " << line.b << "y + " << line.c << " = 0"; | |
return os; | |
} | |
ostream& operator<<(ostream& os, const Circle& circ) { | |
os.precision(std::numeric_limits<double>::max_digits10); | |
os << "Circle " << std::fixed << circ.pos << ", " << circ.radius; | |
return os; | |
} | |
ostream& operator<<(ostream& os, const Matrix& mat) { | |
os << "Matrix " << mat[0][0] << " " << mat[0][1] << " " << mat[0][2] << "\n"; | |
os << " " << mat[1][0] << " " << mat[1][1] << " " << mat[1][2] << "\n"; | |
os << " " << mat[2][0] << " " << mat[2][1] << " " << mat[2][2]; | |
return os; | |
} | |
ostream& operator<<(ostream& os, const Range& range) { | |
os << "Range [" << range.a << ", " << range.b << "]"; | |
return os; | |
} | |
#endif | |
//=========================================== | |
// lineIntersect | |
//=========================================== | |
Point lineIntersect(const Line& l0, const Line& l1, int depth) { | |
assert(depth <= 1); | |
if (l0.hasSteepGradient() || l1.hasSteepGradient()) { | |
Vec3f l0params(l0.a, l0.b, l0.c); | |
Vec3f l1params(l1.a, l1.b, l1.c); | |
static Matrix m(0.25 * PI, Vec2f(0, 0)); | |
static Matrix m_inv = m.inverse(); | |
Vec3f l0params_ = l0params * m_inv; | |
Vec3f l1params_ = l1params * m_inv; | |
Line l0_(l0params_.x, l0params_.y, l0params_.z); | |
Line l1_(l1params_.x, l1params_.y, l1params_.z); | |
if (l0_.hasSteepGradient() || l1_.hasSteepGradient()) { | |
Point p = lineIntersect(l0_, l1_, depth + 1); | |
Vec3f p_(p.x, p.y, 0); | |
p_ = p_ * m; | |
return Point(p_.x, p_.y); | |
} | |
Vec3f p; | |
p.x = (l1_.b * l0_.c - l0_.b * l1_.c) / (l0_.b * l1_.a - l1_.b * l0_.a); | |
p.y = (-l0_.a * p.x - l0_.c) / l0_.b; | |
p.z = 0; | |
p = p * m; | |
return Point(p.x, p.y); | |
} | |
else { | |
Point p; | |
p.x = (l1.b * l0.c - l0.b * l1.c) / (l0.b * l1.a - l1.b * l0.a); | |
p.y = (-l0.a * p.x - l0.c) / l0.b; | |
return p; | |
} | |
} | |
//=========================================== | |
// swapAxes | |
//=========================================== | |
static Point swapAxes(const Point& pt) { | |
return Point(pt.y, pt.x); | |
} | |
//=========================================== | |
// lineSegmentCircleIntersect | |
//=========================================== | |
bool lineSegmentCircleIntersect(const Circle& circ, const LineSegment& lseg) { | |
if (distance(lseg.A, circ.pos) <= circ.radius || distance(lseg.B, circ.pos) <= circ.radius) { | |
return true; | |
} | |
Line l = lseg.line(); | |
const Point& p = circ.pos; | |
double r = circ.radius; | |
if (l.hasSteepGradient()) { | |
return lineSegmentCircleIntersect(Circle{swapAxes(p), r}, | |
LineSegment(swapAxes(lseg.A), swapAxes(lseg.B))); | |
} | |
double a = 1.0 + pow(l.a, 2) / pow(l.b, 2); | |
double b = 2.0 * (-p.x + (l.a * l.c) / pow(l.b, 2) + (p.y * l.a) / l.b); | |
double c = pow(p.x, 2) + pow(l.c, 2) / pow(l.b, 2) + 2.0 * p.y * l.c / l.b + pow(p.y, 2) | |
- pow(r, 2); | |
double discriminant = b * b - 4.0 * a * c; | |
if (discriminant < 0.0) { | |
return false; | |
} | |
double x0 = (-b + sqrt(discriminant)) / (2.0 * a); | |
double y0 = -(l.a * x0 + l.c) / l.b; | |
double x1 = (-b - sqrt(discriminant)) / (2.0 * a); | |
double y1 = -(l.a * x1 + l.c) / l.b; | |
return (isBetween(x0, lseg.A.x, lseg.B.x) && isBetween(y0, lseg.A.y, lseg.B.y)) | |
|| (isBetween(x1, lseg.A.x, lseg.B.x) && isBetween(y1, lseg.A.y, lseg.B.y)); | |
} | |
#include <chrono> | |
#include "raycast/timing.hpp" | |
#include "utils.hpp" | |
namespace chrono = std::chrono; | |
using std::function; | |
//=========================================== | |
// getTime | |
//=========================================== | |
inline static unsigned long getTime() { | |
return chrono::high_resolution_clock::now().time_since_epoch() / chrono::milliseconds(1); | |
} | |
//=========================================== | |
// now | |
//=========================================== | |
inline static double now() { | |
return 0.001 * getTime(); | |
} | |
//=========================================== | |
// Debouncer::Debouncer | |
//=========================================== | |
Debouncer::Debouncer(double seconds) | |
: m_duration(seconds), | |
m_start(now()) {} | |
//=========================================== | |
// Debouncer::ready | |
//=========================================== | |
bool Debouncer::ready() { | |
double t = now(); | |
if (t - m_start >= m_duration) { | |
m_start = t; | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// Debouncer::reset | |
//=========================================== | |
void Debouncer::reset() { | |
m_start = now(); | |
} | |
//=========================================== | |
// TRandomIntervals::TRandomIntervals | |
//=========================================== | |
TRandomIntervals::TRandomIntervals(unsigned long min, unsigned long max) { | |
m_randEngine.seed(randomSeed()); | |
m_distribution = std::uniform_real_distribution<>(min, max); | |
calcDueTime(); | |
} | |
//=========================================== | |
// TRandomIntervals::doIfReady | |
//=========================================== | |
bool TRandomIntervals::doIfReady(function<void()> fn) { | |
double t = getTime(); | |
if (t >= m_dueTime) { | |
fn(); | |
calcDueTime(); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// TRandomIntervals::calcDueTime | |
//=========================================== | |
void TRandomIntervals::calcDueTime() { | |
m_dueTime = getTime() + m_distribution(m_randEngine); | |
} | |
#include <sstream> | |
#include "raycast/time_service.hpp" | |
using std::string; | |
using std::stringstream; | |
using std::function; | |
static long nextId = 0; | |
//=========================================== | |
// TimeService::now | |
//=========================================== | |
double TimeService::now() const { | |
return m_frame / frameRate; | |
} | |
//=========================================== | |
// TimeService::addTween | |
//=========================================== | |
void TimeService::addTween(const Tween& tween, string name) { | |
if (name.empty()) { | |
stringstream ss; | |
ss << "tween" << rand(); | |
name = ss.str(); | |
} | |
if (m_tweens.find(name) == m_tweens.end()) { | |
m_tweens[name] = TweenWrap{tween, m_frame}; | |
} | |
} | |
//=========================================== | |
// TimeService::removeTween | |
//=========================================== | |
void TimeService::removeTween(const string& name) { | |
m_tweens.erase(name); | |
} | |
//=========================================== | |
// TimeService::deletePending | |
//=========================================== | |
void TimeService::deletePending() { | |
for (long id : m_pendingDeletion) { | |
m_timeouts.erase(id); | |
m_intervals.erase(id); | |
} | |
m_pendingDeletion.clear(); | |
} | |
//=========================================== | |
// TimeService::update | |
//=========================================== | |
void TimeService::update() { | |
deletePending(); | |
double elapsed = m_frame / frameRate; | |
for (auto it = m_tweens.begin(); it != m_tweens.end();) { | |
const TweenWrap& tweenWrap = it->second; | |
const Tween& tween = tweenWrap.tween; | |
long i = static_cast<long>(m_frame - tweenWrap.start); | |
if (!tween.tick(i, elapsed, frameRate)) { | |
tween.finish(i, elapsed, frameRate); | |
m_tweens.erase(it++); | |
} | |
else { | |
++it; | |
} | |
} | |
for (auto it = m_timeouts.begin(); it != m_timeouts.end();) { | |
const Timeout& timeout = it->second; | |
long frames = static_cast<long>(m_frame - timeout.start); | |
double elapsed = frames / frameRate; | |
if (elapsed >= timeout.duration) { | |
timeout.fn(); | |
it = m_timeouts.erase(it); | |
} | |
else { | |
++it; | |
} | |
} | |
for (auto it = m_intervals.begin(); it != m_intervals.end();) { | |
Interval& interval = it->second; | |
long frames = static_cast<long>(m_frame - interval.start); | |
double elapsed = frames / frameRate; | |
if (elapsed >= interval.duration) { | |
bool cont = interval.fn(); | |
interval.start = m_frame; | |
if (!cont) { | |
it = m_intervals.erase(it); | |
} | |
} | |
else { | |
++it; | |
} | |
} | |
++m_frame; | |
} | |
//=========================================== | |
// TimeService::onTimeout | |
//=========================================== | |
long TimeService::onTimeout(function<void()> fn, double seconds) { | |
m_timeouts[nextId] = Timeout{fn, seconds, m_frame}; | |
return nextId++; | |
} | |
//=========================================== | |
// TimeService::atIntervals | |
// | |
// fn should return true to continue the interval, false to cancel | |
//=========================================== | |
long TimeService::atIntervals(function<bool()> fn, double seconds) { | |
m_intervals[nextId] = Interval{fn, seconds, m_frame}; | |
return nextId++; | |
} | |
//=========================================== | |
// TimeService::cancelTimeout | |
//=========================================== | |
void TimeService::cancelTimeout(long id) { | |
m_pendingDeletion.insert(id); | |
} | |
#include <regex> | |
#include <algorithm> | |
#include "raycast/c_elevator_behaviour.hpp" | |
#include "raycast/c_switch_behaviour.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_components.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "event.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
using std::vector; | |
//=========================================== | |
// CElevatorBehaviour::CElevatorBehaviour | |
//=========================================== | |
CElevatorBehaviour::CElevatorBehaviour(entityId_t entityId, EntityManager& entityManager, | |
AudioService& audioService, double frameRate, const vector<double>& levels, int initLevelIdx) | |
: CBehaviour(entityId), | |
m_entityManager(entityManager), | |
m_audioService(audioService), | |
m_frameRate(frameRate), | |
m_levels(levels) { | |
if (m_levels.size() < 2) { | |
EXCEPTION("Elevator must have at least 2 levels"); | |
} | |
std::sort(m_levels.begin(), m_levels.end()); | |
if (initLevelIdx >= static_cast<int>(m_levels.size())) { | |
EXCEPTION("Elevator's initial level index out of range"); | |
} | |
m_target = initLevelIdx; | |
CZone& zone = m_entityManager.getComponent<CZone>(this->entityId(), ComponentKind::C_SPATIAL); | |
zone.floorHeight = m_levels[m_target]; | |
} | |
//=========================================== | |
// CElevatorBehaviour::playSound | |
//=========================================== | |
void CElevatorBehaviour::playSound() { | |
CZone& zone = m_entityManager.getComponent<CZone>(entityId(), ComponentKind::C_SPATIAL); | |
const Point& pos = zone.edges.front()->lseg.A; | |
if (!m_audioService.soundIsPlaying("elevator", m_soundId)) { | |
m_soundId = m_audioService.playSoundAtPos("elevator", pos, true); | |
} | |
} | |
//=========================================== | |
// CElevatorBehaviour::stopSound | |
//=========================================== | |
void CElevatorBehaviour::stopSound() const { | |
m_audioService.stopSound("elevator", m_soundId); | |
} | |
//=========================================== | |
// CElevatorBehaviour::setSpeed | |
//=========================================== | |
void CElevatorBehaviour::setSpeed(double speed) { | |
m_speed = speed; | |
} | |
//=========================================== | |
// CElevatorBehaviour::update | |
//=========================================== | |
void CElevatorBehaviour::update() { | |
CZone& zone = m_entityManager.getComponent<CZone>(entityId(), ComponentKind::C_SPATIAL); | |
double y = zone.floorHeight; | |
switch (m_state) { | |
case ST_STOPPED: | |
return; | |
case ST_MOVING: { | |
double targetY = m_levels[m_target]; | |
double dir = targetY > y ? 1.0 : -1.0; | |
double dy = dir * m_speed / m_frameRate; | |
zone.floorHeight += dy; | |
if (fabs(zone.floorHeight + dy - targetY) < fabs(dy)) { | |
m_state = ST_STOPPED; | |
zone.floorHeight = targetY; | |
m_entityManager.broadcastEvent(EElevatorStopped(entityId())); | |
stopSound(); | |
} | |
break; | |
} | |
} | |
} | |
//=========================================== | |
// CElevatorBehaviour::move | |
//=========================================== | |
void CElevatorBehaviour::move(int level) { | |
m_target = level; | |
if (m_state == ST_STOPPED) { | |
m_state = ST_MOVING; | |
playSound(); | |
} | |
} | |
//=========================================== | |
// CElevatorBehaviour::handleTargetedEvent | |
//=========================================== | |
void CElevatorBehaviour::handleTargetedEvent(const GameEvent& e) { | |
if (e.name == "switch_activated") { | |
const ESwitchActivate& event = dynamic_cast<const ESwitchActivate&>(e); | |
DBG_PRINT("Elevator activated\n"); | |
std::regex rx("level(\\d+(?:\\.\\d+)?)"); | |
std::smatch m; | |
std::regex_match(event.message, m, rx); | |
if (m.size() < 2) { | |
EXCEPTION("Error parsing message '" << event.message << "' from switch"); | |
} | |
move(std::stod(m.str(1))); | |
} | |
else if (e.name == "player_activate_entity" && this->isPlayerActivated) { | |
if (m_state == ST_STOPPED) { | |
m_target = (m_target + 1) % m_levels.size(); | |
m_state = ST_MOVING; | |
playSound(); | |
} | |
} | |
} | |
#include "raycast/spawn_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/agent_system.hpp" | |
#include "raycast/time_service.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// SpawnSystem::handleEvent | |
//=========================================== | |
void SpawnSystem::handleEvent(const GameEvent& event) { | |
if (event.name == "entity_destroyed") { | |
const EEntityDestroyed& e = dynamic_cast<const EEntityDestroyed&>(event); | |
auto it = m_spawnables.find(e.entityId); | |
if (it != m_spawnables.end()) { | |
std::shared_ptr<CSpawnable> spawnable(it->second.release()); | |
m_timeService.onTimeout([this, spawnable]() { | |
entityId_t entityId = makeIdForObj(*spawnable->object); | |
m_rootFactory.constructObject(spawnable->typeName, entityId, *spawnable->object, | |
spawnable->parentId, spawnable->parentTransform); | |
SpatialSystem& spatialSystem = | |
m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
const CSpatial& spatial = spatialSystem.getComponent(entityId); | |
// Only deal with V_RECTs for now | |
if (spatial.kind == CSpatialKind::V_RECT) { | |
const CVRect& vRect = dynamic_cast<const CVRect&>(spatial); | |
CVRect& spawnPointVRect = m_entityManager.getComponent<CVRect>(spawnable->spawnPoint, | |
ComponentKind::C_SPATIAL); | |
Point oldPos = vRect.pos; | |
spatialSystem.relocateEntity(entityId, *spawnPointVRect.zone, spawnPointVRect.pos); | |
AgentSystem& agentSystem = | |
m_entityManager.system<AgentSystem>(ComponentKind::C_AGENT); | |
if (agentSystem.hasComponent(entityId)) { | |
agentSystem.navigateTo(entityId, oldPos); | |
} | |
} | |
}, spawnable->delay); | |
} | |
} | |
} | |
//=========================================== | |
// SpawnSystem::addComponent | |
//=========================================== | |
void SpawnSystem::addComponent(pComponent_t component) { | |
CSpawn& spawn = dynamic_cast<CSpawn&>(*component); | |
switch (spawn.kind) { | |
case CSpawnKind::SPAWN_POINT: { | |
pCSpawnPoint_t c(dynamic_cast<CSpawnPoint*>(component.release())); | |
m_spawnPoints.insert(make_pair(c->entityId(), std::move(c))); | |
break; | |
} | |
case CSpawnKind::SPAWNABLE: { | |
pCSpawnable_t c(dynamic_cast<CSpawnable*>(component.release())); | |
m_spawnables.insert(make_pair(c->entityId(), std::move(c))); | |
break; | |
} | |
} | |
} | |
//=========================================== | |
// SpawnSystem::hasComponent | |
//=========================================== | |
bool SpawnSystem::hasComponent(entityId_t entityId) const { | |
return m_spawnPoints.count(entityId) || m_spawnables.count(entityId); | |
} | |
//=========================================== | |
// SpawnSystem::getComponent | |
//=========================================== | |
CSpawn& SpawnSystem::getComponent(entityId_t entityId) const { | |
auto it = m_spawnPoints.find(entityId); | |
if (it != m_spawnPoints.end()) { | |
return *it->second; | |
} | |
auto jt = m_spawnables.find(entityId); | |
if (jt != m_spawnables.end()) { | |
return *jt->second; | |
} | |
EXCEPTION("Component not found"); | |
} | |
//=========================================== | |
// SpawnSystem::removeEntity | |
//=========================================== | |
void SpawnSystem::removeEntity(entityId_t id) { | |
m_spawnPoints.erase(id); | |
m_spawnables.erase(id); | |
} | |
#include <set> | |
#include "raycast/c_switch_behaviour.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/time_service.hpp" | |
#include "event.hpp" | |
using std::string; | |
using std::set; | |
using std::map; | |
//=========================================== | |
// CSwitchBehaviour::CSwitchBehaviour | |
//=========================================== | |
CSwitchBehaviour::CSwitchBehaviour(entityId_t entityId, EntityManager& entityManager, | |
TimeService& timeService, const string& message, SwitchState initialState, bool toggleable, | |
double toggleDelay) | |
: CBehaviour(entityId), | |
m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_message(message), | |
m_state(initialState), | |
m_toggleable(toggleable), | |
m_timer(toggleDelay) { | |
setDecal(); | |
} | |
//=========================================== | |
// CSwitchBehaviour::update | |
//=========================================== | |
void CSwitchBehaviour::update() {} | |
//=========================================== | |
// CSwitchBehaviour::setState | |
//=========================================== | |
void CSwitchBehaviour::setState(SwitchState state) { | |
m_state = state; | |
setDecal(); | |
} | |
//=========================================== | |
// CSwitchBehaviour::getState | |
//=========================================== | |
SwitchState CSwitchBehaviour::getState() const { | |
return m_state; | |
} | |
//=========================================== | |
// CSwitchBehaviour::setDecal | |
//=========================================== | |
void CSwitchBehaviour::setDecal() { | |
QRectF texRect; | |
switch (m_state) { | |
case SwitchState::OFF: | |
texRect = QRectF(0, 0, 0.5, 1); | |
break; | |
case SwitchState::ON: | |
texRect = QRectF(0.5, 0, 0.5, 1); | |
break; | |
} | |
CWallDecal* decal = getDecal(); | |
if (decal != nullptr) { | |
decal->texRect = texRect; | |
} | |
} | |
//=========================================== | |
// CSwitchBehaviour::handleTargetedEvent | |
//=========================================== | |
void CSwitchBehaviour::handleTargetedEvent(const GameEvent& e_) { | |
if (e_.name == "player_activate_entity") { | |
const EPlayerActivateEntity& e = dynamic_cast<const EPlayerActivateEntity&>(e_); | |
if (this->disabled) { | |
return; | |
} | |
if (m_toggleable || m_state == SwitchState::OFF) { | |
if (m_timer.ready()) { | |
const auto& inventorySystem = m_entityManager | |
.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
if (requiredItemType != "") { | |
const map<string, entityId_t>& items = | |
inventorySystem.getBucketItems(e.player.body, requiredItemType); | |
if (!contains(items, requiredItemName)) { | |
FocusSystem& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
focusSystem.showCaption(entityId()); | |
return; | |
} | |
} | |
switch (m_state) { | |
case SwitchState::OFF: | |
m_state = SwitchState::ON; | |
break; | |
case SwitchState::ON: | |
m_state = SwitchState::OFF; | |
break; | |
} | |
setDecal(); | |
ESwitchActivate eActivate(entityId(), m_state, m_message); | |
if (this->target != -1) { | |
m_entityManager.fireEvent(eActivate, {this->target}); | |
} | |
m_entityManager.broadcastEvent(eActivate); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// CSwitchBehaviour::getDecal | |
//=========================================== | |
CWallDecal* CSwitchBehaviour::getDecal() const { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
if (renderSystem.hasComponent(entityId())) { | |
return dynamic_cast<CWallDecal*>(&renderSystem.getComponent(entityId())); | |
} | |
return nullptr; | |
} | |
#include <string> | |
#include <sstream> | |
#include "exception.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "utils.hpp" | |
using std::list; | |
using std::string; | |
using std::map; | |
using std::pair; | |
using std::istream; | |
using std::ostream; | |
using std::stringstream; | |
using tinyxml2::XMLDocument; | |
using tinyxml2::XMLElement; | |
namespace parser { | |
//=========================================== | |
// isTriangle | |
//=========================================== | |
static bool isTriangle(const Path& path) { | |
return path.points.size() == 3 && path.closed; | |
} | |
//=========================================== | |
// transformFromTriangle | |
//=========================================== | |
Matrix transformFromTriangle(const Path& path) { | |
if (!isTriangle(path)) { | |
EXCEPTION("Path is not a triangle"); | |
} | |
Point centre = (path.points[0] + path.points[1] + path.points[2]) / 3.0; | |
Point mostDistantPoint = centre; | |
for (int i = 0; i < 3; ++i) { | |
if (distance(path.points[i], centre) > distance(mostDistantPoint, centre)) { | |
mostDistantPoint = path.points[i]; | |
} | |
} | |
Vec2f v = mostDistantPoint - centre; | |
double a = atan2(v.y, v.x); | |
return Matrix(a, centre); | |
} | |
//=========================================== | |
// getNextToken | |
//=========================================== | |
PathStreamToken getNextToken(istream& is) { | |
PathStreamToken result; | |
string s; | |
is >> s; | |
if (s.length() == 1) { | |
if (s == "m" || s == "l") { | |
result.kind = PathStreamToken::SET_RELATIVE; | |
} | |
else if (s == "M" || s == "L") { | |
result.kind = PathStreamToken::SET_ABSOLUTE; | |
} | |
else if (s == "z" || s == "Z") { | |
result.kind = PathStreamToken::CLOSE_PATH; | |
} | |
else { | |
EXCEPTION("Unknown SVG path operator '" << s << "'"); | |
} | |
} | |
else { | |
result.kind = PathStreamToken::POINT; | |
char comma = '_'; | |
stringstream ss(s); | |
ss >> result.p.x; | |
ss >> comma; | |
PC_ASSERT_EQ(comma, ','); | |
ss >> result.p.y; | |
} | |
return result; | |
} | |
//=========================================== | |
// constructPath | |
//=========================================== | |
void constructPath(const XMLElement& e, Path& path) { | |
string data = e.Attribute("d"); | |
stringstream ss(data); | |
Point cursor; | |
bool relative = false; | |
while (!ss.eof()) { | |
PathStreamToken result = getNextToken(ss); | |
if (result.kind == PathStreamToken::SET_RELATIVE) { | |
relative = true; | |
} | |
else if (result.kind == PathStreamToken::SET_ABSOLUTE) { | |
relative = false; | |
} | |
else if (result.kind == PathStreamToken::CLOSE_PATH) { | |
path.closed = true; | |
PC_ASSERT(ss.eof()); | |
break; | |
} | |
else if (result.kind == PathStreamToken::POINT) { | |
Point p; | |
if (relative) { | |
p = cursor + result.p; | |
} | |
else { | |
p = result.p; | |
} | |
path.points.push_back(p); | |
cursor = p; | |
} | |
} | |
} | |
//=========================================== | |
// constructPathFromRect | |
//=========================================== | |
void constructPathFromRect(const XMLElement& e, Path& path) { | |
double x = std::stod(e.Attribute("x")); | |
double y = std::stod(e.Attribute("y")); | |
double w = std::stod(e.Attribute("width")); | |
double h = std::stod(e.Attribute("height")); | |
path.points.push_back(Point(x, y)); | |
path.points.push_back(Point(x + w, y)); | |
path.points.push_back(Point(x + w, y + h)); | |
path.points.push_back(Point(x, y + h)); | |
path.closed = true; | |
} | |
//=========================================== | |
// parseKvpString | |
//=========================================== | |
pair<string, string> parseKvpString(const string& s) { | |
stringstream ss(s); | |
string key, val; | |
std::getline(ss, key, '='); | |
std::getline(ss, val, '='); | |
return std::make_pair(key, val); | |
} | |
//=========================================== | |
// extractKvPairs | |
//=========================================== | |
void extractKvPairs(const XMLElement& node, map<string, string>& kv) { | |
const XMLElement* e = node.FirstChildElement(); | |
while (e != nullptr) { | |
string tag(e->Name()); | |
if (tag == "text") { | |
const XMLElement* tspan = e->FirstChildElement(); | |
PC_ASSERT_EQ(string(tspan->Name()), "tspan"); | |
kv.insert(parseKvpString(tspan->GetText())); | |
} | |
e = e->NextSiblingElement(); | |
} | |
} | |
//=========================================== | |
// parseTranslateTransform | |
//=========================================== | |
Matrix parseTranslateTransform(const string& s) { | |
stringstream ss(s); | |
string buf(10, '\0'); | |
ss.read(&buf[0], 10); | |
PC_ASSERT_EQ(buf, "translate("); | |
Matrix m; | |
char comma; | |
ss >> m[0][2] >> comma; | |
PC_ASSERT_EQ(comma, ','); | |
ss >> m[1][2] >> buf; | |
PC_ASSERT_EQ(buf, ")"); | |
return m; | |
} | |
//=========================================== | |
// parseMatrixTransform | |
//=========================================== | |
Matrix parseMatrixTransform(const string& s) { | |
stringstream ss(s); | |
string buf(7, '\0'); | |
ss.read(&buf[0], 7); | |
PC_ASSERT_EQ(buf, "matrix("); | |
Matrix m; | |
char comma; | |
ss >> m[0][0] >> comma; | |
PC_ASSERT_EQ(comma, ','); | |
ss >> m[1][0] >> comma; | |
PC_ASSERT_EQ(comma, ','); | |
ss >> m[0][1] >> comma; | |
PC_ASSERT_EQ(comma, ','); | |
ss >> m[1][1] >> comma; | |
PC_ASSERT_EQ(comma, ','); | |
ss >> m[0][2] >> comma; | |
PC_ASSERT_EQ(comma, ','); | |
ss >> m[1][2] >> buf; | |
PC_ASSERT_EQ(buf, ")"); | |
return m; | |
} | |
//=========================================== | |
// parseTransform | |
//=========================================== | |
Matrix parseTransform(const string& s) { | |
PC_ASSERT(s.length() > 0); | |
if (s[0] == 't') { | |
return parseTranslateTransform(s); | |
} | |
else if (s[0] == 'm') { | |
return parseMatrixTransform(s); | |
} | |
EXCEPTION("Error parsing unknown transform"); | |
} | |
//=========================================== | |
// extractGeometry | |
//=========================================== | |
void extractGeometry(const XMLElement& node, Path& path, Matrix& groupTransform, | |
Matrix& pathTransform) { | |
Matrix groupM; | |
Matrix pathM; | |
const char* trans = node.Attribute("transform"); | |
if (trans) { | |
groupM = parseTransform(trans); | |
} | |
const XMLElement* e = node.FirstChildElement(); | |
while (e != nullptr) { | |
string tag(e->Name()); | |
if (tag == "path" || tag == "rect") { | |
if (tag == "path") { | |
constructPath(*e, path); | |
} | |
else if (tag == "rect") { | |
constructPathFromRect(*e, path); | |
} | |
const char* trans = e->Attribute("transform"); | |
if (trans) { | |
pathM = parseTransform(trans); | |
} | |
break; | |
} | |
e = e->NextSiblingElement(); | |
} | |
groupTransform = groupM; | |
pathTransform = pathM; | |
} | |
//=========================================== | |
// getRoot | |
//=========================================== | |
static const Object* getRoot(const Object* obj_) { | |
const Object* obj = obj_; | |
while (obj->parent != nullptr) { | |
obj = obj->parent; | |
} | |
return obj; | |
} | |
//=========================================== | |
// printTrace_r | |
//=========================================== | |
static void printTrace_r(ostream& out, const Object* obj, const Object* problemObj, int depth = 0) { | |
out << string(depth * 2, ' '); | |
string name = getValue(obj->dict, "name", ""); | |
if (obj == problemObj) { | |
out << "***"; | |
} | |
out << (obj->type != "" ? obj->type : "TYPE_MISSING"); | |
if (obj == problemObj) { | |
out << "***"; | |
} | |
if (name != "") { | |
out << ", '" << name << "'"; | |
} | |
out << "\n"; | |
for (auto& child : obj->children) { | |
printTrace_r(out, child.get(), problemObj, depth + 1); | |
} | |
} | |
//=========================================== | |
// printErrors | |
//=========================================== | |
static void printErrors(ostream& out, const ParseErrors& errors) { | |
out << "The following errors occurred parsing map file:\n"; | |
for (auto& error : errors) { | |
const Object* root = getRoot(error.object); | |
out << error.message << "\n"; | |
out << "Trace:\n"; | |
printTrace_r(out, root, error.object); | |
} | |
} | |
//=========================================== | |
// constructObject_r | |
//=========================================== | |
Object* constructObject_r(const Object* parent, const XMLElement& node, ParseErrors& errors) { | |
Object* obj = new Object(parent); | |
extractKvPairs(node, obj->dict); | |
if (obj->dict.count("type") == 0) { | |
errors.push_back(ParseError{obj, "Object has no type key"}); | |
} | |
else { | |
obj->type = obj->dict.at("type"); | |
obj->dict.erase("type"); | |
} | |
extractGeometry(node, obj->path, obj->groupTransform, obj->pathTransform); | |
const XMLElement* e = node.FirstChildElement(); | |
while (e != nullptr) { | |
string tag(e->Name()); | |
if (tag == "g") { | |
obj->children.push_back(pObject_t(constructObject_r(obj, *e, errors))); | |
} | |
e = e->NextSiblingElement(); | |
} | |
return obj; | |
} | |
//=========================================== | |
// parse | |
//=========================================== | |
void parse(const string& file, list<pObject_t>& objects) { | |
XMLDocument doc; | |
doc.LoadFile(file.c_str()); | |
if (doc.Error()) { | |
EXCEPTION("Error parsing map file " << file << "; ErrorID=" << doc.ErrorID()); | |
} | |
ParseErrors errors; | |
XMLElement* root = doc.FirstChildElement("svg"); | |
XMLElement* e = root->FirstChildElement(); | |
while (e != nullptr) { | |
string tag(e->Name()); | |
if (tag == "g") { | |
objects.push_back(pObject_t(constructObject_r(nullptr, *e, errors))); | |
} | |
e = e->NextSiblingElement(); | |
} | |
if (errors.size() > 0) { | |
stringstream errString; | |
printErrors(errString, errors); | |
EXCEPTION(errString.str()); | |
} | |
} | |
//=========================================== | |
// firstObjectOfType | |
//=========================================== | |
Object* firstObjectOfType(const list<pObject_t>& objects, const string& type) { | |
for (auto& pObj : objects) { | |
if (pObj->type == type) { | |
return pObj.get(); | |
} | |
} | |
return nullptr; | |
} | |
} | |
//=========================================== | |
// getValue | |
//=========================================== | |
const string& getValue(const map<string, string>& m, const string& key) { | |
try { | |
return m.at(key); | |
} | |
catch (std::out_of_range&) { | |
EXCEPTION("No '" << key << "' key in map"); | |
} | |
} | |
//=========================================== | |
// getValue | |
//=========================================== | |
const string& getValue(const map<string, string>& m, const string& key, const string& default_) { | |
if (m.find(key) != m.end()) { | |
return m.at(key); | |
} | |
else { | |
return default_; | |
} | |
} | |
//=========================================== | |
// makeIdForObj | |
//=========================================== | |
entityId_t makeIdForObj(const parser::Object& obj) { | |
if (contains<string>(obj.dict, "name")) { | |
return Component::getIdFromString(getValue(obj.dict, "name")); | |
} | |
else { | |
return Component::getNextId(); | |
} | |
} | |
#include "raycast/c_player_behaviour.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// CPlayerBehaviour::CPlayerBehaviour | |
//=========================================== | |
CPlayerBehaviour::CPlayerBehaviour(entityId_t entityId, EntityManager& entityManager, | |
TimeService& timeService) | |
: CBehaviour(entityId), | |
m_entityManager(entityManager), | |
m_timeService(timeService) {} | |
//=========================================== | |
// CPlayerBehaviour::update | |
//=========================================== | |
void CPlayerBehaviour::update() { | |
} | |
//=========================================== | |
// CPlayerBehaviour::handleTargetedEvent | |
//=========================================== | |
void CPlayerBehaviour::handleTargetedEvent(const GameEvent& e_) { | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
Player& player = *spatialSystem.sg.player; | |
if (e_.name == "entity_damaged") { | |
auto& e = dynamic_cast<const EEntityDamaged&>(e_); | |
#ifdef DEBUG | |
CDamage& damage = m_entityManager.getComponent<CDamage>(player.body, ComponentKind::C_DAMAGE); | |
DBG_PRINT("Player health: " << damage.health << "\n"); | |
#endif | |
if (e.health < e.prevHealth && player.red == -1) { | |
player.red = Component::getNextId(); | |
double maxAlpha = 80; | |
CColourOverlay* overlay = new CColourOverlay(player.red, QColor(200, 0, 0, maxAlpha), | |
Point(0, 0), renderSystem.rg.viewport, 10); | |
renderSystem.addComponent(pCRender_t(overlay)); | |
double duration = 0.33; | |
int da = maxAlpha / (duration * m_timeService.frameRate); | |
m_timeService.addTween(Tween{[=](long, double, double) -> bool { | |
int alpha = overlay->colour.alpha() - da; | |
overlay->colour.setAlpha(alpha); | |
return alpha > 0; | |
}, [&](long, double, double) { | |
m_entityManager.deleteEntity(player.red); | |
player.red = -1; | |
}}, "redFade"); | |
} | |
} | |
else if (e_.name == "entity_destroyed") { | |
if (!player.invincible) { | |
player.alive = false; | |
entityId_t entityId = Component::getNextId(); | |
CColourOverlay* overlay = new CColourOverlay(entityId, QColor(200, 0, 0, 80), Point(0, 0), | |
renderSystem.rg.viewport); | |
renderSystem.addComponent(pCRender_t(overlay)); | |
double y = player.feetHeight(); | |
double h = player.getTallness(); | |
player.setFeetHeight(y + 0.6 * h); | |
player.changeTallness(-0.6 * h); | |
m_entityManager.deleteEntity(player.sprite); | |
m_entityManager.deleteEntity(player.crosshair); | |
m_entityManager.broadcastEvent(GameEvent{"player_death"}); | |
} | |
} | |
} | |
#include <algorithm> | |
#include "raycast/root_factory.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "utils.hpp" | |
using std::string; | |
using std::set; | |
//=========================================== | |
// RootFactory::RootFactory | |
//=========================================== | |
RootFactory::RootFactory() {} | |
//=========================================== | |
// RootFactory::addFactory | |
//=========================================== | |
void RootFactory::addFactory(pGameObjectFactory_t factory) { | |
const set<string>& types = factory->types(); | |
for (auto jt = types.begin(); jt != types.end(); ++jt) { | |
m_factoriesByType[*jt] = factory.get(); | |
m_types.insert(*jt); | |
} | |
m_factories.push_back(std::move(factory)); | |
} | |
//=========================================== | |
// RootFactory::types | |
//=========================================== | |
const set<string>& RootFactory::types() const { | |
return m_types; | |
} | |
//=========================================== | |
// RootFactory::constructObject | |
//=========================================== | |
bool RootFactory::constructObject(const string& type, entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
#ifdef DEBUG | |
const string& name = getValue(obj.dict, "name", ""); | |
DBG_PRINT(string(m_dbgIndentLvl++, '\t') << "Constructing " << type | |
<< (name.length() > 0 ? string(" (") + name + ")" : "") << "...\n"); | |
#endif | |
auto it = m_factoriesByType.find(type); | |
if (it != m_factoriesByType.end()) { | |
bool success = it->second->constructObject(type, entityId, obj, parentId, parentTransform); | |
DBG_PRINT(string(--m_dbgIndentLvl, '\t') << (success ? "Success\n" : "Fail\n")); | |
return success; | |
} | |
else { | |
DBG_PRINT("No factory knows how to make object of type '" << type << "'\n"); | |
} | |
return false; | |
} | |
#include <cassert> | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "utils.hpp" | |
#include "exception.hpp" | |
using std::make_pair; | |
using std::string; | |
using std::set; | |
using std::map; | |
//=========================================== | |
// Bucket::~Bucket | |
//=========================================== | |
Bucket::~Bucket() {} | |
//=========================================== | |
// InventorySystem::update | |
//=========================================== | |
void InventorySystem::update() {} | |
//=========================================== | |
// InventorySystem::handleEvent | |
//=========================================== | |
void InventorySystem::handleEvent(const GameEvent& event, const set<entityId_t>& entities) { | |
// TODO: Should listen for movement of all collectors, not just the player | |
if (event.name == "player_move") { | |
const EPlayerMove& e = dynamic_cast<const EPlayerMove&>(event); | |
for (auto it = m_collectables.begin(); it != m_collectables.end(); ++it) { | |
CCollectable& collectable = *it->second; | |
entityId_t id = it->first; | |
if (entities.count(id)) { | |
addToBucket(e.player.body, collectable); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// InventorySystem::addComponent | |
//=========================================== | |
void InventorySystem::addComponent(pComponent_t component) { | |
CInventory* ptr = dynamic_cast<CInventory*>(component.release()); | |
if (ptr->kind == CInventoryKind::COLLECTOR) { | |
addCollector(ptr); | |
} | |
else if (ptr->kind == CInventoryKind::COLLECTABLE) { | |
addCollectable(ptr); | |
} | |
} | |
//=========================================== | |
// InventorySystem::addCollector | |
//=========================================== | |
void InventorySystem::addCollector(CInventory* component) { | |
CCollector* ptr = dynamic_cast<CCollector*>(component); | |
assert(ptr != nullptr); | |
m_collectors.insert(make_pair(ptr->entityId(), pCCollector_t(ptr))); | |
} | |
//=========================================== | |
// InventorySystem::addCollectable | |
//=========================================== | |
void InventorySystem::addCollectable(CInventory* component) { | |
CCollectable* ptr = dynamic_cast<CCollectable*>(component); | |
assert(ptr != nullptr); | |
m_collectables.insert(make_pair(ptr->entityId(), pCCollectable_t(ptr))); | |
} | |
//=========================================== | |
// InventorySystem::hasComponent | |
//=========================================== | |
bool InventorySystem::hasComponent(entityId_t entityId) const { | |
return (m_collectables.find(entityId) != m_collectables.end()) | |
|| (m_collectors.find(entityId) != m_collectors.end()); | |
} | |
//=========================================== | |
// InventorySystem::getComponent | |
//=========================================== | |
CInventory& InventorySystem::getComponent(entityId_t entityId) const { | |
auto it = m_collectables.find(entityId); | |
if (it != m_collectables.end()) { | |
return *it->second; | |
} | |
return *m_collectors.at(entityId); | |
} | |
//=========================================== | |
// InventorySystem::removeEntity | |
//=========================================== | |
void InventorySystem::removeEntity(entityId_t id) { | |
auto it = m_collectors.find(id); | |
if (it != m_collectors.end()) { | |
m_collectors.erase(it); | |
} | |
else { | |
m_collectables.erase(id); | |
} | |
} | |
//=========================================== | |
// InventorySystem::addToBucket | |
// | |
// Countable entities (e.g. ammo) are deleted completely on collection, but items are just moved | |
// very far away. | |
//=========================================== | |
void InventorySystem::addToBucket(entityId_t collectorId, const CCollectable& item) { | |
auto it = m_collectors.find(collectorId); | |
if (it == m_collectors.end()) { | |
EXCEPTION("No such collector with ID '" << collectorId << "'"); | |
} | |
CCollector& collector = *it->second; | |
auto jt = collector.buckets.find(item.collectableType); | |
if (jt == collector.buckets.end()) { | |
m_entityManager.fireEvent(ECollectableEncountered{collectorId, item}, { collectorId }); | |
return; | |
} | |
Bucket& b = *jt->second; | |
switch (b.bucketKind) { | |
case BucketKind::COUNTER_BUCKET: { | |
CounterBucket& bucket = dynamic_cast<CounterBucket&>(b); | |
if (bucket.count < bucket.capacity) { | |
int prev = bucket.count; | |
bucket.count += item.value; | |
if (bucket.count > bucket.capacity) { | |
bucket.count = bucket.capacity; | |
} | |
m_entityManager.fireEvent(EBucketCountChange(collectorId, item.collectableType, bucket, | |
prev), { collectorId }); | |
m_entityManager.deleteEntity(item.entityId()); | |
} | |
break; | |
} | |
case BucketKind::ITEM_BUCKET: { | |
ItemBucket& bucket = dynamic_cast<ItemBucket&>(b); | |
if (static_cast<int>(bucket.items.size()) < bucket.capacity) { | |
if (contains(bucket.items, item.name)) { | |
EXCEPTION("Bucket already contains item with name '" << item.name << "'"); | |
} | |
int prevCount = static_cast<int>(bucket.items.size()); | |
bucket.items[item.name] = item.entityId(); | |
m_entityManager.fireEvent(EBucketItemsChange{collectorId, item.collectableType, bucket, | |
prevCount}, { collectorId }); | |
m_entityManager.fireEvent(EItemCollected{collectorId, item}, { item.entityId() }); | |
CSpatial& spatial = m_entityManager.getComponent<CSpatial>(item.entityId(), | |
ComponentKind::C_SPATIAL); | |
if (spatial.kind == CSpatialKind::V_RECT) { | |
CVRect& vRect = dynamic_cast<CVRect&>(spatial); | |
vRect.pos = Point(10000, 10000); | |
} | |
} | |
break; | |
} | |
} | |
} | |
//=========================================== | |
// InventorySystem::getBucketItems | |
//=========================================== | |
const map<string, entityId_t>& InventorySystem::getBucketItems(entityId_t collectorId, | |
const string& collectableType) const { | |
auto it = m_collectors.find(collectorId); | |
if (it == m_collectors.end()) { | |
EXCEPTION("No such collector with ID '" << collectorId << "'"); | |
} | |
CCollector& collector = *it->second; | |
auto jt = collector.buckets.find(collectableType); | |
if (jt == collector.buckets.end()) { | |
EXCEPTION("Entity does not collect items of type '" << collectableType << "'"); | |
} | |
const Bucket& b = *jt->second; | |
if (b.bucketKind != BucketKind::ITEM_BUCKET) { | |
EXCEPTION("Cannot retrieve items from bucket; Bucket is not of type ItemBucket"); | |
} | |
const ItemBucket& bucket = dynamic_cast<const ItemBucket&>(b); | |
return bucket.items; | |
} | |
//=========================================== | |
// InventorySystem::removeFromBucket | |
//=========================================== | |
void InventorySystem::removeFromBucket(entityId_t collectorId, const string& collectableType, | |
const string& name) { | |
auto it = m_collectors.find(collectorId); | |
if (it == m_collectors.end()) { | |
EXCEPTION("No such collector with ID '" << collectorId << "'"); | |
} | |
CCollector& collector = *it->second; | |
auto jt = collector.buckets.find(collectableType); | |
if (jt == collector.buckets.end()) { | |
EXCEPTION("Entity does not collect items of type '" << collectableType << "'"); | |
} | |
Bucket& b = *jt->second; | |
if (b.bucketKind != BucketKind::ITEM_BUCKET) { | |
EXCEPTION("Cannot remove item from bucket; Bucket is not of type ItemBucket"); | |
} | |
ItemBucket& bucket = dynamic_cast<ItemBucket&>(b); | |
int prevCount = static_cast<int>(bucket.items.size()); | |
bucket.items.erase(name); | |
m_entityManager.fireEvent(EBucketItemsChange(collectorId, collectableType, bucket, prevCount), | |
{ collectorId }); | |
} | |
//=========================================== | |
// InventorySystem::subtractFromBucket | |
//=========================================== | |
int InventorySystem::subtractFromBucket(entityId_t collectorId, const string& collectableType, | |
int value) { | |
auto it = m_collectors.find(collectorId); | |
if (it == m_collectors.end()) { | |
EXCEPTION("No such collector with ID '" << collectorId << "'"); | |
} | |
CCollector& collector = *it->second; | |
auto jt = collector.buckets.find(collectableType); | |
if (jt == collector.buckets.end()) { | |
EXCEPTION("Entity does not collect items of type '" << collectableType << "'"); | |
} | |
Bucket& b = *jt->second; | |
if (b.bucketKind != BucketKind::COUNTER_BUCKET) { | |
EXCEPTION("Cannot subtract from bucket; Bucket is not of type CounterBucket"); | |
} | |
CounterBucket& bucket = dynamic_cast<CounterBucket&>(b); | |
int prev = bucket.count; | |
if (bucket.count >= value) { | |
bucket.count -= value; | |
} | |
m_entityManager.fireEvent(EBucketCountChange(collectorId, collectableType, bucket, prev), | |
{ collectorId }); | |
return bucket.count; | |
} | |
//=========================================== | |
// InventorySystem::getBucketValue | |
//=========================================== | |
int InventorySystem::getBucketValue(entityId_t collectorId, const string& collectableType) const { | |
auto it = m_collectors.find(collectorId); | |
if (it == m_collectors.end()) { | |
EXCEPTION("No such collector with ID '" << collectorId << "'"); | |
} | |
CCollector& collector = *it->second; | |
auto jt = collector.buckets.find(collectableType); | |
if (jt == collector.buckets.end()) { | |
EXCEPTION("Entity does not collect items of type '" << collectableType << "'"); | |
} | |
const Bucket& b = *jt->second; | |
if (b.bucketKind != BucketKind::COUNTER_BUCKET) { | |
EXCEPTION("Cannot retrieve bucket value; Bucket is not of type CounterBucket"); | |
} | |
const CounterBucket& bucket = dynamic_cast<const CounterBucket&>(b); | |
return bucket.count; | |
} | |
#include <set> | |
#include "raycast/component.hpp" | |
using std::set; | |
using std::string; | |
static set<entityId_t> reserved; | |
entityId_t Component::nextId = 0; | |
//=========================================== | |
// hash | |
//=========================================== | |
static entityId_t hash(const char* str) { | |
entityId_t hash = 5381; | |
int c; | |
while ((c = *str++)) { | |
hash = ((hash << 5) + hash) + c; | |
} | |
return hash; | |
} | |
//=========================================== | |
// Component::getNextId | |
//=========================================== | |
entityId_t Component::getNextId() { | |
entityId_t id = nextId++; | |
while (reserved.count(id) == 1) { | |
id = nextId++; | |
} | |
return id; | |
} | |
//=========================================== | |
// Component::getIdFromString | |
//=========================================== | |
entityId_t Component::getIdFromString(const string& s) { | |
entityId_t id = hash(s.c_str()); | |
reserved.insert(id); | |
return id; | |
} | |
#include "raycast/c_sound_source_behaviour.hpp" | |
#include "audio_service.hpp" | |
#include "spatial_system.hpp" | |
//=========================================== | |
// CSoundSourceBehaviour::CSoundSourceBehaviour | |
//=========================================== | |
CSoundSourceBehaviour::CSoundSourceBehaviour(entityId_t entityId, const std::string& name, | |
const Point& pos, double radius, AudioService& audioService) | |
: CBehaviour(entityId), | |
name(name), | |
pos(pos), | |
radius(radius), | |
m_audioService(audioService) {} | |
//=========================================== | |
// CSoundSourceBehaviour::handleBroadcastedEvent | |
//=========================================== | |
void CSoundSourceBehaviour::handleBroadcastedEvent(const GameEvent& e_) { | |
if (e_.name == "player_move" && !m_disabled) { | |
auto& e = dynamic_cast<const EPlayerMove&>(e_); | |
if (distance(pos, e.player.pos()) <= this->radius) { | |
if (!m_audioService.soundIsPlaying(this->name, m_soundId)) { | |
DBG_PRINT("Playing sound " << this->name << " from source " << entityId() << "\n"); | |
m_soundId = m_audioService.playSoundAtPos(this->name, this->pos, true); | |
} | |
} | |
else { | |
if (m_audioService.soundIsPlaying(this->name, m_soundId)) { | |
DBG_PRINT("Stopping sound " << this->name << " from source " << entityId() << "\n"); | |
m_audioService.stopSound(this->name, m_soundId); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// CSoundSourceBehaviour::handleTargetedEvent | |
//=========================================== | |
void CSoundSourceBehaviour::handleTargetedEvent(const GameEvent& e_) { | |
if (e_.name == "disable_sound_source") { | |
setDisabled(true); | |
} | |
else if (e_.name == "enable_sound_source") { | |
setDisabled(false); | |
} | |
} | |
//=========================================== | |
// CSoundSourceBehaviour::setDisabled | |
//=========================================== | |
void CSoundSourceBehaviour::setDisabled(bool disabled) { | |
m_disabled = disabled; | |
if (m_disabled) { | |
m_audioService.stopSound(this->name, m_soundId); | |
} | |
} | |
//=========================================== | |
// CSoundSourceBehaviour::~CSoundSourceBehaviour | |
//=========================================== | |
CSoundSourceBehaviour::~CSoundSourceBehaviour() {} | |
#include <cassert> | |
#include "raycast/entity_manager.hpp" | |
using std::set; | |
//=========================================== | |
// EntityManager::getNextId | |
//=========================================== | |
entityId_t EntityManager::getNextId() const { | |
return Component::getNextId(); | |
} | |
//=========================================== | |
// EntityManager::addSystem | |
//=========================================== | |
void EntityManager::addSystem(ComponentKind kind, pSystem_t system) { | |
m_systems[kind] = std::move(system); | |
} | |
//=========================================== | |
// EntityManager::hasComponent | |
//=========================================== | |
bool EntityManager::hasComponent(entityId_t entityId, ComponentKind kind) const { | |
auto it = m_systems.find(kind); | |
if (it == m_systems.end()) { | |
return false; | |
} | |
System& system = *it->second; | |
return system.hasComponent(entityId); | |
} | |
//=========================================== | |
// EntityManager::addComponent | |
//=========================================== | |
void EntityManager::addComponent(pComponent_t component) { | |
System& system = *m_systems.at(component->kind()); | |
system.addComponent(std::move(component)); | |
} | |
//=========================================== | |
// EntityManager::deleteEntity | |
//=========================================== | |
void EntityManager::deleteEntity(entityId_t entityId) { | |
m_pendingDelete.insert(entityId); | |
EEntityDeleted e{entityId}; | |
fireEvent(e, {entityId}); | |
broadcastEvent(e); | |
} | |
//=========================================== | |
// EntityManager::purgeEntities | |
//=========================================== | |
void EntityManager::purgeEntities() { | |
for (entityId_t id : m_pendingDelete) { | |
for (auto it = m_systems.begin(); it != m_systems.end(); ++it) { | |
it->second->removeEntity(id); | |
} | |
} | |
m_pendingDelete.clear(); | |
} | |
//=========================================== | |
// EntityManager::update | |
//=========================================== | |
void EntityManager::update() { | |
for (auto it = m_systems.begin(); it != m_systems.end(); ++it) { | |
System& system = *it->second; | |
system.update(); | |
} | |
} | |
//=========================================== | |
// EntityManager::broadcastEvent | |
//=========================================== | |
void EntityManager::broadcastEvent(const GameEvent& event) const { | |
for (auto it = m_systems.begin(); it != m_systems.end(); ++it) { | |
System& system = *it->second; | |
system.handleEvent(event); | |
} | |
} | |
//=========================================== | |
// EntityManager::fireEvent | |
//=========================================== | |
void EntityManager::fireEvent(const GameEvent& event, const set<entityId_t>& entities) const { | |
for (auto it = m_systems.begin(); it != m_systems.end(); ++it) { | |
System& system = *it->second; | |
system.handleEvent(event, entities); | |
} | |
} | |
#include <QFileInfo> | |
#include <QUrl> | |
#include "raycast/audio_service.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/time_service.hpp" | |
#include "exception.hpp" | |
using std::make_pair; | |
using std::string; | |
using std::unique_ptr; | |
using std::array; | |
static int nextId = 0; | |
//=========================================== | |
// indexForId | |
//=========================================== | |
static int indexForId(const array<int, CONCURRENT_SOUNDS>& ids, int id) { | |
for (int i = 0; i < CONCURRENT_SOUNDS; ++i) { | |
if (ids[i] == id) { | |
return i; | |
} | |
} | |
return -1; | |
} | |
//=========================================== | |
// AudioService::AudioService | |
//=========================================== | |
AudioService::AudioService(EntityManager& entityManager, TimeService& timeService) | |
: m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_mediaPlayer(nullptr, QMediaPlayer::LowLatency) { | |
m_initialised = false; | |
} | |
//=========================================== | |
// AudioService::checkInitialised | |
//=========================================== | |
void AudioService::checkInitialised() const { | |
if (!m_initialised) { | |
EXCEPTION("Audio service not initialised"); | |
} | |
} | |
//=========================================== | |
// AudioService::initialise | |
// | |
// Should be called once the Event System exists | |
//=========================================== | |
void AudioService::initialise() { | |
m_musicVolume = 0.5; | |
setMasterVolume(0.5); | |
m_mediaPlayer.setPlaylist(&m_playlist); | |
m_entityId = Component::getNextId(); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CEventHandler* events = new CEventHandler(m_entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"player_move", | |
[this](const GameEvent&) { onPlayerMove(); }}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
m_initialised = true; | |
} | |
//=========================================== | |
// AudioService::nextAvailable | |
//=========================================== | |
int AudioService::nextAvailable(const array<SoundInstance, CONCURRENT_SOUNDS>& instances) { | |
for (int i = 0; i < CONCURRENT_SOUNDS; ++i) { | |
if (!instances[i].sound.isPlaying()) { | |
return i; | |
} | |
} | |
return -1; | |
} | |
//=========================================== | |
// AudioService::addSound | |
//=========================================== | |
void AudioService::addSound(const string& name, const string& resourcePath) { | |
pSoundEffect_t sound(new SoundEffect); | |
QString absPath = QFileInfo(resourcePath.c_str()).absoluteFilePath(); | |
for (int i = 0; i < CONCURRENT_SOUNDS; ++i) { | |
sound->instances[i].sound.setSource(QUrl::fromLocalFile(absPath)); | |
} | |
m_sounds.insert(make_pair(name, std::move(sound))); | |
} | |
//=========================================== | |
// AudioService::soundIsPlaying | |
//=========================================== | |
bool AudioService::soundIsPlaying(const string& name, int id) const { | |
checkInitialised(); | |
auto it = m_sounds.find(name); | |
if (it != m_sounds.end()) { | |
SoundEffect& sound = *it->second; | |
int idx = indexForId(sound.soundIds, id); | |
if (idx != -1) { | |
return sound.instances[idx].sound.isPlaying(); | |
} | |
} | |
return false; | |
} | |
//=========================================== | |
// AudioService::onPlayerMove | |
//=========================================== | |
void AudioService::onPlayerMove() { | |
for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) { | |
auto& sound = *it->second; | |
for (auto jt = sound.instances.begin(); jt != sound.instances.end(); ++jt) { | |
SoundInstance& instance = *jt; | |
if (instance.positional) { | |
instance.sound.setVolume(calcVolume(sound, instance)); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// AudioService::addMusicTrack | |
//=========================================== | |
void AudioService::addMusicTrack(const string& name, const string& resourcePath) { | |
QString absPath = QFileInfo(resourcePath.c_str()).absoluteFilePath(); | |
m_musicTracks[name] = QMediaContent(QUrl::fromLocalFile(absPath)); | |
} | |
//=========================================== | |
// AudioService::playSound | |
//=========================================== | |
int AudioService::playSound(const string& name, bool loop) { | |
checkInitialised(); | |
auto it = m_sounds.find(name); | |
if (it != m_sounds.end()) { | |
SoundEffect& sound = *it->second; | |
int i = nextAvailable(sound.instances); | |
if (i != -1) { | |
sound.instances[i].positional = false; | |
sound.instances[i].sound.setVolume(m_masterVolume * sound.volume); | |
sound.instances[i].sound.setLoopCount(loop ? QSoundEffect::Infinite : 0); | |
sound.instances[i].sound.play(); | |
long id = ++nextId; | |
sound.soundIds[i] = id; | |
return id; | |
} | |
} | |
return -1; | |
} | |
//=========================================== | |
// AudioService::stopSound | |
//=========================================== | |
void AudioService::stopSound(const string& name, int id) { | |
checkInitialised(); | |
auto it = m_sounds.find(name); | |
if (it != m_sounds.end()) { | |
SoundEffect& sound = *it->second; | |
int idx = indexForId(sound.soundIds, id); | |
if (idx != -1) { | |
sound.instances[idx].sound.stop(); | |
} | |
} | |
} | |
//=========================================== | |
// AudioService::calcVolume | |
//=========================================== | |
double AudioService::calcVolume(const SoundEffect& sound, const SoundInstance& instance) const { | |
if (!instance.positional) { | |
return m_masterVolume * sound.volume; | |
} | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
double d = distance(spatialSystem.sg.player->pos(), instance.pos); | |
double v = 1.0 - clipNumber(d, Range(0, 2000)) / 2000; | |
return m_masterVolume * sound.volume * v; | |
} | |
//=========================================== | |
// AudioService::playSoundAtPos | |
//=========================================== | |
int AudioService::playSoundAtPos(const string& name, const Point& pos, bool loop) { | |
checkInitialised(); | |
auto it = m_sounds.find(name); | |
if (it != m_sounds.end()) { | |
SoundEffect& sound = *it->second; | |
int i = nextAvailable(sound.instances); | |
if (i != -1) { | |
sound.instances[i].positional = true; | |
sound.instances[i].pos = pos; | |
sound.instances[i].sound.setVolume(calcVolume(sound, sound.instances[i])); | |
sound.instances[i].sound.setLoopCount(loop ? QSoundEffect::Infinite : 0); | |
sound.instances[i].sound.play(); | |
long id = ++nextId; | |
sound.soundIds[i] = id; | |
return id; | |
} | |
} | |
return -1; | |
} | |
//=========================================== | |
// AudioService::playMusic | |
//=========================================== | |
void AudioService::playMusic(const string& name, bool loop, double) { | |
checkInitialised(); | |
// TODO: Fade | |
auto it = m_musicTracks.find(name); | |
if (it != m_musicTracks.end()) { | |
m_playlist.clear(); | |
m_playlist.addMedia(it->second); | |
m_playlist.setPlaybackMode(loop ? QMediaPlaylist::Loop : QMediaPlaylist::CurrentItemOnce); | |
m_mediaPlayer.play(); | |
} | |
} | |
//=========================================== | |
// AudioService::stopMusic | |
//=========================================== | |
void AudioService::stopMusic(double fadeDuration) { | |
checkInitialised(); | |
if (fadeDuration <= 0.0) { | |
m_mediaPlayer.stop(); | |
return; | |
} | |
double delta = m_musicVolume / (fadeDuration * m_timeService.frameRate); | |
Tween tween{[this, delta](long, double, double) -> bool { | |
setMusicVolume(m_musicVolume - delta); | |
return m_musicVolume > 0.0; | |
}, [this](long, double, double) { | |
m_mediaPlayer.stop(); | |
}}; | |
m_timeService.addTween(std::move(tween)); | |
} | |
//=========================================== | |
// AudioService::setMusicVolume | |
//=========================================== | |
void AudioService::setMusicVolume(double volume) { | |
#ifdef __APPLE__ | |
// Boost music volume for OS X | |
volume = smallest(volume * 1.5, 1.0); | |
#endif | |
m_musicVolume = volume; | |
m_mediaPlayer.setVolume(m_masterVolume * m_musicVolume * 100); | |
} | |
//=========================================== | |
// AudioService::setMasterVolume | |
//=========================================== | |
void AudioService::setMasterVolume(double volume) { | |
m_masterVolume = volume; | |
setMusicVolume(m_musicVolume); | |
} | |
#include "raycast/animation_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
using std::vector; | |
using std::set; | |
using std::pair; | |
//=========================================== | |
// constructFrames | |
//=========================================== | |
vector<AnimationFrame> constructFrames(int W, int H, const vector<int>& rows) { | |
double w = 1.0 / W; | |
double h = 1.0 / H; | |
vector<AnimationFrame> frames; | |
for (int f : rows) { | |
AnimationFrame frame; | |
for (int v = 0; v < W; ++v) { | |
frame.texViews.push_back(QRectF(w * v, h * f, w, h)); | |
} | |
frames.push_back(frame); | |
} | |
return frames; | |
} | |
//=========================================== | |
// Animation::start | |
//=========================================== | |
void Animation::start(bool loop) { | |
m_loop = loop; | |
m_state = AnimState::RUNNING; | |
m_elapsed = 0; | |
m_currentFrameIdx = 0; | |
} | |
//=========================================== | |
// Animation::stop | |
//=========================================== | |
void Animation::stop() { | |
m_state = AnimState::STOPPED; | |
m_elapsed = 0; | |
m_currentFrameIdx = 0; | |
} | |
//=========================================== | |
// Animation::update | |
// | |
// Returns true when the animation finishes | |
//=========================================== | |
bool Animation::update() { | |
if (frames.empty()) { | |
return false; | |
} | |
if (m_state == AnimState::RUNNING) { | |
double sim_dt = 1.0 / m_gameFrameRate; | |
double anim_dt = m_duration / frames.size(); | |
// Since first frame | |
m_elapsed += sim_dt; | |
unsigned int next = floor(m_elapsed / anim_dt); | |
if (next == frames.size()) { | |
if (m_loop) { | |
m_currentFrameIdx = 0; | |
m_elapsed = 0; | |
} | |
else { | |
m_state = AnimState::STOPPED; | |
return true; | |
} | |
} | |
else { | |
m_currentFrameIdx = next; | |
} | |
} | |
return false; | |
} | |
//=========================================== | |
// AnimationSystem::playAnimation | |
//=========================================== | |
void AnimationSystem::playAnimation(entityId_t entityId, const std::string& name, bool loop) { | |
auto it = m_components.find(entityId); | |
if (it != m_components.end()) { | |
CAnimation& component = *it->second; | |
auto jt = component.m_animations.find(name); | |
if (jt != component.m_animations.end()) { | |
pair<pAnimation_t, pAnimation_t>& anims = jt->second; | |
stopAnimation(entityId, false); | |
if (anims.first) { | |
anims.first->start(loop); | |
} | |
if (anims.second) { | |
anims.second->start(loop); | |
} | |
component.m_active = name; | |
} | |
} | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
auto& children = renderSystem.children(entityId); | |
for (entityId_t child : children) { | |
playAnimation(child, name, loop); | |
} | |
} | |
//=========================================== | |
// AnimationSystem::stopAnimation | |
//=========================================== | |
void AnimationSystem::stopAnimation(entityId_t entityId, bool recurseIntoChildren) { | |
auto it = m_components.find(entityId); | |
if (it != m_components.end()) { | |
CAnimation& component = *it->second; | |
if (component.m_active != "") { | |
auto& anims = component.m_animations[component.m_active]; | |
if (anims.first) { | |
anims.first->stop(); | |
} | |
if (anims.second) { | |
anims.second->stop(); | |
} | |
} | |
} | |
if (recurseIntoChildren) { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
auto& children = renderSystem.children(entityId); | |
for (entityId_t child : children) { | |
stopAnimation(child, recurseIntoChildren); | |
} | |
} | |
} | |
//=========================================== | |
// AnimationSystem::updateAnimations | |
//=========================================== | |
void AnimationSystem::updateAnimation(CAnimation& c, int which) { | |
if (c.m_active == "") { | |
return; | |
} | |
entityId_t entityId = c.entityId(); | |
Animation* anim = nullptr; | |
auto& anims = c.m_animations[c.m_active]; | |
if (which == 0) { | |
anim = anims.first.get(); | |
} | |
else if (which == 1) { | |
anim = anims.second.get(); | |
} | |
if (anim != nullptr) { | |
bool justFinished = anim->update(); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
CRender& render = renderSystem.getComponent(entityId); | |
if (render.kind == CRenderKind::SPRITE) { | |
CSprite& sprite = dynamic_cast<CSprite&>(render); | |
sprite.texViews = anim->currentFrame().texViews; | |
} | |
else if (render.kind == CRenderKind::OVERLAY) { | |
COverlay& overlay = dynamic_cast<COverlay&>(render); | |
if (overlay.kind == COverlayKind::IMAGE) { | |
CImageOverlay& imgOverlay = dynamic_cast<CImageOverlay&>(render); | |
imgOverlay.texRect = anim->currentFrame().texViews[0]; | |
} | |
} | |
else if (render.kind == CRenderKind::WALL_DECAL) { | |
CWallDecal& decal = dynamic_cast<CWallDecal&>(render); | |
decal.texRect = anim->currentFrame().texViews[0]; | |
} | |
else if (render.kind == CRenderKind::WALL) { | |
CWall& wall = dynamic_cast<CWall&>(render); | |
wall.texRect = anim->currentFrame().texViews[0]; | |
} | |
else if (render.kind == CRenderKind::JOIN) { | |
CJoin& join = dynamic_cast<CJoin&>(render); | |
if (which == 0) { | |
join.bottomTexRect = anim->currentFrame().texViews[0]; | |
} | |
else { | |
join.topTexRect = anim->currentFrame().texViews[0]; | |
} | |
} | |
else if (render.kind == CRenderKind::REGION) { | |
CRegion& region = dynamic_cast<CRegion&>(render); | |
if (which == 0) { | |
region.floorTexRect = anim->currentFrame().texViews[0]; | |
} | |
else { | |
region.ceilingTexRect = anim->currentFrame().texViews[0]; | |
} | |
} | |
if (anim->state() == AnimState::STOPPED) { | |
c.m_active = ""; | |
} | |
if (justFinished) { | |
m_entityManager.fireEvent(EAnimationFinished(entityId, anim->name), { entityId }); | |
} | |
} | |
} | |
//=========================================== | |
// AnimationSystem::update | |
//=========================================== | |
void AnimationSystem::update() { | |
for (auto it = m_components.begin(); it != m_components.end(); ++it) { | |
updateAnimation(*it->second, 0); | |
updateAnimation(*it->second, 1); | |
} | |
} | |
//=========================================== | |
// AnimationSystem::addComponent | |
//=========================================== | |
void AnimationSystem::addComponent(pComponent_t component) { | |
CAnimation* p = dynamic_cast<CAnimation*>(component.release()); | |
m_components.insert(std::make_pair(p->entityId(), pCAnimation_t(p))); | |
} | |
//=========================================== | |
// AnimationSystem::hasComponent | |
//=========================================== | |
bool AnimationSystem::hasComponent(entityId_t entityId) const { | |
return m_components.find(entityId) != m_components.end(); | |
} | |
//=========================================== | |
// AnimationSystem::getComponent | |
//=========================================== | |
CAnimation& AnimationSystem::getComponent(entityId_t entityId) const { | |
return *m_components.at(entityId); | |
} | |
//=========================================== | |
// AnimationSystem::removeEntity | |
//=========================================== | |
void AnimationSystem::removeEntity(entityId_t id) { | |
m_components.erase(id); | |
} | |
#include <cassert> | |
#include "raycast/focus_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/time_service.hpp" | |
#include "exception.hpp" | |
#include "app_config.hpp" | |
#include "utils.hpp" | |
using std::vector; | |
//=========================================== | |
// FocusSystem::FocusSystem | |
//=========================================== | |
FocusSystem::FocusSystem(const AppConfig& appConfig, EntityManager& entityManager, | |
TimeService& timeService) | |
: m_appConfig(appConfig), | |
m_entityManager(entityManager), | |
m_timeService(timeService) {} | |
//=========================================== | |
// FocusSystem::initialise | |
//=========================================== | |
void FocusSystem::initialise() { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
m_toolTipId = Component::getNextId(); | |
CTextOverlay* toolTip = new CTextOverlay(m_toolTipId, "", Point(7.3, 4.8), 0.5, Qt::white, 1); | |
renderSystem.addComponent(pComponent_t(toolTip)); | |
m_focusDistance = spatialSystem.sg.player->activationRadius; | |
m_initialised = true; | |
} | |
//=========================================== | |
// isWallDecal | |
//=========================================== | |
static bool isWallDecal(const RenderSystem& renderSystem, const Intersection& X) { | |
if (renderSystem.hasComponent(X.entityId)) { | |
const CRender& c = renderSystem.getComponent(X.entityId); | |
return c.kind == CRenderKind::WALL_DECAL; | |
} | |
return false; | |
} | |
//=========================================== | |
// getZIndex | |
//=========================================== | |
static int getZIndex(const RenderSystem& renderSystem, const Intersection& X) { | |
const CWallDecal& decal = DYNAMIC_CAST<const CWallDecal&>(renderSystem.getComponent(X.entityId)); | |
return decal.zIndex; | |
} | |
//=========================================== | |
// FocusSystem::update | |
//=========================================== | |
void FocusSystem::update() { | |
if (!m_initialised) { | |
initialise(); | |
} | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
CTextOverlay& toolTip = dynamic_cast<CTextOverlay&>(renderSystem.getComponent(m_toolTipId)); | |
toolTip.text = ""; | |
vector<pIntersection_t> intersections = spatialSystem.entitiesAlong3dRay(Vec2f(1.0, 0), 0, | |
m_focusDistance); | |
if (intersections.size() > 0) { | |
auto first = intersections.begin(); | |
int highestZ = -9999; | |
for (auto it = intersections.begin(); it != intersections.end(); ++it) { | |
if (!isWallDecal(renderSystem, **it)) { | |
break; | |
} | |
int z = getZIndex(renderSystem, **it); | |
if (z > highestZ) { | |
highestZ = z; | |
first = it; | |
} | |
} | |
pIntersection_t& X = *first; | |
auto it = m_components.find(X->entityId); | |
if (it != m_components.end()) { | |
CFocus& c = *it->second; | |
toolTip.text = c.hoverText; | |
if (c.onHover) { | |
c.onHover(); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// FocusSystem::deleteCaption | |
//=========================================== | |
void FocusSystem::deleteCaption() { | |
m_timeService.cancelTimeout(m_captionTimeoutId); | |
m_entityManager.deleteEntity(m_captionBgId); | |
m_entityManager.deleteEntity(m_captionTextId); | |
m_captionBgId = -1; | |
m_captionTextId = -1; | |
m_captionTimeoutId = -1; | |
} | |
//=========================================== | |
// FocusSystem::showCaption | |
//=========================================== | |
void FocusSystem::showCaption(entityId_t entityId) { | |
auto it = m_components.find(entityId); | |
if (it == m_components.end()) { | |
return; | |
} | |
const CFocus& c = *it->second; | |
deleteCaption(); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
const Size& vp_wld = renderSystem.rg.viewport; | |
const Size& vp_px = renderSystem.rg.viewport_px; | |
double pxH = vp_wld.y / vp_px.y; | |
double pxW = vp_wld.x / vp_px.x; | |
double chH = 0.45; | |
double chH_px = chH / pxH; | |
QFont font = m_appConfig.monoFont; | |
font.setPixelSize(chH_px); | |
double margin = 0.2; | |
QFontMetrics fm(font); | |
double textW_px = fm.size(Qt::TextSingleLine, c.captionText.c_str()).width(); | |
double textW = textW_px * pxW; | |
Size sz(2.0 * margin + textW, 2.0 * margin + chH); | |
Point bgPos(0.5 * (vp_wld.x - sz.x), 0.70 * (vp_wld.y - sz.y)); | |
Point textPos = bgPos + Vec2f(margin, margin); | |
QColor bgColour(0, 0, 0, 120); | |
QColor textColour(210, 210, 0); | |
m_captionBgId = Component::getNextId(); | |
m_captionTextId = Component::getNextId(); | |
CColourOverlay* bgOverlay = new CColourOverlay(m_captionBgId, bgColour, bgPos, sz, 8); | |
CTextOverlay* textOverlay = new CTextOverlay(m_captionTextId, c.captionText, textPos, chH, | |
textColour, 9); | |
renderSystem.addComponent(pComponent_t(bgOverlay)); | |
renderSystem.addComponent(pComponent_t(textOverlay)); | |
m_captionTimeoutId = m_timeService.onTimeout([this]() { | |
deleteCaption(); | |
}, 3.0); | |
} | |
//=========================================== | |
// FocusSystem::addComponent | |
//=========================================== | |
void FocusSystem::addComponent(pComponent_t component) { | |
CFocus* ptr = dynamic_cast<CFocus*>(component.release()); | |
m_components.insert(make_pair(ptr->entityId(), pCFocus_t(ptr))); | |
} | |
//=========================================== | |
// FocusSystem::hasComponent | |
//=========================================== | |
bool FocusSystem::hasComponent(entityId_t entityId) const { | |
return m_components.count(entityId); | |
} | |
//=========================================== | |
// FocusSystem::getComponent | |
//=========================================== | |
CFocus& FocusSystem::getComponent(entityId_t entityId) const { | |
return *m_components.at(entityId); | |
} | |
//=========================================== | |
// FocusSystem::removeEntity | |
//=========================================== | |
void FocusSystem::removeEntity(entityId_t id) { | |
m_components.erase(id); | |
} | |
#include "raycast/sprite_factory.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/spawn_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/agent_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
using std::vector; | |
using std::string; | |
using std::set; | |
//=========================================== | |
// SpriteFactory::constructSprite | |
//=========================================== | |
bool SpriteFactory::constructSprite(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
CZone& zone = spatialSystem.zone(parentId); | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
string texName = getValue(obj.dict, "texture", "default"); | |
const Texture& texture = GET_VALUE(renderSystem.rg.textures, texName); | |
double height = std::stod(getValue(obj.dict, "height", "0.0")); | |
CVRect* vRect = new CVRect(entityId, zone.entityId(), texture.size_wd); | |
Matrix m = transformFromTriangle(obj.path); | |
vRect->setTransform(parentTransform * obj.groupTransform * obj.pathTransform * m); | |
vRect->zone = &zone; | |
vRect->y = height; | |
spatialSystem.addComponent(pComponent_t(vRect)); | |
CSprite* sprite = new CSprite(entityId, zone.entityId(), texName); | |
sprite->texViews = { | |
QRectF(0, 0, 1, 1) | |
}; | |
renderSystem.addComponent(pComponent_t(sprite)); | |
return true; | |
} | |
//=========================================== | |
// SpriteFactory::constructAmmo | |
//=========================================== | |
bool SpriteFactory::constructAmmo(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "ammo"; | |
if (m_rootFactory.constructObject("sprite", entityId, obj, parentId, parentTransform)) { | |
InventorySystem& inventorySystem = | |
m_entityManager.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
CCollectable* collectable = new CCollectable(entityId, "ammo"); | |
collectable->value = 5; | |
inventorySystem.addComponent(pComponent_t(collectable)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// SpriteFactory::constructHealthPack | |
//=========================================== | |
bool SpriteFactory::constructHealthPack(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "health_pack"; | |
if (m_rootFactory.constructObject("sprite", entityId, obj, parentId, parentTransform)) { | |
InventorySystem& inventorySystem = | |
m_entityManager.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
// Health packs don't have a bucket, but the Player class listens for their collection and | |
// removes them accordingly | |
CCollectable* collectable = new CCollectable(entityId, "health_pack"); | |
collectable->value = 1; | |
inventorySystem.addComponent(pComponent_t(collectable)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// SpriteFactory::constructCivilian | |
//=========================================== | |
bool SpriteFactory::constructCivilian(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (!contains<string>(obj.dict, "texture")) { | |
obj.dict["texture"] = "civilian"; | |
} | |
if (m_rootFactory.constructObject("sprite", entityId, obj, parentId, parentTransform)) { | |
AnimationSystem& animationSystem = | |
m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
DamageSystem& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
AgentSystem& agentSystem = m_entityManager.system<AgentSystem>(ComponentKind::C_AGENT); | |
InventorySystem& inventorySystem = | |
m_entityManager.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
FocusSystem& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
CCollector* inventory = new CCollector(entityId); | |
inventory->buckets["item"] = pBucket_t(new ItemBucket(1)); | |
inventorySystem.addComponent(pComponent_t(inventory)); | |
string name = getValue(obj.dict, "name", ""); | |
if (name != "") { | |
CFocus* focus = new CFocus(entityId); | |
focus->hoverText = name.replace(0, 1, 1, asciiToUpper(name[0])); | |
focusSystem.addComponent(pComponent_t(focus)); | |
} | |
// Number of frames in sprite sheet | |
const int W = 8; | |
const int H = 14; | |
CAnimation* anim = new CAnimation(entityId); | |
vector<AnimationFrame> frames = constructFrames(W, H, | |
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }); | |
anim->addAnimation(pAnimation_t(new Animation("run", m_timeService.frameRate, 1.0, frames))); | |
frames = constructFrames(W, H, { 0 }); | |
anim->addAnimation(pAnimation_t(new Animation("idle", m_timeService.frameRate, 1.0, frames))); | |
frames = {{ | |
AnimationFrame{{QRectF{0.0 / W, 13.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{1.0 / W, 13.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{2.0 / W, 13.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{3.0 / W, 13.0 / H, 1.0 / W, 1.0 / H}}}, | |
}}; | |
anim->addAnimation(pAnimation_t(new Animation("death", m_timeService.frameRate, 0.5, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "idle", true); | |
CDamage* damage = new CDamage(entityId, 2, 2); | |
damageSystem.addComponent(pComponent_t(damage)); | |
CVRect& vRect = m_entityManager.getComponent<CVRect>(entityId, ComponentKind::C_SPATIAL); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->targetedEventHandlers.push_back(EventHandler{"entity_damaged", | |
[=, &animationSystem, &spatialSystem, &agentSystem, &vRect](const GameEvent&) { | |
DBG_PRINT("Civilian health: " << damage->health << "\n"); | |
//animationSystem.playAnimation(entityId, "hurt", false); | |
if (damage->health == 0) { | |
m_audioService.playSoundAtPos("civilian_death", vRect.pos); | |
auto& bucket = dynamic_cast<ItemBucket&>(*inventory->buckets["item"]); | |
for (auto it = bucket.items.begin(); it != bucket.items.end(); ++it) { | |
entityId_t itemId = it->second; | |
const CVRect& body = dynamic_cast<const CVRect&>(spatialSystem.getComponent(entityId)); | |
spatialSystem.relocateEntity(itemId, *body.zone, body.pos); | |
} | |
agentSystem.removeEntity(entityId); | |
animationSystem.playAnimation(entityId, "death", false); | |
} | |
else { | |
m_audioService.playSoundAtPos("civilian_hurt", vRect.pos); | |
} | |
}}); | |
events->targetedEventHandlers.push_back(EventHandler{"animation_finished", | |
[this, entityId](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EAnimationFinished&>(e_); | |
if (e.animName == "death") { | |
m_entityManager.deleteEntity(entityId); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
CAgent* agent = new CAgent(entityId); | |
agent->stPatrollingTrigger = getValue(obj.dict, "st_patrolling_trigger", ""); | |
agent->isHostile = false; | |
string s = getValue(obj.dict, "patrol_path", ""); | |
if (s != "") { | |
agent->patrolPath = Component::getIdFromString(s); | |
} | |
agentSystem.addComponent(pComponent_t(agent)); | |
for (auto it = obj.children.begin(); it != obj.children.end(); ++it) { | |
parser::Object& ch = **it; | |
entityId_t invItemId = makeIdForObj(ch); | |
if (m_rootFactory.constructObject(ch.type, invItemId, ch, entityId, | |
parentTransform * obj.groupTransform)) { | |
const CCollectable& item = | |
dynamic_cast<const CCollectable&>(inventorySystem.getComponent(invItemId)); | |
inventorySystem.addToBucket(entityId, item); | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// SpriteFactory::constructBadGuy | |
//=========================================== | |
bool SpriteFactory::constructBadGuy(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "bad_guy"; | |
if (m_rootFactory.constructObject("sprite", entityId, obj, parentId, parentTransform)) { | |
AnimationSystem& animationSystem = | |
m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
DamageSystem& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
SpawnSystem& spawnSystem = m_entityManager.system<SpawnSystem>(ComponentKind::C_SPAWN); | |
AgentSystem& agentSystem = m_entityManager.system<AgentSystem>(ComponentKind::C_AGENT); | |
// Number of frames in sprite sheet | |
const int W = 8; | |
const int H = 15; | |
CAnimation* anim = new CAnimation(entityId); | |
vector<AnimationFrame> frames = constructFrames(W, H, | |
{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }); | |
anim->addAnimation(pAnimation_t(new Animation("run", m_timeService.frameRate, 1.0, frames))); | |
frames = constructFrames(W, H, { 1, 0 }); | |
anim->addAnimation(pAnimation_t(new Animation("shoot", m_timeService.frameRate, 1.0, frames))); | |
frames = constructFrames(W, H, { 0 }); | |
anim->addAnimation(pAnimation_t(new Animation("idle", m_timeService.frameRate, 1.0, frames))); | |
frames = {{ | |
AnimationFrame{{QRectF{0.0 / W, 14.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{1.0 / W, 14.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{2.0 / W, 14.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{3.0 / W, 14.0 / H, 1.0 / W, 1.0 / H}}}, | |
}}; | |
anim->addAnimation(pAnimation_t(new Animation("death", m_timeService.frameRate, 0.5, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "idle", true); | |
string s = getValue(obj.dict, "spawn_point", ""); | |
if (s != "") { | |
CSpawnable* spawnable = new CSpawnable(entityId, Component::getIdFromString(s), | |
"bad_guy", parser::pObject_t(obj.clone()), parentId, parentTransform); | |
string delay = getValue(obj.dict, "spawn_delay", ""); | |
if (delay != "") { | |
spawnable->delay = std::stod(delay); | |
} | |
spawnSystem.addComponent(pComponent_t(spawnable)); | |
} | |
CDamage* damage = new CDamage(entityId, 2, 2); | |
damageSystem.addComponent(pComponent_t(damage)); | |
CVRect& vRect = m_entityManager.getComponent<CVRect>(entityId, ComponentKind::C_SPATIAL); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->targetedEventHandlers.push_back(EventHandler{"entity_damaged", | |
[=, &animationSystem, &agentSystem, &vRect](const GameEvent& e) { | |
const EEntityDamaged& event = dynamic_cast<const EEntityDamaged&>(e); | |
if (event.entityId == entityId) { | |
DBG_PRINT("Enemy health: " << damage->health << "\n"); | |
//animationSystem.playAnimation(entityId, "hurt", false); | |
if (damage->health == 0) { | |
m_audioService.playSoundAtPos("monster_death", vRect.pos); | |
agentSystem.removeEntity(entityId); | |
animationSystem.playAnimation(entityId, "death", false); | |
} | |
else { | |
m_audioService.playSoundAtPos("monster_hurt", vRect.pos); | |
} | |
} | |
}}); | |
events->targetedEventHandlers.push_back(EventHandler{"animation_finished", | |
[this, entityId](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EAnimationFinished&>(e_); | |
if (e.animName == "death") { | |
m_entityManager.deleteEntity(entityId); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
CAgent* agent = new CAgent(entityId); | |
agent->isHostile = true; | |
agent->stPatrollingTrigger = getValue(obj.dict, "st_patrolling_trigger", ""); | |
agent->stChasingTrigger = getValue(obj.dict, "st_chasing_trigger", ""); | |
s = getValue(obj.dict, "patrol_path", ""); | |
if (s != "") { | |
agent->patrolPath = Component::getIdFromString(s); | |
} | |
agentSystem.addComponent(pComponent_t(agent)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// SpriteFactory::SpriteFactory | |
//=========================================== | |
SpriteFactory::SpriteFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
AudioService& audioService, TimeService& timeService) | |
: m_rootFactory(rootFactory), | |
m_entityManager(entityManager), | |
m_audioService(audioService), | |
m_timeService(timeService) {} | |
//=========================================== | |
// SpriteFactory::types | |
//=========================================== | |
const set<string>& SpriteFactory::types() const { | |
static const set<string> types{ | |
"sprite", | |
"bad_guy", | |
"civilian", | |
"ammo", | |
"health_pack"}; | |
return types; | |
} | |
//=========================================== | |
// SpriteFactory::constructObject | |
//=========================================== | |
bool SpriteFactory::constructObject(const string& type, entityId_t entityId, | |
parser::Object& obj, entityId_t region, const Matrix& parentTransform) { | |
if (type == "sprite") { | |
return constructSprite(entityId, obj, region, parentTransform); | |
} | |
else if (type == "bad_guy") { | |
return constructBadGuy(entityId, obj, region, parentTransform); | |
} | |
else if (type == "civilian") { | |
return constructCivilian(entityId, obj, region, parentTransform); | |
} | |
else if (type == "ammo") { | |
return constructAmmo(entityId, obj, region, parentTransform); | |
} | |
else if (type == "health_pack") { | |
return constructHealthPack(entityId, obj, region, parentTransform); | |
} | |
return false; | |
} | |
#include "raycast/event_handler_system.hpp" | |
using std::set; | |
//=========================================== | |
// sendEventToComponent | |
//=========================================== | |
static void sendEventToComponent(CEventHandler& c, const GameEvent& e, bool targeted) { | |
auto& handlers = targeted ? c.targetedEventHandlers : c.broadcastedEventHandlers; | |
for (auto it = handlers.begin(); it != handlers.end(); ++it) { | |
EventHandler& handler = *it; | |
if (handler.name == e.name || handler.name == "*") { | |
handler.handler(e); | |
} | |
} | |
} | |
//=========================================== | |
// EventHandlerSystem::addComponent | |
//=========================================== | |
void EventHandlerSystem::addComponent(pComponent_t component) { | |
pCEventHandler_t c(dynamic_cast<CEventHandler*>(component.release())); | |
m_components.insert(make_pair(c->entityId(), std::move(c))); | |
} | |
//=========================================== | |
// EventHandlerSystem::hasComponent | |
//=========================================== | |
bool EventHandlerSystem::hasComponent(entityId_t entityId) const { | |
return m_components.find(entityId) != m_components.end(); | |
} | |
//=========================================== | |
// EventHandlerSystem::getComponent | |
//=========================================== | |
CEventHandler& EventHandlerSystem::getComponent(entityId_t entityId) const { | |
return *m_components.at(entityId); | |
} | |
//=========================================== | |
// EventHandlerSystem::removeEntity | |
//=========================================== | |
void EventHandlerSystem::removeEntity(entityId_t id) { | |
m_components.erase(id); | |
} | |
//=========================================== | |
// EventHandlerSystem::handleEvent | |
//=========================================== | |
void EventHandlerSystem::handleEvent(const GameEvent& event) { | |
for (auto it = m_components.begin(); it != m_components.end(); ++it) { | |
sendEventToComponent(*it->second, event, false); | |
} | |
} | |
//=========================================== | |
// EventHandlerSystem::handleEvent | |
//=========================================== | |
void EventHandlerSystem::handleEvent(const GameEvent& event, const set<entityId_t>& entities) { | |
for (auto it = m_components.begin(); it != m_components.end(); ++it) { | |
if (entities.count(it->first)) { | |
sendEventToComponent(*it->second, event, true); | |
} | |
} | |
} | |
#include <string> | |
#include <algorithm> | |
#include <cassert> | |
#include <sstream> | |
#include <ostream> | |
#include <vector> | |
#include <iterator> | |
#include <deque> | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/tween_curves.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
#include "event_system.hpp" | |
using std::stringstream; | |
using std::unique_ptr; | |
using std::function; | |
using std::string; | |
using std::map; | |
using std::set; | |
using std::ostream; | |
using std::pair; | |
using std::make_pair; | |
using std::find_if; | |
using std::for_each; | |
using std::vector; | |
static const double GRID_CELL_SIZE = 25.0; | |
static const double SNAP_DISTANCE = 4.0; | |
static const int MAX_ANCESTOR_SEARCH = 2; | |
static const double ACCELERATION_DUE_TO_GRAVITY = -600.0; | |
static const double JUMP_V = 220; | |
ostream& operator<<(ostream& os, CSpatialKind kind) { | |
switch (kind) { | |
case CSpatialKind::ZONE: os << "ZONE"; break; | |
case CSpatialKind::HARD_EDGE: os << "HARD_EDGE"; break; | |
case CSpatialKind::SOFT_EDGE: os << "SOFT_EDGE"; break; | |
case CSpatialKind::H_RECT: os << "H_RECT"; break; | |
case CSpatialKind::V_RECT: os << "V_RECT"; break; | |
case CSpatialKind::PATH: os << "PATH"; break; | |
} | |
return os; | |
} | |
//=========================================== | |
// contains | |
//=========================================== | |
static bool contains(vector<entityId_t>& vec, entityId_t id) { | |
for (entityId_t i : vec) { | |
if (i == id) { | |
return true; | |
} | |
} | |
return false; | |
} | |
//=========================================== | |
// forEachConstZone | |
// | |
// fn can return false to abort the loop | |
//=========================================== | |
static bool forEachConstZone(const CZone& zone, function<bool(const CZone&)> fn) { | |
if (fn(zone)) { | |
for (auto& child : zone.children) { | |
if (!forEachConstZone(*child, fn)) { | |
break; | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// forEachZone | |
// | |
// fn can return false to abort the loop | |
//=========================================== | |
static bool forEachZone(CZone& zone, function<bool(CZone&)> fn) { | |
if (fn(zone)) { | |
for (auto& child : zone.children) { | |
if (!forEachZone(*child, fn)) { | |
break; | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// nthAncestor | |
//=========================================== | |
static CZone& nthAncestor(CZone& z, int n) { | |
return (n <= 0 || z.parent == nullptr) ? z : nthAncestor(*z.parent, n - 1); | |
} | |
//=========================================== | |
// nthConstAncestor | |
//=========================================== | |
static const CZone& nthConstAncestor(const CZone& z, int n) { | |
return (n <= 0 || z.parent == nullptr) ? z : nthConstAncestor(*z.parent, n - 1); | |
} | |
//=========================================== | |
// getNextZone | |
//=========================================== | |
static CZone* getNextZone(const CZone& current, const CSoftEdge& se) { | |
return se.zoneA == ¤t ? se.zoneB : se.zoneA; | |
} | |
//=========================================== | |
// canStepAcrossEdge | |
//=========================================== | |
static bool canStepAcrossEdge(const CZone& zone, double height, const Size& bodySize, | |
const CEdge& edge) { | |
if (edge.kind != CSpatialKind::SOFT_EDGE) { | |
return false; | |
} | |
const CSoftEdge& se = DYNAMIC_CAST<const CSoftEdge&>(edge); | |
CZone* nextZone = getNextZone(zone, se); | |
bool canStep = nextZone->floorHeight - height <= PLAYER_STEP_HEIGHT; | |
bool hasHeadroom = !nextZone->hasCeiling || (height + bodySize.y < nextZone->ceilingHeight); | |
return canStep && hasHeadroom; | |
} | |
//=========================================== | |
// pathBlocked | |
// | |
// Returns true if a body moving through vector v is blocked by an edge it cannot cross. | |
//=========================================== | |
static bool pathBlocked(const CZone& zone, const Point& pos, double height, const Size& bodySize, | |
const Vec2f v) { | |
LineSegment lseg(pos, pos + v); | |
bool b = false; | |
forEachConstZone(nthConstAncestor(zone, MAX_ANCESTOR_SEARCH), [&](const CZone& r) { | |
for (auto it = r.edges.begin(); it != r.edges.end(); ++it) { | |
const CEdge& edge = **it; | |
Point p; | |
if (lineSegmentIntersect(lseg, edge.lseg, p)) { | |
if (!canStepAcrossEdge(zone, height, bodySize, edge)) { | |
b = true; | |
return false; | |
} | |
} | |
} | |
return true; | |
}); | |
return b; | |
} | |
//=========================================== | |
// getDelta | |
// | |
// Takes the vector the player wants to move in (oldV) and returns a modified vector that doesn't | |
// allow the player within radius units of a wall. | |
//=========================================== | |
static Vec2f getDelta(const CVRect& body, double height, double radius, const Vec2f& oldV, | |
int depth = 0) { | |
if (depth > 4) { | |
return Vec2f(0, 0); | |
} | |
const Point& pos = body.pos; | |
const CZone& zone = *body.zone; | |
assert(zone.parent != nullptr); | |
Vec2f newV(9999, 9999); // Value closest to oldV | |
bool collision = false; | |
bool newVFound = false; | |
Circle circle{pos + oldV, radius}; | |
forEachConstZone(nthConstAncestor(zone, MAX_ANCESTOR_SEARCH), [&](const CZone& r) { | |
for (auto it = r.edges.begin(); it != r.edges.end(); ++it) { | |
const CEdge& edge = **it; | |
// If moving by oldV will intersect something, we need to change it | |
if (lineSegmentCircleIntersect(circle, edge.lseg) && | |
!canStepAcrossEdge(*body.zone, height, body.size, edge)) { | |
collision = true; | |
// The body's position if it were moved by oldV | |
Point nextPos = pos + oldV; | |
Point X = projectionOntoLine(edge.lseg.line(), nextPos); | |
Vec2f toEdge = nextPos - X; | |
Vec2f adjustment = normalise(toEdge) * (radius - length(toEdge)) * 1.0001; | |
Vec2f v = getDelta(body, height, radius, oldV + adjustment, depth + 1); | |
// This should prevent corner cutting | |
if (!pathBlocked(zone, pos, height, body.size, v) | |
&& length(v - oldV) < length(newV - oldV)) { | |
newV = v; | |
newVFound = true; | |
} | |
} | |
} | |
return true; | |
}); | |
if (collision) { | |
if (newVFound) { | |
return newV; | |
} | |
else { | |
DBG_PRINT("Warning: Couldn't find good collision delta\n"); | |
return Vec2f{0, 0}; | |
} | |
} | |
else { | |
return oldV; | |
} | |
} | |
//=========================================== | |
// playerBounce | |
//=========================================== | |
static void playerBounce(SpatialSystem& spatialSystem, TimeService& timeService) { | |
double y = 5.0; | |
double duration = 0.33; | |
int nFrames = duration * timeService.frameRate; | |
double dy = y / (0.5 * nFrames); | |
timeService.addTween(Tween{[&, nFrames, dy](long i, double, double) -> bool { | |
if (i < 0.5 * nFrames) { | |
spatialSystem.sg.player->changeTallness(dy); | |
} | |
else { | |
spatialSystem.sg.player->changeTallness(-dy); | |
} | |
return i < nFrames; | |
}, [](long, double, double) {}}, "player_bounce"); | |
} | |
//=========================================== | |
// overlapsCircle | |
//=========================================== | |
static bool overlapsCircle(const Circle& circle, const CEdge& edge) { | |
return lineSegmentCircleIntersect(circle, edge.lseg); // TODO | |
} | |
//=========================================== | |
// overlapsCircle | |
//=========================================== | |
static bool overlapsCircle(const Circle& circle, const CVRect& vRect) { | |
return distance(circle.pos, vRect.pos) <= circle.radius; | |
} | |
//=========================================== | |
// overlapsCircle | |
//=========================================== | |
static bool overlapsCircle(const Circle& circle, const LineSegment& wall, const CVRect& vRect) { | |
Vec2f v = normalise(wall.B - wall.A); | |
return distance(circle.pos, wall.A + v * vRect.pos.x) <= circle.radius; | |
} | |
//=========================================== | |
// overlapsCircle | |
//=========================================== | |
static bool overlapsCircle(const Circle&, const CHRect&) { | |
// TODO | |
return false; | |
} | |
//=========================================== | |
// addToZone | |
//=========================================== | |
static void addToZone(SceneGraph& sg, CZone& zone, pCSpatial_t child) { | |
switch (child->kind) { | |
case CSpatialKind::ZONE: { | |
pCZone_t ptr(DYNAMIC_CAST<CZone*>(child.release())); | |
ptr->parent = &zone; | |
zone.children.push_back(std::move(ptr)); | |
break; | |
} | |
case CSpatialKind::SOFT_EDGE: | |
case CSpatialKind::HARD_EDGE: { | |
pCEdge_t ptr(DYNAMIC_CAST<CEdge*>(child.release())); | |
zone.edges.push_back(ptr.get()); | |
sg.edges.push_back(std::move(ptr)); | |
break; | |
} | |
case CSpatialKind::H_RECT: { | |
pCHRect_t ptr(DYNAMIC_CAST<CHRect*>(child.release())); | |
zone.hRects.push_back(std::move(ptr)); | |
break; | |
} | |
case CSpatialKind::V_RECT: { | |
pCVRect_t ptr(DYNAMIC_CAST<CVRect*>(child.release())); | |
zone.vRects.push_back(std::move(ptr)); | |
break; | |
} | |
case CSpatialKind::PATH: { | |
pCPath_t ptr(DYNAMIC_CAST<CPath*>(child.release())); | |
zone.paths.push_back(std::move(ptr)); | |
break; | |
} | |
default: { | |
EXCEPTION("Cannot add component of kind " << child->kind << " to zone"); | |
} | |
} | |
} | |
//=========================================== | |
// addToHardEdge | |
//=========================================== | |
static void addToHardEdge(CHardEdge& edge, pCSpatial_t child) { | |
switch (child->kind) { | |
case CSpatialKind::V_RECT: { | |
pCVRect_t ptr(DYNAMIC_CAST<CVRect*>(child.release())); | |
edge.vRects.push_back(std::move(ptr)); | |
break; | |
} | |
default: { | |
EXCEPTION("Cannot add component of kind " << child->kind << " to HardEdge"); | |
} | |
} | |
} | |
//=========================================== | |
// addToSoftEdge | |
//=========================================== | |
static void addToSoftEdge(CSoftEdge& edge, pCSpatial_t child) { | |
switch (child->kind) { | |
case CSpatialKind::V_RECT: { | |
pCVRect_t ptr(DYNAMIC_CAST<CVRect*>(child.release())); | |
edge.vRects.push_back(std::move(ptr)); | |
break; | |
} | |
default: { | |
EXCEPTION("Cannot add component of kind " << child->kind << " to SoftEdge"); | |
} | |
} | |
} | |
//=========================================== | |
// SpatialSystem::addChildToComponent | |
//=========================================== | |
void SpatialSystem::addChildToComponent(CSpatial& parent, pCSpatial_t child) { | |
CSpatial* ptr = child.get(); | |
switch (parent.kind) { | |
case CSpatialKind::ZONE: | |
addToZone(sg, DYNAMIC_CAST<CZone&>(parent), std::move(child)); | |
break; | |
case CSpatialKind::HARD_EDGE: | |
addToHardEdge(DYNAMIC_CAST<CHardEdge&>(parent), std::move(child)); | |
break; | |
case CSpatialKind::SOFT_EDGE: | |
addToSoftEdge(DYNAMIC_CAST<CSoftEdge&>(parent), std::move(child)); | |
break; | |
default: | |
EXCEPTION("Cannot add component of kind " << child->kind << " to component of kind " | |
<< parent.kind); | |
}; | |
m_entityChildren[parent.entityId()].insert(ptr); | |
} | |
//=========================================== | |
// removeFromZone | |
//=========================================== | |
static bool removeFromZone(SceneGraph& sg, CZone& zone, const CSpatial& child, bool keepAlive) { | |
bool found = false; | |
switch (child.kind) { | |
case CSpatialKind::ZONE: { | |
auto it = find_if(zone.children.begin(), zone.children.end(), [&](const pCZone_t& e) { | |
return e.get() == DYNAMIC_CAST<const CZone*>(&child); | |
}); | |
if (it != zone.children.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(zone.children, it); | |
found = true; | |
} | |
break; | |
} | |
case CSpatialKind::SOFT_EDGE: | |
case CSpatialKind::HARD_EDGE: { | |
auto it = find_if(zone.edges.begin(), zone.edges.end(), [&](const CEdge* e) { | |
return e == DYNAMIC_CAST<const CEdge*>(&child); | |
}); | |
erase(zone.edges, it); | |
auto jt = find_if(sg.edges.begin(), sg.edges.end(), [&](const pCEdge_t& e) { | |
return e.get() == DYNAMIC_CAST<const CEdge*>(&child); | |
}); | |
if (jt != sg.edges.end()) { | |
if (keepAlive) { | |
jt->release(); | |
} | |
erase(sg.edges, jt); | |
found = true; | |
} | |
break; | |
} | |
case CSpatialKind::H_RECT: { | |
auto it = find_if(zone.hRects.begin(), zone.hRects.end(), [&](const pCHRect_t& e) { | |
return e.get() == DYNAMIC_CAST<const CHRect*>(&child); | |
}); | |
if (it != zone.hRects.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(zone.hRects, it); | |
found = true; | |
} | |
break; | |
} | |
case CSpatialKind::V_RECT: { | |
auto it = find_if(zone.vRects.begin(), zone.vRects.end(), [&](const pCVRect_t& e) { | |
return e.get() == DYNAMIC_CAST<const CVRect*>(&child); | |
}); | |
if (it != zone.vRects.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(zone.vRects, it); | |
found = true; | |
} | |
break; | |
} | |
case CSpatialKind::PATH: { | |
auto it = find_if(zone.paths.begin(), zone.paths.end(), [&](const pCPath_t& e) { | |
return e.get() == DYNAMIC_CAST<const CPath*>(&child); | |
}); | |
if (it != zone.paths.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(zone.paths, it); | |
found = true; | |
} | |
break; | |
} | |
default: { | |
EXCEPTION("Cannot add component of kind " << child.kind << " to zone"); | |
} | |
} | |
return found; | |
} | |
//=========================================== | |
// removeFromHardEdge | |
//=========================================== | |
static bool removeFromHardEdge(CHardEdge& edge, const CSpatial& child, bool keepAlive) { | |
bool found = false; | |
switch (child.kind) { | |
case CSpatialKind::V_RECT: { | |
auto it = find_if(edge.vRects.begin(), edge.vRects.end(), [&](const pCVRect_t& e) { | |
return e.get() == DYNAMIC_CAST<const CVRect*>(&child); | |
}); | |
if (it != edge.vRects.end()) { | |
if (keepAlive) { | |
it->release(); | |
} | |
erase(edge.vRects, it); | |
found = true; | |
} | |
break; | |
} | |
default: { | |
EXCEPTION("Cannot remove component of kind " << child.kind << " from HardEdge"); | |
} | |
} | |
return found; | |
} | |
//=========================================== | |
// similar | |
//=========================================== | |
static bool similar(const LineSegment& l1, const LineSegment& l2) { | |
return (distance(l1.A, l2.A) <= SNAP_DISTANCE && distance(l1.B, l2.B) <= SNAP_DISTANCE) | |
|| (distance(l1.A, l2.B) <= SNAP_DISTANCE && distance(l1.B, l2.A) <= SNAP_DISTANCE); | |
} | |
//=========================================== | |
// SpatialSystem::getAncestors | |
//=========================================== | |
set<entityId_t> SpatialSystem::getAncestors(entityId_t entityId) const { | |
set<entityId_t> ancestors; | |
const CSpatial* c = &getComponent(entityId); | |
entityId_t parent = c->parentId; | |
while (parent != -1) { | |
ancestors.insert(parent); | |
c = &getComponent(parent); | |
parent = c->parentId; | |
} | |
return ancestors; | |
} | |
//=========================================== | |
// SpatialSystem::zone | |
//=========================================== | |
CZone& SpatialSystem::zone(entityId_t entityId) { | |
return const_cast<CZone&>(constZone(entityId)); | |
} | |
//=========================================== | |
// SpatialSystem::constZone | |
//=========================================== | |
const CZone& SpatialSystem::constZone(entityId_t entityId) const { | |
while (entityId != -1) { | |
CSpatial& c = *GET_VALUE(m_components, entityId); | |
if (c.kind == CSpatialKind::ZONE) { | |
return DYNAMIC_CAST<const CZone&>(c); | |
} | |
entityId = c.parentId; | |
} | |
EXCEPTION("Entity with id " << entityId << " is not inside a zone"); | |
} | |
//=========================================== | |
// SpatialSystem::isAncestor | |
//=========================================== | |
bool SpatialSystem::isAncestor(entityId_t a, entityId_t b) const { | |
if (a == -1 || b == -1) { | |
return false; | |
} | |
const CSpatial& bComp = *GET_VALUE(m_components, b); | |
if (bComp.parentId == -1) { | |
return false; | |
} | |
const CSpatial* anc = GET_VALUE(m_components, bComp.parentId); | |
while (anc->parentId != -1) { | |
if (anc->entityId() == a) { | |
return true; | |
} | |
anc = GET_VALUE(m_components, anc->parentId); | |
}; | |
return false; | |
} | |
//=========================================== | |
// SpatialSystem::areTwins | |
//=========================================== | |
bool SpatialSystem::areTwins(const CSoftEdge& se1, const CSoftEdge& se2) const { | |
return similar(se1.lseg, se2.lseg) && | |
!(isAncestor(se1.parentId, se2.parentId) || isAncestor(se2.parentId, se1.parentId)); | |
} | |
//=========================================== | |
// SpatialSystem::connectSubzones | |
//=========================================== | |
void SpatialSystem::connectSubzones(CZone& zone) { | |
int i = 0; | |
forEachZone(zone, [&](CZone& r) { | |
for (auto it = r.edges.begin(); it != r.edges.end(); ++it) { | |
if ((*it)->kind == CSpatialKind::SOFT_EDGE) { | |
CSoftEdge* se = DYNAMIC_CAST<CSoftEdge*>(*it); | |
assert(se != nullptr); | |
bool hasTwin = false; | |
int j = 0; | |
forEachZone(zone, [&](CZone& r_) { | |
for (auto lt = r_.edges.begin(); lt != r_.edges.end(); ++lt) { | |
if ((*lt)->kind == CSpatialKind::SOFT_EDGE) { | |
CSoftEdge* other = DYNAMIC_CAST<CSoftEdge*>(*lt); | |
if (other != se) { | |
if (areTwins(*se, *other)) { | |
hasTwin = true; | |
se->joinId = other->joinId; | |
se->zoneA = &r; | |
se->zoneB = &r_; | |
se->lseg = other->lseg; | |
se->twinId = other->entityId(); | |
other->zoneA = &r_; | |
other->zoneB = &r; | |
other->twinId = se->entityId(); | |
return false; | |
} | |
// If they're already joined by id (i.e. portals) | |
if (se->joinId != -1 && se->joinId == other->joinId) { | |
hasTwin = true; | |
se->zoneA = &r; | |
se->zoneB = &r_; | |
se->twinId = other->entityId(); | |
double a1 = se->lseg.angle(); | |
double a2 = other->lseg.angle(); | |
double a = a2 - a1; | |
Matrix toOrigin(0, -se->lseg.A); | |
Matrix rotate(a, Vec2f(0, 0)); | |
Matrix translate(0, other->lseg.A); | |
Matrix m = translate * rotate * toOrigin; | |
se->toTwin = m; | |
other->zoneA = &r_; | |
other->zoneB = &r; | |
other->twinId = se->entityId(); | |
other->toTwin = m.inverse(); | |
other->lseg.A = m * se->lseg.A; | |
other->lseg.B = m * se->lseg.B; | |
return false; | |
} | |
} | |
} | |
} | |
if (j > i) { | |
return false; | |
} | |
++j; | |
return true; | |
}); | |
if (!hasTwin) { | |
assert(r.parent != nullptr); | |
se->zoneA = &r; | |
se->zoneB = r.parent; | |
} | |
} | |
} | |
++i; | |
return true; | |
}); | |
} | |
//=========================================== | |
// SpatialSystem::SpatialSystem | |
//=========================================== | |
SpatialSystem::SpatialSystem(EntityManager& entityManager, TimeService& timeService, | |
double frameRate) | |
: m_entityManager(entityManager), | |
m_timeService(timeService) { | |
m_frameRate = frameRate; | |
} | |
//=========================================== | |
// SpatialSystem::handleEvent | |
//=========================================== | |
void SpatialSystem::handleEvent(const GameEvent& event) { | |
if (event.name == "player_activate") { | |
EPlayerActivateEntity e(*sg.player); | |
const CZone& zone = getCurrentZone(); | |
const CVRect& body = DYNAMIC_CAST<const CVRect&>(getComponent(sg.player->body)); | |
double y = sg.player->feetHeight() - zone.floorHeight + 0.5 * body.size.y; | |
e.inRadius = entitiesInRadius(zone, sg.player->pos(), sg.player->activationRadius, y); | |
auto vec = entitiesAlong3dRay(Vec2f{1, 0}, 0.0, sg.player->activationRadius); | |
for (auto& pX : vec) { | |
e.lookingAt.insert(pX->entityId); | |
} | |
set<entityId_t> entities; | |
entities.insert(e.inRadius.begin(), e.inRadius.end()); | |
entities.insert(e.lookingAt.begin(), e.lookingAt.end()); | |
m_entityManager.fireEvent(e, entities); | |
} | |
} | |
//=========================================== | |
// SpatialSystem::vRotateCamera | |
//=========================================== | |
void SpatialSystem::vRotateCamera(double da) { | |
sg.player->vRotate(da); | |
} | |
//=========================================== | |
// SpatialSystem::hRotateCamera | |
//=========================================== | |
void SpatialSystem::hRotateCamera(double da) { | |
sg.player->hRotate(da); | |
} | |
//=========================================== | |
// SpatialSystem::crossZones | |
// | |
// Doesn't set the zone on the entity | |
//=========================================== | |
void SpatialSystem::crossZones(entityId_t entityId, entityId_t oldZoneId, entityId_t newZoneId) { | |
DBG_PRINT("Crossing from zone " << oldZoneId << " to zone " << newZoneId << "\n"); | |
if (oldZoneId == newZoneId) { | |
return; | |
} | |
auto it = m_components.find(entityId); | |
if (it != m_components.end()) { | |
CSpatial& c = *it->second; | |
CZone& oldZone = DYNAMIC_CAST<CZone&>(getComponent(oldZoneId)); | |
CZone& newZone = DYNAMIC_CAST<CZone&>(getComponent(newZoneId)); | |
if (removeChildFromComponent(oldZone, c, true)) { | |
addChildToComponent(newZone, pCSpatial_t(&c)); | |
set<entityId_t> oldZones = getAncestors(oldZoneId); | |
set<entityId_t> newZones = getAncestors(newZoneId); | |
set<entityId_t> zonesLeft = { oldZoneId }; | |
set<entityId_t> zonesEntered = { newZoneId }; | |
std::set_difference(oldZones.begin(), oldZones.end(), newZones.begin(), newZones.end(), | |
std::inserter(zonesLeft, zonesLeft.end())); | |
std::set_difference(newZones.begin(), newZones.end(), oldZones.begin(), oldZones.end(), | |
std::inserter(zonesEntered, zonesEntered.end())); | |
m_entityManager.broadcastEvent(EChangedZone(entityId, oldZoneId, newZoneId, zonesLeft, | |
zonesEntered)); | |
} | |
} | |
} | |
//=========================================== | |
// SpatialSystem::relocateEntity | |
//=========================================== | |
void SpatialSystem::relocateEntity(entityId_t id, CZone& zone, const Point& point) { | |
auto it = m_components.find(id); | |
if (it != m_components.end()) { | |
CSpatial& c = *it->second; | |
// Currently, only VRects can be moved | |
if (c.kind == CSpatialKind::V_RECT) { | |
CVRect& body = DYNAMIC_CAST<CVRect&>(c); | |
crossZones(id, body.zone->entityId(), zone.entityId()); | |
body.zone = &zone; | |
body.pos = point; | |
} | |
} | |
} | |
//=========================================== | |
// SpatialSystem::moveEntity | |
//=========================================== | |
void SpatialSystem::moveEntity(entityId_t id, Vec2f dv, double heightAboveFloor) { | |
auto it = m_components.find(id); | |
if (it != m_components.end()) { | |
CSpatial& c = *it->second; | |
// Currently, only VRects can be moved | |
if (c.kind == CSpatialKind::V_RECT) { | |
CVRect& body = DYNAMIC_CAST<CVRect&>(c); | |
// TODO | |
double radius = 10.0; //body.size.x * 0.5; | |
dv = getDelta(body, body.zone->floorHeight + heightAboveFloor, radius, dv); | |
assert(body.zone->parent != nullptr); | |
vector<const CSoftEdge*> edgesToCross; | |
LineSegment lseg(body.pos, body.pos + dv); | |
forEachConstZone(nthAncestor(*body.zone, MAX_ANCESTOR_SEARCH), [&](const CZone& r) { | |
for (auto it = r.edges.begin(); it != r.edges.end(); ++it) { | |
const CEdge& edge = **it; | |
Point X; | |
if (lineSegmentIntersect(lseg, edge.lseg, X)) { | |
if (edge.kind != CSpatialKind::SOFT_EDGE) { | |
DBG_PRINT("Warning: Crossed edge is not soft edge\n"); | |
DBG_PRINT_VAR(dv); | |
return false; | |
} | |
const CSoftEdge& se = DYNAMIC_CAST<const CSoftEdge&>(edge); | |
edgesToCross.push_back(&se); | |
} | |
} | |
return true; | |
}); | |
Matrix m; | |
map<entityId_t, bool> crossed; | |
vector<const CSoftEdge*> excluded; | |
while (!edgesToCross.empty()) { | |
const CSoftEdge* se = edgesToCross.front(); | |
if (se->zoneA == body.zone || se->zoneB == body.zone) { | |
edgesToCross.erase(edgesToCross.begin()); | |
if (!crossed[se->joinId]) { | |
CZone* nextZone = getNextZone(*body.zone, *se); | |
crossZones(id, body.zone->entityId(), nextZone->entityId()); | |
body.zone = nextZone; | |
m = m * se->toTwin; | |
if (se->joinId != -1) { | |
crossed[se->joinId] = true; | |
} | |
} | |
for (auto it = excluded.rbegin(); it != excluded.rend(); ++it) { | |
edgesToCross.insert(edgesToCross.begin(), *it); | |
} | |
excluded.clear(); | |
} | |
else { | |
excluded.push_back(se); | |
edgesToCross.erase(edgesToCross.begin()); | |
} | |
} | |
if (excluded.size() > 0) { | |
// TODO: Find out why this happens | |
DBG_PRINT("Warning: Bad intersections omitted\n"); | |
} | |
body.pos = body.pos + dv; | |
body.pos = m * body.pos; | |
body.angle += m.a(); | |
} | |
} | |
} | |
//=========================================== | |
// SpatialSystem::movePlayer | |
//=========================================== | |
void SpatialSystem::movePlayer(const Vec2f& v) { | |
Player& player = *sg.player; | |
const CVRect& body = DYNAMIC_CAST<const CVRect&>(getComponent(player.body)); | |
const CZone& zone = getCurrentZone(); | |
Vec2f dv(cos(body.angle) * v.x - sin(body.angle) * v.y, | |
sin(body.angle) * v.x + cos(body.angle) * v.y); | |
moveEntity(player.body, dv, player.feetHeight() - zone.floorHeight); | |
playerBounce(*this, m_timeService); | |
Point pos = player.pos(); | |
Vec2i cell(pos.x / GRID_CELL_SIZE, pos.y / GRID_CELL_SIZE); | |
if (cell != m_playerCell) { | |
m_playerCell = cell; | |
EPlayerMove e(player); | |
auto inRadius = entitiesInRadius(zone, player.pos(), player.collectionRadius, | |
player.feetHeight() - zone.floorHeight); | |
inRadius.insert(player.body); | |
m_entityManager.fireEvent(e, inRadius); | |
m_entityManager.broadcastEvent(e); | |
} | |
} | |
//=========================================== | |
// SpatialSystem::shortestPath | |
//=========================================== | |
vector<Point> SpatialSystem::shortestPath(entityId_t entityA, entityId_t entityB, | |
double radius) const { | |
const CSpatial& compA = getComponent(entityA); | |
if (compA.kind != CSpatialKind::V_RECT) { | |
EXCEPTION("Component not of type V_RECT"); | |
} | |
const CSpatial& compB = getComponent(entityB); | |
if (compB.kind != CSpatialKind::V_RECT) { | |
EXCEPTION("Component not of type V_RECT"); | |
} | |
const CVRect& rectA = DYNAMIC_CAST<const CVRect&>(compA); | |
const CVRect& rectB = DYNAMIC_CAST<const CVRect&>(compB); | |
return shortestPath(rectA.pos, rectB.pos, radius); | |
} | |
//=========================================== | |
// SpatialSystem::shortestPath | |
//=========================================== | |
vector<Point> SpatialSystem::shortestPath(const Point&, const Point& B, double) const { | |
// TODO: A* | |
vector<Point> path; | |
path.push_back(B); | |
return path; | |
} | |
//=========================================== | |
// SpatialSystem::jump | |
//=========================================== | |
void SpatialSystem::jump() { | |
if (!sg.player->aboveGround()) { | |
sg.player->vVelocity = JUMP_V; | |
} | |
} | |
//=========================================== | |
// SpatialSystem::gravity | |
//=========================================== | |
void SpatialSystem::gravity() { | |
const double TERMINAL_VELOCITY = -2000.0; | |
const double ONE_PT_DAMAGE_SPEED = 600.0; | |
const double TEN_PT_DAMAGE_SPEED = 2000.0; | |
CZone& currentZone = getCurrentZone(); | |
Player& player = *sg.player; | |
double diff = (player.feetHeight() - 0.1) - currentZone.floorHeight; | |
if (fabs(player.vVelocity) > 0.001 || diff > 0.0) { | |
double a = ACCELERATION_DUE_TO_GRAVITY; | |
double dt = 1.0 / m_frameRate; | |
double dv = dt * a; | |
if (player.vVelocity > TERMINAL_VELOCITY) { | |
player.vVelocity += dv; | |
} | |
double dy = player.vVelocity * dt; | |
double dy_ = dy > 0.0 ? dy : smallest<double>(diff, dy); | |
player.changeHeight(currentZone, dy_); | |
moveEntity(player.body, Vec2f(0, 0), player.feetHeight() - currentZone.floorHeight); | |
if (!player.aboveGround()) { | |
if (player.vVelocity <= -ONE_PT_DAMAGE_SPEED) { | |
const double m = ONE_PT_DAMAGE_SPEED; | |
const double n = TEN_PT_DAMAGE_SPEED; | |
const double k = 2.0; | |
double v = -player.vVelocity; | |
double damage = 1.0 + 9.0 * (pow(pow(v, 2) - pow(m, 2), k)) / pow(pow(n, 2) - pow(m, 2), k); | |
DBG_PRINT_VAR(player.vVelocity); | |
DBG_PRINT_VAR(damage); | |
DamageSystem& damageSystem = | |
m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
damageSystem.damageEntity(player.body, floor(damage)); | |
} | |
player.vVelocity = 0; | |
} | |
} | |
} | |
//=========================================== | |
// SpatialSystem::buoyancy | |
//=========================================== | |
void SpatialSystem::buoyancy() { | |
CZone& currentZone = getCurrentZone(); | |
double diff = currentZone.floorHeight - (sg.player->feetHeight() + 0.1); | |
if (diff > 0.0) { | |
double dy = smallest<double>(diff, 150.0 / m_frameRate); | |
sg.player->changeHeight(currentZone, dy); | |
} | |
} | |
//=========================================== | |
// SpatialSystem::connectZones | |
//=========================================== | |
void SpatialSystem::connectZones() { | |
connectSubzones(*sg.rootZone); | |
} | |
//=========================================== | |
// SpatialSystem::findIntersections_r | |
// | |
// point and dir are in the space given by matrix. i.e. if matrix is the camera matrix, point and | |
// dir are in camera space. | |
//=========================================== | |
void SpatialSystem::findIntersections_r(const Point& point, const Vec2f& dir, const Matrix& matrix, | |
const CZone& zone, const CSpatial& parent, vector<pIntersection_t>& intersections, | |
vector<entityId_t>& visited, double cullNearerThan, double& cullFartherThan) const { | |
Matrix invMatrix = matrix.inverse(); | |
LineSegment ray(point + Vec2f(0.01, 0), 10000.0 * dir); | |
visited.push_back(parent.entityId()); | |
auto& children = GET_VALUE(m_entityChildren, parent.entityId()); | |
for (const CSpatial* pChild : children) { | |
const CSpatial& c = *pChild; | |
switch (c.kind) { | |
case CSpatialKind::ZONE: { | |
if (!contains(visited, c.entityId())) { | |
findIntersections_r(point, dir, matrix, DYNAMIC_CAST<const CZone&>(c), c, intersections, | |
visited, cullNearerThan, cullFartherThan); | |
} | |
break; | |
} | |
case CSpatialKind::V_RECT: { | |
const CVRect& vRect = DYNAMIC_CAST<const CVRect&>(c); | |
if (parent.kind == CSpatialKind::ZONE) { | |
Point pos = matrix * vRect.pos; | |
double w = vRect.size.x; | |
LineSegment lseg(Point(pos.x, pos.y - 0.5 * w), Point(pos.x, pos.y + 0.5 * w)); | |
Point pt; | |
if (lineSegmentIntersect(ray, lseg, pt)) { | |
if (pt.x < cullNearerThan || pt.x > cullFartherThan) { | |
continue; | |
} | |
pIntersection_t X(new Intersection(CSpatialKind::V_RECT, parent.kind)); | |
X->entityId = vRect.entityId(); | |
X->point_rel = pt; | |
X->point_wld = invMatrix * pt; | |
X->viewPoint = invMatrix * point; | |
X->distanceFromOrigin = pt.x; | |
X->distanceAlongTarget = distance(lseg.A, pt); | |
X->zoneA = X->zoneB = zone.entityId(); | |
X->heightRanges = make_pair(Range(vRect.zone->floorHeight + vRect.y, | |
vRect.zone->floorHeight + vRect.y + vRect.size.y), Range(0, 0)); | |
intersections.push_back(std::move(X)); | |
} | |
} | |
else if (parent.kind == CSpatialKind::HARD_EDGE || parent.kind == CSpatialKind::SOFT_EDGE) { | |
const CEdge& edge = DYNAMIC_CAST<const CEdge&>(parent); | |
LineSegment wallLseg = transform(edge.lseg, matrix); | |
Vec2f v = normalise(wallLseg.B - wallLseg.A); | |
Point pos = wallLseg.A + v * vRect.pos.x; | |
double w = vRect.size.x; | |
LineSegment lseg(pos, pos + w * v); | |
Point pt; | |
if (lineSegmentIntersect(ray, lseg, pt)) { | |
if (pt.x < cullNearerThan || pt.x > cullFartherThan) { | |
continue; | |
} | |
assert(parent.parentId == zone.entityId()); | |
pt.x -= 0.01; | |
double vRectFloorH = vRect.zone->floorHeight; | |
if (edge.kind == CSpatialKind::SOFT_EDGE) { | |
const CSoftEdge& se = DYNAMIC_CAST<const CSoftEdge&>(edge); | |
vRectFloorH = smallest(se.zoneA->floorHeight, se.zoneB->floorHeight); | |
} | |
double y0 = vRectFloorH + vRect.pos.y; | |
double y1 = y0 + vRect.size.y; | |
pIntersection_t X(new Intersection(CSpatialKind::V_RECT, parent.kind)); | |
X->entityId = vRect.entityId(); | |
X->point_rel = pt; | |
X->point_wld = invMatrix * pt; | |
X->viewPoint = invMatrix * point; | |
X->distanceFromOrigin = pt.x; | |
X->distanceAlongTarget = distance(lseg.A, pt); | |
X->zoneA = X->zoneB = zone.entityId(); | |
X->heightRanges = make_pair(Range(y0, y1), Range(0, 0)); | |
intersections.push_back(std::move(X)); | |
} | |
} | |
break; | |
} | |
case CSpatialKind::HARD_EDGE: | |
case CSpatialKind::SOFT_EDGE: { | |
const CEdge& edge = DYNAMIC_CAST<const CEdge&>(c); | |
LineSegment lseg = transform(edge.lseg, matrix); | |
Point pt; | |
if (lineSegmentIntersect(ray, lseg, pt)) { | |
if (pt.x < cullNearerThan || pt.x > cullFartherThan) { | |
continue; | |
} | |
if (c.kind == CSpatialKind::HARD_EDGE && pt.x < cullFartherThan) { | |
// Add a small offset in case there's something very close to the wall that we | |
// don't want to get culled | |
cullFartherThan = pt.x + 1.0; | |
} | |
if (!contains(visited, c.entityId())) { | |
findIntersections_r(point, dir, matrix, zone, c, intersections, visited, cullNearerThan, | |
cullFartherThan); | |
} | |
pIntersection_t X(new Intersection(edge.kind, parent.kind)); | |
X->entityId = edge.entityId(); | |
X->point_rel = pt; | |
X->point_wld = invMatrix * pt; | |
X->viewPoint = invMatrix * point; | |
X->distanceFromOrigin = pt.x; | |
X->distanceAlongTarget = distance(lseg.A, pt); | |
X->zoneA = zone.entityId(); | |
if (edge.kind == CSpatialKind::HARD_EDGE) { | |
X->zoneB = zone.parent != nullptr ? zone.parent->entityId() : X->zoneA; | |
X->heightRanges = make_pair(Range(-10000, 10000), Range(0, 0)); // TODO | |
intersections.push_back(std::move(X)); | |
} | |
else if (edge.kind == CSpatialKind::SOFT_EDGE) { | |
const CSoftEdge& se = DYNAMIC_CAST<const CSoftEdge&>(edge); | |
const CZone& next = se.zoneA == &zone ? *se.zoneB : *se.zoneA; | |
if (contains(visited, se.joinId)) { | |
continue; | |
} | |
visited.push_back(se.joinId); | |
X->zoneB = next.entityId(); | |
X->heightRanges = make_pair(Range(se.zoneA->floorHeight, se.zoneB->floorHeight), | |
Range(se.zoneA->ceilingHeight, se.zoneB->ceilingHeight)); | |
Matrix mat = matrix; | |
double cullNearerThan_ = cullNearerThan; | |
vector<entityId_t> visited_{se.joinId}; | |
vector<entityId_t>* pVisited = &visited; | |
if (se.isPortal) { | |
mat = (se.toTwin * invMatrix).inverse(); | |
cullNearerThan_ = X->distanceFromOrigin; | |
cullFartherThan = 10000; | |
pVisited = &visited_; | |
} | |
intersections.push_back(std::move(X)); | |
if (!contains(visited, next.entityId()) || se.isPortal) { | |
findIntersections_r(point, dir, mat, next, next, intersections, *pVisited, | |
cullNearerThan_, cullFartherThan); | |
} | |
} | |
else { | |
EXCEPTION("Unexpected intersection type"); | |
} | |
} | |
break; | |
} | |
default: break; | |
} | |
} | |
} | |
//=========================================== | |
// SpatialSystem::entitiesAlongRay | |
//=========================================== | |
vector<pIntersection_t> SpatialSystem::entitiesAlongRay(const Vec2f& ray, double distance) const { | |
const Camera& camera = sg.player->camera(); | |
Matrix matrix = camera.matrix().inverse(); | |
vector<pIntersection_t> intersections = entitiesAlongRay(getCurrentZone(), Point(0, 0), ray, | |
matrix, distance); | |
return intersections; | |
} | |
//=========================================== | |
// SpatialSystem::entitiesAlongRay | |
//=========================================== | |
vector<pIntersection_t> SpatialSystem::entitiesAlongRay(const CZone& zone, const Point& pos, | |
const Vec2f& dir, const Matrix& matrix, double distance) const { | |
vector<pIntersection_t> intersections; | |
intersections.reserve(20); | |
vector<entityId_t> visited; | |
visited.reserve(30); | |
double cullFartherThan = distance; | |
findIntersections_r(pos, dir, matrix, zone, zone, intersections, visited, 0, cullFartherThan); | |
std::sort(intersections.begin(), intersections.end(), | |
[](const pIntersection_t& a, const pIntersection_t& b) { | |
return a->distanceFromOrigin < b->distanceFromOrigin; | |
}); | |
std::vector<pIntersection_t> Xs; | |
std::vector<pIntersection_t> excluded; | |
entityId_t currentZone = zone.entityId(); | |
while (!intersections.empty()) { | |
const auto& X = intersections.front(); | |
if (X->zoneA == currentZone || X->zoneB == currentZone) { | |
auto other = currentZone == X->zoneA ? X->zoneB : X->zoneA; | |
Xs.push_back(std::move(intersections.front())); | |
intersections.erase(intersections.begin()); | |
currentZone = other; | |
for (auto ex = excluded.rbegin(); ex != excluded.rend(); ++ex) { | |
intersections.insert(intersections.begin(), std::move(*ex)); | |
} | |
excluded.clear(); | |
} | |
else { | |
excluded.push_back(std::move(intersections.front())); | |
intersections.erase(intersections.begin()); | |
} | |
} | |
if (excluded.size() > 0) { | |
// TODO: Find out why this happens | |
//DBG_PRINT("Warning: Bad intersections omitted\n"); | |
} | |
auto it = find_if(Xs.begin(), Xs.end(), [](const pIntersection_t& i) { | |
return i->kind == CSpatialKind::HARD_EDGE; | |
}); | |
if (it != Xs.end()) { | |
Xs.erase(++it, Xs.end()); | |
} | |
return Xs; | |
} | |
//=========================================== | |
// SpatialSystem::entitiesAlong3dRay | |
//=========================================== | |
vector<pIntersection_t> SpatialSystem::entitiesAlong3dRay(const CZone& zone, const Point& pos, | |
double height, const Vec2f& dir, double vAngle, const Matrix& matrix, double distance) const { | |
vector<pIntersection_t> intersections = entitiesAlongRay(zone, pos, dir, matrix, distance); | |
for (auto it = intersections.begin(); it != intersections.end();) { | |
Intersection& X = **it; | |
double y = height + X.distanceFromOrigin * tan(vAngle); | |
if (!isBetween(y, X.heightRanges.first.a, X.heightRanges.first.b) | |
&& !isBetween(y, X.heightRanges.second.a, X.heightRanges.second.b)) { | |
it = intersections.erase(it); | |
} | |
else { | |
X.height = y - X.heightRanges.first.a; | |
++it; | |
} | |
} | |
return intersections; | |
} | |
//=========================================== | |
// SpatialSystem::entitiesAlong3dRay | |
//=========================================== | |
vector<pIntersection_t> SpatialSystem::entitiesAlong3dRay(const Vec2f& ray, double camSpaceVAngle, | |
double distance) const { | |
const Camera& camera = sg.player->camera(); | |
double height = camera.height; | |
double vAngle = camSpaceVAngle + camera.vAngle; | |
Matrix matrix = camera.matrix().inverse(); | |
vector<pIntersection_t> intersections = entitiesAlong3dRay(getCurrentZone(), Point(0, 0), height, | |
ray, vAngle, matrix, distance); | |
return intersections; | |
} | |
//=========================================== | |
// entitiesInRadius_r | |
//=========================================== | |
static void entitiesInRadius_r(const CZone& searchZone, const CZone& zone, const Circle& circle, | |
double heightAboveFloor, set<entityId_t>& entities) { | |
const double MAX_VERTICAL_DISTANCE = 40.0; | |
for (auto it = searchZone.edges.begin(); it != searchZone.edges.end(); ++it) { | |
const CEdge& edge = **it; | |
if (overlapsCircle(circle, edge)) { | |
entities.insert(edge.entityId()); | |
entities.insert(searchZone.entityId()); | |
if (edge.kind == CSpatialKind::HARD_EDGE || edge.kind == CSpatialKind::SOFT_EDGE) { | |
for (auto jt = edge.vRects.begin(); jt != edge.vRects.end(); ++jt) { | |
const CVRect& vRect = **jt; | |
if (overlapsCircle(circle, edge.lseg, vRect)) { | |
assert(vRect.zone != nullptr); | |
double vRectFloorH = vRect.zone->floorHeight; | |
double y1 = zone.floorHeight + heightAboveFloor; | |
double y2 = vRectFloorH + vRect.pos.y + 0.5 * vRect.size.y; | |
if (fabs(y1 - y2) <= MAX_VERTICAL_DISTANCE) { | |
entities.insert(vRect.entityId()); | |
} | |
} | |
} | |
} | |
} | |
} | |
for (auto it = searchZone.vRects.begin(); it != searchZone.vRects.end(); ++it) { | |
const CVRect& vRect = **it; | |
if (overlapsCircle(circle, vRect)) { | |
assert(vRect.zone != nullptr); | |
double y1 = zone.floorHeight + heightAboveFloor; | |
double y2 = vRect.zone->floorHeight + vRect.y + 0.5 * vRect.size.y; | |
if (fabs(y1 - y2) <= MAX_VERTICAL_DISTANCE) { | |
entities.insert(vRect.entityId()); | |
} | |
} | |
} | |
for (auto it = searchZone.hRects.begin(); it != searchZone.hRects.end(); ++it) { | |
if (overlapsCircle(circle, **it)) { | |
entities.insert((*it)->entityId()); | |
} | |
} | |
for (auto it = searchZone.children.begin(); it != searchZone.children.end(); ++it) { | |
entitiesInRadius_r(**it, zone, circle, heightAboveFloor, entities); | |
} | |
} | |
//=========================================== | |
// SpatialSystem::entitiesInRadius | |
//=========================================== | |
set<entityId_t> SpatialSystem::entitiesInRadius(const CZone& zone, const Point& pos, double radius, | |
double heightAboveFloor) const { | |
set<entityId_t> entities; | |
Circle circle{pos, radius}; | |
entitiesInRadius_r(nthConstAncestor(getCurrentZone(), MAX_ANCESTOR_SEARCH), zone, circle, | |
heightAboveFloor, entities); | |
return entities; | |
} | |
//=========================================== | |
// SpatialSystem::update | |
//=========================================== | |
void SpatialSystem::update() { | |
buoyancy(); | |
gravity(); | |
} | |
//=========================================== | |
// SpatialSystem::hasComponent | |
//=========================================== | |
bool SpatialSystem::hasComponent(entityId_t entityId) const { | |
return m_components.find(entityId) != m_components.end(); | |
} | |
//=========================================== | |
// SpatialSystem::getComponent | |
//=========================================== | |
CSpatial& SpatialSystem::getComponent(entityId_t entityId) const { | |
return *GET_VALUE(m_components, entityId); | |
} | |
//=========================================== | |
// SpatialSystem::addComponent | |
//=========================================== | |
void SpatialSystem::addComponent(pComponent_t component) { | |
if (component->kind() != ComponentKind::C_SPATIAL) { | |
EXCEPTION("Component is not of kind C_SPATIAL"); | |
} | |
CSpatial* ptr = DYNAMIC_CAST<CSpatial*>(component.release()); | |
pCSpatial_t c(ptr); | |
if (c->parentId == -1) { | |
if (c->kind != CSpatialKind::ZONE) { | |
EXCEPTION("Component has no parent and is not root zone"); | |
} | |
if (sg.rootZone) { | |
EXCEPTION("Root zone already set"); | |
} | |
pCZone_t z(DYNAMIC_CAST<CZone*>(c.release())); | |
sg.rootZone = std::move(z); | |
m_components.clear(); | |
} | |
else { | |
auto it = m_components.find(c->parentId); | |
if (it == m_components.end()) { | |
EXCEPTION("Could not find parent component with id " << c->parentId); | |
} | |
CSpatial* parent = it->second; | |
assert(parent->entityId() == c->parentId); | |
addChildToComponent(*parent, std::move(c)); | |
} | |
m_entityChildren[ptr->entityId()]; | |
m_components.insert(make_pair(ptr->entityId(), ptr)); | |
} | |
//=========================================== | |
// SpatialSystem::isRoot | |
//=========================================== | |
bool SpatialSystem::isRoot(const CSpatial& c) const { | |
if (c.kind != CSpatialKind::ZONE) { | |
return false; | |
} | |
if (sg.rootZone == nullptr) { | |
return false; | |
} | |
const CZone* ptr = DYNAMIC_CAST<const CZone*>(&c); | |
return ptr == sg.rootZone.get(); | |
} | |
//=========================================== | |
// SpatialSystem::removeChildFromComponent | |
//=========================================== | |
bool SpatialSystem::removeChildFromComponent(CSpatial& parent, const CSpatial& child, | |
bool keepAlive) { | |
GET_VALUE(m_entityChildren, parent.entityId()).erase(const_cast<CSpatial*>(&child)); | |
switch (parent.kind) { | |
case CSpatialKind::ZONE: | |
return removeFromZone(sg, DYNAMIC_CAST<CZone&>(parent), child, keepAlive); | |
case CSpatialKind::HARD_EDGE: | |
return removeFromHardEdge(DYNAMIC_CAST<CHardEdge&>(parent), child, keepAlive); | |
default: | |
EXCEPTION("Cannot remove component of kind " << child.kind << " from component of kind " | |
<< parent.kind); | |
}; | |
} | |
//=========================================== | |
// SpatialSystem::removeEntity_r | |
//=========================================== | |
void SpatialSystem::removeEntity_r(entityId_t id) { | |
m_components.erase(id); | |
auto it = m_entityChildren.find(id); | |
if (it != m_entityChildren.end()) { | |
set<CSpatial*>& children = it->second; | |
for (CSpatial* child : children) { | |
removeEntity_r(child->entityId()); | |
} | |
} | |
m_entityChildren.erase(id); | |
} | |
//=========================================== | |
// SpatialSystem::removeEntity | |
//=========================================== | |
void SpatialSystem::removeEntity(entityId_t id) { | |
auto it = m_components.find(id); | |
if (it == m_components.end()) { | |
return; | |
} | |
CSpatial& c = *it->second; | |
auto jt = m_components.find(c.parentId); | |
if (jt != m_components.end()) { | |
CSpatial& parent = *jt->second; | |
removeChildFromComponent(parent, c); | |
} | |
removeEntity_r(id); | |
} | |
#include "raycast/damage_system.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
using std::vector; | |
using std::set; | |
using std::make_pair; | |
const double PENETRATION_DISTANCE = 0.01; | |
//=========================================== | |
// DamageSystem::addComponent | |
//=========================================== | |
void DamageSystem::addComponent(pComponent_t component) { | |
pCDamage_t c(dynamic_cast<CDamage*>(component.release())); | |
m_components.insert(make_pair(c->entityId(), std::move(c))); | |
} | |
//=========================================== | |
// DamageSystem::hasComponent | |
//=========================================== | |
bool DamageSystem::hasComponent(entityId_t entityId) const { | |
return m_components.find(entityId) != m_components.end(); | |
} | |
//=========================================== | |
// DamageSystem::getComponent | |
//=========================================== | |
CDamage& DamageSystem::getComponent(entityId_t entityId) const { | |
return *m_components.at(entityId); | |
} | |
//=========================================== | |
// DamageSystem::removeEntity | |
//=========================================== | |
void DamageSystem::removeEntity(entityId_t id) { | |
m_components.erase(id); | |
} | |
//=========================================== | |
// fireDamagedEvents | |
//=========================================== | |
static void fireDamagedEvents(const EntityManager& entityManager, entityId_t id, int health, | |
int prevHealth, const Point* pt_rel = nullptr, const Point* pt_wld = nullptr) { | |
EEntityDamaged damaged(id, health, prevHealth); | |
if (pt_rel != nullptr) { | |
damaged.point_rel = *pt_rel; | |
} | |
if (pt_wld != nullptr) { | |
damaged.point_wld = *pt_wld; | |
} | |
entityManager.fireEvent(damaged, {id}); | |
entityManager.broadcastEvent(damaged); | |
} | |
//=========================================== | |
// fireDestroyedEvents | |
//=========================================== | |
static void fireDestroyedEvents(const EntityManager& entityManager, entityId_t id, | |
const Point* pt_rel = nullptr, const Point* pt_wld = nullptr) { | |
EEntityDestroyed destroyed(id); | |
if (pt_rel != nullptr) { | |
destroyed.point_rel = *pt_rel; | |
} | |
if (pt_wld != nullptr) { | |
destroyed.point_wld = *pt_wld; | |
} | |
entityManager.fireEvent(destroyed, {id}); | |
entityManager.broadcastEvent(EEntityDestroyed(id)); | |
} | |
//=========================================== | |
// DamageSystem::damageEntity | |
//=========================================== | |
void DamageSystem::damageEntity(entityId_t entityId, int damage) { | |
const SpatialSystem& spatialSystem = | |
m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
set<entityId_t> entities = spatialSystem.getAncestors(entityId); | |
entities.insert(entityId); | |
for (entityId_t id : entities) { | |
auto it = m_components.find(id); | |
if (it != m_components.end()) { | |
DBG_PRINT("Damaging entity " << id << "\n"); | |
CDamage& component = *it->second; | |
if (component.health > 0 || damage < 0) { | |
int prevHealth = component.health; | |
component.health -= damage; | |
if (component.health < 0) { | |
component.health = 0; | |
} | |
if (component.health > component.maxHealth) { | |
component.health = component.maxHealth; | |
} | |
fireDamagedEvents(m_entityManager, id, component.health, prevHealth); | |
if (component.health == 0) { | |
fireDestroyedEvents(m_entityManager, id); | |
} | |
} | |
} | |
} | |
} | |
//=========================================== | |
// DamageSystem::getHealth | |
//=========================================== | |
int DamageSystem::getHealth(entityId_t entityId) const { | |
return m_components.at(entityId)->health; | |
} | |
//=========================================== | |
// DamageSystem::getMaxHealth | |
//=========================================== | |
int DamageSystem::getMaxHealth(entityId_t entityId) const { | |
return m_components.at(entityId)->maxHealth; | |
} | |
//=========================================== | |
// DamageSystem::damageWithinRadius | |
//=========================================== | |
void DamageSystem::damageWithinRadius(const CZone& zone, const Point& pos, double radius, | |
int damage, AttenuationCurve) { | |
// TODO: Attenuation | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
set<entityId_t> entities = spatialSystem.entitiesInRadius(zone, pos, radius); | |
for (auto it = entities.begin(); it != entities.end(); ++it) { | |
damageEntity(*it, damage); | |
} | |
} | |
//=========================================== | |
// DamageSystem::damageAtIntersection_ | |
//=========================================== | |
void DamageSystem::damageAtIntersection_(const Intersection& X, int damage) { | |
const SpatialSystem& spatialSystem = | |
m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
set<entityId_t> entities = spatialSystem.getAncestors(X.entityId); | |
entities.insert(X.entityId); | |
for (entityId_t id : entities) { | |
auto it = m_components.find(id); | |
if (it != m_components.end()) { | |
CDamage& component = *it->second; | |
if (component.health > 0) { | |
int prevHealth = component.health; | |
component.health -= damage; | |
if (component.health < 0) { | |
component.health = 0; | |
} | |
Point pt_rel(X.distanceAlongTarget, X.height); | |
fireDamagedEvents(m_entityManager, id, component.health, prevHealth, &pt_rel, &X.point_wld); | |
if (component.health == 0) { | |
Point pt_rel(X.distanceAlongTarget, X.height); | |
fireDestroyedEvents(m_entityManager, id, &pt_rel, &X.point_wld); | |
} | |
} | |
} | |
} | |
} | |
//=========================================== | |
// DamageSystem::damageAtIntersection | |
//=========================================== | |
void DamageSystem::damageAtIntersection(const Vec2f& ray, double camSpaceVAngle, int damage) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
vector<pIntersection_t> intersections = spatialSystem.entitiesAlong3dRay(ray, camSpaceVAngle); | |
if (intersections.size() > 0) { | |
double dist = intersections.front()->distanceFromOrigin; | |
for (auto& i : intersections) { | |
if (i->distanceFromOrigin <= dist + PENETRATION_DISTANCE) { | |
damageAtIntersection_(*i, damage); | |
} | |
else { | |
break; | |
} | |
} | |
} | |
} | |
//=========================================== | |
// DamageSystem::damageAtIntersection | |
//=========================================== | |
void DamageSystem::damageAtIntersection(const CZone& zone, const Point& pos, double height, | |
const Vec2f& dir, double vAngle, const Matrix& matrix, int damage) { | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
vector<pIntersection_t> intersections = spatialSystem.entitiesAlong3dRay(zone, pos, height, dir, | |
vAngle, matrix); | |
if (intersections.size() > 0) { | |
double dist = intersections.front()->distanceFromOrigin; | |
for (auto& i : intersections) { | |
if (i->distanceFromOrigin <= dist + PENETRATION_DISTANCE) { | |
damageAtIntersection_(*i, damage); | |
} | |
else { | |
break; | |
} | |
} | |
} | |
} | |
#include <iostream> | |
#include <memory> | |
#include <thread> | |
#include <QDir> | |
#include "application.hpp" | |
#include "exception.hpp" | |
#include "app_config.hpp" | |
#include "f_main_spec_factory.hpp" | |
#include "event_system.hpp" | |
#include "update_loop.hpp" | |
#include "platform.hpp" | |
#include "utils.hpp" | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_main_spec.hpp" | |
using std::cerr; | |
using std::unique_ptr; | |
//=========================================== | |
// main | |
//=========================================== | |
int main(int argc, char** argv) { | |
try { | |
Application app(argc, argv); | |
#ifdef __APPLE__ | |
QDir dir(QCoreApplication::applicationDirPath()); | |
dir.cdUp(); | |
dir.cd("Plugins"); | |
QCoreApplication::setLibraryPaths(QStringList(dir.absolutePath())); | |
#endif | |
AppConfig appConfig{argc, argv}; | |
DBG_PRINT("App version: " << appConfig.version << "\n"); | |
DBG_PRINT("App state: " << appConfig.stateId << "\n"); | |
DBG_PRINT("Hardware concurrency: " << std::thread::hardware_concurrency() << "\n"); | |
std::shared_ptr<EventSystem> eventSystem{new EventSystem}; | |
UpdateLoop updateLoop(50); | |
unique_ptr<FMainSpec> mainSpec(makeFMainSpec(appConfig)); | |
FMain mainFragment({appConfig, *eventSystem, updateLoop}); | |
mainFragment.rebuild(*mainSpec, false); | |
mainFragment.show(); | |
EventHandle hQuit = eventSystem->listen("quit", [](const Event&) { | |
QApplication::exit(0); | |
}); | |
EventHandle hSetConfigParam = eventSystem->listen("setConfigParam", [&](const Event& e_) { | |
auto& e = dynamic_cast<const SetConfigParamEvent&>(e_); | |
appConfig.setParam(e.name, e.value); | |
}); | |
EventHandle hStateChange = eventSystem->listen("requestStateChange", [&](const Event& e_) { | |
const RequestStateChangeEvent& e = dynamic_cast<const RequestStateChangeEvent&>(e_); | |
appConfig.stateId = e.stateId; | |
updateLoop.finishAll(); | |
app.processEvents(); | |
mainSpec.reset(makeFMainSpec(appConfig)); | |
mainFragment.rebuild(*mainSpec, e.hardReset); | |
}); | |
int code = app.exec(); | |
appConfig.persistState(); | |
return code; | |
} | |
catch (std::exception& e) { | |
cerr << "Encountered fatal error; " << e.what() << "\n"; | |
return EXIT_FAILURE; | |
} | |
catch (...) { | |
cerr << "Encountered fatal error; Cause unknown\n"; | |
return EXIT_FAILURE; | |
} | |
} | |
#include <random> | |
#include <QWidget> | |
#include <QImage> | |
#include "effects.hpp" | |
#include "update_loop.hpp" | |
#include "exception.hpp" | |
using std::function; | |
static std::mt19937 randEngine; | |
//=========================================== | |
// mod | |
//=========================================== | |
inline static int mod(int n, int quot) { | |
if (n < 1) { | |
return mod(quot + n, quot); | |
} | |
else { | |
return n % quot; | |
} | |
} | |
//=========================================== | |
// rotateHue | |
//=========================================== | |
void rotateHue(QImage& img, int deg) { | |
for (int i = 0; i < img.width(); ++i) { | |
for (int j = 0; j < img.height(); ++j) { | |
QColor c = img.pixel(i, j); | |
c.setHsv((c.hue() + deg) % 360, c.saturation(), c.value()); | |
img.setPixel(i, j, c.rgb()); | |
} | |
} | |
} | |
//=========================================== | |
// colourize | |
//=========================================== | |
void colourize(QImage& img, const QColor& c, double x) { | |
int w = img.width(); | |
int h = img.height(); | |
for (int j = 0; j < h; ++j) { | |
for (int i = 0; i < w; ++i) { | |
img.setPixel(i, j, tweenColour(img.pixel(i, j), c, x).rgb()); | |
} | |
} | |
} | |
//=========================================== | |
// tweenColour | |
// | |
// Ignores alpha | |
//=========================================== | |
QColor tweenColour(const QColor& a, const QColor& b, double i) { | |
if (i < 0.0 || i > 1.0) { | |
EXCEPTION("Tween val of " << i << " is out of range"); | |
} | |
return QColor(a.red() + (b.red() - a.red()) * i, | |
a.green() + (b.green() - a.green()) * i, | |
a.blue() + (b.blue() - a.blue()) * i); | |
} | |
//=========================================== | |
// setColour | |
//=========================================== | |
void setColour(QWidget& widget, const QColor& colour, QPalette::ColorRole colourRole) { | |
QPalette palette = widget.palette(); | |
palette.setColor(colourRole, colour); | |
widget.setPalette(palette); | |
} | |
//=========================================== | |
// transitionColour | |
//=========================================== | |
void transitionColour(UpdateLoop& updateLoop, QWidget& widget, const QColor& colour, | |
QPalette::ColorRole colourRole, double duration, function<void()> fnOnFinish) { | |
QColor origCol = widget.palette().color(colourRole); | |
int frames = updateLoop.fps() * duration; | |
int i = 0; | |
updateLoop.add([=,&widget]() mutable { | |
double f = static_cast<double>(i) / static_cast<double>(frames); | |
QColor c = tweenColour(origCol, colour, f); | |
setColour(widget, c, colourRole); | |
++i; | |
return i <= frames; | |
}, fnOnFinish); | |
} | |
//=========================================== | |
// setBackgroundImage | |
//=========================================== | |
void setBackgroundImage(QWidget& widget, const QString& path) { | |
QPixmap bg(path); | |
bg = bg.scaled(widget.size(), Qt::IgnoreAspectRatio); | |
QPalette palette = widget.palette(); | |
palette.setBrush(QPalette::Background, bg); | |
widget.setPalette(palette); | |
} | |
//=========================================== | |
// garbleImage | |
//=========================================== | |
void garbleImage(const QImage& src, QImage& dest) { | |
if (src.size() != dest.size()) { | |
EXCEPTION("Source and destination images must be of same size"); | |
} | |
int w = dest.width(); | |
int h = dest.height(); | |
const double prob = 0.03; | |
std::uniform_int_distribution<int> rollDie(0, 1.0 / prob); | |
std::normal_distribution<double> randShift(0, 10); | |
int shift = randShift(randEngine); | |
for (int j = 0; j < h; ++j) { | |
if (rollDie(randEngine) == 0) { | |
shift = randShift(randEngine); | |
} | |
for (int i = 0; i < w; ++i) { | |
dest.setPixel(i, j, src.pixel(mod(i + shift, w), j)); | |
} | |
} | |
std::uniform_int_distribution<int> randHue(0, 359); | |
colourize(dest, QColor(255, 0, 0), 0.08); | |
rotateHue(dest, randHue(randEngine)); | |
} | |
#include <map> | |
#include <sstream> | |
#include <iomanip> | |
#include <cassert> | |
#include "calculator.hpp" | |
#include "exception.hpp" | |
using std::string; | |
using std::stringstream; | |
const std::map<Calculator::OpStack::operator_t, int> Calculator::OpStack::PRECEDENCE { | |
{ Calculator::OpStack::OP_NONE, 0 }, | |
{ Calculator::OpStack::OP_PLUS, 1 }, | |
{ Calculator::OpStack::OP_MINUS, 1 }, | |
{ Calculator::OpStack::OP_TIMES, 2 }, | |
{ Calculator::OpStack::OP_DIVIDE, 3 } | |
}; | |
//=========================================== | |
// formatNumber | |
//=========================================== | |
static string formatNumber(double num) { | |
stringstream ss; | |
ss << std::fixed << std::setprecision(8) << num; | |
string str = ss.str(); | |
if (str.find('.') != string::npos) { | |
while (str.back() == '0') { | |
str.pop_back(); | |
} | |
if (str.back() == '.') { | |
str.pop_back(); | |
} | |
} | |
return str; | |
} | |
//=========================================== | |
// Calculator::OpStack::dbg_print | |
//=========================================== | |
#ifdef DEBUG | |
void Calculator::OpStack::dbg_print(std::ostream& os) const { | |
std::stack<Expr> stack = m_stack; | |
os << "OpStack\n"; | |
while (!stack.empty()) { | |
Expr e = stack.top(); | |
stack.pop(); | |
os << e.lhs << ", " << e.op << "\n"; | |
} | |
} | |
#endif | |
//=========================================== | |
// Calculator::OpStack::apply | |
//=========================================== | |
double Calculator::OpStack::apply(Expr expr, double rhs) const { | |
switch (expr.op) { | |
case OP_PLUS: return expr.lhs + rhs; | |
case OP_MINUS: return expr.lhs - rhs; | |
case OP_TIMES: return expr.lhs * rhs; | |
case OP_DIVIDE: return expr.lhs / rhs; | |
default: EXCEPTION("Unrecognised operator"); | |
} | |
} | |
//=========================================== | |
// Calculator::OpStack::putValue | |
//=========================================== | |
void Calculator::OpStack::putValue(double val) { | |
if (m_stack.size() > 0 && m_stack.top().op == OP_NONE) { | |
m_stack.top().lhs = val; | |
} | |
else { | |
m_stack.push(Expr{val, OP_NONE}); | |
} | |
} | |
//=========================================== | |
// Calculator::OpStack::putOperator | |
//=========================================== | |
void Calculator::OpStack::putOperator(operator_t op) { | |
if (m_stack.empty()) { | |
m_stack.push(Expr{0, OP_NONE}); | |
} | |
m_stack.top().op = op; | |
collapseStack(); | |
} | |
//=========================================== | |
// Calculator::OpStack::collapseStack | |
//=========================================== | |
void Calculator::OpStack::collapseStack() { | |
while (m_stack.size() > 1) { | |
Expr curr = m_stack.top(); | |
m_stack.pop(); | |
Expr prev = m_stack.top(); | |
m_stack.push(curr); | |
if (PRECEDENCE.at(curr.op) <= PRECEDENCE.at(prev.op)) { | |
m_stack.pop(); | |
m_stack.pop(); | |
double val = apply(prev, curr.lhs); | |
m_stack.push(Expr{val, curr.op}); | |
} | |
else { | |
break; | |
} | |
} | |
} | |
//=========================================== | |
// Calculator::OpStack::evaluate | |
//=========================================== | |
double Calculator::OpStack::evaluate() { | |
if (m_stack.empty()) { | |
return 0; | |
} | |
m_stack.top().op = OP_NONE; | |
collapseStack(); | |
assert(m_stack.size() == 1); | |
return m_stack.top().lhs; | |
} | |
//=========================================== | |
// Calculator::OpStack::clear | |
//=========================================== | |
void Calculator::OpStack::clear() { | |
m_stack = std::stack<Expr>(); | |
} | |
//=========================================== | |
// Calculator::OpStack::op | |
//=========================================== | |
Calculator::OpStack::operator_t Calculator::OpStack::op() const { | |
return m_stack.size() > 0 ? m_stack.top().op : OP_NONE; | |
} | |
//=========================================== | |
// Calculator::number | |
//=========================================== | |
void Calculator::number(int n) { | |
if (n < 0 || n > 9) { | |
EXCEPTION("Calculator expects single digit"); | |
} | |
if (m_reset || m_opStack.op() != OpStack::OP_NONE) { | |
m_display = ""; | |
m_reset = false; | |
} | |
m_display.append(std::to_string(n).c_str()); | |
double value = std::strtod(m_display.c_str(), nullptr); | |
m_opStack.putValue(value); | |
} | |
//=========================================== | |
// Calculator::point | |
//=========================================== | |
void Calculator::point() { | |
if (m_display.find('.') == string::npos) { | |
m_display.append("."); | |
} | |
} | |
//=========================================== | |
// Calculator::plus | |
//=========================================== | |
void Calculator::plus() { | |
m_opStack.putOperator(OpStack::OP_PLUS); | |
} | |
//=========================================== | |
// Calculator::times | |
//=========================================== | |
void Calculator::times() { | |
m_opStack.putOperator(OpStack::OP_TIMES); | |
} | |
//=========================================== | |
// Calculator::divide | |
//=========================================== | |
void Calculator::divide() { | |
m_opStack.putOperator(OpStack::OP_DIVIDE); | |
} | |
//=========================================== | |
// Calculator::minus | |
//=========================================== | |
void Calculator::minus() { | |
m_opStack.putOperator(OpStack::OP_MINUS); | |
} | |
//=========================================== | |
// Calculator::equals | |
//=========================================== | |
double Calculator::equals() { | |
double result = m_opStack.evaluate(); | |
m_display = formatNumber(result).c_str(); | |
m_reset = true; | |
return result; | |
} | |
//=========================================== | |
// Calculator::clear | |
//=========================================== | |
void Calculator::clear() { | |
m_opStack.clear(); | |
m_display = ""; | |
} | |
//=========================================== | |
// Calculator::display | |
//=========================================== | |
const std::string& Calculator::display() const { | |
return m_display; | |
} | |
#include "fragment_factory.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch.hpp" | |
#include "fragments/relocatable/f_tetrominos/f_tetrominos.hpp" | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_countdown_to_start/f_countdown_to_start.hpp" | |
#include "fragments/f_main/f_login_screen/f_login_screen.hpp" | |
#include "fragments/f_main/f_desktop/f_desktop.hpp" | |
#include "fragments/f_main/f_desktop/f_server_room_init/f_server_room_init.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_settings_dialog.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_loading_screen/f_loading_screen.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/f_config_maze.hpp" | |
#include "fragments/f_main/f_troubleshooter_dialog/f_troubleshooter_dialog.hpp" | |
#include "fragments/f_main/f_app_dialog/f_app_dialog.hpp" | |
#include "fragments/f_main/f_app_dialog/f_mail_client/f_mail_client.hpp" | |
#include "fragments/f_main/f_app_dialog/f_server_room/f_server_room.hpp" | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/f_procalc_setup.hpp" | |
#include "fragments/f_main/f_app_dialog/f_text_editor/f_text_editor.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system/f_file_system.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/f_file_system_2.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/f_minesweeper.hpp" | |
#include "fragments/f_main/f_app_dialog/f_console/f_console.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/f_doomsweeper.hpp" | |
#include "fragments/f_main/f_maze_3d/f_maze_3d.hpp" | |
#include "fragments/relocatable/f_calculator/f_calculator.hpp" | |
#include "fragments/relocatable/f_calculator/f_normal_calc_trigger/f_normal_calc_trigger.hpp" | |
#include "fragments/relocatable/f_calculator/f_partial_calc/f_partial_calc.hpp" | |
#include "fragments/f_main/f_shuffled_calc/f_shuffled_calc.hpp" | |
using std::string; | |
//=========================================== | |
// constructFragment | |
//=========================================== | |
Fragment* constructFragment(const string& name, Fragment& parent, FragmentData& parentData, | |
const CommonFragData& commonData) { | |
DBG_PRINT("constructFragment(), name=" << name << "\n"); | |
if (name == "FGlitch") { | |
return new FGlitch(parent, parentData, commonData); | |
} | |
else if (name == "FCalculator") { | |
return new FCalculator(parent, parentData, commonData); | |
} | |
else if (name == "FNormalCalcTrigger") { | |
return new FNormalCalcTrigger(parent, parentData, commonData); | |
} | |
else if (name == "FPartialCalc") { | |
return new FPartialCalc(parent, parentData, commonData); | |
} | |
else if (name == "FShuffledCalc") { | |
return new FShuffledCalc(parent, parentData, commonData); | |
} | |
else if (name == "FCountdownToStart") { | |
return new FCountdownToStart(parent, parentData, commonData); | |
} | |
else if (name == "FSettingsDialog") { | |
return new FSettingsDialog(parent, parentData, commonData); | |
} | |
else if (name == "FLoadingScreen") { | |
return new FLoadingScreen(parent, parentData, commonData); | |
} | |
else if (name == "FLoginScreen") { | |
return new FLoginScreen(parent, parentData, commonData); | |
} | |
else if (name == "FDesktop") { | |
return new FDesktop(parent, parentData, commonData); | |
} | |
else if (name == "FServerRoomInit") { | |
return new FServerRoomInit(parent, parentData, commonData); | |
} | |
else if (name == "FMaze3d") { | |
return new FMaze3d(parent, parentData, commonData); | |
} | |
else if (name == "FTroubleshooterDialog") { | |
return new FTroubleshooterDialog(parent, parentData, commonData); | |
} | |
else if (name == "FAppDialog") { | |
return new FAppDialog(parent, parentData, commonData); | |
} | |
else if (name == "FMailClient") { | |
return new FMailClient(parent, parentData, commonData); | |
} | |
else if (name == "FServerRoom") { | |
return new FServerRoom(parent, parentData, commonData); | |
} | |
else if (name == "FFileSystem") { | |
return new FFileSystem(parent, parentData, commonData); | |
} | |
else if (name == "FFileSystem2") { | |
return new FFileSystem2(parent, parentData, commonData); | |
} | |
else if (name == "FMinesweeper") { | |
return new FMinesweeper(parent, parentData, commonData); | |
} | |
else if (name == "FConsole") { | |
return new FConsole(parent, parentData, commonData); | |
} | |
else if (name == "FDoomsweeper") { | |
return new FDoomsweeper(parent, parentData, commonData); | |
} | |
else if (name == "FProcalcSetup") { | |
return new FProcalcSetup(parent, parentData, commonData); | |
} | |
else if (name == "FTextEditor") { | |
return new FTextEditor(parent, parentData, commonData); | |
} | |
else if (name == "FConfigMaze") { | |
return new FConfigMaze(parent, parentData, commonData); | |
} | |
else if (name == "FTetrominos") { | |
return new FTetrominos(parent, parentData, commonData); | |
} | |
EXCEPTION("Cannot construct fragment with unrecognised name '" << name << "'\n"); | |
} | |
#include <sstream> | |
#include "console_widget.hpp" | |
#include "app_config.hpp" | |
using std::vector; | |
using std::string; | |
using std::stringstream; | |
using std::istream_iterator; | |
//=========================================== | |
// ConsoleWidget::ConsoleWidget | |
//=========================================== | |
ConsoleWidget::ConsoleWidget(const AppConfig& appConfig, const string& initialContent, | |
vector<string> initialHistory) | |
: QPlainTextEdit(nullptr), | |
m_commandHistory(initialHistory.begin(), initialHistory.end()) { | |
setMinimumWidth(330); | |
QPalette p = palette(); | |
p.setColor(QPalette::Base, Qt::black); | |
p.setColor(QPalette::Text, Qt::white); | |
setPalette(p); | |
QFont font = appConfig.monoFont; | |
font.setPixelSize(12); | |
document()->setDefaultFont(font); | |
insertPlainText(initialContent.c_str()); | |
m_commandPos = textCursor().position(); | |
} | |
//=========================================== | |
// ConsoleWidget::addCommand | |
//=========================================== | |
void ConsoleWidget::addCommand(const string& name, const ConsoleWidget::CommandFn& fn) { | |
m_commandFns[name] = fn; | |
} | |
//=========================================== | |
// ConsoleWidget::executeCommand | |
//=========================================== | |
string ConsoleWidget::executeCommand(const string& commandString) { | |
string output; | |
stringstream ss(commandString); | |
ArgList vec{istream_iterator<string>(ss), istream_iterator<string>{}}; | |
if (vec.size() > 0) { | |
m_commandHistory.push_front(commandString); | |
const string& cmd = vec[0]; | |
ArgList args(++vec.begin(), vec.end()); | |
auto it = m_commandFns.find(cmd); | |
if (it != m_commandFns.end()) { | |
output = it->second(args); | |
} | |
else { | |
output = "Unknown command"; | |
} | |
} | |
return output; | |
} | |
//=========================================== | |
// ConsoleWidget::cursorToEnd | |
//=========================================== | |
void ConsoleWidget::cursorToEnd() { | |
QTextCursor cursor = textCursor(); | |
cursor.movePosition(QTextCursor::End); | |
setTextCursor(cursor); | |
} | |
//=========================================== | |
// ConsoleWidget::applyCommand | |
//=========================================== | |
void ConsoleWidget::applyCommand() { | |
cursorToEnd(); | |
string output = executeCommand(m_buffer.toPlainText().toStdString()); | |
insertPlainText("\n"); | |
insertPlainText(output.c_str()); | |
insertPlainText("\n> "); | |
m_commandPos = textCursor().position(); | |
m_buffer.clear(); | |
} | |
//=========================================== | |
// ConsoleWidget::resetCursorPos | |
//=========================================== | |
void ConsoleWidget::resetCursorPos() { | |
QTextCursor cursor = textCursor(); | |
cursor.setPosition(m_commandPos + m_buffer.textCursor().position()); | |
setTextCursor(cursor); | |
} | |
//=========================================== | |
// ConsoleWidget::syncCommandText | |
//=========================================== | |
void ConsoleWidget::syncCommandText() { | |
QTextCursor cursor = textCursor(); | |
cursor.setPosition(m_commandPos); | |
while (!cursor.atEnd()) { | |
cursor.deleteChar(); | |
} | |
QString str = m_buffer.toPlainText(); | |
insertPlainText(str); | |
} | |
//=========================================== | |
// ConsoleWidget::keyPressEvent | |
//=========================================== | |
void ConsoleWidget::keyPressEvent(QKeyEvent* event) { | |
resetCursorPos(); | |
switch (event->key()) { | |
case Qt::Key_Return: | |
applyCommand(); | |
m_historyIdx = -1; | |
break; | |
case Qt::Key_Up: | |
if (m_historyIdx < static_cast<int>(m_commandHistory.size()) - 1) { | |
m_buffer.clear(); | |
m_buffer.insertPlainText(m_commandHistory[++m_historyIdx].c_str()); | |
syncCommandText(); | |
} | |
break; | |
case Qt::Key_Down: | |
if (m_historyIdx > 0) { | |
m_buffer.clear(); | |
m_buffer.insertPlainText(m_commandHistory[--m_historyIdx].c_str()); | |
} | |
else { | |
m_historyIdx = -1; | |
m_buffer.clear(); | |
} | |
syncCommandText(); | |
break; | |
default: { | |
m_buffer.keyPressEvent(event); | |
m_historyIdx = -1; | |
syncCommandText(); | |
} | |
} | |
resetCursorPos(); | |
} | |
#include <stdexcept> | |
#include <iostream> | |
#include "application.hpp" | |
using namespace std; | |
//=========================================== | |
// Application::notify | |
//=========================================== | |
bool Application::notify(QObject* receiver, QEvent* event) { | |
try { | |
return QApplication::notify(receiver, event); | |
} | |
catch (exception& e) { | |
cerr << "Encountered fatal error: " << e.what() << "\n"; | |
exit(EXIT_FAILURE); | |
} | |
catch (...) { | |
cerr << "Encountered fatal error: Cause unknown\n"; | |
exit(EXIT_FAILURE); | |
} | |
return false; | |
} | |
#include <sstream> | |
#include "exception.hpp" | |
using std::runtime_error; | |
using std::string; | |
using std::stringstream; | |
//=========================================== | |
// Exception::Exception | |
//=========================================== | |
Exception::Exception(const string& msg) | |
: runtime_error(msg), m_msg(msg) {} | |
//=========================================== | |
// Exception::Exception | |
//=========================================== | |
Exception::Exception(const string& msg, const char* file, int line) | |
: runtime_error(msg), m_msg(msg), m_file(file), m_line(line) {} | |
//=========================================== | |
// Exception::what | |
//=========================================== | |
const char* Exception::what() const throw() { | |
static string msg; | |
stringstream ss; | |
ss << m_msg << " (FILE: " << m_file << ", LINE: " << m_line << ")"; | |
msg = ss.str(); | |
return msg.data(); | |
} | |
//=========================================== | |
// Exception::append | |
//=========================================== | |
void Exception::append(const string& text) throw() { | |
m_msg.append(text); | |
} | |
//=========================================== | |
// Exception::prepend | |
//=========================================== | |
void Exception::prepend(const string& text) throw() { | |
m_msg.insert(0, text); | |
} | |
//=========================================== | |
// Exception::~Exception | |
//=========================================== | |
Exception::~Exception() throw() {} | |
#include "update_loop.hpp" | |
using std::weak_ptr; | |
using std::function; | |
//=========================================== | |
// UpdateLoop::UpdateLoop | |
//=========================================== | |
UpdateLoop::UpdateLoop(int interval) | |
: m_interval(interval) { | |
m_timer = makeQtObjPtr<QTimer>(); | |
connect(m_timer.get(), SIGNAL(timeout()), this, SLOT(tick())); | |
} | |
//=========================================== | |
// UpdateLoop::addFunction | |
//=========================================== | |
void UpdateLoop::add(function<bool()> fn, function<void()> fnOnFinish) { | |
m_functions.push_back(FuncPair{fn, fnOnFinish}); | |
if (!m_timer->isActive()) { | |
m_timer->start(m_interval); | |
} | |
} | |
//=========================================== | |
// UpdateLoop::finishAll | |
//=========================================== | |
void UpdateLoop::finishAll() { | |
m_timer->stop(); | |
} | |
//=========================================== | |
// UpdateLoop::size | |
//=========================================== | |
int UpdateLoop::size() const { | |
return static_cast<int>(m_functions.size()); | |
} | |
//=========================================== | |
// UpdateLoop::fps | |
//=========================================== | |
double UpdateLoop::fps() const { | |
return 1000.0 / static_cast<double>(m_interval); | |
} | |
//=========================================== | |
// UpdateLoop::tick | |
//=========================================== | |
void UpdateLoop::tick() { | |
auto it = m_functions.begin(); | |
while (it != m_functions.end()) { | |
bool result = false; | |
if (m_timer->isActive()) { | |
result = it->fnPeriodic(); | |
} | |
if (!result) { | |
it->fnFinish(); | |
m_functions.erase(it++); | |
} | |
else { | |
++it; | |
} | |
} | |
if (m_functions.empty()) { | |
m_timer->stop(); | |
} | |
} | |
#include <QButtonGroup> | |
#include <QPushButton> | |
#include "calculator_widget.hpp" | |
#include "event_system.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// CalculatorWidget::CalculatorWidget | |
//=========================================== | |
CalculatorWidget::CalculatorWidget(EventSystem& eventSystem) | |
: QWidget(nullptr), | |
m_eventSystem(eventSystem) { | |
QFont f = font(); | |
f.setPixelSize(18); | |
setFont(f); | |
wgtDigitDisplay = makeQtObjPtr<QLineEdit>(this); | |
wgtDigitDisplay->setMaximumHeight(40); | |
wgtDigitDisplay->setAlignment(Qt::AlignRight); | |
wgtDigitDisplay->setReadOnly(true); | |
wgtButtonGrid = makeQtObjPtr<ButtonGrid>(this); | |
wgtOpDisplay = makeQtObjPtr<QLabel>(this); | |
wgtOpDisplay->setFixedHeight(18); | |
wgtOpDisplay->setAlignment(Qt::AlignRight | Qt::AlignVCenter); | |
if (vbox) { | |
delete vbox.release(); | |
} | |
vbox = makeQtObjPtr<QVBoxLayout>(); | |
vbox->setSpacing(0); | |
vbox->addWidget(wgtDigitDisplay.get()); | |
vbox->addWidget(wgtOpDisplay.get()); | |
vbox->addWidget(wgtButtonGrid.get()); | |
setLayout(vbox.get()); | |
connect(wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), this, SLOT(onButtonClick(int))); | |
} | |
//=========================================== | |
// CalculatorWidget::onButtonClick | |
//=========================================== | |
void CalculatorWidget::onButtonClick(int id) { | |
wgtOpDisplay->setText(""); | |
if (id <= 9) { | |
calculator.number(id); | |
} | |
else { | |
auto btn = dynamic_cast<const QPushButton*>(wgtButtonGrid->buttonGroup->button(id)); | |
QString symbol = btn->text(); | |
switch (id) { | |
case BTN_PLUS: | |
calculator.plus(); | |
wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_MINUS: | |
calculator.minus(); | |
wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_TIMES: | |
calculator.times(); | |
wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_DIVIDE: | |
calculator.divide(); | |
wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_POINT: | |
calculator.point(); | |
break; | |
case BTN_EQUALS: { | |
calculator.equals(); | |
break; | |
} | |
case BTN_CLEAR: | |
calculator.clear(); | |
break; | |
} | |
} | |
wgtDigitDisplay->setText(calculator.display().c_str()); | |
m_eventSystem.fire(pEvent_t(new CalculatorButtonPressEvent(id, calculator))); | |
} | |
//=========================================== | |
// CalculatorWidget::~CalculatorWidget | |
//=========================================== | |
CalculatorWidget::~CalculatorWidget() {} | |
#include <QMenuBar> | |
#include <QMessageBox> | |
#include <QApplication> | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_main_spec.hpp" | |
#include "utils.hpp" | |
#include "exception.hpp" | |
#include "effects.hpp" | |
#include "event_system.hpp" | |
using std::string; | |
//=========================================== | |
// FMain::FMain | |
//=========================================== | |
FMain::FMain(const CommonFragData& commonData) | |
: QMainWindow(nullptr), | |
Fragment("FMain", m_data, commonData) { | |
DBG_PRINT("FMain::FMain\n"); | |
setWindowTitle("Pro Office Calculator"); | |
m_data.wgtCentral = makeQtObjPtr<QWidget>(); | |
setCentralWidget(m_data.wgtCentral.get()); | |
m_data.wgtCentral->setLayout(m_data.box.get()); | |
menuBar()->setNativeMenuBar(false); | |
m_data.mnuFile = makeQtObjPtrFromRawPtr(menuBar()->addMenu("File")); | |
m_data.actQuit = makeQtObjPtr<QAction>("Quit", this); | |
m_data.mnuFile->addAction(m_data.actQuit.get()); | |
m_data.mnuHelp = makeQtObjPtrFromRawPtr(menuBar()->addMenu("Help")); | |
m_data.actAbout = makeQtObjPtr<QAction>("About", this); | |
m_data.mnuHelp->addAction(m_data.actAbout.get()); | |
connect(m_data.actAbout.get(), SIGNAL(triggered()), this, SLOT(showAbout())); | |
connect(m_data.actQuit.get(), SIGNAL(triggered()), this, SLOT(close())); | |
} | |
//=========================================== | |
// FMain::reload | |
//=========================================== | |
void FMain::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FMain::reload\n"); | |
auto& spec = dynamic_cast<const FMainSpec&>(spec_); | |
setFixedSize(spec.width, spec.height); | |
m_data.mnuFile->setTitle(spec.fileLabel); | |
m_data.actQuit->setText(spec.quitLabel); | |
setColour(*this, spec.bgColour, QPalette::Window); | |
setWindowTitle(spec.windowTitle); | |
if (spec.backgroundImage.length() > 0) { | |
setStyleSheet(QString("") + | |
"QMainWindow {" | |
" background-image: url(\"" + spec.backgroundImage + "\");" | |
"}"); | |
} | |
else { | |
setStyleSheet(""); | |
} | |
m_aboutDialogTitle = spec.aboutDialogTitle; | |
m_aboutDialogText = spec.aboutDialogText; | |
m_data.mnuHelp->setTitle(spec.helpLabel); | |
m_data.actAbout->setText(spec.aboutLabel); | |
} | |
//=========================================== | |
// FMain::showAbout | |
//=========================================== | |
void FMain::showAbout() { | |
QMessageBox msgBox(this); | |
msgBox.setTextFormat(Qt::RichText); | |
msgBox.setWindowTitle(m_aboutDialogTitle); | |
msgBox.setText(m_aboutDialogText); | |
msgBox.exec(); | |
} | |
//=========================================== | |
// FMain::closeEvent | |
//=========================================== | |
void FMain::closeEvent(QCloseEvent*) { | |
m_data.fnOnQuit(); | |
DBG_PRINT("Quitting\n"); | |
commonData.eventSystem.fire(pEvent_t(new Event("quit"))); | |
} | |
//=========================================== | |
// FMain::cleanUp | |
//=========================================== | |
void FMain::cleanUp() { | |
DBG_PRINT("FMain::cleanUp\n"); | |
} | |
//=========================================== | |
// FMain::~FMain | |
//=========================================== | |
FMain::~FMain() { | |
DBG_PRINT("FMain::~FMain()\n"); | |
} | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_maze_3d/f_maze_3d.hpp" | |
#include "fragments/f_main/f_maze_3d/f_maze_3d_spec.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
#include "event_system.hpp" | |
//=========================================== | |
// FMaze3d::FMaze3d | |
//=========================================== | |
FMaze3d::FMaze3d(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FMaze3d", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FMaze3d::FMaze3d\n"); | |
} | |
//=========================================== | |
// FMaze3d::reload | |
//=========================================== | |
void FMaze3d::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FMaze3d::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
auto& spec = dynamic_cast<const FMaze3dSpec&>(spec_); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_data.vbox->setSpacing(0); | |
m_data.vbox->setContentsMargins(0, 0, 0, 0); | |
setLayout(m_data.vbox.get()); | |
m_data.wgtRaycast = makeQtObjPtr<RaycastWidget>(commonData.appConfig, commonData.eventSystem, | |
spec.width, spec.height, spec.frameRate); | |
m_data.vbox->addWidget(m_data.wgtRaycast.get()); | |
#if PROFILING_ON | |
double profileDuration = commonData.appConfig.getDoubleArg(1, -1); | |
DBG_PRINT("Profile duration = " << profileDuration << " seconds...\n"); | |
if (profileDuration > 0) { | |
const TimeService& timeService = m_data.wgtRaycast->timeService(); | |
double start = timeService.now(); | |
commonData.updateLoop.add([=, &timeService]() { | |
if (timeService.now() - start > profileDuration) { | |
commonData.eventSystem.fire(pEvent_t(new Event("quit"))); | |
return false; | |
} | |
return true; | |
}, []() {}); | |
} | |
#endif | |
m_data.wgtRaycast->initialise(spec.mapFile); | |
m_data.wgtRaycast->start(); | |
} | |
//=========================================== | |
// FMaze3d::cleanUp | |
//=========================================== | |
void FMaze3d::cleanUp() { | |
DBG_PRINT("FMaze3d::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FMaze3d::~FMaze3d | |
//=========================================== | |
FMaze3d::~FMaze3d() { | |
DBG_PRINT("FMaze3d::~FMaze3d\n"); | |
} | |
#include <algorithm> | |
#include <cassert> | |
#include <QPushButton> | |
#include <QButtonGroup> | |
#include <QPainter> | |
#include <vector> | |
#include "event_system.hpp" | |
#include "update_loop.hpp" | |
#include "effects.hpp" | |
#include "strings.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
#include "effects.hpp" | |
#include "fragments/f_main/f_shuffled_calc/f_shuffled_calc.hpp" | |
#include "fragments/f_main/f_shuffled_calc/f_shuffled_calc_spec.hpp" | |
#include "fragments/f_main/f_main.hpp" | |
using std::shuffle; | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// idToChar | |
//=========================================== | |
static QChar idToChar(int id) { | |
if (id < 10) { | |
return QString::number(id)[0]; | |
} | |
switch (id) { | |
case BTN_PLUS: return '+'; | |
case BTN_MINUS: return '-'; | |
case BTN_TIMES: return '*'; | |
case BTN_DIVIDE: return '/'; | |
case BTN_POINT: return '.'; | |
case BTN_CLEAR: return 'C'; | |
case BTN_EQUALS: return '='; | |
} | |
return '_'; | |
} | |
//=========================================== | |
// FShuffledCalc::FShuffledCalc | |
//=========================================== | |
FShuffledCalc::FShuffledCalc(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FShuffledCalc", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FShuffledCalc::FShuffledCalc\n"); | |
} | |
//=========================================== | |
// FShuffledCalc::reload | |
//=========================================== | |
void FShuffledCalc::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FShuffledCalc::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
QFont f = font(); | |
f.setPixelSize(18); | |
setFont(f); | |
m_wgtDigitDisplay = makeQtObjPtr<QLineEdit>(this); | |
m_wgtDigitDisplay->setMaximumHeight(40); | |
m_wgtDigitDisplay->setAlignment(Qt::AlignRight); | |
m_wgtDigitDisplay->setReadOnly(true); | |
m_wgtOpDisplay = makeQtObjPtr<QLabel>(this); | |
m_wgtOpDisplay->setFixedHeight(18); | |
m_wgtOpDisplay->setAlignment(Qt::AlignRight | Qt::AlignVCenter); | |
m_wgtButtonGrid = makeQtObjPtr<ButtonGrid>(this); | |
m_vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_vbox->setSpacing(0); | |
m_vbox->addWidget(m_wgtDigitDisplay.get()); | |
m_vbox->addWidget(m_wgtOpDisplay.get()); | |
m_vbox->addWidget(m_wgtButtonGrid.get()); | |
setLayout(m_vbox.get()); | |
connect(m_wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), this, SLOT(onButtonClick(int))); | |
auto& spec = dynamic_cast<const FShuffledCalcSpec&>(spec_); | |
m_targetValue = spec.targetValue; | |
ucs4string_t symbols_ = utf8ToUcs4(spec.symbols.toStdString()); | |
shuffle(symbols_.begin(), symbols_.end(), randEngine); | |
QString symbols(ucs4ToUtf8(symbols_).c_str()); | |
auto& buttons = m_wgtButtonGrid->buttons; | |
auto& group = m_wgtButtonGrid->buttonGroup; | |
assert(symbols.length() == static_cast<int>(buttons.size())); | |
std::vector<int> ids({ | |
BTN_ZERO, | |
BTN_ONE, | |
BTN_TWO, | |
BTN_THREE, | |
BTN_FOUR, | |
BTN_FIVE, | |
BTN_SIX, | |
BTN_SEVEN, | |
BTN_EIGHT, | |
BTN_NINE, | |
BTN_PLUS, | |
BTN_MINUS, | |
BTN_TIMES, | |
BTN_DIVIDE, | |
BTN_POINT, | |
BTN_CLEAR, | |
BTN_EQUALS | |
}); | |
std::vector<int> newIds = ids; | |
shuffle(newIds.begin(), newIds.end(), randEngine); | |
for (int i = 0; i < symbols.length(); ++i) { | |
group->setId(buttons[i].get(), newIds[i]); | |
QChar ch = symbols[i]; | |
buttons[i]->setText(ch); | |
m_symbols[idToChar(newIds[i])] = ch; | |
} | |
DBG_PRINT("Answer = " << translateToSymbols(m_targetValue.c_str()).toStdString() << "\n"); | |
setColour(*m_wgtDigitDisplay, spec.displayColour, QPalette::Base); | |
} | |
//=========================================== | |
// FShuffledCalc::cleanUp | |
//=========================================== | |
void FShuffledCalc::cleanUp() { | |
DBG_PRINT("FShuffledCalc::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FShuffledCalc::translateToSymbols | |
//=========================================== | |
QString FShuffledCalc::translateToSymbols(const QString& str) const { | |
QString result; | |
for (int i = 0; i < str.length(); ++i) { | |
auto it = m_symbols.find(str[i]); | |
result += (it == m_symbols.end() ? str[i] : it->second); | |
} | |
return result; | |
} | |
//=========================================== | |
// FShuffledCalc::onButtonClick | |
//=========================================== | |
void FShuffledCalc::onButtonClick(int id) { | |
m_wgtOpDisplay->setText(""); | |
if (id <= 9) { | |
m_calculator.number(id); | |
} | |
else { | |
auto btn = dynamic_cast<const QPushButton*>(m_wgtButtonGrid->buttonGroup->button(id)); | |
QString symbol = btn->text(); | |
switch (id) { | |
case BTN_PLUS: | |
m_calculator.plus(); | |
m_wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_MINUS: | |
m_calculator.minus(); | |
m_wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_TIMES: | |
m_calculator.times(); | |
m_wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_DIVIDE: | |
m_calculator.divide(); | |
m_wgtOpDisplay->setText(symbol); | |
break; | |
case BTN_POINT: | |
m_calculator.point(); | |
break; | |
case BTN_EQUALS: { | |
m_calculator.equals(); | |
break; | |
} | |
case BTN_CLEAR: | |
m_calculator.clear(); | |
break; | |
} | |
} | |
QString symbols = translateToSymbols(m_calculator.display().c_str()); | |
m_wgtDigitDisplay->setText(symbols); | |
if (m_calculator.display() == m_targetValue) { | |
auto& buttons = m_wgtButtonGrid->buttons; | |
int i = 0; | |
int n = commonData.updateLoop.fps() / 2; | |
commonData.updateLoop.add([&, i, n]() mutable { | |
for (auto it = buttons.begin(); it != buttons.end(); ++it) { | |
QSize sz = (*it)->size(); | |
(*it)->resize(sz * 0.8); | |
if (i == n - 1) { | |
(*it)->setVisible(false); | |
} | |
} | |
++i; | |
return i < n; | |
}, [&]() { | |
m_wgtDigitDisplay->setText(""); | |
int i = 0; | |
int n = commonData.updateLoop.fps() / 2; | |
commonData.updateLoop.add([&, i, n]() mutable { | |
int x = m_wgtDigitDisplay->geometry().x(); | |
int y = m_wgtDigitDisplay->geometry().y(); | |
m_wgtDigitDisplay->move(x, y + 30); | |
++i; | |
return i < n; | |
}, | |
[&]() { | |
commonData.eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_ARE_YOU_SURE))); | |
}); | |
}); | |
} | |
} | |
//=========================================== | |
// FShuffledCalc::~FShuffledCalc | |
//=========================================== | |
FShuffledCalc::~FShuffledCalc() { | |
DBG_PRINT("FShuffledCalc::~FShuffledCalc\n"); | |
} | |
#include <cassert> | |
#include <QMessageBox> | |
#include <QMenuBar> | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_troubleshooter_dialog/f_troubleshooter_dialog.hpp" | |
#include "fragments/f_main/f_troubleshooter_dialog/f_troubleshooter_dialog_spec.hpp" | |
#include "event_system.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using its_raining_tetrominos::GameLogic; | |
//=========================================== | |
// FTroubleshooterDialog::FTroubleshooterDialog | |
//=========================================== | |
FTroubleshooterDialog::FTroubleshooterDialog(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FTroubleshooterDialog", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FTroubleshooterDialog::FTroubleshooterDialog\n"); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::reload | |
//=========================================== | |
void FTroubleshooterDialog::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FTroubleshooterDialog::reload\n"); | |
auto& parentData = parentFragData<FMainData>(); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
setLayout(m_data.vbox.get()); | |
m_data.actPreferences = makeQtObjPtr<QAction>("Troubleshooter", this); | |
parentData.mnuHelp->addAction(m_data.actPreferences.get()); | |
m_data.wgtTabs = makeQtObjPtr<QTabWidget>(this); | |
m_data.vbox->addWidget(m_data.wgtTabs.get()); | |
setupTab1(); | |
setupTab2(); | |
setupTab3(); | |
m_data.wgtTabs->addTab(m_data.tab1.page.get(), "Fix automatically"); | |
connect(m_data.actPreferences.get(), SIGNAL(triggered()), this, SLOT(showTroubleshooterDialog())); | |
auto& spec = dynamic_cast<const FTroubleshooterDialogSpec&>(spec_); | |
setWindowTitle(spec.titleText); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::setupTab1 | |
//=========================================== | |
void FTroubleshooterDialog::setupTab1() { | |
auto& tab = m_data.tab1; | |
tab.page = makeQtObjPtr<QWidget>(); | |
tab.vbox = makeQtObjPtr<QVBoxLayout>(); | |
tab.page->setLayout(tab.vbox.get()); | |
tab.wgtCaption = makeQtObjPtr<QLabel>("Attempt to fix problem automatically"); | |
tab.wgtRunTroubleshooter = makeQtObjPtr<QPushButton>("Run troubleshooter"); | |
tab.wgtProgressBar = makeQtObjPtr<QProgressBar>(); | |
tab.wgtGroupbox = makeQtObjPtr<QGroupBox>("Result"); | |
tab.resultsVbox = makeQtObjPtr<QVBoxLayout>(); | |
tab.btnsHbox = makeQtObjPtr<QHBoxLayout>(); | |
tab.wgtNoProblemsFound = makeQtObjPtr<QLabel>("> No problems found"); | |
tab.wgtProblemResolved = makeQtObjPtr<QLabel>("Has this resolved the problem?"); | |
tab.wgtYes = makeQtObjPtr<QPushButton>("Yes"); | |
tab.wgtNo = makeQtObjPtr<QPushButton>("No"); | |
tab.btnsHbox->addWidget(tab.wgtYes.get()); | |
tab.btnsHbox->addWidget(tab.wgtNo.get()); | |
tab.resultsVbox->addWidget(tab.wgtNoProblemsFound.get()); | |
tab.resultsVbox->addWidget(tab.wgtProblemResolved.get()); | |
tab.resultsVbox->addLayout(tab.btnsHbox.get()); | |
tab.timer = makeQtObjPtr<QTimer>(); | |
tab.wgtGroupbox->setLayout(tab.resultsVbox.get()); | |
tab.wgtGroupbox->setVisible(false); | |
auto sp = tab.wgtGroupbox->sizePolicy(); | |
sp.setRetainSizeWhenHidden(true); | |
tab.wgtGroupbox->setSizePolicy(sp); | |
tab.wgtProgressBar->setVisible(false); | |
tab.wgtProgressBar->setRange(0, 10); | |
tab.vbox->addWidget(tab.wgtCaption.get()); | |
tab.vbox->addWidget(tab.wgtRunTroubleshooter.get()); | |
tab.vbox->addWidget(tab.wgtProgressBar.get()); | |
tab.vbox->addWidget(tab.wgtGroupbox.get(), 1); | |
connect(tab.wgtRunTroubleshooter.get(), SIGNAL(clicked()), this, SLOT(onRunTroubleshooter())); | |
connect(tab.timer.get(), SIGNAL(timeout()), this, SLOT(onTick())); | |
connect(tab.wgtNo.get(), SIGNAL(clicked()), this, SLOT(onNoClick())); | |
connect(tab.wgtYes.get(), SIGNAL(clicked()), this, SLOT(onYesClick())); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::setupTab2 | |
//=========================================== | |
void FTroubleshooterDialog::setupTab2() { | |
auto& tab = m_data.tab2; | |
tab.page = makeQtObjPtr<QWidget>(); | |
tab.vbox = makeQtObjPtr<QVBoxLayout>(); | |
tab.wgtTextBrowser = makeQtObjPtr<QTextBrowser>(); | |
tab.vbox->addWidget(tab.wgtTextBrowser.get()); | |
tab.wgtTextBrowser->setSearchPaths(QStringList() | |
<< commonData.appConfig.dataPath("its_raining_tetrominos").c_str()); | |
tab.wgtTextBrowser->setSource(QUrl("troubleshooter1.html")); | |
tab.page->setLayout(tab.vbox.get()); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::setupTab3 | |
//=========================================== | |
void FTroubleshooterDialog::setupTab3() { | |
auto& tab = m_data.tab3; | |
tab.page = makeQtObjPtr<QWidget>(); | |
tab.vbox = makeQtObjPtr<QVBoxLayout>(); | |
tab.wgtRaycast = makeQtObjPtr<RaycastWidget>(commonData.appConfig, commonData.eventSystem); | |
tab.wgtRaycast->setFixedSize(320, 240); | |
tab.wgtRaycast->initialise(commonData.appConfig.dataPath("its_raining_tetrominos/map.svg")); | |
tab.gameLogic.reset(new GameLogic(commonData.eventSystem, tab.wgtRaycast->entityManager())); | |
tab.wgtRaycast->start(); | |
tab.vbox->addWidget(tab.wgtRaycast.get()); | |
tab.page->setLayout(tab.vbox.get()); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::onNoClick | |
//=========================================== | |
void FTroubleshooterDialog::onNoClick() { | |
assert(m_data.wgtTabs->count() == 2); | |
m_data.tab1.wgtGroupbox->setVisible(false); | |
m_data.wgtTabs->insertTab(1, m_data.tab2.page.get(), "Common problems"); | |
m_data.wgtTabs->setCurrentIndex(1); | |
m_data.tab2.wgtTextBrowser->setSource(QUrl("troubleshooter1.html")); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::onYesClick | |
//=========================================== | |
void FTroubleshooterDialog::onYesClick() { | |
auto& tab = m_data.tab1; | |
tab.wgtGroupbox->setVisible(false); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::onTick | |
//=========================================== | |
void FTroubleshooterDialog::onTick() { | |
auto& tab = m_data.tab1; | |
if (tab.wgtProgressBar->value() == 10) { | |
tab.timer->stop(); | |
tab.wgtProgressBar->setVisible(false); | |
tab.wgtGroupbox->setVisible(true); | |
tab.wgtRunTroubleshooter->setDisabled(false); | |
m_data.wgtTabs->addTab(m_data.tab3.page.get(), "⚙"); | |
commonData.eventSystem.fire(pEvent_t(new Event("increaseTetrominoRain"))); | |
} | |
else { | |
tab.wgtProgressBar->setValue(tab.wgtProgressBar->value() + 1); | |
} | |
} | |
//=========================================== | |
// FTroubleshooterDialog::onRunTroubleshooter | |
//=========================================== | |
void FTroubleshooterDialog::onRunTroubleshooter() { | |
auto& tab = m_data.tab1; | |
m_data.wgtTabs->removeTab(2); | |
m_data.wgtTabs->removeTab(1); | |
tab.wgtProgressBar->setVisible(true); | |
tab.wgtGroupbox->setVisible(false); | |
tab.wgtRunTroubleshooter->setDisabled(true); | |
tab.wgtProgressBar->setValue(0); | |
tab.timer->start(300); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::cleanUp | |
//=========================================== | |
void FTroubleshooterDialog::cleanUp() { | |
DBG_PRINT("FTroubleshooterDialog::cleanUp\n"); | |
auto& parentData = parentFragData<FMainData>(); | |
parentData.mnuHelp->removeAction(m_data.actPreferences.get()); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::showTroubleshooterDialog | |
//=========================================== | |
void FTroubleshooterDialog::showTroubleshooterDialog() { | |
show(); | |
} | |
//=========================================== | |
// FTroubleshooterDialog::~FTroubleshooterDialog | |
//=========================================== | |
FTroubleshooterDialog::~FTroubleshooterDialog() { | |
DBG_PRINT("FTroubleshooterDialog::~FTroubleshooterDialog\n"); | |
} | |
#include "fragments/f_main/f_troubleshooter_dialog/game_logic.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/c_switch_behaviour.hpp" | |
#include "event_system.hpp" | |
#include "app_config.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
using std::string; | |
namespace its_raining_tetrominos { | |
//=========================================== | |
// GameLogic::GameLogic | |
//=========================================== | |
GameLogic::GameLogic(EventSystem& eventSystem, EntityManager& entityManager) | |
: m_eventSystem(eventSystem), | |
m_entityManager(entityManager), | |
m_entityId(Component::getNextId()) { | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CEventHandler* events = new CEventHandler(m_entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"switch_activated", | |
std::bind(&GameLogic::onSwitchActivate, this, std::placeholders::_1)}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_changed_zone", | |
std::bind(&GameLogic::onChangeZone, this, std::placeholders::_1)}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
} | |
//=========================================== | |
// GameLogic::onChangeZone | |
//=========================================== | |
void GameLogic::onChangeZone(const GameEvent& e_) { | |
static entityId_t playerId = Component::getIdFromString("player"); | |
static entityId_t doorId = Component::getIdFromString("exit_door"); | |
const auto& e = dynamic_cast<const EChangedZone&>(e_); | |
if (e.entityId == playerId && e.newZone == doorId) { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_MAKING_PROGRESS))); | |
} | |
} | |
//=========================================== | |
// GameLogic::onSwitchActivate | |
//=========================================== | |
void GameLogic::onSwitchActivate(const GameEvent& e_) { | |
static entityId_t doorId = Component::getIdFromString("exit_door"); | |
const auto& e = dynamic_cast<const ESwitchActivate&>(e_); | |
if (e.state == SwitchState::ON) { | |
// TODO: Cool transition | |
m_entityManager.fireEvent(EActivateEntity{doorId}, {doorId}); | |
} | |
else { | |
m_entityManager.broadcastEvent(GameEvent("machine_deactivated")); | |
} | |
} | |
//=========================================== | |
// GameLogic::~GameLogic | |
//=========================================== | |
GameLogic::~GameLogic() { | |
m_entityManager.deleteEntity(m_entityId); | |
} | |
} | |
#include <regex> | |
#include <random> | |
#include <cassert> | |
#include <algorithm> | |
#include <QPixmap> | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/are_you_sure_widget.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::string; | |
const int NUM_QUESTIONS = 8; | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// AreYouSureWidget::Template::generate_ | |
//=========================================== | |
string AreYouSureWidget::Template::generate_(const TemplateMap& templates, const string& text, | |
int maxDepth) const { | |
if (maxDepth < 0) { | |
return ""; | |
} | |
std::regex rx("<(\\w+),(\\d+)-(\\d+)>"); | |
auto begin = std::sregex_iterator(text.begin(), text.end(), rx); | |
auto end = std::sregex_iterator(); | |
string result = text; | |
int offset = 0; | |
for (auto it = begin; it != end; ++it) { | |
std::smatch m = *it; | |
string name = m.str(1); | |
int minReps = std::atoi(m.str(2).c_str()); | |
int maxReps = std::atoi(m.str(3).c_str()); | |
std::uniform_int_distribution<int> randReps(minReps, maxReps); | |
int reps = randReps(randEngine); | |
string expanded; | |
for (int rep = 0; rep < reps; ++rep) { | |
string subresult = templates.at(name).generate(templates, maxDepth - 1); | |
if (subresult.length() == 0 && minReps > 0) { | |
return ""; | |
} | |
expanded += subresult; | |
} | |
result.replace(m.position() + offset, m.length(), expanded); | |
offset += static_cast<int>(expanded.length() - m.length()); | |
} | |
if (result.back() != ' ') { | |
result.push_back(' '); | |
} | |
return result; | |
} | |
//=========================================== | |
// AreYouSureWidget::Template::generate | |
//=========================================== | |
string AreYouSureWidget::Template::generate(const TemplateMap& templates, int maxDepth) const { | |
string text = text1; | |
if (text2.length() > 0) { | |
std::uniform_int_distribution<int> flipCoin(0, 1); | |
int coin = flipCoin(randEngine); | |
text = (coin == 0 ? text1 : text2); | |
const string& altText = (coin == 1 ? text1 : text2); | |
string result = generate_(templates, text, maxDepth); | |
if (result.length() == 0) { | |
return generate_(templates, altText, maxDepth); | |
} | |
return result; | |
} | |
else { | |
return generate_(templates, text, maxDepth); | |
} | |
} | |
//=========================================== | |
// numOccurrences | |
//=========================================== | |
static int numOccurrences(const string& str, const string& substr) { | |
int i = 0; | |
auto it = str.begin(); | |
while (true) { | |
it = std::search(it, str.end(), substr.begin(), substr.end()); | |
if (it == str.end()) { | |
break; | |
} | |
else { | |
++i; | |
++it; | |
} | |
} | |
return i; | |
} | |
//=========================================== | |
// numNegatives | |
//=========================================== | |
static inline int numNegatives(const string& str) { | |
return numOccurrences(str, "abort") + numOccurrences(str, "not"); | |
} | |
//=========================================== | |
// AreYouSureWidget::AreYouSureWidget | |
//=========================================== | |
AreYouSureWidget::AreYouSureWidget(const AppConfig& appConfig) | |
: QWidget(nullptr) { | |
setMouseTracking(true); | |
m_pages = makeQtObjPtr<QStackedLayout>(this); | |
// Page1 | |
// | |
m_page1.widget = makeQtObjPtr<QWidget>(); | |
m_page1.widget->setMouseTracking(true); | |
QFont font = appConfig.normalFont; | |
font.setPixelSize(12); | |
m_page1.widget->setFont(font); | |
m_page1.grid = makeQtObjPtr<QGridLayout>(); | |
m_page1.widget->setLayout(m_page1.grid.get()); | |
QPixmap pixmap{appConfig.dataPath("common/images/warning.png").c_str()}; | |
m_page1.wgtWarning = makeQtObjPtr<QLabel>(); | |
m_page1.wgtWarning->setMouseTracking(true); | |
m_page1.wgtWarning->setPixmap(pixmap); | |
m_page1.wgtPrompt = makeQtObjPtr<QLabel>(); | |
m_page1.wgtPrompt->setWordWrap(true); | |
m_page1.wgtPrompt->setMouseTracking(true); | |
m_page1.wgtYes = makeQtObjPtr<QPushButton>("Yes"); | |
m_page1.wgtNo = makeQtObjPtr<QPushButton>("No"); | |
m_page1.grid->addWidget(m_page1.wgtWarning.get(), 0, 1); | |
m_page1.grid->addWidget(m_page1.wgtPrompt.get(), 1, 0, 1, 3); | |
m_page1.grid->addWidget(m_page1.wgtNo.get(), 2, 0); | |
m_page1.grid->addWidget(m_page1.wgtYes.get(), 2, 2); | |
connect(m_page1.wgtYes.get(), SIGNAL(clicked()), this, SLOT(onYesClick())); | |
connect(m_page1.wgtNo.get(), SIGNAL(clicked()), this, SLOT(onNoClick())); | |
m_pages->addWidget(m_page1.widget.get()); | |
// Page 2 | |
// | |
m_page2.widget = makeQtObjPtr<QWidget>(); | |
m_page2.widget->setMouseTracking(true); | |
m_page2.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_page2.widget->setLayout(m_page2.vbox.get()); | |
pixmap = QPixmap{appConfig.dataPath("common/images/console.png").c_str()}; | |
m_page2.wgtConsole = makeQtObjPtr<QLabel>(); | |
m_page2.wgtConsole->setMouseTracking(true); | |
m_page2.wgtConsole->setPixmap(pixmap); | |
m_page2.wgtConsole->setFixedSize(pixmap.size() * 0.8); | |
m_page2.wgtConsole->setScaledContents(true); | |
m_page2.wgtPrompt = makeQtObjPtr<QLabel>("The admin console is for advanced users only. " | |
"Enter at your own risk."); | |
m_page2.wgtPrompt->setWordWrap(true); | |
m_page2.wgtPrompt->setMouseTracking(true); | |
m_page2.wgtBackToSafety = makeQtObjPtr<QPushButton>("Back to safety"); | |
m_page2.wgtBackToSafety->setMaximumWidth(140); | |
m_page2.wgtProceed = makeQtObjPtr<EvasiveButton>("Proceed"); | |
m_page2.wgtProceed->setMaximumWidth(90); | |
m_page2.vbox->addWidget(m_page2.wgtConsole.get()); | |
m_page2.vbox->addWidget(m_page2.wgtProceed.get()); | |
m_page2.vbox->addItem(new QSpacerItem(100, 20)); | |
m_page2.vbox->addWidget(m_page2.wgtPrompt.get()); | |
m_page2.vbox->addWidget(m_page2.wgtBackToSafety.get()); | |
m_page2.vbox->setAlignment(m_page2.wgtConsole.get(), Qt::AlignHCenter); | |
m_page2.vbox->setAlignment(m_page2.wgtProceed.get(), Qt::AlignHCenter); | |
connect(m_page2.wgtProceed.get(), SIGNAL(pressed()), this, SLOT(onFinalYesClick())); | |
connect(m_page2.wgtBackToSafety.get(), SIGNAL(clicked()), this, SLOT(onFinalNoClick())); | |
m_pages->addWidget(m_page2.widget.get()); | |
// Setup templates | |
// | |
m_templates["not"] = Template("not"); | |
m_templates["sureYou"] = Template("<not,0-3>sure you"); | |
m_templates["sureYouAre"] = Template("<sureYou,1-1>are"); | |
m_templates["sureYou_"] = Template("<sureYouAre,0-1><sureYou,1-1>"); | |
m_templates["sureYouWantTo"] = Template("<sureYou_,1-1>want to"); | |
m_templates["continue"] = Template("continue", "proceed"); | |
m_templates["verb"] = Template("<not,0-3><continue,1-1>", "<not,0-3>abort"); | |
m_templates["continuing"] = Template("continuing", "proceeding"); | |
m_templates["ing"] = Template("<not,0-3><continuing,1-1>", "<not,0-3>aborting"); | |
m_templates["continueTo"] = Template("<continue,1-1><ingTo,1-1>", "<continueTo,1-1><verbTo,1-1>"); | |
m_templates["verbTo"] = Template("<continueTo,1-1>", "abort <ingTo,1-1>"); | |
m_templates["ingTo"] = Template("<ingIng,1-1><continuing,1-1>to"); | |
m_templates["ingVerbIng"] = Template("<ingTo,1-1><verbIng,1-1>"); | |
m_templates["ingIng"] = Template("<ing,1-2><ingVerbIng,0-1>"); | |
m_templates["verbIng"] = Template("<verb,1-1><ingIng,1-1>"); | |
m_templates["verbVerb"] = Template("<verbTo,1-1><verb,1-1>"); | |
m_templates["verb_"] = Template("<verbIng,1-1>", "<verbVerb,1-1>"); | |
m_templates["question"] = Template("Are you sure you are <sureYouWantTo,1-1><verb_,1-1>"); | |
restart(); | |
} | |
//=========================================== | |
// AreYouSureWidget::restart | |
//=========================================== | |
void AreYouSureWidget::restart() { | |
m_count = 0; | |
m_pages->setCurrentIndex(0); | |
nextQuestion(); | |
} | |
//=========================================== | |
// AreYouSureWidget::nextQuestion | |
//=========================================== | |
void AreYouSureWidget::nextQuestion() { | |
if (m_count < NUM_QUESTIONS) { | |
string question; | |
if (m_count == 0) { | |
question = "Are you sure you want to continue?"; | |
} | |
else { | |
while (question.length() == 0 || question.length() > 300) { | |
question = m_templates.at("question").generate(m_templates, 6 + m_count); | |
question.pop_back(); | |
question.push_back('?'); | |
} | |
} | |
DBG_PRINT((numNegatives(question) % 2 ? "N\n" : "Y\n")); | |
m_page1.wgtPrompt->setText(question.c_str()); | |
} | |
else { | |
m_pages->setCurrentIndex(1); | |
m_page2.wgtProceed->reset(); | |
} | |
++m_count; | |
} | |
//=========================================== | |
// AreYouSureWidget::onYesClick | |
//=========================================== | |
void AreYouSureWidget::onYesClick() { | |
string question = m_page1.wgtPrompt->text().toStdString(); | |
bool yesToContinue = numNegatives(question) % 2 == 0; | |
if (yesToContinue) { | |
nextQuestion(); | |
} | |
else { | |
emit finished(false); | |
restart(); | |
} | |
} | |
//=========================================== | |
// AreYouSureWidget::onNoClick | |
//=========================================== | |
void AreYouSureWidget::onNoClick() { | |
string question = m_page1.wgtPrompt->text().toStdString(); | |
bool yesToContinue = numNegatives(question) % 2 == 0; | |
if (yesToContinue) { | |
emit finished(false); | |
restart(); | |
} | |
else { | |
nextQuestion(); | |
} | |
} | |
//=========================================== | |
// AreYouSureWidget::onFinalYesClick | |
//=========================================== | |
void AreYouSureWidget::onFinalYesClick() { | |
emit finished(true); | |
} | |
//=========================================== | |
// AreYouSureWidget::onFinalNoClick | |
//=========================================== | |
void AreYouSureWidget::onFinalNoClick() { | |
emit finished(false); | |
restart(); | |
} | |
//=========================================== | |
// AreYouSureWidget::mouseMoveEvent | |
//=========================================== | |
void AreYouSureWidget::mouseMoveEvent(QMouseEvent*) { | |
m_page2.wgtProceed->onMouseMove(); | |
} | |
#include <random> | |
#include <algorithm> | |
#include <QMouseEvent> | |
#include "fragments/f_main/f_settings_dialog/f_settings_dialog.hpp" | |
#include "fragments/f_main/f_login_screen/f_login_screen.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/f_config_maze.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/f_config_maze_spec.hpp" | |
#include "utils.hpp" | |
#include "event_system.hpp" | |
#include "strings.hpp" | |
#include "app_config.hpp" | |
using std::string; | |
using std::vector; | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// generatePassword | |
//=========================================== | |
static string generatePassword() { | |
string syms("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); | |
std::uniform_int_distribution<int> randIdx(0, static_cast<int>(syms.length()) - 1); | |
std::uniform_int_distribution<int> randLen(8, 14); | |
int len = randLen(randEngine); | |
string pwd; | |
for (int i = 0; i < len; ++i) { | |
pwd.push_back(syms[randIdx(randEngine)]); | |
} | |
return pwd; | |
} | |
//=========================================== | |
// FConfigMaze::FConfigMaze | |
//=========================================== | |
FConfigMaze::FConfigMaze(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FConfigMaze", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FConfigMaze::FConfigMaze\n"); | |
auto& parentData = parentFragData<FSettingsDialogData>(); | |
setMouseTracking(true); | |
m_data.stackedLayout = makeQtObjPtr<QStackedLayout>(this); | |
setLayout(m_data.stackedLayout.get()); | |
constructConsoleLaunchPage(); | |
constructAreYouSurePage(); | |
constructConsolePage(); | |
m_data.stackedLayout->addWidget(m_data.consoleLaunchPage.widget.get()); | |
m_data.stackedLayout->addWidget(m_data.consoleAreYouSurePage.widget.get()); | |
m_data.stackedLayout->addWidget(m_data.consolePage.widget.get()); | |
m_layoutIdxOfConsoleLaunchPage = 0; | |
m_layoutIdxOfAreYouSurePage = 1; | |
m_layoutIdxOfConsolePage = 2; | |
m_layoutIdxOfFirstConfigPage = 3; | |
parentData.vbox->addWidget(this); | |
} | |
//=========================================== | |
// FConfigMaze::constructConsoleLaunchPage | |
//=========================================== | |
void FConfigMaze::constructConsoleLaunchPage() { | |
m_data.consoleLaunchPage.widget = makeQtObjPtr<QWidget>(); | |
m_data.consoleLaunchPage.wgtToConsole = makeQtObjPtr<QPushButton>("Admin console"); | |
m_data.consoleLaunchPage.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_data.consoleLaunchPage.vbox->addWidget(m_data.consoleLaunchPage.wgtToConsole.get()); | |
m_data.consoleLaunchPage.widget->setLayout(m_data.consoleLaunchPage.vbox.get()); | |
connect(m_data.consoleLaunchPage.wgtToConsole.get(), SIGNAL(clicked()), this, | |
SLOT(onEnterConsoleClick())); | |
} | |
//=========================================== | |
// FConfigMaze::constructAreYouSurePage | |
//=========================================== | |
void FConfigMaze::constructAreYouSurePage() { | |
m_data.consoleAreYouSurePage.widget = makeQtObjPtr<QWidget>(); | |
m_data.consoleAreYouSurePage.wgtAreYouSure = makeQtObjPtr<AreYouSureWidget>(commonData.appConfig); | |
m_data.consoleAreYouSurePage.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_data.consoleAreYouSurePage.vbox->addWidget(m_data.consoleAreYouSurePage.wgtAreYouSure.get()); | |
m_data.consoleAreYouSurePage.widget->setLayout(m_data.consoleAreYouSurePage.vbox.get()); | |
connect(m_data.consoleAreYouSurePage.wgtAreYouSure.get(), SIGNAL(finished(bool)), this, | |
SLOT(onAreYouSureFinish(bool))); | |
} | |
//=========================================== | |
// FConfigMaze::constructConsolePage | |
//=========================================== | |
void FConfigMaze::constructConsolePage() { | |
string pwd = generatePassword(); | |
commonData.eventSystem.fire(pEvent_t(new PasswordGeneratedEvent(pwd))); | |
string initialContent = | |
"┌───────────────────────────────────────┐\n" | |
"│ Admin Console v1.1.16 │\n" | |
"├───────────────────────────────────────┤\n" | |
"│ Date and time 1993/03/14 16:11:23 │\n" | |
"│ Logged in as rob │\n" | |
"│ Logged in since 1993/03/14 15:22:49 │\n" | |
"│ │\n" | |
"│ ↑↓ cycle history │\n" | |
"└───────────────────────────────────────┘\n" | |
"> "; | |
m_data.consolePage.widget = makeQtObjPtr<QWidget>(); | |
m_data.consolePage.wgtConsole = makeQtObjPtr<ConsoleWidget>(commonData.appConfig, initialContent, | |
vector<string>{ | |
"logouut", | |
string("chpwd ") + pwd | |
}); | |
m_data.consolePage.wgtConsole->addCommand("logout", [](const ConsoleWidget::ArgList&) { | |
return "An error occurred"; | |
}); | |
m_data.consolePage.wgtConsole->addCommand("chpwd", [](const ConsoleWidget::ArgList&) { | |
return "An error occurred"; | |
}); | |
m_data.consolePage.wgtBack = makeQtObjPtr<QPushButton>("Exit"); | |
m_data.consolePage.wgtBack->setMaximumWidth(50); | |
m_data.consolePage.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_data.consolePage.vbox->addWidget(m_data.consolePage.wgtConsole.get()); | |
m_data.consolePage.vbox->addWidget(m_data.consolePage.wgtBack.get()); | |
m_data.consolePage.widget->setLayout(m_data.consolePage.vbox.get()); | |
connect(m_data.consolePage.wgtBack.get(), SIGNAL(clicked()), this, SLOT(onExitConsoleClick())); | |
} | |
//=========================================== | |
// FConfigMaze::reload | |
//=========================================== | |
void FConfigMaze::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FConfigMaze::reload\n"); | |
auto& spec = dynamic_cast<const FConfigMazeSpec&>(spec_); | |
ucs4string_t symbols_ = utf8ToUcs4(spec.symbols); | |
std::shuffle(symbols_.begin(), symbols_.end(), randEngine); | |
QString symbols(ucs4ToUtf8(symbols_).c_str()); | |
m_data.pages[0] = makeQtObjPtr<ConfigPage>(symbols[0], vector<int>{ 1, 2 }); | |
m_data.pages[1] = makeQtObjPtr<ConfigPage>(symbols[1], vector<int>{ 0, 3 }); | |
m_data.pages[2] = makeQtObjPtr<ConfigPage>(symbols[2], vector<int>{ 0, 3 }); | |
m_data.pages[3] = makeQtObjPtr<ConfigPage>(symbols[3], vector<int>{ 1, 2, 4, 5 }); | |
m_data.pages[4] = makeQtObjPtr<ConfigPage>(symbols[4], vector<int>{ 3, 6 }); | |
m_data.pages[5] = makeQtObjPtr<ConfigPage>(symbols[5], vector<int>{ 3, 6, 9 }); | |
m_data.pages[6] = makeQtObjPtr<ConfigPage>(symbols[6], vector<int>{ 4, 5, 7, 10 }); | |
m_data.pages[7] = makeQtObjPtr<ConfigPage>(symbols[7], vector<int>{ 6, 11 }); | |
m_data.pages[8] = makeQtObjPtr<ConfigPage>(symbols[8], vector<int>{ 9, 13 }); | |
m_data.pages[9] = makeQtObjPtr<ConfigPage>(symbols[9], vector<int>{ 5, 8, 10, 14 }); | |
m_data.pages[10] = makeQtObjPtr<ConfigPage>(symbols[10], vector<int>{ 6, 9, 11 }); | |
m_data.pages[11] = makeQtObjPtr<ConfigPage>(symbols[11], vector<int>{ 7, 10, 12, 15 }); | |
m_data.pages[12] = makeQtObjPtr<ConfigPage>(symbols[12], vector<int>{ 11, 100 }); | |
m_data.pages[13] = makeQtObjPtr<ConfigPage>(symbols[13], vector<int>{ 8, 14 }); | |
m_data.pages[14] = makeQtObjPtr<ConfigPage>(symbols[14], vector<int>{ 9, 13 }); | |
m_data.pages[15] = makeQtObjPtr<ConfigPage>(symbols[15], vector<int>{ 11, 100 }); | |
QPixmap pixmap{commonData.appConfig.dataPath("are_you_sure/config_maze.png").c_str()}; | |
m_data.wgtMap = makeQtObjPtr<QLabel>(); | |
m_data.wgtMap->setPixmap(pixmap); | |
m_data.pages[1]->grid->addWidget(m_data.wgtMap.get(), 0, 1); | |
for (int i = 0; i <= 15; ++i) { | |
connect(m_data.pages[i].get(), SIGNAL(nextClicked(int)), this, SLOT(onPageNextClick(int))); | |
m_data.stackedLayout->addWidget(m_data.pages[i].get()); | |
} | |
// Skipping the config maze because it's boring | |
// | |
//m_data.stackedLayout->setCurrentIndex(m_layoutIdxOfFirstConfigPage + 1); | |
m_data.stackedLayout->setCurrentIndex(m_layoutIdxOfConsoleLaunchPage); | |
} | |
//=========================================== | |
// FConfigMaze::onPageNextClick | |
//=========================================== | |
void FConfigMaze::onPageNextClick(int pageIdx) { | |
DBG_PRINT("Showing page " << pageIdx << "\n"); | |
if (pageIdx != 100) { | |
m_data.pages[pageIdx]->reset(); | |
m_data.stackedLayout->setCurrentIndex(m_layoutIdxOfFirstConfigPage + pageIdx); | |
} | |
else { | |
m_data.stackedLayout->setCurrentIndex(m_layoutIdxOfConsoleLaunchPage); | |
} | |
} | |
//=========================================== | |
// FConfigMaze::onAreYouSureFinish | |
//=========================================== | |
void FConfigMaze::onAreYouSureFinish(bool passed) { | |
if (passed) { | |
m_data.stackedLayout->setCurrentIndex(m_layoutIdxOfConsolePage); | |
} | |
else { | |
m_data.stackedLayout->setCurrentIndex(m_layoutIdxOfConsoleLaunchPage); | |
} | |
} | |
//=========================================== | |
// FConfigMaze::onEnterConsoleClick | |
//=========================================== | |
void FConfigMaze::onEnterConsoleClick() { | |
m_data.stackedLayout->setCurrentIndex(1); | |
m_data.consoleAreYouSurePage.wgtAreYouSure->restart(); | |
} | |
//=========================================== | |
// FConfigMaze::onExitConsoleClick | |
//=========================================== | |
void FConfigMaze::onExitConsoleClick() { | |
m_data.stackedLayout->setCurrentIndex(0); | |
} | |
//=========================================== | |
// FConfigMaze::cleanUp | |
//=========================================== | |
void FConfigMaze::cleanUp() { | |
DBG_PRINT("FConfigMaze::cleanUp\n"); | |
auto& parentData = parentFragData<FSettingsDialogData>(); | |
parentData.vbox->removeWidget(this); | |
} | |
//=========================================== | |
// FConfigMaze::~FConfigMaze | |
//=========================================== | |
FConfigMaze::~FConfigMaze() { | |
DBG_PRINT("FConfigMaze::~FConfigMaze\n"); | |
} | |
#include <algorithm> | |
#include <random> | |
#include "fragments/f_main/f_settings_dialog/f_config_maze/config_page.hpp" | |
#include "utils.hpp" | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// ConfigPage::ConfigPage | |
//=========================================== | |
ConfigPage::ConfigPage(QChar symbol, std::vector<int> neighbours) | |
: QWidget(nullptr), | |
m_neighbours(neighbours) { | |
std::shuffle(m_neighbours.begin(), m_neighbours.end(), randEngine); | |
grid = makeQtObjPtr<QGridLayout>(); | |
setLayout(grid.get()); | |
QFont f = font(); | |
f.setPixelSize(18); | |
m_label = makeQtObjPtr<QLabel>(symbol); | |
m_label->setFont(f); | |
grid->addWidget(m_label.get(), 0, 0); | |
m_btnGroup = makeQtObjPtr<QButtonGroup>(); | |
QString labels = "ABCD"; | |
for (unsigned int i = 0; i < m_neighbours.size(); ++i) { | |
m_radioBtns.push_back(makeQtObjPtr<QRadioButton>(QString("Option ") + QString(labels[i]))); | |
m_btnGroup->addButton(m_radioBtns.back().get(), m_neighbours[i]); | |
grid->addWidget(m_radioBtns.back().get(), i + 1, 1); | |
} | |
m_wgtNext = makeQtObjPtr<QPushButton>("Next"); | |
grid->addWidget(m_wgtNext.get(), 5, 2); | |
connect(m_wgtNext.get(), SIGNAL(clicked()), this, SLOT(onNextClick())); | |
reset(); | |
} | |
//=========================================== | |
// ConfigPage::reset | |
//=========================================== | |
void ConfigPage::reset() { | |
m_radioBtns.front()->setChecked(true); | |
} | |
//=========================================== | |
// ConfigPage::onNextClick | |
//=========================================== | |
void ConfigPage::onNextClick() { | |
emit nextClicked(m_btnGroup->checkedId()); | |
} | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_settings_dialog.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_settings_dialog_spec.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FSettingsDialog::FSettingsDialog | |
//=========================================== | |
FSettingsDialog::FSettingsDialog(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FSettingsDialog", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FSettingsDialog::FSettingsDialog\n"); | |
auto& parentData = parentFragData<FMainData>(); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
setLayout(m_data.vbox.get()); | |
m_data.actSettings = makeQtObjPtr<QAction>("Settings", this); | |
parentData.mnuFile->addAction(m_data.actSettings.get()); | |
connect(m_data.actSettings.get(), SIGNAL(triggered()), this, SLOT(showSettingsDialog())); | |
} | |
//=========================================== | |
// FSettingsDialog::reload | |
//=========================================== | |
void FSettingsDialog::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FSettingsDialog::reload\n"); | |
auto& spec = dynamic_cast<const FSettingsDialogSpec&>(spec_); | |
setWindowTitle(spec.titleText); | |
setFixedSize(spec.width, spec.height); | |
if (spec.backgroundImage.length() > 0) { | |
setStyleSheet(QString("background-image:url(\"") + spec.backgroundImage + "\");"); | |
} | |
else { | |
setStyleSheet(""); | |
} | |
} | |
//=========================================== | |
// FSettingsDialog::cleanUp | |
//=========================================== | |
void FSettingsDialog::cleanUp() { | |
DBG_PRINT("FSettingsDialog::cleanUp\n"); | |
auto& parentData = parentFragData<FMainData>(); | |
parentData.mnuFile->removeAction(m_data.actSettings.get()); | |
} | |
//=========================================== | |
// FSettingsDialog::showSettingsDialog | |
//=========================================== | |
void FSettingsDialog::showSettingsDialog() { | |
show(); | |
} | |
//=========================================== | |
// FSettingsDialog::~FSettingsDialog | |
//=========================================== | |
FSettingsDialog::~FSettingsDialog() { | |
DBG_PRINT("FSettingsDialog::~FSettingsDialog\n"); | |
} | |
#include "fragments/f_main/f_settings_dialog/f_settings_dialog.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_loading_screen/f_loading_screen.hpp" | |
#include "fragments/f_main/f_settings_dialog/f_loading_screen/f_loading_screen_spec.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FLoadingScreen::FLoadingScreen | |
//=========================================== | |
FLoadingScreen::FLoadingScreen(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QLabel(nullptr), | |
Fragment("FLoadingScreen", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FLoadingScreen::FLoadingScreen\n"); | |
} | |
//=========================================== | |
// FLoadingScreen::reload | |
//=========================================== | |
void FLoadingScreen::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FLoadingScreen::reload\n"); | |
auto& spec = dynamic_cast<const FLoadingScreenSpec&>(spec_); | |
auto& parentData = parentFragData<FSettingsDialogData>(); | |
m_origParentState.spacing = parentData.vbox->spacing(); | |
m_origParentState.margins = parentData.vbox->contentsMargins(); | |
parentData.vbox->setSpacing(0); | |
parentData.vbox->setContentsMargins(0, 0, 0, 0); | |
parentData.vbox->addWidget(this); | |
updateGeometry(); | |
m_data.label = makeQtObjPtr<QLabel>(this); | |
m_data.label->setText(spec.targetValue.c_str()); | |
QPalette p = m_data.label->palette(); | |
p.setColor(QPalette::WindowText, Qt::white); | |
m_data.label->setPalette(p); | |
QFont font = m_data.label->font(); | |
font.setPixelSize(20); | |
m_data.label->setFont(font); | |
m_data.label->move(168, 59); | |
} | |
//=========================================== | |
// FLoadingScreen::cleanUp | |
//=========================================== | |
void FLoadingScreen::cleanUp() { | |
DBG_PRINT("FLoadingScreen::cleanUp\n"); | |
auto& parentData = parentFragData<FSettingsDialogData>(); | |
parentData.vbox->setSpacing(m_origParentState.spacing); | |
parentData.vbox->setContentsMargins(m_origParentState.margins); | |
parentData.vbox->removeWidget(this); | |
} | |
//=========================================== | |
// FLoadingScreen::~FLoadingScreen | |
//=========================================== | |
FLoadingScreen::~FLoadingScreen() { | |
DBG_PRINT("FLoadingScreen::~FLoadingScreen\n"); | |
} | |
#include "fragments/f_main/f_countdown_to_start/f_countdown_to_start.hpp" | |
#include "fragments/f_main/f_countdown_to_start/f_countdown_to_start_spec.hpp" | |
#include "fragments/f_main/f_main.hpp" | |
#include "state_ids.hpp" | |
#include "event_system.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FCountdownToStart::FCountdownToStart | |
//=========================================== | |
FCountdownToStart::FCountdownToStart(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FCountdownToStart", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FCountdownToStart::FCountdownToStart\n"); | |
} | |
//=========================================== | |
// FCountdownToStart::reload | |
//=========================================== | |
void FCountdownToStart::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FCountdownToStart::reload\n"); | |
auto& parentData = parentFragData<FMainData>(); | |
m_origParentState.fnOnQuit = parentData.fnOnQuit; | |
parentData.fnOnQuit = [this]() { onQuit(); }; | |
auto& spec = dynamic_cast<const FCountdownToStartSpec&>(spec_); | |
m_stateId = spec.stateId; | |
} | |
//=========================================== | |
// FCountdownToStart::cleanUp | |
//=========================================== | |
void FCountdownToStart::cleanUp() { | |
DBG_PRINT("FCountdownToStart::cleanUp\n"); | |
parentFragData<FMainData>().fnOnQuit = m_origParentState.fnOnQuit; | |
} | |
//=========================================== | |
// FCountdownToStart::onQuit | |
//=========================================== | |
void FCountdownToStart::onQuit() { | |
if (m_stateId <= ST_NORMAL_CALCULATOR_9) { | |
commonData.eventSystem.fire(pEvent_t(new RequestStateChangeEvent(m_stateId + 1))); | |
} | |
} | |
//=========================================== | |
// FCountdownToStart::~FCountdownToStart | |
//=========================================== | |
FCountdownToStart::~FCountdownToStart() { | |
DBG_PRINT("FCountdownToStart::~FCountdownToStart\n"); | |
} | |
#include <sstream> | |
#include <QDialog> | |
#include <QApplication> | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/game_logic.hpp" | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/events.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/c_switch_behaviour.hpp" | |
#include "raycast/c_elevator_behaviour.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "event_system.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::set; | |
using std::string; | |
namespace making_progress { | |
static const int MAX_KEY_PRESSES = 20; | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// GameLogic::GameLogic | |
//=========================================== | |
GameLogic::GameLogic(QDialog& dialog, EventSystem& eventSystem, EntityManager& entityManager) | |
: m_dialog(dialog), | |
m_eventSystem(eventSystem), | |
m_entityManager(entityManager), | |
m_entityId(Component::getNextId()), | |
m_raiseDialogEvent(static_cast<QEvent::Type>(QEvent::registerEventType())) { | |
DBG_PRINT("GameLogic::GameLogic\n"); | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
pCEventHandler_t forwardEvent(new CEventHandler(m_entityId)); | |
forwardEvent->broadcastedEventHandlers.push_back(EventHandler{"entity_changed_zone", | |
std::bind(&GameLogic::onEntityChangeZone, this, std::placeholders::_1)}); | |
eventHandlerSystem.addComponent(std::move(forwardEvent)); | |
m_hButtonPress = m_eventSystem.listen("makingProgress/buttonPress", [this](const Event& event) { | |
onButtonPress(event); | |
}); | |
} | |
//=========================================== | |
// GameLogic::setFeatures | |
//=========================================== | |
void GameLogic::setFeatures(const set<buttonId_t>& features) { | |
m_features = features; | |
setElevatorSpeed(); | |
generateTargetNumber(); | |
} | |
//=========================================== | |
// GameLogic::generateTargetNumber | |
//=========================================== | |
void GameLogic::generateTargetNumber() { | |
std::uniform_int_distribution<int> randInt(100000, 999999); | |
m_targetNumber = randInt(randEngine) / 100.0; | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
Texture& tex = renderSystem.rg.textures.at("number"); | |
std::stringstream ss; | |
ss << m_targetNumber; | |
string strNumber = ss.str(); | |
QFont font; | |
font.setPixelSize(14); | |
QPainter painter(&tex.image); | |
painter.setFont(font); | |
painter.setPen(QColor(0, 255, 0)); | |
tex.image.fill(QColor(20, 20, 50)); | |
painter.drawText(8, 16, strNumber.c_str()); | |
Texture& remainingTex = renderSystem.rg.textures.at("keypresses_remaining"); | |
remainingTex.image.fill(QColor(20, 20, 50)); | |
} | |
//=========================================== | |
// GameLogic::onButtonPress | |
//=========================================== | |
void GameLogic::onButtonPress(const Event& event) { | |
if (m_success) { | |
return; | |
} | |
const ButtonPressEvent& e = dynamic_cast<const ButtonPressEvent&>(event); | |
double value = 0; | |
std::stringstream ss(e.calcDisplay); | |
if (ss >> value) { | |
if (fabs(value - m_targetNumber) < 0.001) { | |
entityId_t doorId = Component::getIdFromString("exit_door"); | |
EActivateEntity e(doorId); | |
m_success = true; | |
m_entityManager.fireEvent(e, {doorId}); | |
return; | |
} | |
} | |
++m_numKeysPressed; | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
Texture& tex = renderSystem.rg.textures.at("keypresses_remaining"); | |
tex.image.fill(QColor(20, 20, 50)); | |
if (m_numKeysPressed > MAX_KEY_PRESSES) { | |
generateTargetNumber(); | |
m_numKeysPressed = 0; | |
} | |
else { | |
int w = tex.image.width(); | |
int h = tex.image.height(); | |
double delta = static_cast<double>(w) / (MAX_KEY_PRESSES + 1); | |
QPainter painter(&tex.image); | |
painter.setBrush(QColor(240, 10, 10)); | |
painter.drawRect(0, 0, delta * m_numKeysPressed, h); | |
} | |
} | |
//=========================================== | |
// GameLogic::setElevatorSpeed | |
//=========================================== | |
void GameLogic::setElevatorSpeed() { | |
entityId_t elevatorId = Component::getIdFromString("progress_lift"); | |
CElevatorBehaviour& elevator = | |
m_entityManager.getComponent<CElevatorBehaviour>(elevatorId, ComponentKind::C_BEHAVIOUR); | |
const double MIN_SPEED = 4; | |
const double MAX_SPEED = 22; | |
const double MAX_FEATURES = 17; | |
double delta = (MAX_SPEED - MIN_SPEED) / MAX_FEATURES; | |
elevator.setSpeed(MAX_SPEED - delta * m_features.size()); | |
} | |
//=========================================== | |
// GameLogic::customEvent | |
//=========================================== | |
void GameLogic::customEvent(QEvent* event) { | |
if (event->type() == m_raiseDialogEvent) { | |
m_dialog.activateWindow(); | |
} | |
} | |
//=========================================== | |
// GameLogic::onEntityChangeZone | |
//=========================================== | |
void GameLogic::onEntityChangeZone(const GameEvent& event) { | |
const EChangedZone& e = dynamic_cast<const EChangedZone&>(event); | |
entityId_t player = | |
m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL).sg.player->body; | |
if (e.entityId != player) { | |
return; | |
} | |
if (e.newZone == Component::getIdFromString("puzzle_room_entrance")) { | |
m_eventSystem.fire(pEvent_t(new SetupCompleteEvent(m_features))); | |
QApplication::postEvent(this, new QEvent(m_raiseDialogEvent)); | |
} | |
else if (e.newZone == Component::getIdFromString("level_exit")) { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_YOUVE_GOT_MAIL))); | |
} | |
} | |
//=========================================== | |
// GameLogic::~GameLogic | |
//=========================================== | |
GameLogic::~GameLogic() {} | |
} | |
#include <QLabel> | |
#include <QVBoxLayout> | |
#include <QHBoxLayout> | |
#include <QDialog> | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/f_procalc_setup.hpp" | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/f_procalc_setup_spec.hpp" | |
#include "event_system.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::set; | |
using making_progress::GameLogic; | |
//=========================================== | |
// addHeaderItem | |
//=========================================== | |
static void addHeaderItem(QListWidget& wgtList, const QString& text) { | |
QListWidgetItem* item = new QListWidgetItem(text); | |
QFont bold = item->font(); | |
bold.setBold(true); | |
item->setFont(bold); | |
Qt::ItemFlags on = Qt::NoItemFlags; | |
Qt::ItemFlags off = Qt::ItemIsSelectable; | |
item->setFlags(item->flags() | on); | |
item->setFlags(item->flags() & ~off); | |
wgtList.addItem(item); | |
} | |
//=========================================== | |
// addSpaceItem | |
//=========================================== | |
static void addSpaceItem(QListWidget& wgtList) { | |
addHeaderItem(wgtList, ""); | |
} | |
//=========================================== | |
// FProcalcSetup::FProcalcSetup | |
//=========================================== | |
FProcalcSetup::FProcalcSetup(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FProcalcSetup", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FProcalcSetup::FProcalcSetup\n"); | |
} | |
//=========================================== | |
// FProcalcSetup::reload | |
//=========================================== | |
void FProcalcSetup::reload(const FragmentSpec&) { | |
DBG_PRINT("FProcalcSetup::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
m_data.stackedLayout = makeQtObjPtr<QStackedLayout>(this); | |
setLayout(m_data.stackedLayout.get()); | |
setupPage1(); | |
setupPage2(); | |
m_data.stackedLayout->addWidget(m_data.page1.widget.get()); | |
m_data.stackedLayout->addWidget(m_data.page2.widget.get()); | |
} | |
//=========================================== | |
// FProcalcSetup::addCheckableItem | |
//=========================================== | |
void FProcalcSetup::addCheckableItem(QListWidget& wgtList, const QString& text, buttonId_t btnId) { | |
QListWidgetItem* item = new QListWidgetItem(text); | |
Qt::ItemFlags on = Qt::ItemIsUserCheckable; | |
Qt::ItemFlags off = Qt::ItemIsSelectable; | |
item->setFlags(item->flags() | on); | |
item->setFlags(item->flags() & ~off); | |
item->setCheckState(Qt::Unchecked); | |
m_featureIndices[btnId] = wgtList.count(); | |
wgtList.addItem(item); | |
} | |
//=========================================== | |
// FProcalcSetup::populateListWidget | |
//=========================================== | |
void FProcalcSetup::populateListWidget() { | |
QListWidget& wgtList = *m_data.page1.wgtList; | |
addHeaderItem(wgtList, "Essential features"); | |
addCheckableItem(wgtList, "Equals button", BTN_EQUALS); | |
addSpaceItem(wgtList); | |
addHeaderItem(wgtList, "Recommended extras"); | |
addCheckableItem(wgtList, "Number 0", BTN_ZERO); | |
addCheckableItem(wgtList, "Number 1", BTN_ONE); | |
addCheckableItem(wgtList, "Number 2", BTN_TWO); | |
addCheckableItem(wgtList, "Number 4", BTN_FOUR); | |
addCheckableItem(wgtList, "Number 7", BTN_SEVEN); | |
addCheckableItem(wgtList, "Number 6", BTN_SIX); | |
addCheckableItem(wgtList, "Number 8", BTN_EIGHT); | |
addCheckableItem(wgtList, "Number 9", BTN_NINE); | |
addCheckableItem(wgtList, "Addition operator", BTN_PLUS); | |
addCheckableItem(wgtList, "Multiplication operator", BTN_TIMES); | |
addCheckableItem(wgtList, "Division operator", BTN_DIVIDE); | |
addCheckableItem(wgtList, "Clear button", BTN_CLEAR); | |
addSpaceItem(wgtList); | |
addHeaderItem(wgtList, "Advanced features"); | |
addCheckableItem(wgtList, "Number 5", BTN_FIVE); | |
addSpaceItem(wgtList); | |
addHeaderItem(wgtList, "Experimental features"); | |
addCheckableItem(wgtList, "Number 3", BTN_THREE); | |
addCheckableItem(wgtList, "Subtraction operator", BTN_MINUS); | |
addCheckableItem(wgtList, "Decimal point", BTN_POINT); | |
} | |
//=========================================== | |
// FProcalcSetup::resizeEvent | |
//=========================================== | |
void FProcalcSetup::resizeEvent(QResizeEvent*) { | |
auto& parent = parentFrag<QWidget>(); | |
// Hack to prevent dialog being too long on Windows | |
int h = parent.height(); | |
setFixedHeight(h); | |
} | |
//=========================================== | |
// FProcalcSetup::setupPage1 | |
//=========================================== | |
void FProcalcSetup::setupPage1() { | |
auto& page = m_data.page1; | |
page.widget = makeQtObjPtr<QWidget>(); | |
QPixmap icon(commonData.appConfig.dataPath("common/images/warning.png").c_str()); | |
QLabel* wgtIcon = new QLabel; | |
wgtIcon->setPixmap(icon); | |
wgtIcon->setMaximumWidth(50); | |
wgtIcon->setMaximumHeight(50); | |
wgtIcon->setScaledContents(true); | |
QLabel* wgtCaption = new QLabel("Selecting more features will result in a longer setup time"); | |
wgtCaption->setWordWrap(true); | |
QHBoxLayout* topHBox = new QHBoxLayout; | |
topHBox->addWidget(wgtIcon); | |
topHBox->addWidget(wgtCaption); | |
page.wgtList = makeQtObjPtr<QListWidget>(); | |
page.wgtNext = makeQtObjPtr<QPushButton>("Next"); | |
QHBoxLayout* bottomHBox = new QHBoxLayout; | |
bottomHBox->addStretch(0); | |
bottomHBox->addWidget(page.wgtNext.get()); | |
QVBoxLayout* vbox = new QVBoxLayout; | |
vbox->addLayout(topHBox); | |
vbox->addWidget(page.wgtList.get()); | |
vbox->addLayout(bottomHBox); | |
page.widget->setLayout(vbox); | |
populateListWidget(); | |
connect(page.wgtNext.get(), SIGNAL(clicked()), this, SLOT(onNextClick())); | |
} | |
//=========================================== | |
// FProcalcSetup::setupPage2 | |
//=========================================== | |
void FProcalcSetup::setupPage2() { | |
auto& page = m_data.page2; | |
page.widget = makeQtObjPtr<QWidget>(); | |
QVBoxLayout* vbox = new QVBoxLayout; | |
vbox->setSpacing(0); | |
vbox->setContentsMargins(0, 0, 0, 0); | |
page.widget->setLayout(vbox); | |
page.wgtRaycast = makeQtObjPtr<RaycastWidget>(commonData.appConfig, commonData.eventSystem); | |
vbox->addWidget(page.wgtRaycast.get()); | |
page.wgtRaycast->initialise(commonData.appConfig.dataPath("making_progress/map.svg")); | |
QDialog& dialog = parentFrag<QDialog>(); | |
page.gameLogic = makeQtObjPtr<GameLogic>(dialog, commonData.eventSystem, | |
page.wgtRaycast->entityManager()); | |
page.wgtRaycast->start(); | |
} | |
//=========================================== | |
// FProcalcSetup::onNextClick | |
//=========================================== | |
void FProcalcSetup::onNextClick() { | |
m_data.stackedLayout->setCurrentIndex(1); | |
set<buttonId_t> features; | |
for (auto it = m_featureIndices.begin(); it != m_featureIndices.end(); ++it) { | |
buttonId_t feature = it->first; | |
QListWidgetItem& item = *m_data.page1.wgtList->item(it->second); | |
if (item.checkState() == Qt::Checked) { | |
features.insert(feature); | |
} | |
} | |
m_data.page2.gameLogic->setFeatures(features); | |
} | |
//=========================================== | |
// FProcalcSetup::cleanUp | |
//=========================================== | |
void FProcalcSetup::cleanUp() { | |
DBG_PRINT("FProcalcSetup::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FProcalcSetup::~FProcalcSetup | |
//=========================================== | |
FProcalcSetup::~FProcalcSetup() { | |
DBG_PRINT("FProcalcSetup::~FProcalcSetup\n"); | |
} | |
#include "fragments/f_main/f_app_dialog/f_file_system/object_factory.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/audio_service.hpp" | |
using std::vector; | |
using std::set; | |
using std::string; | |
namespace going_in_circles { | |
//=========================================== | |
// ObjectFactory::ObjectFactory | |
//=========================================== | |
ObjectFactory::ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
TimeService& timeService, AudioService& audioService) | |
: m_rootFactory(rootFactory), | |
m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_audioService(audioService) {} | |
//=========================================== | |
// ObjectFactory::types | |
//=========================================== | |
const set<string>& ObjectFactory::types() const { | |
static const set<string> types{"jeff", "donald"}; | |
return types; | |
} | |
//=========================================== | |
// ObjectFactory::constructJeff | |
//=========================================== | |
bool ObjectFactory::constructJeff(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
string name = obj.dict["name"] = "jeff"; | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "jeff"; | |
if (m_rootFactory.constructObject("sprite", entityId, obj, parentId, parentTransform)) { | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
auto& animationSystem = m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
auto& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
CSprite& sprite = dynamic_cast<CSprite&>(renderSystem.getComponent(entityId)); | |
double H = 2.0; | |
double W = 8.0; | |
double dW = 1.0 / W; | |
vector<QRectF> texViews = { | |
QRectF(dW * 0.0, 0, dW, 1.0 / H), | |
QRectF(dW * 1.0, 0, dW, 1.0 / H), | |
QRectF(dW * 2.0, 0, dW, 1.0 / H), | |
QRectF(dW * 3.0, 0, dW, 1.0 / H), | |
QRectF(dW * 4.0, 0, dW, 1.0 / H), | |
QRectF(dW * 5.0, 0, dW, 1.0 / H), | |
QRectF(dW * 6.0, 0, dW, 1.0 / H), | |
QRectF(dW * 7.0, 0, dW, 1.0 / H) | |
}; | |
sprite.texViews = texViews; | |
CDamage* damage = new CDamage(entityId, 2, 2); | |
damageSystem.addComponent(pComponent_t(damage)); | |
CFocus* focus = new CFocus(entityId); | |
focus->hoverText = name.replace(0, 1, 1, asciiToUpper(name[0])); | |
focusSystem.addComponent(pComponent_t(focus)); | |
CAnimation* anim = new CAnimation(entityId); | |
vector<AnimationFrame> frames{{ | |
AnimationFrame{{QRectF{0.0 / W, 1.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{1.0 / W, 1.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{2.0 / W, 1.0 / H, 1.0 / W, 1.0 / H}}}, | |
AnimationFrame{{QRectF{3.0 / W, 1.0 / H, 1.0 / W, 1.0 / H}}}, | |
}}; | |
anim->addAnimation(pAnimation_t(new Animation("death", m_timeService.frameRate, 0.5, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
CEventHandler* events = new CEventHandler(entityId); | |
bool inCircles = false; | |
events->targetedEventHandlers.push_back(EventHandler{"player_activate_entity", | |
[=, &focusSystem, &damageSystem](const GameEvent&) mutable { | |
if (damageSystem.getHealth(entityId) != 0) { | |
if (inCircles) { | |
focus->captionText = "\"Do you ever feel you're going in circles?\""; | |
} | |
else { | |
focus->captionText = "\"People head North to die\""; | |
} | |
inCircles = !inCircles; | |
} | |
else { | |
focus->captionText = "\"The snacks here are pretty good\""; | |
} | |
focusSystem.showCaption(entityId); | |
}}); | |
events->targetedEventHandlers.push_back(EventHandler{"entity_damaged", | |
[=, &animationSystem, &spatialSystem](const GameEvent&) { | |
CVRect& body = dynamic_cast<CVRect&>(spatialSystem.getComponent(entityId)); | |
DBG_PRINT("Jeff health: " << damage->health << "\n"); | |
//animationSystem.playAnimation(entityId, "hurt", false); | |
if (damage->health == 0) { | |
m_audioService.playSoundAtPos("civilian_death", body.pos); | |
animationSystem.playAnimation(entityId, "death", false); | |
} | |
else { | |
m_audioService.playSoundAtPos("civilian_hurt", body.pos); | |
} | |
}}); | |
events->targetedEventHandlers.push_back(EventHandler{"animation_finished", | |
[=, &spatialSystem, &sprite](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EAnimationFinished&>(e_); | |
if (e.animName == "death") { | |
entityId_t spawnPointId = Component::getIdFromString("jeff_spawn_point"); | |
CVRect& spawnPoint = dynamic_cast<CVRect&>(spatialSystem.getComponent(spawnPointId)); | |
spatialSystem.relocateEntity(entityId, *spawnPoint.zone, spawnPoint.pos); | |
sprite.texViews = texViews; | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructDonald | |
//=========================================== | |
bool ObjectFactory::constructDonald(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
string name = obj.dict["name"] = "donald"; | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "donald"; | |
if (m_rootFactory.constructObject("sprite", entityId, obj, parentId, parentTransform)) { | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CSprite& sprite = dynamic_cast<CSprite&>(renderSystem.getComponent(entityId)); | |
double W = 8.0; | |
double dW = 1.0 / W; | |
sprite.texViews = { | |
QRectF(dW * 0.0, 0, dW, 1), | |
QRectF(dW * 1.0, 0, dW, 1), | |
QRectF(dW * 2.0, 0, dW, 1), | |
QRectF(dW * 3.0, 0, dW, 1), | |
QRectF(dW * 4.0, 0, dW, 1), | |
QRectF(dW * 5.0, 0, dW, 1), | |
QRectF(dW * 6.0, 0, dW, 1), | |
QRectF(dW * 7.0, 0, dW, 1) | |
}; | |
CFocus* focus = new CFocus(entityId); | |
focus->hoverText = "Donald"; | |
focus->captionText = "\"Have you seen my product? It's a great product\""; | |
focusSystem.addComponent(pComponent_t(focus)); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->targetedEventHandlers.push_back(EventHandler{"player_activate_entity", | |
[=, &focusSystem](const GameEvent&) { | |
focusSystem.showCaption(entityId); | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructObject | |
//=========================================== | |
bool ObjectFactory::constructObject(const string& type, entityId_t entityId, | |
parser::Object& obj, entityId_t region, const Matrix& parentTransform) { | |
if (type == "jeff") { | |
return constructJeff(entityId, obj, region, parentTransform); | |
} | |
else if (type == "donald") { | |
return constructDonald(entityId, obj, region, parentTransform); | |
} | |
return false; | |
} | |
} | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system/f_file_system.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system/f_file_system_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system/object_factory.hpp" | |
#include "raycast/render_system.hpp" | |
#include "utils.hpp" | |
#include "event_system.hpp" | |
#include "app_config.hpp" | |
//=========================================== | |
// FFileSystem::FFileSystem | |
//=========================================== | |
FFileSystem::FFileSystem(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FFileSystem", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FFileSystem::FFileSystem\n"); | |
} | |
//=========================================== | |
// FFileSystem::reload | |
//=========================================== | |
void FFileSystem::reload(const FragmentSpec&) { | |
DBG_PRINT("FFileSystem::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_data.vbox->setSpacing(0); | |
m_data.vbox->setContentsMargins(0, 0, 0, 0); | |
setLayout(m_data.vbox.get()); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
m_data.wgtRaycast = makeQtObjPtr<RaycastWidget>(commonData.appConfig, commonData.eventSystem); | |
auto& rootFactory = m_data.wgtRaycast->rootFactory(); | |
auto& timeService = m_data.wgtRaycast->timeService(); | |
auto& entityManager = m_data.wgtRaycast->entityManager(); | |
auto& audioService = m_data.wgtRaycast->audioService(); | |
GameObjectFactory* factory = new going_in_circles::ObjectFactory(rootFactory, entityManager, | |
timeService, audioService); | |
m_data.wgtRaycast->rootFactory().addFactory(pGameObjectFactory_t(factory)); | |
m_data.wgtRaycast->initialise(commonData.appConfig.dataPath("going_in_circles/map.svg")); | |
m_data.gameLogic.reset(new going_in_circles::GameLogic(commonData.eventSystem, audioService, | |
timeService, entityManager)); | |
m_data.wgtRaycast->start(); | |
m_data.vbox->addWidget(m_data.wgtRaycast.get()); | |
} | |
//=========================================== | |
// FFileSystem::cleanUp | |
//=========================================== | |
void FFileSystem::cleanUp() { | |
DBG_PRINT("FFileSystem::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
} | |
//=========================================== | |
// FFileSystem::~FFileSystem | |
//=========================================== | |
FFileSystem::~FFileSystem() { | |
DBG_PRINT("FFileSystem::~FFileSystem\n"); | |
} | |
#include "fragments/f_main/f_app_dialog/f_file_system/game_logic.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/agent_system.hpp" | |
#include "raycast/c_switch_behaviour.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/time_service.hpp" | |
#include "event_system.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::string; | |
namespace going_in_circles { | |
static const double TIME_LIMIT = 5.2; | |
//=========================================== | |
// GameLogic::GameLogic | |
//=========================================== | |
GameLogic::GameLogic(EventSystem& eventSystem, AudioService& audioService, TimeService& timeService, | |
EntityManager& entityManager) | |
: m_eventSystem(eventSystem), | |
m_audioService(audioService), | |
m_timeService(timeService), | |
m_entityManager(entityManager) { | |
DBG_PRINT("GameLogic::GameLogic\n"); | |
m_entityId = Component::getNextId(); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
Player& player = *spatialSystem.sg.player; | |
player.invincible = true; | |
EventHandlerSystem& eventHandlerSystem | |
= m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CEventHandler* events = new CEventHandler(m_entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_destroyed", | |
std::bind(&GameLogic::onEntityDestroyed, this, std::placeholders::_1)}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_changed_zone", | |
std::bind(&GameLogic::onEntityChangeZone, this, std::placeholders::_1)}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
events->broadcastedEventHandlers.push_back(EventHandler{"switch_activated", | |
std::bind(&GameLogic::onSwitchActivated, this, std::placeholders::_1)}); | |
setupLarry(eventHandlerSystem); | |
resetSwitches(); | |
} | |
//=========================================== | |
// GameLogic::setupLarry | |
//=========================================== | |
void GameLogic::setupLarry(EventHandlerSystem& eventHandlerSystem) { | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
auto& agentSystem = m_entityManager.system<AgentSystem>(ComponentKind::C_AGENT); | |
entityId_t larryId = Component::getIdFromString("larry"); | |
CFocus& focus = dynamic_cast<CFocus&>(focusSystem.getComponent(larryId)); | |
focus.captionText = "\"Life is pleasant here\""; | |
auto& larryEvents = dynamic_cast<CEventHandler&>(eventHandlerSystem.getComponent(larryId)); | |
larryEvents.targetedEventHandlers.push_back(EventHandler{"player_activate_entity", | |
[=, &focusSystem](const GameEvent&) { | |
focusSystem.showCaption(larryId); | |
}}); | |
entityId_t switchId = Component::getIdFromString("exit_switch"); | |
CEventHandler* switchEvents = nullptr; | |
if (eventHandlerSystem.hasComponent(switchId)) { | |
switchEvents = &dynamic_cast<CEventHandler&>(eventHandlerSystem.getComponent(switchId)); | |
} | |
else { | |
switchEvents = new CEventHandler(switchId); | |
eventHandlerSystem.addComponent(pComponent_t(switchEvents)); | |
} | |
switchEvents->targetedEventHandlers.push_back(EventHandler{"player_activate_entity", | |
[=, &spatialSystem, &agentSystem](const GameEvent&) { | |
entityId_t navPointId = Component::getIdFromString("exit_nav_point"); | |
auto& vRect = dynamic_cast<CVRect&>(spatialSystem.getComponent(navPointId)); | |
agentSystem.navigateTo(larryId, vRect.pos); | |
}}); | |
} | |
//=========================================== | |
// GameLogic::getSwitch | |
//=========================================== | |
CSwitchBehaviour& GameLogic::getSwitch(entityId_t id) const { | |
auto& behaviourSystem = m_entityManager.system<BehaviourSystem>(ComponentKind::C_BEHAVIOUR); | |
return dynamic_cast<CSwitchBehaviour&>(behaviourSystem.getComponent(id)); | |
} | |
//=========================================== | |
// GameLogic::resetSwitches | |
//=========================================== | |
void GameLogic::resetSwitches() { | |
static const entityId_t switch0Id = Component::getIdFromString("switch0"); | |
static const entityId_t switch1Id = Component::getIdFromString("switch1"); | |
static const entityId_t switch2Id = Component::getIdFromString("switch2"); | |
static const entityId_t switch3Id = Component::getIdFromString("switch3"); | |
auto& switch0 = getSwitch(switch0Id); | |
auto& switch1 = getSwitch(switch1Id); | |
auto& switch2 = getSwitch(switch2Id); | |
auto& switch3 = getSwitch(switch3Id); | |
switch0.setState(SwitchState::OFF); | |
switch1.setState(SwitchState::OFF); | |
switch2.setState(SwitchState::OFF); | |
switch3.setState(SwitchState::OFF); | |
switch0.disabled = false; | |
switch1.disabled = true; | |
switch2.disabled = true; | |
switch3.disabled = true; | |
} | |
//=========================================== | |
// GameLogic::onSwitchActivated | |
//=========================================== | |
void GameLogic::onSwitchActivated(const GameEvent& e_) { | |
auto& e = dynamic_cast<const ESwitchActivate&>(e_); | |
static const entityId_t switch0Id = Component::getIdFromString("switch0"); | |
static const entityId_t switch1Id = Component::getIdFromString("switch1"); | |
static const entityId_t switch2Id = Component::getIdFromString("switch2"); | |
static const entityId_t switch3Id = Component::getIdFromString("switch3"); | |
auto& switch1 = getSwitch(switch1Id); | |
auto& switch2 = getSwitch(switch2Id); | |
auto& switch3 = getSwitch(switch3Id); | |
if (e.switchEntityId == switch0Id) { | |
switch1.disabled = false; | |
m_switchSoundId = m_audioService.playSound("tick"); | |
m_timeService.onTimeout([this]() { | |
resetSwitches(); | |
m_audioService.stopSound("tick", m_switchSoundId); | |
}, TIME_LIMIT); | |
} | |
else if (e.switchEntityId == switch1Id) { | |
switch2.disabled = false; | |
} | |
else if (e.switchEntityId == switch2Id) { | |
switch3.disabled = false; | |
} | |
else if (e.switchEntityId == switch3Id) { | |
m_audioService.stopSound("tick", m_switchSoundId); | |
} | |
} | |
//=========================================== | |
// GameLogic::onEntityDestroyed | |
//=========================================== | |
void GameLogic::onEntityDestroyed(const GameEvent& event) { | |
auto& e = dynamic_cast<const EEntityDestroyed&>(event); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
Player& player = *spatialSystem.sg.player; | |
if (e.entityId == player.body) { | |
auto& inventorySystem = m_entityManager.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
auto& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
damageSystem.damageEntity(player.body, -10); | |
int ammo = inventorySystem.getBucketValue(player.body, "ammo"); | |
inventorySystem.subtractFromBucket(player.body, "ammo", ammo); | |
entityId_t spawnPointId = Component::getIdFromString("spawn_point"); | |
CVRect& spawnPoint = dynamic_cast<CVRect&>(spatialSystem.getComponent(spawnPointId)); | |
spatialSystem.relocateEntity(player.body, *spawnPoint.zone, spawnPoint.pos); | |
player.setFeetHeight(0); | |
m_audioService.playMusic("birds", true); | |
} | |
} | |
//=========================================== | |
// GameLogic::onEntityChangeZone | |
//=========================================== | |
void GameLogic::onEntityChangeZone(const GameEvent& event) { | |
const EChangedZone& e = dynamic_cast<const EChangedZone&>(event); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
Player& player = *spatialSystem.sg.player; | |
if (e.entityId == player.body) { | |
if (e.newZone == Component::getIdFromString("level_exit")) { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_DOOMSWEEPER))); | |
} | |
} | |
} | |
//=========================================== | |
// GameLogic::~GameLogic | |
//=========================================== | |
GameLogic::~GameLogic() { | |
} | |
} | |
#include <cassert> | |
#include <QMouseEvent> | |
#include <QApplication> | |
#include "fragments/f_main/f_app_dialog/f_server_room/object_factory.hpp" | |
#include "raycast/sprite_factory.hpp" | |
#include "raycast/geometry.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/spawn_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/agent_system.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "calculator_widget.hpp" | |
#include "exception.hpp" | |
#include "utils.hpp" | |
using std::vector; | |
using std::string; | |
using std::set; | |
namespace youve_got_mail { | |
//=========================================== | |
// ObjectFactory::constructBigScreen | |
//=========================================== | |
bool ObjectFactory::constructBigScreen(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
assert(entityId == -1); | |
entityId = Component::getIdFromString("big_screen"); | |
entityId_t calculatorId = Component::getIdFromString("calculator"); | |
if (m_rootFactory.constructObject("wall_decal", entityId, obj, parentId, parentTransform)) { | |
AnimationSystem& animationSystem = | |
m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
// Number of frames in sprite sheet | |
const int W = 1; | |
const int H = 13; | |
CAnimation* anim = new CAnimation(entityId); | |
vector<AnimationFrame> frames = constructFrames(W, H, | |
{ 0, 1, 2, 1 }); | |
anim->addAnimation(pAnimation_t(new Animation("idle", m_timeService.frameRate, 2.0, frames))); | |
frames = constructFrames(W, H, | |
{ 3, 4, 5, 4 }); | |
anim->addAnimation(pAnimation_t(new Animation("panic", m_timeService.frameRate, 2.0, frames))); | |
frames = constructFrames(W, H, | |
{ 6, 7, 8, 9, 10, 11, 12 }); | |
anim->addAnimation(pAnimation_t(new Animation("escape", m_timeService.frameRate, 3.5, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "idle", true); | |
CEventHandler* handlers = new CEventHandler(entityId); | |
handlers->targetedEventHandlers.push_back(EventHandler{"animation_finished", | |
[=, &animationSystem](const GameEvent& e) { | |
const EAnimationFinished& event = dynamic_cast<const EAnimationFinished&>(e); | |
if (event.animName == "panic") { | |
m_entityManager.deleteEntity(calculatorId); | |
animationSystem.playAnimation(entityId, "escape", false); | |
} | |
else if (event.animName == "escape") { | |
m_timeService.onTimeout([=]() { | |
m_entityManager.broadcastEvent(GameEvent("enter_larry")); | |
}, 2.0); | |
} | |
}}); | |
handlers->broadcastedEventHandlers.push_back(EventHandler{"div_by_zero", | |
[=, &animationSystem](const GameEvent&) { | |
animationSystem.stopAnimation(entityId); | |
animationSystem.playAnimation(entityId, "panic", false); | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(handlers)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructServerRack | |
//=========================================== | |
bool ObjectFactory::constructServerRack(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("region", entityId, obj, parentId, parentTransform)) { | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
DamageSystem& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
AnimationSystem& animationSystem = | |
m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
// Number of frames in sprite sheet | |
const int W = 1; | |
const int H = 8; | |
vector<AnimationFrame> damageFrames = constructFrames(W, H, { 1, 0 }); | |
CAnimation* anim = new CAnimation(entityId); | |
anim->addAnimation(pAnimation_t(new Animation("damage", m_timeService.frameRate, 0.3, | |
damageFrames))); | |
vector<AnimationFrame> explodeFrames = constructFrames(W, H, { 2, 3, 4, 5, 6, 7 }); | |
anim->addAnimation(pAnimation_t(new Animation("explode", m_timeService.frameRate, 0.6, | |
explodeFrames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
auto& children = renderSystem.children(entityId); | |
for (entityId_t childId : children) { | |
CAnimation* anim = new CAnimation(childId); | |
anim->addAnimation(pAnimation_t(new Animation("damage", m_timeService.frameRate, 0.3, | |
damageFrames))); | |
anim->addAnimation(pAnimation_t(new Animation("explode", m_timeService.frameRate, 0.6, | |
explodeFrames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
} | |
// Play once just to setup the initial texRect | |
animationSystem.playAnimation(entityId, "damage", false); | |
CDamage* damage = new CDamage(entityId, 3, 3); | |
damageSystem.addComponent(pComponent_t(damage)); | |
CEventHandler* handlers = new CEventHandler(entityId); | |
handlers->targetedEventHandlers.push_back(EventHandler{"entity_damaged", | |
[=, &animationSystem](const GameEvent&) { | |
animationSystem.playAnimation(entityId, "damage", false); | |
}}); | |
handlers->targetedEventHandlers.push_back(EventHandler{"entity_destroyed", | |
[=, &animationSystem](const GameEvent&) { | |
animationSystem.playAnimation(entityId, "explode", false); | |
m_electricitySoundId = m_audioService.playSound("electricity"); | |
m_entityManager.broadcastEvent(GameEvent("server_destroyed")); | |
}}); | |
handlers->targetedEventHandlers.push_back(EventHandler{"animation_finished", | |
[this](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EAnimationFinished&>(e_); | |
if (e.animName == "explode") { | |
m_audioService.stopSound("electricity", m_electricitySoundId); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(handlers)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::renderCalc | |
//=========================================== | |
void ObjectFactory::renderCalc() const { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
int W = m_wgtCalculator.width(); | |
int H = m_wgtCalculator.height(); | |
renderSystem.rg.textures["calculator"] = Texture{QImage(W, H, QImage::Format_ARGB32), | |
Size(0, 0)}; | |
QImage& calcImg = renderSystem.rg.textures["calculator"].image; | |
calcImg.fill(QColor(255, 0, 0)); | |
QImage buf(W, H, QImage::Format_ARGB32); | |
m_wgtCalculator.render(&buf); | |
QPainter painter; | |
painter.begin(&calcImg); | |
QTransform t = painter.transform(); | |
t.scale(-1, 1); | |
painter.setTransform(t); | |
painter.drawImage(QPoint(-W, 0), buf); | |
painter.end(); | |
} | |
//=========================================== | |
// ObjectFactory::constructCalculator | |
//=========================================== | |
bool ObjectFactory::constructCalculator(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
assert(entityId == -1); | |
entityId = Component::getIdFromString("calculator"); | |
if (m_rootFactory.constructObject("wall_decal", entityId, obj, parentId, parentTransform)) { | |
DamageSystem& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
renderCalc(); | |
CDamage* damage = new CDamage(entityId, 1000, 1000); | |
damageSystem.addComponent(pComponent_t(damage)); | |
CEventHandler* handlers = new CEventHandler(entityId); | |
handlers->targetedEventHandlers.push_back(EventHandler{"entity_damaged", | |
[=](const GameEvent& e) { | |
const EEntityDamaged& event = dynamic_cast<const EEntityDamaged&>(e); | |
const Point& pt = event.point_rel; | |
const CVRect& vRect = m_entityManager.getComponent<CVRect>(event.entityId, | |
ComponentKind::C_SPATIAL); | |
double W = m_wgtCalculator.width(); | |
double H = m_wgtCalculator.height(); | |
double x_norm = pt.x / vRect.size.x; | |
double y_norm = pt.y / vRect.size.y; | |
double x = W - x_norm * W; | |
double y = H - y_norm * H; | |
QWidget* child = m_wgtCalculator.childAt(x, y); | |
if (child != nullptr) { | |
QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPointF(0, 0), Qt::LeftButton, | |
Qt::LeftButton, Qt::NoModifier); | |
QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, QPointF(0, 0), Qt::LeftButton, | |
Qt::NoButton, Qt::NoModifier); | |
QApplication::sendEvent(child, &mousePressEvent); | |
QApplication::sendEvent(child, &mouseReleaseEvent); | |
} | |
renderCalc(); | |
if (m_wgtCalculator.wgtDigitDisplay->text() == "inf") { | |
m_entityManager.broadcastEvent(GameEvent("div_by_zero")); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(handlers)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::ObjectFactory | |
//=========================================== | |
ObjectFactory::ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
TimeService& timeService, AudioService& audioService, CalculatorWidget& wgtCalculator) | |
: m_rootFactory(rootFactory), | |
m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_audioService(audioService), | |
m_wgtCalculator(wgtCalculator) {} | |
//=========================================== | |
// ObjectFactory::types | |
//=========================================== | |
const set<string>& ObjectFactory::types() const { | |
static const set<string> types{"big_screen", "calculator", "server_rack"}; | |
return types; | |
} | |
//=========================================== | |
// ObjectFactory::constructObject | |
//=========================================== | |
bool ObjectFactory::constructObject(const string& type, entityId_t entityId, | |
parser::Object& obj, entityId_t region, const Matrix& parentTransform) { | |
if (type == "big_screen") { | |
return constructBigScreen(entityId, obj, region, parentTransform); | |
} | |
else if (type == "calculator") { | |
return constructCalculator(entityId, obj, region, parentTransform); | |
} | |
else if (type == "server_rack") { | |
return constructServerRack(entityId, obj, region, parentTransform); | |
} | |
return false; | |
} | |
} | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "fragments/f_main/f_app_dialog/f_server_room/f_server_room.hpp" | |
#include "fragments/f_main/f_app_dialog/f_server_room/f_server_room_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_server_room/object_factory.hpp" | |
#include "raycast/render_system.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
//=========================================== | |
// FServerRoom::FServerRoom | |
//=========================================== | |
FServerRoom::FServerRoom(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FServerRoom", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FServerRoom::FServerRoom\n"); | |
} | |
//=========================================== | |
// FServerRoom::reload | |
//=========================================== | |
void FServerRoom::reload(const FragmentSpec&) { | |
DBG_PRINT("FServerRoom::reload\n"); | |
m_hLaunch = commonData.eventSystem.listen("launchServerRoom", [this](const Event&) { | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_data.vbox->setSpacing(0); | |
m_data.vbox->setContentsMargins(0, 0, 0, 0); | |
setLayout(m_data.vbox.get()); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
for (int i = 0; i < parentData.box->count(); ++i) { | |
QWidget* wgt = parentData.box->itemAt(i)->widget(); | |
if (wgt) { | |
wgt->hide(); | |
} | |
} | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
m_data.wgtRaycast = makeQtObjPtr<RaycastWidget>(commonData.appConfig, commonData.eventSystem); | |
auto& rootFactory = m_data.wgtRaycast->rootFactory(); | |
auto& timeService = m_data.wgtRaycast->timeService(); | |
auto& audioService = m_data.wgtRaycast->audioService(); | |
auto& entityManager = m_data.wgtRaycast->entityManager(); | |
m_data.wgtCalculator = makeQtObjPtr<CalculatorWidget>(commonData.eventSystem); | |
m_data.wgtCalculator->show(); | |
m_data.wgtCalculator->hide(); | |
GameObjectFactory* factory = new youve_got_mail::ObjectFactory(rootFactory, entityManager, | |
timeService, audioService, *m_data.wgtCalculator); | |
m_data.wgtRaycast->rootFactory().addFactory(pGameObjectFactory_t(factory)); | |
m_data.wgtRaycast->initialise(commonData.appConfig.dataPath("youve_got_mail/map.svg")); | |
m_data.gameLogic.reset(new youve_got_mail::GameLogic(commonData.eventSystem, entityManager)); | |
m_data.wgtRaycast->start(); | |
m_data.vbox->addWidget(m_data.wgtRaycast.get()); | |
}); | |
} | |
//=========================================== | |
// FServerRoom::cleanUp | |
//=========================================== | |
void FServerRoom::cleanUp() { | |
DBG_PRINT("FServerRoom::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
for (int i = 0; i < parentData.box->count(); ++i) { | |
QWidget* wgt = parentData.box->itemAt(i)->widget(); | |
if (wgt) { | |
wgt->show(); | |
} | |
} | |
} | |
//=========================================== | |
// FServerRoom::~FServerRoom | |
//=========================================== | |
FServerRoom::~FServerRoom() { | |
DBG_PRINT("FServerRoom::~FServerRoom\n"); | |
} | |
#include <random> | |
#include "fragments/f_main/f_app_dialog/f_app_dialog.hpp" | |
#include "fragments/f_main/f_app_dialog/f_server_room/game_logic.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "event_system.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::string; | |
namespace youve_got_mail { | |
static std::mt19937 randEngine(randomSeed()); | |
static std::uniform_int_distribution<int> randInt(1000, 9999); | |
extern const std::string exitDoorCode = std::to_string(randInt(randEngine)); | |
//=========================================== | |
// GameLogic::GameLogic | |
//=========================================== | |
GameLogic::GameLogic(EventSystem& eventSystem, EntityManager& entityManager) | |
: m_eventSystem(eventSystem), | |
m_entityManager(entityManager) { | |
DBG_PRINT("GameLogic::GameLogic\n"); | |
m_hDivByZero = m_eventSystem.listen("youveGotMail/divByZero", [this](const Event& event) { | |
onDivByZero(event); | |
}); | |
m_hDialogClosed = m_eventSystem.listen("dialogClosed", | |
[this](const Event& event) { | |
const DialogClosedEvent& e = dynamic_cast<const DialogClosedEvent&>(event); | |
if (e.name == "procalc") { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_YOUVE_GOT_MAIL, true))); | |
} | |
}); | |
EventHandlerSystem& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
SpatialSystem& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
const Player& player = *spatialSystem.sg.player; | |
entityId_t entityId = Component::getNextId(); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_changed_zone", | |
[=, &player](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EChangedZone&>(e_); | |
if (e.entityId == player.body) { | |
if (e.newZone == Component::getIdFromString("level_exit")) { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_GOING_IN_CIRCLES, true))); | |
} | |
else if (e.newZone == Component::getIdFromString("input_sensor_a") | |
|| e.newZone == Component::getIdFromString("input_sensor_b")) { | |
if (m_exitDoorSelectedNum != '\0') { | |
if (m_exitDoorInput.length() == 4) { | |
m_exitDoorInput = m_exitDoorSelectedNum; | |
} | |
else { | |
m_exitDoorInput += m_exitDoorSelectedNum; | |
} | |
DBG_PRINT("Current door input: " << m_exitDoorInput << "\n"); | |
drawExitDoorDigitDisplay(); | |
if (m_exitDoorInput == exitDoorCode) { | |
DBG_PRINT("Correct door code entered. Opening door.\n"); | |
entityId_t doorId = Component::getIdFromString("exit_door"); | |
EActivateEntity e(doorId); | |
m_entityManager.fireEvent(e, {doorId}); | |
} | |
m_exitDoorSelectedNum = '\0'; | |
} | |
} | |
else if (e.newZone == Component::getIdFromString("zero_sensor")) { | |
DBG_PRINT("Selected 0 input\n"); | |
m_exitDoorSelectedNum = '0'; | |
} | |
else if (e.newZone == Component::getIdFromString("one_sensor")) { | |
DBG_PRINT("Selected 1 input\n"); | |
m_exitDoorSelectedNum = '1'; | |
} | |
else if (e.newZone == Component::getIdFromString("two_sensor")) { | |
DBG_PRINT("Selected 2 input\n"); | |
m_exitDoorSelectedNum = '2'; | |
} | |
else if (e.newZone == Component::getIdFromString("three_sensor")) { | |
DBG_PRINT("Selected 3 input\n"); | |
m_exitDoorSelectedNum = '3'; | |
} | |
else if (e.newZone == Component::getIdFromString("four_sensor")) { | |
DBG_PRINT("Selected 4 input\n"); | |
m_exitDoorSelectedNum = '4'; | |
} | |
else if (e.newZone == Component::getIdFromString("five_sensor")) { | |
DBG_PRINT("Selected 5 input\n"); | |
m_exitDoorSelectedNum = '5'; | |
} | |
else if (e.newZone == Component::getIdFromString("six_sensor")) { | |
DBG_PRINT("Selected 6 input\n"); | |
m_exitDoorSelectedNum = '6'; | |
} | |
else if (e.newZone == Component::getIdFromString("seven_sensor")) { | |
DBG_PRINT("Selected 7 input\n"); | |
m_exitDoorSelectedNum = '7'; | |
} | |
else if (e.newZone == Component::getIdFromString("eight_sensor")) { | |
DBG_PRINT("Selected 8 input\n"); | |
m_exitDoorSelectedNum = '8'; | |
} | |
else if (e.newZone == Component::getIdFromString("nine_sensor")) { | |
DBG_PRINT("Selected 9 input\n"); | |
m_exitDoorSelectedNum = '9'; | |
} | |
} | |
}}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_destroyed", | |
[this](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EEntityDestroyed&>(e_); | |
if (e.entityId == Component::getIdFromString("larry")) { | |
m_eventSystem.fire(pEvent_t(new Event("youveGotMail/larryKilled"))); | |
} | |
}}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_destroyed", | |
[this](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EEntityDestroyed&>(e_); | |
if (e.entityId == Component::getIdFromString("larry")) { | |
m_eventSystem.fire(pEvent_t(new Event("youveGotMail/larryKilled"))); | |
} | |
}}); | |
int serversDestroyed = 0; | |
events->broadcastedEventHandlers.push_back(EventHandler{"server_destroyed", | |
[this, serversDestroyed](const GameEvent&) mutable { | |
if (++serversDestroyed == 2) { | |
m_eventSystem.fire(pEvent_t(new Event("youveGotMail/serversDestroyed"))); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
drawExitDoorDigitDisplay(); | |
DBG_PRINT("Exit door code: " << exitDoorCode << "\n"); | |
} | |
//=========================================== | |
// GameLogic::drawExitDoorDigitDisplay | |
//=========================================== | |
void GameLogic::drawExitDoorDigitDisplay() { | |
RenderSystem& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
Texture& tex = renderSystem.rg.textures.at("number"); | |
QFont font; | |
font.setPixelSize(14); | |
QPainter painter(&tex.image); | |
painter.setFont(font); | |
painter.setPen(QColor(0, 255, 0)); | |
tex.image.fill(QColor(20, 20, 50)); | |
painter.drawText(8, 16, m_exitDoorInput.c_str()); | |
} | |
//=========================================== | |
// GameLogic::onDivByZero | |
//=========================================== | |
void GameLogic::onDivByZero(const Event&) { | |
m_entityManager.broadcastEvent(GameEvent("div_by_zero")); | |
} | |
//=========================================== | |
// GameLogic::~GameLogic | |
//=========================================== | |
GameLogic::~GameLogic() {} | |
} | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/object_factory.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/c_elevator_behaviour.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/audio_service.hpp" | |
using std::vector; | |
using std::set; | |
using std::string; | |
namespace t_minus_two_minutes { | |
//=========================================== | |
// ObjectFactory::ObjectFactory | |
//=========================================== | |
ObjectFactory::ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
TimeService& timeService, AudioService& audioService) | |
: m_rootFactory(rootFactory), | |
m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_audioService(audioService) {} | |
//=========================================== | |
// ObjectFactory::types | |
//=========================================== | |
const set<string>& ObjectFactory::types() const { | |
static const set<string> types{"covfefe", "cog", "smoke", "bridge_section"}; | |
return types; | |
} | |
//=========================================== | |
// ObjectFactory::constructCovfefe | |
//=========================================== | |
bool ObjectFactory::constructCovfefe(entityId_t entityId, parser::Object& obj, entityId_t region, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "covfefe"; | |
if (m_rootFactory.constructObject("collectable_item", entityId, obj, region, parentTransform)) { | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CFocus* focus = new CFocus(entityId); | |
focus->captionText = "Press C to throw the covfefe"; | |
focusSystem.addComponent(pComponent_t(focus)); | |
CEventHandler* events = nullptr; | |
if (eventHandlerSystem.hasComponent(entityId)) { | |
events = &dynamic_cast<CEventHandler&>(eventHandlerSystem.getComponent(entityId)); | |
} | |
else { | |
events = new CEventHandler(entityId); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
} | |
events->targetedEventHandlers.push_back(EventHandler{"item_collected", | |
[entityId, &focusSystem](const GameEvent&) { | |
focusSystem.showCaption(entityId); | |
}}); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructBridgeSection | |
//=========================================== | |
bool ObjectFactory::constructBridgeSection(entityId_t entityId, parser::Object& obj, | |
entityId_t region, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("elevator", entityId, obj, region, parentTransform)) { | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
auto& behaviour = m_entityManager.getComponent<CElevatorBehaviour>(entityId, | |
ComponentKind::C_BEHAVIOUR); | |
CEventHandler* events = nullptr; | |
if (eventHandlerSystem.hasComponent(entityId)) { | |
events = &dynamic_cast<CEventHandler&>(eventHandlerSystem.getComponent(entityId)); | |
} | |
else { | |
events = new CEventHandler(entityId); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
} | |
events->broadcastedEventHandlers.push_back(EventHandler{"t_minus_two_minutes/machine_jammed", | |
[&behaviour](const GameEvent&) { | |
behaviour.move(0); | |
}}); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructCog | |
//=========================================== | |
bool ObjectFactory::constructCog(entityId_t entityId, parser::Object& obj, entityId_t region, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "cog"; | |
if (m_rootFactory.constructObject("wall_decal", entityId, obj, region, parentTransform)) { | |
auto& animationSystem = m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CAnimation* anim = new CAnimation(entityId); | |
// Number of frames in sprite sheet | |
const int W = 1; | |
const int H = 8; | |
vector<AnimationFrame> frames = constructFrames(W, H, { 0, 1, 2, 3, 4, 5, 6, 7 }); | |
anim->addAnimation(pAnimation_t(new Animation("idle", m_timeService.frameRate, 1.0, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "idle", true); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"t_minus_two_minutes/machine_jammed", | |
[entityId, &animationSystem](const GameEvent&) { | |
animationSystem.stopAnimation(entityId); | |
}}); | |
events->targetedEventHandlers.push_back(EventHandler{"t_minus_two_minutes/covfefe_impact", | |
[this](const GameEvent&) { | |
m_entityManager.broadcastEvent(GameEvent{"t_minus_two_minutes/machine_jammed"}); | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructSmoke | |
//=========================================== | |
bool ObjectFactory::constructSmoke(entityId_t entityId, parser::Object& obj, entityId_t region, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["texture"] = "smoke"; | |
if (m_rootFactory.constructObject("sprite", entityId, obj, region, parentTransform)) { | |
auto& animationSystem = m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CAnimation* anim = new CAnimation(entityId); | |
// Number of frames in sprite sheet | |
const int W = 1; | |
const int H = 3; | |
vector<AnimationFrame> frames = constructFrames(W, H, { 0, 1, 2 }); | |
anim->addAnimation(pAnimation_t(new Animation("idle", m_timeService.frameRate, 1.0, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "idle", true); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"t_minus_two_minutes/machine_jammed", | |
[this, entityId](const GameEvent&) { | |
m_entityManager.deleteEntity(entityId); | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructObject | |
//=========================================== | |
bool ObjectFactory::constructObject(const string& type, entityId_t entityId, | |
parser::Object& obj, entityId_t region, const Matrix& parentTransform) { | |
if (type == "covfefe") { | |
return constructCovfefe(entityId, obj, region, parentTransform); | |
} | |
else if (type == "cog") { | |
return constructCog(entityId, obj, region, parentTransform); | |
} | |
else if (type == "smoke") { | |
return constructSmoke(entityId, obj, region, parentTransform); | |
} | |
else if (type == "bridge_section") { | |
return constructBridgeSection(entityId, obj, region, parentTransform); | |
} | |
return false; | |
} | |
} | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "fragments/f_main/f_app_dialog/f_app_dialog.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/f_file_system_2.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/f_file_system_2_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/object_factory.hpp" | |
#include "raycast/render_system.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
#include "state_ids.hpp" | |
//=========================================== | |
// FFileSystem2::FFileSystem2 | |
//=========================================== | |
FFileSystem2::FFileSystem2(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FFileSystem2", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FFileSystem2::FFileSystem2\n"); | |
m_hDialogClosed = commonData.eventSystem.listen("dialogClosed", | |
[&commonData](const Event& event) { | |
const DialogClosedEvent& e = dynamic_cast<const DialogClosedEvent&>(event); | |
if (e.name == "fileBrowser") { | |
commonData.eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_T_MINUS_TWO_MINUTES, | |
true))); | |
} | |
}); | |
} | |
//=========================================== | |
// FFileSystem2::reload | |
//=========================================== | |
void FFileSystem2::reload(const FragmentSpec&) { | |
DBG_PRINT("FFileSystem2::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
if (m_data.vbox) { | |
delete m_data.vbox.release(); | |
} | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
m_data.vbox->setSpacing(0); | |
m_data.vbox->setContentsMargins(0, 0, 0, 0); | |
setLayout(m_data.vbox.get()); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
m_data.wgtRaycast = makeQtObjPtr<RaycastWidget>(commonData.appConfig, commonData.eventSystem); | |
auto& rootFactory = m_data.wgtRaycast->rootFactory(); | |
auto& timeService = m_data.wgtRaycast->timeService(); | |
auto& entityManager = m_data.wgtRaycast->entityManager(); | |
auto& audioService = m_data.wgtRaycast->audioService(); | |
GameObjectFactory* factory = new t_minus_two_minutes::ObjectFactory(rootFactory, entityManager, | |
timeService, audioService); | |
m_data.wgtRaycast->rootFactory().addFactory(pGameObjectFactory_t(factory)); | |
m_data.wgtRaycast->initialise(commonData.appConfig.dataPath("t_minus_two_minutes/map.svg")); | |
m_data.gameLogic.reset(new t_minus_two_minutes::GameLogic(commonData.eventSystem, audioService, | |
timeService, entityManager)); | |
m_data.wgtRaycast->start(); | |
m_data.vbox->addWidget(m_data.wgtRaycast.get()); | |
} | |
//=========================================== | |
// FFileSystem2::cleanUp | |
//=========================================== | |
void FFileSystem2::cleanUp() { | |
DBG_PRINT("FFileSystem2::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
} | |
//=========================================== | |
// FFileSystem2::~FFileSystem2 | |
//=========================================== | |
FFileSystem2::~FFileSystem2() { | |
DBG_PRINT("FFileSystem2::~FFileSystem2\n"); | |
} | |
#include <sstream> | |
#include <iomanip> | |
#include "fragments/f_main/f_app_dialog/f_file_system_2/game_logic.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/audio_service.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/c_switch_behaviour.hpp" | |
#include "event_system.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::string; | |
using std::map; | |
using std::set; | |
using std::vector; | |
using std::stringstream; | |
namespace t_minus_two_minutes { | |
//=========================================== | |
// GameLogic::GameLogic | |
//=========================================== | |
GameLogic::GameLogic(EventSystem& eventSystem, AudioService& audioService, TimeService& timeService, | |
EntityManager& entityManager) | |
: m_eventSystem(eventSystem), | |
m_audioService(audioService), | |
m_timeService(timeService), | |
m_entityManager(entityManager) { | |
DBG_PRINT("GameLogic::GameLogic\n"); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
m_entityId = Component::getNextId(); | |
CFocus* focus = new CFocus(m_entityId); | |
focus->captionText = "The covfefe has jammed the machine"; | |
focusSystem.addComponent(pComponent_t(focus)); | |
CEventHandler* events = new CEventHandler(m_entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"key_pressed", | |
[this](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EKeyPressed&>(e_); | |
if (e.key == Qt::Key_C) { | |
useCovfefe(); | |
} | |
}}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"t_minus_two_minutes/machine_jammed", | |
[this, &focusSystem](const GameEvent&) { | |
DBG_PRINT("Machine jammed\n"); | |
m_entityManager.deleteEntity(Component::getIdFromString("covfefe")); | |
focusSystem.showCaption(m_entityId); | |
m_entityManager.fireEvent(GameEvent{"disable_sound_source"}, | |
{ Component::getIdFromString("machine_sound") }); | |
}}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"switch_activated", | |
[this](const GameEvent& e_) { | |
auto& e = dynamic_cast<const ESwitchActivate&>(e_); | |
if (e.switchEntityId == Component::getIdFromString("final_switch")) { | |
DBG_PRINT("Mission accomplished!\n"); | |
m_missionAccomplished = true; | |
fadeToBlack(); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
setupTimer(); | |
} | |
//=========================================== | |
// GameLogic::fadeToBlack | |
//=========================================== | |
void GameLogic::fadeToBlack() { | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
entityId_t entityId = Component::getNextId(); | |
CColourOverlay* overlay = new CColourOverlay(entityId, QColor{0, 0, 0, 0}, Point{0, 0}, | |
renderSystem.rg.viewport, 1); | |
renderSystem.addComponent(pComponent_t(overlay)); | |
double alpha = 0; | |
int nFrames = 20; | |
double t = 3.0; | |
double delta = 255.0 / nFrames; | |
m_fadeOutHandle = m_timeService.atIntervals([=]() mutable -> bool { | |
alpha += delta; | |
overlay->colour = QColor{0, 0, 0, static_cast<int>(alpha)}; | |
return alpha < 255.0; | |
}, t / nFrames); | |
m_entityManager.broadcastEvent(GameEvent{"immobilise_player"}); | |
} | |
//=========================================== | |
// GameLogic::onMidnight | |
//=========================================== | |
void GameLogic::onMidnight() { | |
if (m_missionAccomplished) { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_BACK_TO_NORMAL))); | |
} | |
else { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_T_MINUS_TWO_MINUTES))); | |
} | |
} | |
//=========================================== | |
// GameLogic::updateTimer | |
//=========================================== | |
void GameLogic::updateTimer() { | |
static auto fnFormatTimePart = [](int n) { | |
stringstream ss; | |
ss << std::setw(2) << std::setfill('0') << n; | |
return ss.str(); | |
}; | |
int minutes = m_timeRemaining / 60; | |
int seconds = m_timeRemaining % 60; | |
std::stringstream ss; | |
if (m_timeRemaining > -1) { | |
ss << "23:" << fnFormatTimePart(59 - minutes) << ":" << fnFormatTimePart(59 - seconds); | |
} | |
else { | |
ss << "00:00:00"; | |
} | |
auto& overlay = m_entityManager.getComponent<CTextOverlay>(m_entityId, ComponentKind::C_RENDER); | |
overlay.text = ss.str(); | |
} | |
//=========================================== | |
// GameLogic::setupTimer | |
//=========================================== | |
void GameLogic::setupTimer() { | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
m_timeRemaining = 120; | |
m_timerHandle = m_timeService.atIntervals([this]() -> bool { | |
m_timeRemaining--; | |
updateTimer(); | |
if (m_timeRemaining == -1) { | |
onMidnight(); | |
} | |
if (m_timeRemaining == 4) { | |
m_audioService.stopMusic(4); | |
} | |
return m_timeRemaining > -1; | |
}, 1.0); | |
CTextOverlay* overlay = new CTextOverlay(m_entityId, "00:00:00", Point{0.0, 7.5}, 1.0, Qt::green, | |
2); | |
renderSystem.centreTextOverlay(*overlay); | |
renderSystem.addComponent(pComponent_t(overlay)); | |
updateTimer(); | |
} | |
//=========================================== | |
// GameLogic::useCovfefe | |
//=========================================== | |
void GameLogic::useCovfefe() { | |
entityId_t covfefeId = Component::getIdFromString("covfefe"); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
auto& inventorySystem = m_entityManager.system<InventorySystem>(ComponentKind::C_INVENTORY); | |
const Player& player = *spatialSystem.sg.player; | |
auto& playerBody = dynamic_cast<const CVRect&>(spatialSystem.getComponent(player.body)); | |
const map<string, entityId_t>& items = inventorySystem.getBucketItems(player.body, "item"); | |
if (contains(items, string{"covfefe"})) { | |
DBG_PRINT("Using covfefe\n"); | |
m_audioService.playSound("covfefe"); | |
inventorySystem.removeFromBucket(player.body, "item", "covfefe"); | |
double distance = 100.0; | |
double margin = 10.0; | |
Point pos = playerBody.pos + player.dir() * distance; | |
vector<pIntersection_t> intersections = spatialSystem.entitiesAlongRay(Vec2f{1, 0}); | |
if (intersections.front()->distanceFromOrigin - margin < distance) { | |
pos = intersections.front()->point_wld - player.dir() * margin; | |
} | |
spatialSystem.relocateEntity(covfefeId, *playerBody.zone, pos); | |
double heightAboveFloor = 50.0; | |
set<entityId_t> entities = spatialSystem.entitiesInRadius(*playerBody.zone, pos, 50.0, | |
heightAboveFloor); | |
m_entityManager.fireEvent(GameEvent{"t_minus_two_minutes/covfefe_impact"}, entities); | |
} | |
} | |
//=========================================== | |
// GameLogic::~GameLogic | |
//=========================================== | |
GameLogic::~GameLogic() { | |
m_timeService.cancelTimeout(m_timerHandle); | |
m_timeService.cancelTimeout(m_fadeOutHandle); | |
} | |
} | |
#include <vector> | |
#include <array> | |
#include <QPushButton> | |
#include <QHeaderView> | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_app_dialog/f_mail_client/f_mail_client.hpp" | |
#include "fragments/f_main/f_app_dialog/f_mail_client/f_mail_client_spec.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::vector; | |
using std::to_string; | |
namespace youve_got_mail { | |
extern const std::string exitDoorCode; | |
} | |
struct Email { | |
QString subject; | |
QString from; | |
QString to; | |
QString date; | |
QString subjectGarbled; | |
QString fromGarbled; | |
QString dateGarbled; | |
QString body; | |
QString attachment; | |
}; | |
// Emails in chronological order | |
static std::array<Email, 11> EMAILS = {{ | |
// 0 | |
{ | |
"Meeting to discuss y2k issue", | |
"Brian Williams", | |
"devteam", | |
"11/03/96 12:55:36", | |
"M̨e̴eti͟ng ҉to d͜i̵sc̛u̕ss̀ y2ḱ i̛ssúe̸", | |
"B̡r̸i̶a͘n ̨W̢iļl̸i̕a͠m҉s", | |
"11͟/̧03̧/̛96̷ ͠1̢2:͝5̷5̀:͏3͏6҉", | |
// Message body | |
// | |
"I've arranged a meeting for tomorrow afternoon to discuss the y2k or \"millennium bug\" " | |
"issue. See the attached document for a brief overview.\n\n" | |
"Brian Williams,\n" | |
"Lead Kernel Developer,\n" | |
"Apex Systems\n", | |
"y2k_threat.doc" | |
}, | |
// 1 | |
{ | |
"Potentially dangerous app - remove ASAP", | |
"Alan Shand", | |
"devteam", | |
"12/03/96 13:06:44", | |
"Pǫt̡entia҉l̵ly̕ ̢dangero͢u̵s̴ ͏app̡ ̕-͝ re̛m̴o͜vę A̕SAP", | |
"Al̛a̕n S̵han̡ḑ", | |
"1̵2/͞0͜3̸/̛96͡ ́13:͘0͠6:4͜4", | |
// Message body | |
// | |
"Just a quick update before I leave.\n\n" | |
"This is an urgent request to retract validation of Pro Office Calculator due to potentially " | |
"dangerous behaviour not uncovered during initial testing. I've had Brian and his team look " | |
"into this. It's clearly doing something odd, but they're unsure what.\n\n" | |
"In my absence, please ensure this application does NOT ship with the upcoming release of Apex " | |
"OS.\n\n" | |
"See you all next week.\n\n" | |
"Alan Shand,\n" | |
"Chief QA Officer,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
// 2 | |
{ | |
"Quick question", | |
"Alan Shand", | |
"devteam", | |
"12/03/96 13:08:17", | |
"Q͏uic͟k̢ q̷u͝e̛sţio͝ņ", | |
"Àl͞a̴n̨ Sha̕nd", | |
"12/̀03͞/͞9̀5̨ ͘1͟3:͢08:1̀7", | |
// Message body | |
// | |
"Sorry, one more thing. Is Rob Jinman one of our ex-contractors?\n\n" | |
"Alan Shand,\n" | |
"Chief QA Officer,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
// Outage happens here when you destroy the servers, which erases the last three messages. | |
// Mysteriously, an email from Rob Jinman seems to have appeared while the servers were down. | |
// | |
// 3 | |
{ | |
"Please Read", | |
"Rob Jinman", | |
"larry", | |
"12/03/96 13:22:20", | |
"Please Read", | |
"Rob Jinman", | |
"12/03/96 13:22:20", | |
// Message body | |
// | |
"Good work. The code for the exit door is EXIT_DOOR_CODE.\n\n" | |
"Rob\n", | |
"" | |
}, | |
// Servers come back online | |
// | |
// 4 | |
{ | |
"Mail server outage", | |
"Richard Nelms", | |
"all", | |
"12/03/96 14:01:23", | |
"Mail server outage", | |
"Richard Nelms", | |
"12/03/96 14:01:23", | |
// Message body | |
// | |
"We had an outage of the mail servers this afternoon between around 13:10 and 13:40. It's " | |
"likely that some mail was lost.\n\n" | |
"Richard Nelms,\n" | |
"Systems Administator,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
// 5 | |
{ | |
"Larry?", | |
"Daniel Bright", | |
"Me", | |
"12/03/96 13:09:25", | |
"L͝a͡rry̢?̛", | |
"Dani̴el̨ B̀r̴ig̸h̸t̡", | |
"12/03́/͢9͡6҉ 13:̀09:̕25̶", | |
// Message body | |
// | |
"You weren't in the meeting earlier. Did you forget? Dave was trying to get hold of you this " | |
"afternoon also. In future could you let someone know before disappearing like that?\n\n" | |
"Daniel Bright,\n" | |
"Application Development Manager,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
// 6 | |
{ | |
"Re: Quick question", | |
"Paul Gresham", | |
"devteam", | |
"12/03/96 14:04:35", | |
"Ŗe: ̨Q͟uick ̴q̛u̸esti͝ón̨", | |
"Paul͢ ̨Gr͘es̵ham̷", | |
"12/̴03̧/96͢ 1̢4:0̕4̵:͝3̢5", | |
// Message body | |
// | |
"Hi Alan,\n\n" | |
"Yes, he one day dropped off the radar and we haven't seen him since - never even got his last " | |
"invoice IIRC.\n\n" | |
"Paul Gresham,\n" | |
"Systems Architect,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
// 7 | |
{ | |
"Latest regarding Pro Office Calc", | |
"Brian Williams", | |
"devteam", | |
"12/03/96 16:20:09", | |
"La̧tes҉t r͟eg̴ard͢iǹg Pr̡o ͏Of̕fíc͏e ͞Cal̛ç", | |
"B̀ri̧a͘n̢ ̷W̷i̢l̕l̨i͞a̕ms̢", | |
"12/̧0҉3͢/̡9̶6 ̶1́6:̧2̨0͟:0̧9", | |
// Message body | |
// | |
"It's using library functions and system calls that weren't implemented at the time the app " | |
"was submitted. That doesn't make any sense.\n\n" | |
"Brian Williams,\n" | |
"Lead Kernel Developer,\n" | |
"Apex Systems\n", | |
"syscalls.txt" | |
}, | |
// 8 | |
{ | |
"re: Latest regarding Pro Office Calc", | |
"Michael Considine", | |
"devteam", | |
"12/03/96 16:38:12", | |
"r͟e͏: ҉Latest r͏e͞ga͢r͞dińg ͝P̛ro͠ O͡f̴fic͏e̴ ͝C͟a͝l͠c̀", | |
"M̀i̧çha͜e͟l͡ ̵Con͢s͡id͜i҉n͡e", | |
"12/͡0͏3̧/96͘ ͠16:38:̀12", | |
// Message body | |
// | |
"We hadn't even thought of those functions yet. How is that possible?\n\n" | |
"Michael Considine,\n" | |
"Senior Kernel Developer,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
// 9 | |
{ | |
"re: re: Latest regarding Pro Office Calc", | |
"Brian Williams", | |
"devteam", | |
"12/03/96 16:42:19", | |
"re: re:͘ ̡Ļate̡s̢t ̴r̴e̶gar̴d͝ing̛ ͞P͠r͏ó O̴f҉f͜ice̴ Ca̵lc", | |
"Br̨i̵an W̛i̴l͜l̴i̕am̷s͢", | |
"1̴2/0̀3/͡96̀ ́16:42:19", | |
// Message body | |
// | |
"It's not.\n\n" | |
"Brian Williams,\n" | |
"Lead Kernel Developer,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
// 10 | |
{ | |
"y2k issue", | |
"Brian Williams", | |
"devteam", | |
"12/03/96 17:10:58", | |
"y2k issue", | |
"Brian Williams", | |
"12/03/96 17:10:58", | |
// Message body | |
// | |
"Following our meeting earlier, it's become clear that the Y2K problem should take priority " | |
"over the coming weeks. Expect changes in your schedule/workload to reflect this.\n\n" | |
"Brian Williams,\n" | |
"Lead Kernel Developer,\n" | |
"Apex Systems\n", | |
"" | |
}, | |
}}; | |
const vector<vector<int>> INBOX_STATES = { | |
{ 0, 1, 2, 6, 7, 8, 9 }, // ST_INITIAL | |
{ 0, 1, 2, 5, 6, 7, 8, 9 }, // ST_LARRY_DEAD | |
{ 0, 3, 4, 5, 10 } // ST_SERVERS_DESTROYED | |
}; | |
//=========================================== | |
// getEmails | |
//=========================================== | |
static vector<const Email*> getEmails(int state) { | |
vector<const Email*> emails; | |
for (int idx : INBOX_STATES[state]) { | |
emails.push_back(&EMAILS[idx]); | |
} | |
return emails; | |
} | |
//=========================================== | |
// FMailClient::FMailClient | |
//=========================================== | |
FMailClient::FMailClient(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FMailClient", parent_, parentData_, m_data, commonData) { | |
for (auto& email : EMAILS) { | |
email.body.replace("EXIT_DOOR_CODE", youve_got_mail::exitDoorCode.c_str()); | |
} | |
DBG_PRINT("FMailClient::FMailClient\n"); | |
} | |
//=========================================== | |
// FMailClient::reload | |
//=========================================== | |
void FMailClient::reload(const FragmentSpec&) { | |
DBG_PRINT("FMailClient::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
QFont font = commonData.appConfig.normalFont; | |
font.setPixelSize(12); | |
setFont(font); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
setLayout(m_data.vbox.get()); | |
m_data.wgtTabs = makeQtObjPtr<QTabWidget>(this); | |
m_data.wgtTabs->setTabsClosable(true); | |
m_data.vbox->addWidget(m_data.wgtTabs.get()); | |
setupInboxTab(); | |
setupEmailTab(); | |
connect(m_data.wgtTabs.get(), SIGNAL(tabCloseRequested(int)), this, SLOT(onTabClose(int))); | |
m_hLarryKilled = commonData.eventSystem.listen("youveGotMail/larryKilled", [this](const Event&) { | |
m_inboxState = ST_LARRY_DEAD; | |
populateInbox(); | |
enableEmails(0, -1); | |
}); | |
m_hServersDestroyed = commonData.eventSystem.listen("youveGotMail/serversDestroyed", | |
[this](const Event&) { | |
m_inboxState = ST_SERVERS_DESTROYED; | |
m_data.wgtTabs->removeTab(1); | |
populateInbox(); | |
enableEmails(0, -1); | |
}); | |
} | |
//=========================================== | |
// FMailClient::onTabClose | |
//=========================================== | |
void FMailClient::onTabClose(int idx) { | |
if (idx > 0) { | |
m_data.wgtTabs->removeTab(idx); | |
} | |
} | |
//=========================================== | |
// constructTableItem | |
//=========================================== | |
static QTableWidgetItem* constructTableItem(const QString& text) { | |
static const Qt::ItemFlags disableFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | | |
Qt::ItemIsSelectable | Qt::ItemIsEditable; | |
QTableWidgetItem* item = new QTableWidgetItem(text); | |
item->setFlags(item->flags() & ~disableFlags); | |
return item; | |
} | |
//=========================================== | |
// constructInboxEntry | |
//=========================================== | |
static void constructInboxEntry(QTableWidget& table, int row, const QString& subject, | |
bool attachment, const QString& from, const QString& date) { | |
QTableWidgetItem* subItem = constructTableItem(subject); | |
QTableWidgetItem* attItem = constructTableItem(attachment ? "✓" : ""); | |
QTableWidgetItem* fromItem = constructTableItem(from); | |
QTableWidgetItem* dateItem = constructTableItem(date); | |
table.setItem(row, 0, subItem); | |
table.setItem(row, 1, attItem); | |
table.setItem(row, 2, fromItem); | |
table.setItem(row, 3, dateItem); | |
} | |
//=========================================== | |
// enableRow | |
//=========================================== | |
static void enableRow(QTableWidget& table, int row) { | |
for (int i = 0; i < table.columnCount(); ++i) { | |
QTableWidgetItem* item = table.item(row, i); | |
if (item != nullptr) { | |
item->setFlags(item->flags() | Qt::ItemIsEnabled); | |
} | |
} | |
} | |
//=========================================== | |
// FMailClient::enableEmails | |
// | |
// If num is -1, all emails will be enabled | |
//=========================================== | |
void FMailClient::enableEmails(int startIdx, int num) { | |
vector<const Email*> emails = getEmails(m_inboxState); | |
auto& inbox = m_data.inboxTab; | |
if (num == -1) { | |
num = static_cast<int>(emails.size()); | |
} | |
for (int idx = startIdx; idx < startIdx + num; ++idx) { | |
if (idx >= static_cast<int>(emails.size())) { | |
break; | |
} | |
auto& email = *emails[idx]; | |
enableRow(*inbox.wgtTable, idx); | |
inbox.wgtTable->item(idx, 0)->setText(email.subject); | |
if (email.attachment != "") { | |
inbox.wgtTable->item(idx, 1)->setText("✓"); | |
} | |
inbox.wgtTable->item(idx, 2)->setText(email.from); | |
inbox.wgtTable->item(idx, 3)->setText(email.date); | |
} | |
} | |
//=========================================== | |
// FMailClient::onCellDoubleClick | |
//=========================================== | |
void FMailClient::onCellDoubleClick(int row, int col) { | |
vector<const Email*> emails = getEmails(m_inboxState); | |
auto& email = *emails[row]; | |
auto& tab = m_data.emailTab; | |
auto& inbox = m_data.inboxTab; | |
if (inbox.wgtTable->item(row, col)->flags() & Qt::ItemIsEnabled) { | |
m_data.wgtTabs->setCurrentIndex(1); | |
tab.wgtText->setText(email.body); | |
tab.wgtFrom->setText(email.from); | |
tab.wgtDate->setText(email.date); | |
tab.wgtSubject->setText(email.subject); | |
tab.wgtAttachments->setText(email.attachment); | |
tab.wgtTo->setText(email.to); | |
m_data.wgtTabs->addTab(tab.page.get(), email.subject); | |
if (row + 1 < static_cast<int>(emails.size())) { | |
enableEmails(row + 1, 1); | |
} | |
else { | |
if (!m_serverRoomLaunched) { | |
m_serverRoomLaunched = true; | |
commonData.eventSystem.fire(pEvent_t(new Event("launchServerRoom"))); | |
} | |
} | |
} | |
m_data.wgtTabs->setCurrentIndex(1); | |
} | |
//=========================================== | |
// FMailClient::setupEmailTab | |
//=========================================== | |
void FMailClient::setupEmailTab() { | |
auto& tab = m_data.emailTab; | |
tab.page = makeQtObjPtr<QWidget>(); | |
tab.grid = makeQtObjPtr<QGridLayout>(tab.page.get()); | |
tab.wgtText = makeQtObjPtr<QTextBrowser>(); | |
QLabel* wgtFromLabel = new QLabel("From"); | |
QLabel* wgtDateLabel = new QLabel("Date"); | |
QLabel* wgtSubjectLabel = new QLabel("Subject"); | |
QLabel* wgtAttachmentsLabel = new QLabel("Attachments"); | |
QLabel* wgtToLabel = new QLabel("To"); | |
tab.wgtFrom = makeQtObjPtr<QLabel>("..."); | |
tab.wgtDate = makeQtObjPtr<QLabel>("..."); | |
tab.wgtSubject = makeQtObjPtr<QLabel>("..."); | |
tab.wgtAttachments = makeQtObjPtr<QLabel>("..."); | |
tab.wgtTo = makeQtObjPtr<QLabel>("..."); | |
QPushButton* wgtReply = new QPushButton("R̵̛e͞p͠ļ̧̀y̶̨"); | |
wgtReply->setDisabled(true); | |
QPushButton* wgtForward = new QPushButton("̨Fo̕ŗ͡w̛a̸r̵͏͟d҉"); | |
wgtForward->setDisabled(true); | |
QPushButton* wgtDelete = new QPushButton("̵D̴̀́e̴͟l͏et͠͝ȩ͟"); | |
wgtDelete->setDisabled(true); | |
tab.grid->addWidget(tab.wgtText.get(), 6, 0, 1, 4); | |
tab.grid->addWidget(wgtFromLabel, 0, 0); | |
tab.grid->addWidget(wgtDateLabel, 1, 0); | |
tab.grid->addWidget(wgtSubjectLabel, 2, 0); | |
tab.grid->addWidget(wgtToLabel, 3, 0); | |
tab.grid->addWidget(wgtAttachmentsLabel, 4, 0); | |
tab.grid->addWidget(tab.wgtFrom.get(), 0, 1, 1, 3); | |
tab.grid->addWidget(tab.wgtDate.get(), 1, 1, 1, 3); | |
tab.grid->addWidget(tab.wgtSubject.get(), 2, 1, 1, 3); | |
tab.grid->addWidget(tab.wgtTo.get(), 3, 1, 1, 3); | |
tab.grid->addWidget(tab.wgtAttachments.get(), 4, 1, 1, 3); | |
tab.grid->addWidget(wgtReply, 5, 1); | |
tab.grid->addWidget(wgtForward, 5, 2); | |
tab.grid->addWidget(wgtDelete, 5, 3); | |
} | |
//=========================================== | |
// FMailClient::populateInbox | |
//=========================================== | |
void FMailClient::populateInbox() { | |
vector<const Email*> emails = getEmails(m_inboxState); | |
QTableWidget& table = *m_data.inboxTab.wgtTable; | |
table.clearContents(); | |
table.setRowCount(static_cast<int>(emails.size())); | |
int i = 0; | |
for (auto email : emails) { | |
constructInboxEntry(table, i, email->subjectGarbled, email->attachment != "", | |
email->fromGarbled, email->dateGarbled); | |
++i; | |
} | |
table.resizeColumnToContents(3); | |
} | |
//=========================================== | |
// FMailClient::setupInboxTab | |
//=========================================== | |
void FMailClient::setupInboxTab() { | |
auto& tab = m_data.inboxTab; | |
tab.page = makeQtObjPtr<QWidget>(); | |
tab.vbox = makeQtObjPtr<QVBoxLayout>(tab.page.get()); | |
tab.wgtTable = makeQtObjPtr<QTableWidget>(0, 4); | |
tab.wgtTable->setShowGrid(false); | |
tab.wgtTable->setContextMenuPolicy(Qt::NoContextMenu); | |
tab.wgtTable->setHorizontalHeaderLabels({"Subject", "", "From", "Date"}); | |
tab.wgtTable->horizontalHeaderItem(1) | |
->setIcon(QIcon(commonData.appConfig.dataPath("youve_got_mail/attachment.png").c_str())); | |
tab.wgtTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); | |
tab.wgtTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); | |
tab.wgtTable->verticalHeader()->setVisible(false); | |
connect(tab.wgtTable.get(), SIGNAL(cellDoubleClicked(int, int)), this, | |
SLOT(onCellDoubleClick(int, int))); | |
populateInbox(); | |
enableEmails(0, 1); | |
tab.vbox->addWidget(tab.wgtTable.get()); | |
m_data.wgtTabs->addTab(tab.page.get(), "larrym@apex.com"); | |
} | |
//=========================================== | |
// FMailClient::cleanUp | |
//=========================================== | |
void FMailClient::cleanUp() { | |
DBG_PRINT("FMailClient::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FMailClient::~FMailClient | |
//=========================================== | |
FMailClient::~FMailClient() { | |
DBG_PRINT("FMailClient::~FMailClient\n"); | |
} | |
#include <string> | |
#include <vector> | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_app_dialog/f_console/f_console.hpp" | |
#include "fragments/f_main/f_app_dialog/f_console/f_console_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/events.hpp" | |
#include "utils.hpp" | |
using std::string; | |
using std::vector; | |
typedef ConsoleWidget::ArgList ArgList; | |
//=========================================== | |
// FConsole::FConsole | |
//=========================================== | |
FConsole::FConsole(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FConsole", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FConsole::FConsole\n"); | |
} | |
//=========================================== | |
// FConsole::reload | |
//=========================================== | |
void FConsole::reload(const FragmentSpec&) { | |
DBG_PRINT("FConsole::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
delete m_data.vbox.release(); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
setLayout(m_data.vbox.get()); | |
string initialContent = | |
"┌───────────────────────────────────────┐\n" | |
"│ Apex OS Terminal │\n" | |
"├───────────────────────────────────────┤\n" | |
"│ Host name zpx11 │\n" | |
"│ Date and time 1998/12/14 14:08:15 │\n" | |
"│ Logged in as susan │\n" | |
"│ Logged in since 1998/12/14 11:50:06 │\n" | |
"└───────────────────────────────────────┘\n" | |
"> "; | |
m_commandsEntered = 0; | |
m_data.wgtConsole = makeQtObjPtr<ConsoleWidget>(commonData.appConfig, initialContent, | |
vector<string>{}); | |
m_data.vbox->addWidget(m_data.wgtConsole.get()); | |
m_hCommandsGenerated = commonData.eventSystem.listen("doomsweeper/commandsGenerated", | |
[this](const Event& e_) { | |
auto& e = dynamic_cast<const doomsweeper::CommandsGeneratedEvent&>(e_); | |
addCommands(e.commands); | |
}); | |
} | |
//=========================================== | |
// FConsole::addCommands | |
//=========================================== | |
void FConsole::addCommands(const vector<vector<string>>& commands) { | |
int numCommands = static_cast<int>(commands.size()); | |
for (int i = 0; i < numCommands; ++i) { | |
auto& cmd = commands[i]; | |
const string& prog = cmd.front(); | |
vector<string> parts{cmd.begin() + 1, cmd.end()}; | |
m_data.wgtConsole->addCommand(prog, [this, parts, i, numCommands](const ArgList& args) { | |
if (args == parts && m_commandsEntered == i) { | |
m_commandsEntered = i + 1; | |
if (m_commandsEntered == numCommands) { | |
commonData.eventSystem.fire(pEvent_t(new Event{"doomsweeper/commandsEntered"})); | |
} | |
return "Success"; | |
} | |
return "Failed"; | |
}); | |
} | |
} | |
//=========================================== | |
// FConsole::cleanUp | |
//=========================================== | |
void FConsole::cleanUp() { | |
DBG_PRINT("FConsole::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FConsole::~FConsole | |
//=========================================== | |
FConsole::~FConsole() { | |
DBG_PRINT("FConsole::~FConsole\n"); | |
} | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_app_dialog/f_text_editor/f_text_editor.hpp" | |
#include "fragments/f_main/f_app_dialog/f_text_editor/f_text_editor_spec.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FTextEditor::FTextEditor | |
//=========================================== | |
FTextEditor::FTextEditor(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FTextEditor", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FTextEditor::FTextEditor\n"); | |
} | |
//=========================================== | |
// FTextEditor::reload | |
//=========================================== | |
void FTextEditor::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FTextEditor::reload\n"); | |
const auto& spec = dynamic_cast<const FTextEditorSpec&>(spec_); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
m_data.vbox = makeQtObjPtr<QVBoxLayout>(); | |
setLayout(m_data.vbox.get()); | |
m_data.wgtTextBrowser = makeQtObjPtr<QTextBrowser>(this); | |
m_data.vbox->addWidget(m_data.wgtTextBrowser.get()); | |
m_data.wgtTextBrowser->setText(spec.content); | |
} | |
//=========================================== | |
// FTextEditor::cleanUp | |
//=========================================== | |
void FTextEditor::cleanUp() { | |
DBG_PRINT("FTextEditor::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FTextEditor::~FTextEditor | |
//=========================================== | |
FTextEditor::~FTextEditor() { | |
DBG_PRINT("FTextEditor::~FTextEditor\n"); | |
} | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/object_factory.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/game_events.hpp" | |
#include "raycast/map_parser.hpp" | |
#include "raycast/animation_system.hpp" | |
#include "raycast/behaviour_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/c_door_behaviour.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/time_service.hpp" | |
#include "raycast/audio_service.hpp" | |
using std::vector; | |
using std::set; | |
using std::string; | |
namespace doomsweeper { | |
//=========================================== | |
// ObjectFactory::ObjectFactory | |
//=========================================== | |
ObjectFactory::ObjectFactory(RootFactory& rootFactory, EntityManager& entityManager, | |
TimeService& timeService, AudioService& audioService) | |
: m_rootFactory(rootFactory), | |
m_entityManager(entityManager), | |
m_timeService(timeService), | |
m_audioService(audioService) {} | |
//=========================================== | |
// ObjectFactory::types | |
//=========================================== | |
const set<string>& ObjectFactory::types() const { | |
static const set<string> types{ | |
"cell", | |
"cell_inner", | |
"cell_corner", | |
"cell_door", | |
"slime", | |
"command_screen"}; | |
return types; | |
} | |
//=========================================== | |
// ObjectFactory::constructCell | |
//=========================================== | |
bool ObjectFactory::constructCell(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (!firstPassComplete) { | |
region = parentId; | |
this->parentTransform = parentTransform; | |
this->objects.insert(make_pair(entityId, parser::pObject_t(obj.clone()))); | |
} | |
else { | |
return m_rootFactory.constructObject("region", entityId, obj, parentId, parentTransform); | |
} | |
return true; | |
} | |
//=========================================== | |
// ObjectFactory::constructCellCorner | |
//=========================================== | |
bool ObjectFactory::constructCellCorner(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
if (m_rootFactory.constructObject("v_rect", entityId, obj, parentId, parentTransform)) { | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
const CVRect& vRect = dynamic_cast<const CVRect&>(spatialSystem.getComponent(entityId)); | |
string cellName = getValue(obj.dict, "cell_name"); | |
entityId_t cellId = Component::getIdFromString(cellName); | |
DBG_PRINT("Cell '" << cellName << "'" << " is positioned at " << vRect.pos << "\n"); | |
this->objectPositions[cellId] = vRect.pos; | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructCellInner | |
//=========================================== | |
bool ObjectFactory::constructCellInner(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
string type = "region"; | |
if (getValue(obj.dict, "is_slime_pit", "false") == "true") { | |
type = "slime"; | |
} | |
if (m_rootFactory.constructObject(type, entityId, obj, parentId, parentTransform)) { | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CEventHandler* events = nullptr; | |
if (eventHandlerSystem.hasComponent(entityId)) { | |
events = &eventHandlerSystem.getComponent(entityId); | |
} | |
else { | |
events = new CEventHandler(entityId); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
} | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_changed_zone", | |
[this, entityId, parentId](const GameEvent& e_) { | |
const auto& e = dynamic_cast<const EChangedZone&>(e_); | |
if (e.newZone == entityId) { | |
m_entityManager.broadcastEvent(EPlayerEnterCellInner{parentId}); | |
} | |
}}); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructCellDoor | |
//=========================================== | |
bool ObjectFactory::constructCellDoor(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["denied_caption"] = "The door is locked"; | |
if (m_rootFactory.constructObject("door", entityId, obj, parentId, parentTransform)) { | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
auto& behaviour = | |
m_entityManager.getComponent<CDoorBehaviour>(entityId, ComponentKind::C_BEHAVIOUR); | |
behaviour.speed = 120.0; | |
behaviour.setPauseTime(1.5); | |
// Cell should be 2 levels up | |
entityId_t cellId = Component::getIdFromString(obj.parent->parent->dict.at("name")); | |
string pos = GET_VALUE(obj.dict, "position"); | |
if (pos == "north") { | |
this->cellDoors[cellId].north = entityId; | |
} | |
else if (pos == "east") { | |
this->cellDoors[cellId].east = entityId; | |
} | |
else if (pos == "south") { | |
this->cellDoors[cellId].south = entityId; | |
} | |
else if (pos == "west") { | |
this->cellDoors[cellId].west = entityId; | |
} | |
CEventHandler* events = new CEventHandler(entityId); | |
events->targetedEventHandlers.push_back(EventHandler{"door_open_start", | |
[this, cellId](const GameEvent&) { | |
m_entityManager.broadcastEvent(ECellDoorOpened{cellId}); | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructCommandScreen | |
//=========================================== | |
bool ObjectFactory::constructCommandScreen(entityId_t entityId, parser::Object& obj, | |
entityId_t parentId, const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
string cellName = obj.parent->parent->parent->dict.at("name"); | |
if (cellName == "clue_cell_0") { | |
obj.dict["texture"] = "command_0"; | |
} | |
else if (cellName == "clue_cell_1") { | |
obj.dict["texture"] = "command_1"; | |
} | |
else if (cellName == "clue_cell_2") { | |
obj.dict["texture"] = "command_2"; | |
} | |
return m_rootFactory.constructObject("wall_decal", entityId, obj, parentId, parentTransform); | |
} | |
//=========================================== | |
// ObjectFactory::constructSlime | |
//=========================================== | |
bool ObjectFactory::constructSlime(entityId_t entityId, parser::Object& obj, entityId_t parentId, | |
const Matrix& parentTransform) { | |
if (entityId == -1) { | |
entityId = makeIdForObj(obj); | |
} | |
obj.dict["floor_texture"] = "slime"; | |
if (m_rootFactory.constructObject("region", entityId, obj, parentId, parentTransform)) { | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
const Player& player = *spatialSystem.sg.player; | |
auto& damageSystem = m_entityManager.system<DamageSystem>(ComponentKind::C_DAMAGE); | |
auto& animationSystem = m_entityManager.system<AnimationSystem>(ComponentKind::C_ANIMATION); | |
CAnimation* anim = new CAnimation(entityId); | |
vector<AnimationFrame> frames = constructFrames(1, 3, { 0, 1, 2 }); | |
anim->addAnimation(pAnimation_t(new Animation("gurgle", m_timeService.frameRate, 1.0, frames))); | |
animationSystem.addComponent(pComponent_t(anim)); | |
animationSystem.playAnimation(entityId, "gurgle", true); | |
CEventHandler* events = new CEventHandler(entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_changed_zone", | |
[=, &damageSystem, &player](const GameEvent& e_) mutable { | |
auto& e = dynamic_cast<const EChangedZone&>(e_); | |
if (e.newZone == entityId) { | |
m_timeService.atIntervals([=, &player, &damageSystem]() { | |
if (player.region() == e.newZone && !player.aboveGround()) { | |
damageSystem.damageEntity(player.body, 1); | |
} | |
return player.region() == entityId; | |
}, 0.5); | |
} | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
return true; | |
} | |
return false; | |
} | |
//=========================================== | |
// ObjectFactory::constructObject | |
//=========================================== | |
bool ObjectFactory::constructObject(const string& type, entityId_t entityId, | |
parser::Object& obj, entityId_t region, const Matrix& parentTransform) { | |
if (type == "cell") { | |
return constructCell(entityId, obj, region, parentTransform); | |
} | |
else if (type == "cell_inner") { | |
return constructCellInner(entityId, obj, region, parentTransform); | |
} | |
else if (type == "cell_door") { | |
return constructCellDoor(entityId, obj, region, parentTransform); | |
} | |
else if (type == "cell_corner") { | |
return constructCellCorner(entityId, obj, region, parentTransform); | |
} | |
else if (type == "slime") { | |
return constructSlime(entityId, obj, region, parentTransform); | |
} | |
else if (type == "command_screen") { | |
return constructCommandScreen(entityId, obj, region, parentTransform); | |
} | |
return false; | |
} | |
} | |
#include <chrono> | |
#include <regex> | |
#include <QHeaderView> | |
#include <QLineEdit> | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/f_doomsweeper.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/f_doomsweeper_spec.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/object_factory.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/events.hpp" | |
#include "raycast/render_system.hpp" | |
#include "utils.hpp" | |
#include "update_loop.hpp" | |
#include "app_config.hpp" | |
#include "state_ids.hpp" | |
using std::string; | |
//=========================================== | |
// FDoomsweeper::FDoomsweeper | |
//=========================================== | |
FDoomsweeper::FDoomsweeper(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FDoomsweeper", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FDoomsweeper::FDoomsweeper\n"); | |
} | |
//=========================================== | |
// FDoomsweeper::reload | |
//=========================================== | |
void FDoomsweeper::reload(const FragmentSpec&) { | |
DBG_PRINT("FDoomsweeper::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
if (m_data.stackedLayout) { | |
delete m_data.stackedLayout.release(); | |
} | |
m_data.stackedLayout = makeQtObjPtr<QStackedLayout>(); | |
setLayout(m_data.stackedLayout.get()); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
setupRaycastPage(); | |
setupHighScorePage(); | |
m_data.stackedLayout->setCurrentIndex(0); | |
m_hLevelComplete = commonData.eventSystem.listen("doomsweeper/levelComplete", | |
[this](const Event&) { | |
m_data.stackedLayout->setCurrentIndex(1); | |
}); | |
} | |
//=========================================== | |
// FDoomsweeper::setupRaycastPage | |
//=========================================== | |
void FDoomsweeper::setupRaycastPage() { | |
auto& page = m_data.raycastPage; | |
page.wgtRaycast = makeQtObjPtr<RaycastWidget>(commonData.appConfig, commonData.eventSystem); | |
auto& rootFactory = page.wgtRaycast->rootFactory(); | |
auto& timeService = page.wgtRaycast->timeService(); | |
auto& entityManager = page.wgtRaycast->entityManager(); | |
auto& audioService = page.wgtRaycast->audioService(); | |
auto factory = new doomsweeper::ObjectFactory(rootFactory, entityManager, timeService, | |
audioService); | |
page.wgtRaycast->rootFactory().addFactory(pGameObjectFactory_t(factory)); | |
page.wgtRaycast->initialise(commonData.appConfig.dataPath("doomsweeper/map.svg")); | |
page.gameLogic.reset(new doomsweeper::GameLogic(commonData.eventSystem, | |
entityManager, page.wgtRaycast->rootFactory(), *factory, timeService)); | |
m_hSetup = commonData.eventSystem.listen("doomsweeper/minesweeperSetupComplete", | |
[=](const Event& e_) { | |
auto& e = dynamic_cast<const doomsweeper::MinesweeperSetupEvent&>(e_); | |
m_initFuture = m_data.raycastPage.gameLogic->initialise(e.mineCoords); | |
DBG_PRINT("Initialising game logic...\n"); | |
commonData.updateLoop.add([this]() -> bool { | |
return waitForInit(); | |
}, []() { | |
DBG_PRINT("DONE initialising game logic\n"); | |
}); | |
}); | |
m_data.stackedLayout->addWidget(page.wgtRaycast.get()); | |
} | |
//=========================================== | |
// constructTableItem | |
//=========================================== | |
static QTableWidgetItem* constructTableItem(const QString& text) { | |
Qt::ItemFlags disableFlags = Qt::ItemIsSelectable | Qt::ItemIsEditable; | |
QTableWidgetItem* item = new QTableWidgetItem(text); | |
item->setFlags(item->flags() & ~disableFlags); | |
return item; | |
} | |
//=========================================== | |
// constructNameItem | |
//=========================================== | |
static QWidget* constructNameItem(const QString& text) { | |
QLineEdit* item = new QLineEdit(text); | |
QPalette p = item->palette(); | |
p.setColor(QPalette::Base, Qt::green); | |
item->setPalette(p); | |
item->setToolTip("Enter your name to continue"); | |
return item; | |
} | |
//=========================================== | |
// FDoomsweeper::setupHighScorePage | |
//=========================================== | |
void FDoomsweeper::setupHighScorePage() { | |
auto& page = m_data.highScorePage; | |
page.widget = makeQtObjPtr<QWidget>(); | |
page.vbox = makeQtObjPtr<QVBoxLayout>(); | |
page.widget->setLayout(page.vbox.get()); | |
page.wgtLabel = makeQtObjPtr<QLabel>("New high score!"); | |
page.wgtTable = makeQtObjPtr<QTableWidget>(4, 2); | |
page.wgtTable->setShowGrid(true); | |
page.wgtTable->setContextMenuPolicy(Qt::NoContextMenu); | |
page.wgtTable->setHorizontalHeaderLabels({"Name", "Score"}); | |
page.wgtTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); | |
page.wgtTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); | |
page.wgtTable->verticalHeader()->setVisible(true); | |
page.wgtTable->setItem(0, 0, constructTableItem("Dave Smith")); | |
page.wgtTable->setItem(0, 1, constructTableItem("4412")); | |
auto nameItem = constructNameItem("ENTER YOUR NAME"); | |
page.wgtTable->setCellWidget(1, 0, nameItem); | |
auto scoreItem = constructTableItem("4052"); | |
scoreItem->setBackground(Qt::green); | |
page.wgtTable->setItem(1, 1, scoreItem); | |
page.wgtTable->setItem(2, 0, constructTableItem("Claire Pilch")); | |
page.wgtTable->setItem(2, 1, constructTableItem("3787")); | |
page.wgtTable->setItem(3, 0, constructTableItem("Herman Lewis")); | |
page.wgtTable->setItem(3, 1, constructTableItem("3110")); | |
page.wgtContinue = makeQtObjPtr<QPushButton>("Continue"); | |
page.wgtContinue->setToolTip("Enter your name to continue"); | |
page.wgtContinue->setDisabled(true); | |
page.vbox->addWidget(page.wgtLabel.get()); | |
page.vbox->addWidget(page.wgtTable.get()); | |
page.vbox->addWidget(page.wgtContinue.get()); | |
m_data.stackedLayout->addWidget(page.widget.get()); | |
connect(nameItem, SIGNAL(textChanged(QString)), this, SLOT(onTableEdit())); | |
connect(page.wgtContinue.get(), SIGNAL(clicked()), this, SLOT(onContinueClick())); | |
} | |
//=========================================== | |
// FDoomsweeper::onTableEdit | |
//=========================================== | |
void FDoomsweeper::onTableEdit() { | |
const int MIN_SZ = 5; | |
const int MAX_SZ = 28; | |
auto& page = m_data.highScorePage; | |
auto item = dynamic_cast<const QLineEdit*>(page.wgtTable->cellWidget(1, 0)); | |
m_playerName = item->text().toStdString(); | |
std::regex rxName{"^[a-zA-Z]+(?:\\s[a-zA-Z]+)+$"}; | |
std::smatch m; | |
if (m_playerName != "ENTER YOUR NAME" | |
&& m_playerName.size() >= MIN_SZ && m_playerName.size() <= MAX_SZ | |
&& std::regex_match(m_playerName, m, rxName)) { | |
page.wgtContinue->setDisabled(false); | |
} | |
else { | |
page.wgtContinue->setDisabled(true); | |
} | |
} | |
//=========================================== | |
// FDoomsweeper::onContinueClick | |
//=========================================== | |
void FDoomsweeper::onContinueClick() { | |
commonData.eventSystem.fire(pEvent_t(new SetConfigParamEvent{"player-name", m_playerName})); | |
commonData.eventSystem.fire(pEvent_t(new RequestStateChangeEvent{ST_T_MINUS_TWO_MINUTES})); | |
} | |
//=========================================== | |
// FDoomsweeper::waitForInit | |
//=========================================== | |
bool FDoomsweeper::waitForInit() { | |
auto status = m_initFuture.wait_for(std::chrono::milliseconds(0)); | |
if (status == std::future_status::ready) { | |
m_initFuture.get(); | |
m_data.raycastPage.wgtRaycast->start(); | |
return false; | |
} | |
return true; | |
} | |
//=========================================== | |
// FDoomsweeper::cleanUp | |
//=========================================== | |
void FDoomsweeper::cleanUp() { | |
DBG_PRINT("FDoomsweeper::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
} | |
//=========================================== | |
// FDoomsweeper::~FDoomsweeper | |
//=========================================== | |
FDoomsweeper::~FDoomsweeper() { | |
DBG_PRINT("FDoomsweeper::~FDoomsweeper\n"); | |
} | |
#include <sstream> | |
#include <vector> | |
#include <random> | |
#include <map> | |
#include <cassert> | |
#include <QPainter> | |
#include <QFont> | |
#include "fragments/f_main/f_app_dialog/f_app_dialog.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/game_logic.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/game_events.hpp" | |
#include "fragments/f_main/f_app_dialog/f_doomsweeper/object_factory.hpp" | |
#include "raycast/entity_manager.hpp" | |
#include "raycast/root_factory.hpp" | |
#include "raycast/spatial_system.hpp" | |
#include "raycast/event_handler_system.hpp" | |
#include "raycast/render_system.hpp" | |
#include "raycast/damage_system.hpp" | |
#include "raycast/inventory_system.hpp" | |
#include "raycast/focus_system.hpp" | |
#include "raycast/agent_system.hpp" | |
#include "raycast/c_door_behaviour.hpp" | |
#include "raycast/time_service.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::vector; | |
using std::set; | |
using std::string; | |
using std::stringstream; | |
using std::map; | |
namespace doomsweeper { | |
static const int ROWS = 8; | |
static const int COLS = 8; | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// GameLogic::GameLogic | |
//=========================================== | |
GameLogic::GameLogic(EventSystem& eventSystem, EntityManager& entityManager, | |
RootFactory& rootFactory, ObjectFactory& objectFactory, TimeService& timeService) | |
: m_initialised(false), | |
m_eventSystem(eventSystem), | |
m_entityManager(entityManager), | |
m_rootFactory(rootFactory), | |
m_objectFactory(objectFactory), | |
m_timeService(timeService) { | |
DBG_PRINT("GameLogic::GameLogic\n"); | |
m_hClickMine = m_eventSystem.listen("doomsweeper/clickMine", | |
std::bind(&GameLogic::onClickMine, this, std::placeholders::_1)); | |
m_hCommandsEntered = m_eventSystem.listen("doomsweeper/commandsEntered", | |
std::bind(&GameLogic::onCommandsEntered, this, std::placeholders::_1)); | |
m_hDoomClosed = m_eventSystem.listen("dialogClosed", [this](const Event& e_) { | |
auto& e = dynamic_cast<const DialogClosedEvent&>(e_); | |
if (e.name == "doom") { | |
onDoomWindowClose(); | |
} | |
}); | |
m_entityId = Component::getNextId(); | |
auto& eventHandlerSystem = | |
m_entityManager.system<EventHandlerSystem>(ComponentKind::C_EVENT_HANDLER); | |
CEventHandler* events = new CEventHandler(m_entityId); | |
events->broadcastedEventHandlers.push_back(EventHandler{"entity_changed_zone", | |
std::bind(&GameLogic::onEntityChangeZone, this, std::placeholders::_1)}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"player_enter_cell_inner", | |
[this](const GameEvent& e_) { | |
auto& e = dynamic_cast<const EPlayerEnterCellInner&>(e_); | |
onPlayerEnterCellInner(e.cellId); | |
}}); | |
events->broadcastedEventHandlers.push_back(EventHandler{"cell_door_opened", | |
[this](const GameEvent& e_) { | |
auto& e = dynamic_cast<const ECellDoorOpened&>(e_); | |
onCellDoorOpened(e.cellId); | |
}}); | |
eventHandlerSystem.addComponent(pComponent_t(events)); | |
} | |
//=========================================== | |
// GameLogic::drawMazeMap | |
//=========================================== | |
void GameLogic::drawMazeMap(const std::set<Coord>& clueCells) { | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
QImage& img = renderSystem.rg.textures.at("maze_map").image; | |
QPainter painter; | |
painter.begin(&img); | |
double x = img.width() * 0.25; | |
double y = img.height() * 0.9; | |
double h = img.height() * 0.1; | |
QFont font; | |
font.setPixelSize(h); | |
stringstream ss; | |
for (auto& c : clueCells) { | |
ss << static_cast<char>('A' + c.col) << c.row << " "; | |
} | |
painter.setFont(font); | |
painter.setPen(Qt::black); | |
painter.drawText(x, y, ss.str().c_str()); | |
painter.end(); | |
} | |
//=========================================== | |
// commandPartToImageName | |
//=========================================== | |
static string commandPartToImageName(const string& cmdPart) { | |
static const map<string, string> names = { | |
{"16:2", "16_2"}, | |
{"+772", "772"}, | |
{"auto=false", "auto_false"}, | |
{"bin/extra", "bin_extra"}, | |
{"ccbuild", "ccbuild"}, | |
{"dopler", "dopler"}, | |
{"-g", "g"}, | |
{"hconf", "hconf"}, | |
{"-kbr", "kbr"}, | |
{"&&linkf", "linkf"}, | |
{"psched", "psched"}, | |
{"purge-all", "purge_all"}, | |
{"--retry=10", "retry_10"}, | |
{"--single-pass", "single_pass"}, | |
{"update", "update"}, | |
{"xiff", "xiff"} | |
}; | |
return names.at(cmdPart); | |
} | |
//=========================================== | |
// GameLogic::drawCommandScreens | |
//=========================================== | |
void GameLogic::drawCommandScreens(const vector<vector<string>>& commands) const { | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
for (unsigned int i = 0; i < commands.size(); ++i) { | |
auto& cmd = commands[i]; | |
stringstream ss; | |
ss << "command_" << i; | |
QImage& whiteboardImg = GET_VALUE(renderSystem.rg.textures, ss.str()).image; | |
QPainter painter; | |
painter.begin(&whiteboardImg); | |
double gap = whiteboardImg.width() * 0.05; | |
ss.str(""); | |
ss << i + 1 << "_" << commands.size(); | |
string numImgName = ss.str(); | |
const QImage& numImg = GET_VALUE(renderSystem.rg.textures, ss.str()).image; | |
painter.drawImage(gap, whiteboardImg.height() * 0.2, numImg); | |
double x = gap; | |
double y = whiteboardImg.height() * 0.4; | |
for (unsigned int j = 0; j < cmd.size(); ++j) { | |
string imgName = commandPartToImageName(cmd[j]); | |
const QImage& cmdPartImg = GET_VALUE(renderSystem.rg.textures, imgName).image; | |
painter.drawImage(x, y, cmdPartImg); | |
x += cmdPartImg.width() + gap; | |
} | |
painter.end(); | |
} | |
} | |
//=========================================== | |
// GameLogic::generateCommands | |
//=========================================== | |
void GameLogic::generateCommands() { | |
DBG_PRINT("Generating commands:\n"); | |
vector<string> progs{ | |
"hconf", | |
"dopler", | |
"psched", | |
"xiff" | |
}; | |
vector<string> cmdParts{ | |
"update", | |
"-kbr", | |
"auto=false", | |
"purge-all", | |
"-g", | |
"bin/extra", | |
"ccbuild", | |
"+772", | |
"16:2", | |
"--single-pass", | |
"--retry=10", | |
"&&linkf" | |
}; | |
size_t partsPerCommand = cmdParts.size() / progs.size(); | |
std::shuffle(progs.begin(), progs.end(), randEngine); | |
std::shuffle(cmdParts.begin(), cmdParts.end(), randEngine); | |
vector<vector<string>> commands; | |
for (auto& prog : progs) { | |
vector<string> command; | |
command.push_back(prog); | |
command.insert(command.end(), cmdParts.begin(), cmdParts.begin() + partsPerCommand); | |
cmdParts.erase(cmdParts.begin(), cmdParts.begin() + partsPerCommand); | |
commands.push_back(command); | |
#ifdef DEBUG | |
for (auto& part : command) { | |
std::cout << part << " "; | |
} | |
std::cout << "\n"; | |
#endif | |
} | |
drawCommandScreens(commands); | |
m_eventSystem.fire(pEvent_t(new CommandsGeneratedEvent{commands})); | |
} | |
//=========================================== | |
// GameLogic::initialise | |
//=========================================== | |
std::future<void> GameLogic::initialise(const set<Coord>& mineCoords) { | |
return std::async(std::launch::async, [&, mineCoords]() { | |
const double cellW = 1600.0; | |
const double cellH = 1600.0; | |
m_objectFactory.firstPassComplete = true; | |
vector<entityId_t> safeCells = { | |
Component::getIdFromString("safe_cell_0"), | |
Component::getIdFromString("safe_cell_1"), | |
Component::getIdFromString("safe_cell_2") | |
}; | |
std::uniform_int_distribution<int> randomSafeCell(0, static_cast<int>(safeCells.size()) - 1); | |
vector<entityId_t> unsafeCells = { | |
Component::getIdFromString("unsafe_cell_0"), | |
Component::getIdFromString("unsafe_cell_1"), | |
Component::getIdFromString("unsafe_cell_2") | |
}; | |
std::uniform_int_distribution<int> randomUnsafeCell(0, static_cast<int>(unsafeCells.size()) - 1); | |
// Construct start cell | |
// | |
entityId_t startCellId = Component::getIdFromString("start_cell"); | |
parser::pObject_t startCellObj(m_objectFactory.objects.at(startCellId)->clone()); | |
Point startCellPos = m_objectFactory.objectPositions.at(startCellId); | |
m_rootFactory.constructObject("cell", -1, *startCellObj, m_objectFactory.region, | |
m_objectFactory.parentTransform); | |
sealDoor(m_objectFactory.cellDoors[startCellId].south); | |
Coord startCellCoords{0, 0}; | |
m_cellIds[startCellId] = startCellCoords; | |
// Construct end cell | |
// | |
entityId_t endCellId = Component::getIdFromString("end_cell"); | |
parser::pObject_t endCellObj(m_objectFactory.objects.at(endCellId)->clone()); | |
const Point& endCellPos = m_objectFactory.objectPositions.at(endCellId); | |
const Matrix& endCellTransform = endCellObj->groupTransform; | |
Point endCellTargetPos = startCellPos + Vec2f(cellW * (COLS - 1), -cellH * (ROWS - 1)); | |
Matrix endCellShift(0, endCellTargetPos - endCellPos); | |
endCellObj->groupTransform = endCellTransform * endCellShift; | |
m_rootFactory.constructObject("cell", -1, *endCellObj, m_objectFactory.region, | |
m_objectFactory.parentTransform); | |
sealDoor(m_objectFactory.cellDoors[endCellId].north); | |
Coord endCellCoords = Coord{ROWS - 1, COLS - 1}; | |
m_cellIds[endCellId] = endCellCoords; | |
// Position clue cells | |
// | |
std::uniform_int_distribution<int> randRow{0, ROWS - 1}; | |
std::uniform_int_distribution<int> randCol{0, COLS - 1}; | |
set<Coord> clueCellCoords; | |
for (int i = 0; i < 3; ++i) { | |
while (true) { | |
Coord c{randRow(randEngine), randCol(randEngine)}; | |
if (clueCellCoords.count(c) == 0 && mineCoords.count(c) == 0 | |
&& c != startCellCoords && c != endCellCoords) { | |
clueCellCoords.insert(c); | |
break; | |
} | |
} | |
} | |
// Construct remaining cells | |
// | |
int clueCellIdx = 0; | |
for (int i = 0; i < ROWS; ++i) { | |
for (int j = 0; j < COLS; ++j) { | |
if (i == 0 && j == 0) { | |
continue; | |
} | |
if (i == ROWS - 1 && j == COLS - 1) { | |
continue; | |
} | |
entityId_t protoCellId = -1; | |
string cellName; | |
if (mineCoords.count(Coord{i, j})) { | |
protoCellId = unsafeCells[randomUnsafeCell(randEngine)]; | |
stringstream ss; | |
ss << "cell_" << i << "_" << j; | |
cellName = ss.str(); | |
} | |
else if (clueCellCoords.count(Coord{i, j})) { | |
protoCellId = Component::getIdFromString("clue_cell"); | |
stringstream ss; | |
ss << "clue_cell_" << clueCellIdx; | |
cellName = ss.str(); | |
++clueCellIdx; | |
} | |
else { | |
protoCellId = safeCells[randomSafeCell(randEngine)]; | |
stringstream ss; | |
ss << "cell_" << i << "_" << j; | |
cellName = ss.str(); | |
} | |
assert(protoCellId != -1); | |
parser::pObject_t cellObj(m_objectFactory.objects.at(protoCellId)->clone()); | |
const Point& cellPos = m_objectFactory.objectPositions.at(protoCellId); | |
const Matrix& cellTransform = cellObj->groupTransform; | |
Point targetPos = startCellPos + Vec2f(cellW * j, -cellH * i); | |
Matrix m(0, targetPos - cellPos); | |
entityId_t cellId = Component::getIdFromString(cellName); | |
m_cellIds[cellId] = Coord{i, j}; | |
cellObj->dict["name"] = cellName; | |
cellObj->groupTransform = cellTransform * m; | |
m_rootFactory.constructObject("cell", -1, *cellObj, m_objectFactory.region, | |
m_objectFactory.parentTransform); | |
if (i == 0) { | |
sealDoor(m_objectFactory.cellDoors[cellId].south); | |
} | |
if (i + 1 == ROWS) { | |
sealDoor(m_objectFactory.cellDoors[cellId].north); | |
} | |
if (j == 0) { | |
sealDoor(m_objectFactory.cellDoors[cellId].west); | |
} | |
if (j + 1 == COLS) { | |
sealDoor(m_objectFactory.cellDoors[cellId].east); | |
} | |
} | |
} | |
drawMazeMap(clueCellCoords); | |
#ifdef DEBUG | |
std::cout << "Clue cells at: "; | |
for (auto& c : clueCellCoords) { | |
std::cout << c << " "; | |
} | |
std::cout << "\n"; | |
#endif | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
auto& renderSystem = m_entityManager.system<RenderSystem>(ComponentKind::C_RENDER); | |
DBG_PRINT("Connecting zones...\n"); | |
spatialSystem.connectZones(); | |
DBG_PRINT("Connecting regions...\n"); | |
renderSystem.connectRegions(); | |
#ifdef DEBUG | |
entityId_t playerId = spatialSystem.sg.player->body; | |
entityId_t dbgPointId = Component::getIdFromString("dbg_point"); | |
if (spatialSystem.hasComponent(dbgPointId)) { | |
auto& dbgPoint = dynamic_cast<const CVRect&>(spatialSystem.getComponent(dbgPointId)); | |
spatialSystem.relocateEntity(playerId, *dbgPoint.zone, dbgPoint.pos); | |
} | |
#endif | |
generateCommands(); | |
m_initialised = true; | |
}); | |
} | |
//=========================================== | |
// GameLogic::onClickMine | |
//=========================================== | |
void GameLogic::onClickMine(const Event&) { | |
m_timeService.onTimeout([this]() { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_DOOMSWEEPER))); | |
}, 1.0); | |
} | |
//=========================================== | |
// GameLogic::onCommandsEntered | |
//=========================================== | |
void GameLogic::onCommandsEntered(const Event&) { | |
m_entityManager.broadcastEvent(GameEvent{"commands_entered"}); | |
} | |
//=========================================== | |
// GameLogic::onDoomWindowClose | |
//=========================================== | |
void GameLogic::onDoomWindowClose() { | |
if (m_initialised) { | |
m_eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_DOOMSWEEPER))); | |
} | |
} | |
//=========================================== | |
// GameLogic::onEntityChangeZone | |
//=========================================== | |
void GameLogic::onEntityChangeZone(const GameEvent& e_) { | |
const EChangedZone& e = dynamic_cast<const EChangedZone&>(e_); | |
auto& spatialSystem = m_entityManager.system<SpatialSystem>(ComponentKind::C_SPATIAL); | |
Player& player = *spatialSystem.sg.player; | |
if (e.entityId == player.body) { | |
if (e.newZone == Component::getIdFromString("level_exit")) { | |
m_eventSystem.fire(pEvent_t(new Event{"doomsweeper/levelComplete"})); | |
} | |
} | |
} | |
//=========================================== | |
// GameLogic::sealDoor | |
//=========================================== | |
void GameLogic::sealDoor(entityId_t doorId) { | |
auto& region = m_entityManager.getComponent<CRegion>(doorId, ComponentKind::C_RENDER); | |
auto& behaviour = | |
m_entityManager.getComponent<CDoorBehaviour>(doorId, ComponentKind::C_BEHAVIOUR); | |
DBG_PRINT("Sealing door with id " << doorId << "\n"); | |
for (auto& pBoundary : region.boundaries) { | |
if (pBoundary->kind == CRenderKind::JOIN) { | |
CJoin& join = dynamic_cast<CJoin&>(*pBoundary); | |
join.topTexture = "slimy_bricks"; | |
join.bottomTexture = "slimy_bricks"; | |
} | |
} | |
behaviour.isPlayerActivated = false; | |
auto& focusSystem = m_entityManager.system<FocusSystem>(ComponentKind::C_FOCUS); | |
focusSystem.removeEntity(doorId); | |
} | |
//=========================================== | |
// GameLogic::onPlayerEnterCellInner | |
//=========================================== | |
void GameLogic::onPlayerEnterCellInner(entityId_t cellId) { | |
Coord coord = m_cellIds[cellId]; | |
m_eventSystem.fire(pEvent_t(new InnerCellEnteredEvent{coord})); | |
} | |
//=========================================== | |
// GameLogic::onCellDoorOpened | |
//=========================================== | |
void GameLogic::onCellDoorOpened(entityId_t cellId) { | |
if (m_history.newest() != cellId) { | |
m_history.push(cellId); | |
} | |
lockDoors(); | |
} | |
//=========================================== | |
// GameLogic::lockDoors | |
//=========================================== | |
void GameLogic::lockDoors() { | |
#ifdef DEBUG | |
std::cout << "History: "; | |
for (int i = 0; i < m_history.size(); ++i) { | |
std::cout << "(" << m_cellIds[m_history.nthNewest(i)] << ") "; | |
} | |
std::cout << "\n"; | |
#endif | |
if (m_history.size() >= 2) { | |
entityId_t prevCellId = m_history.nthNewest(1); | |
auto& doors = m_objectFactory.cellDoors[prevCellId]; | |
DBG_PRINT("Locking doors of cell " << m_cellIds[prevCellId] << "\n"); | |
for (entityId_t door : doors) { | |
if (door == -1) { | |
continue; | |
} | |
DBG_PRINT("Locking door with id " << door << "\n"); | |
auto& behaviour = | |
m_entityManager.getComponent<CDoorBehaviour>(door, ComponentKind::C_BEHAVIOUR); | |
behaviour.isPlayerActivated = false; | |
} | |
} | |
if (m_history.size() >= 3) { | |
entityId_t prevPrevCellId = m_history.nthNewest(2); | |
auto& doors = m_objectFactory.cellDoors[prevPrevCellId]; | |
DBG_PRINT("Unlocking doors of cell " << m_cellIds[prevPrevCellId] << "\n"); | |
for (entityId_t door : doors) { | |
if (door == -1) { | |
continue; | |
} | |
DBG_PRINT("Unlocking door with id " << door << "\n"); | |
auto& behaviour = | |
m_entityManager.getComponent<CDoorBehaviour>(door, ComponentKind::C_BEHAVIOUR); | |
behaviour.isPlayerActivated = true; | |
} | |
} | |
} | |
//=========================================== | |
// GameLogic::~GameLogic | |
//=========================================== | |
GameLogic::~GameLogic() {} | |
} | |
#include <QPainter> | |
#include <random> | |
#include <vector> | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/f_minesweeper.hpp" | |
#include "fragments/f_main/f_app_dialog/f_minesweeper/f_minesweeper_spec.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
using std::set; | |
using std::vector; | |
using doomsweeper::Coord; | |
static const int ROWS = 8; | |
static const int COLS = 8; | |
static const int MINES = 24; | |
// Value used to represent a mine | |
static const int MINE = 10; | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// MinesweeperCell::MinesweeperCell | |
//=========================================== | |
MinesweeperCell::MinesweeperCell(int row, int col, const IconSet& icons) | |
: QWidget(), | |
row(row), | |
col(col), | |
m_icons(icons), | |
m_pixmap(32, 32) { | |
QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); | |
setSizePolicy(sizePolicy); | |
m_label = makeQtObjPtr<QLabel>(); | |
m_label->setSizePolicy(sizePolicy); | |
m_button = makeQtObjPtr<GoodButton>(); | |
m_button->setSizePolicy(sizePolicy); | |
m_label->setPixmap(m_pixmap); | |
m_label->setScaledContents(true); | |
m_stackedLayout = makeQtObjPtr<QStackedLayout>(); | |
m_stackedLayout->addWidget(m_label.get()); | |
m_stackedLayout->addWidget(m_button.get()); | |
setLayout(m_stackedLayout.get()); | |
setHidden(true); | |
setValue(0); | |
} | |
//=========================================== | |
// MinesweeperCell::render | |
//=========================================== | |
void MinesweeperCell::render() { | |
QPainter painter; | |
painter.begin(&m_pixmap); | |
int cellW = m_pixmap.width(); | |
int cellH = m_pixmap.height(); | |
QColor bgColour(240, 240, 240); | |
m_pixmap.fill(bgColour); | |
if (m_value >= 1 && m_value <= 8) { | |
QColor colour; | |
switch (m_value) { | |
case 1: { | |
colour = Qt::blue; | |
break; | |
} | |
case 2: { | |
colour = QColor(0, 128, 0); | |
break; | |
} | |
case 3: { | |
colour = Qt::red; | |
break; | |
} | |
case 4: { | |
colour = Qt::darkBlue; | |
break; | |
} | |
case 5: { | |
colour = QColor(178, 34, 34); | |
break; | |
} | |
case 6: { | |
colour = Qt::darkCyan; | |
break; | |
} | |
case 7: { | |
colour = Qt::magenta; | |
break; | |
} | |
case 8: { | |
colour = Qt::black; | |
break; | |
} | |
} | |
int w = 10; // TODO | |
int h = 14; | |
QFont font; | |
font.setPixelSize(h); | |
painter.setFont(font); | |
painter.setPen(colour); | |
painter.drawText(0.5 * (cellW - w), 0.5 * (cellH + h), std::to_string(m_value).c_str()); | |
} | |
else if (m_value == MINE) { | |
if (m_exploded) { | |
m_pixmap.fill(Qt::red); | |
} | |
int w = 18; | |
int h = 18; | |
m_icons.mine.paint(&painter, 0.5 * (cellW - w), 0.5 * (cellH - h), w, h); | |
} | |
if (m_hasPlayer) { | |
m_icons.player.paint(&painter, 0, 0, cellW, cellH); | |
} | |
painter.end(); | |
m_label->setPixmap(m_pixmap); | |
} | |
//=========================================== | |
// MinesweeperCell::setValue | |
//=========================================== | |
void MinesweeperCell::setValue(int val) { | |
m_value = val; | |
render(); | |
} | |
//=========================================== | |
// MinesweeperCell::setFlagged | |
//=========================================== | |
void MinesweeperCell::setFlagged(bool flagged) { | |
m_flagged = flagged; | |
if (m_flagged) { | |
m_button->setIcon(m_icons.flag); | |
} | |
else { | |
m_button->setIcon(QIcon()); | |
} | |
} | |
//=========================================== | |
// MinesweeperCell::hidden | |
//=========================================== | |
bool MinesweeperCell::hidden() const { | |
return m_stackedLayout->currentIndex() == 1; | |
} | |
//=========================================== | |
// MinesweeperCell::setHidden | |
//=========================================== | |
void MinesweeperCell::setHidden(bool hidden) { | |
m_stackedLayout->setCurrentIndex(hidden ? 1 : 0); | |
} | |
//=========================================== | |
// MinesweeperCell::onPlayerChangeCell | |
//=========================================== | |
void MinesweeperCell::onPlayerChangeCell(int row, int col) { | |
if (row == this->row && col == this->col) { | |
m_hasPlayer = true; | |
setHidden(false); | |
} | |
else { | |
m_hasPlayer = false; | |
} | |
render(); | |
} | |
//=========================================== | |
// MinesweeperCell::onPlayerClick | |
//=========================================== | |
void MinesweeperCell::onPlayerClick() { | |
if (m_value == MINE) { | |
m_exploded = true; | |
render(); | |
} | |
} | |
//=========================================== | |
// FMinesweeper::FMinesweeper | |
//=========================================== | |
FMinesweeper::FMinesweeper(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FMinesweeper", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FMinesweeper::FMinesweeper\n"); | |
} | |
//=========================================== | |
// FMinesweeper::onInnerCellEntered | |
//=========================================== | |
void FMinesweeper::onInnerCellEntered(const Event& e_) { | |
auto& e = dynamic_cast<const doomsweeper::InnerCellEnteredEvent&>(e_); | |
DBG_PRINT("Player has entered cell " << e.coords.row << ", " << e.coords.col << "\n"); | |
for (int i = 0; i < ROWS; ++i) { | |
for (int j = 0; j < COLS; ++j) { | |
m_mainPage.cells[i][j]->onPlayerChangeCell(e.coords.row, e.coords.col); | |
} | |
} | |
set<const MinesweeperCell*> visited{}; | |
clearNeighbours_r(*m_mainPage.cells[e.coords.row][e.coords.col], visited); | |
} | |
//=========================================== | |
// FMinesweeper::placeMines | |
//=========================================== | |
set<Coord> FMinesweeper::placeMines() { | |
vector<Coord> coords; | |
set<Coord> mineCoords; | |
for (int i = 0; i < ROWS; ++i) { | |
for (int j = 0; j < COLS; ++j) { | |
if (i == 0 && j == 0) { | |
continue; | |
} | |
if (i == ROWS - 1 && j == COLS - 1) { | |
continue; | |
} | |
coords.push_back(Coord{i, j}); | |
} | |
} | |
std::shuffle(coords.begin(), coords.end(), randEngine); | |
for (int i = 0; i < MINES; ++i) { | |
int row = coords[i].row; | |
int col = coords[i].col; | |
m_mainPage.cells[row][col]->setValue(MINE); | |
mineCoords.insert(coords[i]); | |
} | |
return mineCoords; | |
} | |
//=========================================== | |
// FMinesweeper::getNeighbours | |
//=========================================== | |
set<MinesweeperCell*> FMinesweeper::getNeighbours(const MinesweeperCell& cell) const { | |
set<MinesweeperCell*> neighbours; | |
for (int i = cell.row - 1; i <= cell.row + 1; ++i) { | |
for (int j = cell.col - 1; j <= cell.col + 1; ++j) { | |
if (i == cell.row && j == cell.col) { | |
continue; | |
} | |
if (i < 0 || i >= ROWS) { | |
continue; | |
} | |
if (j < 0 || j >= COLS) { | |
continue; | |
} | |
neighbours.insert(m_mainPage.cells[i][j].get()); | |
} | |
} | |
return neighbours; | |
} | |
//=========================================== | |
// FMinesweeper::setNumbers | |
//=========================================== | |
void FMinesweeper::setNumbers() { | |
for (int i = 0; i < ROWS; ++i) { | |
for (int j = 0; j < COLS; ++j) { | |
MinesweeperCell& cell = *m_mainPage.cells[i][j]; | |
if (cell.value() == MINE) { | |
set<MinesweeperCell*> neighbours = getNeighbours(cell); | |
for (auto neighbour : neighbours) { | |
if (neighbour->value() != MINE) { | |
neighbour->setValue(neighbour->value() + 1); | |
} | |
} | |
} | |
} | |
} | |
} | |
//=========================================== | |
// FMinesweeper::clearNeighbours_r | |
//=========================================== | |
void FMinesweeper::clearNeighbours_r(const MinesweeperCell& cell, | |
set<const MinesweeperCell*>& visited) { | |
if (cell.value() == 0) { | |
auto neighbours = getNeighbours(cell); | |
for (MinesweeperCell* neighbour : neighbours) { | |
if (visited.count(neighbour) == 0 && neighbour->hidden() && !neighbour->flagged()) { | |
neighbour->setHidden(false); | |
visited.insert(neighbour); | |
clearNeighbours_r(*neighbour, visited); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// FMinesweeper::onBtnClick | |
//=========================================== | |
void FMinesweeper::onBtnClick(int id) { | |
if (m_dead) { | |
return; | |
} | |
int row = id / COLS; | |
int col = id % COLS; | |
DBG_PRINT("Left clicked button " << row << ", " << col << "\n"); | |
MinesweeperCell& cell = *m_mainPage.cells[row][col]; | |
if (cell.hidden() && !cell.flagged()) { | |
switch (cell.value()) { | |
case 0: { | |
set<const MinesweeperCell*> visited{}; | |
clearNeighbours_r(cell, visited); | |
break; | |
} | |
case MINE: { | |
DBG_PRINT("Boom!\n"); | |
cell.onPlayerClick(); | |
commonData.eventSystem.fire(pEvent_t(new Event("doomsweeper/clickMine"))); | |
m_dead = true; | |
break; | |
} | |
default: { | |
// Do nothing | |
break; | |
} | |
} | |
cell.setHidden(false); | |
} | |
} | |
//=========================================== | |
// FMinesweeper::onBtnRightClick | |
//=========================================== | |
void FMinesweeper::onBtnRightClick(int id) { | |
if (m_dead) { | |
return; | |
} | |
int row = id / COLS; | |
int col = id % COLS; | |
DBG_PRINT("Right clicked button " << row << ", " << col << "\n"); | |
auto& cell = m_mainPage.cells[row][col]; | |
if (cell->hidden()) { | |
cell->setFlagged(!cell->flagged()); | |
} | |
} | |
//=========================================== | |
// FMinesweeper::constructLoadingPage | |
//=========================================== | |
void FMinesweeper::constructLoadingPage() { | |
m_loadingPage.widget = makeQtObjPtr<QLabel>("Loading..."); | |
} | |
//=========================================== | |
// FMinesweeper::constructMainPage | |
//=========================================== | |
void FMinesweeper::constructMainPage() { | |
m_mainPage.widget = makeQtObjPtr<QWidget>(); | |
m_mainPage.grid = makeQtObjPtr<QGridLayout>(); | |
m_mainPage.grid->setSpacing(0); | |
m_mainPage.grid->setContentsMargins(0, 0, 0, 0); | |
m_mainPage.buttonGroup = makeQtObjPtr<GoodButtonGroup>(); | |
for (int i = 0; i < ROWS; ++i) { | |
for (int j = 0; j < COLS; ++j) { | |
m_mainPage.cells[i][j] = makeQtObjPtr<MinesweeperCell>(i, j, m_icons); | |
MinesweeperCell* cell = m_mainPage.cells[i][j].get(); | |
m_mainPage.buttonGroup->addGoodButton(&cell->button(), i * COLS + j); | |
m_mainPage.grid->addWidget(cell, ROWS - 1 - i, j); | |
} | |
} | |
m_mainPage.widget->setLayout(m_mainPage.grid.get()); | |
connect(m_mainPage.buttonGroup.get(), SIGNAL(buttonClicked(int)), this, SLOT(onBtnClick(int))); | |
connect(m_mainPage.buttonGroup.get(), SIGNAL(rightClicked(int)), this, | |
SLOT(onBtnRightClick(int))); | |
m_icons.flag = QIcon(commonData.appConfig.dataPath("doomsweeper/flag.png").c_str()); | |
m_icons.mine = QIcon(commonData.appConfig.dataPath("doomsweeper/skull_crossbones.png").c_str()); | |
m_icons.noMine = QIcon(commonData.appConfig.dataPath("doomsweeper/no_mine.png").c_str()); | |
m_icons.player = QIcon(commonData.appConfig.dataPath("doomsweeper/player.png").c_str()); | |
set<Coord> coords = placeMines(); | |
setNumbers(); | |
m_hInnerCellEntered = commonData.eventSystem.listen("doomsweeper/innerCellEntered", | |
std::bind(&FMinesweeper::onInnerCellEntered, this, std::placeholders::_1)); | |
commonData.eventSystem.fire(pEvent_t(new doomsweeper::MinesweeperSetupEvent(coords))); | |
} | |
//=========================================== | |
// FMinesweeper::reload | |
//=========================================== | |
void FMinesweeper::reload(const FragmentSpec&) { | |
DBG_PRINT("FMinesweeper::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
if (m_stackedLayout) { | |
delete m_stackedLayout.release(); | |
} | |
m_stackedLayout = makeQtObjPtr<QStackedLayout>(); | |
constructLoadingPage(); | |
constructMainPage(); | |
m_stackedLayout->addWidget(m_loadingPage.widget.get()); | |
m_stackedLayout->addWidget(m_mainPage.widget.get()); | |
setLayout(m_stackedLayout.get()); | |
parentData.box->addWidget(this); | |
m_hStart = commonData.eventSystem.listen("raycast/start", [this](const Event&) { | |
m_stackedLayout->setCurrentIndex(1); | |
}); | |
m_dead = false; | |
} | |
//=========================================== | |
// FMinesweeper::cleanUp | |
//=========================================== | |
void FMinesweeper::cleanUp() { | |
DBG_PRINT("FMinesweeper::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FMinesweeper::~FMinesweeper | |
//=========================================== | |
FMinesweeper::~FMinesweeper() { | |
DBG_PRINT("FMinesweeper::~FMinesweeper\n"); | |
} | |
#include <QKeyEvent> | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_app_dialog/f_app_dialog.hpp" | |
#include "fragments/f_main/f_app_dialog/f_app_dialog_spec.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FAppDialog::FAppDialog | |
//=========================================== | |
FAppDialog::FAppDialog(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FAppDialog", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FAppDialog::FAppDialog\n"); | |
setLayout(m_data.box.get()); | |
} | |
//=========================================== | |
// FAppDialog::reload | |
//=========================================== | |
void FAppDialog::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FAppDialog::reload\n"); | |
auto& spec = dynamic_cast<const FAppDialogSpec&>(spec_); | |
m_name = spec.name; | |
setWindowTitle(spec.titleText); | |
setFixedSize(spec.width, spec.height); | |
hide(); | |
m_hShow = commonData.eventSystem.listen(spec.showOnEvent, [this](const Event&) { | |
show(); | |
}); | |
} | |
//=========================================== | |
// FAppDialog::keyPressEvent | |
//=========================================== | |
void FAppDialog::keyPressEvent(QKeyEvent* e) { | |
if (e->key() == Qt::Key_Escape) {} | |
} | |
//=========================================== | |
// FAppDialog::closeEvent | |
//=========================================== | |
void FAppDialog::closeEvent(QCloseEvent*) { | |
commonData.eventSystem.fire(pEvent_t(new DialogClosedEvent(m_name))); | |
} | |
//=========================================== | |
// FAppDialog::cleanUp | |
//=========================================== | |
void FAppDialog::cleanUp() { | |
DBG_PRINT("FAppDialog::cleanUp\n"); | |
} | |
//=========================================== | |
// FAppDialog::~FAppDialog | |
//=========================================== | |
FAppDialog::~FAppDialog() { | |
DBG_PRINT("FAppDialog::~FAppDialog\n"); | |
} | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_login_screen/f_login_screen.hpp" | |
#include "fragments/f_main/f_login_screen/f_login_screen_spec.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FLoginScreen::FLoginScreen | |
//=========================================== | |
FLoginScreen::FLoginScreen(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QLabel(nullptr), | |
Fragment("FLoginScreen", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FLoginScreen::FLoginScreen\n"); | |
} | |
//=========================================== | |
// FLoginScreen::reload | |
//=========================================== | |
void FLoginScreen::reload(const FragmentSpec&) { | |
DBG_PRINT("FLoginScreen::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
m_hPwdGen = commonData.eventSystem.listen("passwordGeneratedEvent", [this](const Event& event_) { | |
auto& event = dynamic_cast<const PasswordGeneratedEvent&>(event_); | |
DBG_PRINT_VAR(event.password); | |
m_data.password = event.password; | |
}); | |
m_data.wgtUser = makeQtObjPtr<QLineEdit>(this); | |
m_data.wgtUser->setGeometry(205, 160, 100, 20); | |
m_data.wgtPassword = makeQtObjPtr<QLineEdit>(this); | |
m_data.wgtPassword->setGeometry(205, 185, 100, 20); | |
connect(m_data.wgtPassword.get(), SIGNAL(returnPressed()), this, SLOT(onLoginAttempt())); | |
} | |
//=========================================== | |
// FLoginScreen::onLoginAttempt | |
//=========================================== | |
void FLoginScreen::onLoginAttempt() { | |
if (m_data.wgtUser->text() == "rob" && | |
m_data.wgtPassword->text().toStdString() == m_data.password) { | |
DBG_PRINT("Login success!\n"); | |
commonData.eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_ITS_RAINING_TETROMINOS))); | |
} | |
else { | |
DBG_PRINT("Access denied\n"); | |
// TODO | |
} | |
} | |
//=========================================== | |
// FLoginScreen::cleanUp | |
//=========================================== | |
void FLoginScreen::cleanUp() { | |
DBG_PRINT("FLoginScreen::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FLoginScreen::~FLoginScreen | |
//=========================================== | |
FLoginScreen::~FLoginScreen() { | |
DBG_PRINT("FLoginScreen::~FLoginScreen\n"); | |
} | |
#include <QSpacerItem> | |
#include "fragments/f_main/f_main.hpp" | |
#include "fragments/f_main/f_desktop/f_desktop.hpp" | |
#include "fragments/f_main/f_desktop/f_desktop_spec.hpp" | |
#include "utils.hpp" | |
#include "event_system.hpp" | |
using std::string; | |
const int ICON_WIDTH = 40; | |
const int ICON_HEIGHT = 40; | |
//=========================================== | |
// FDesktop::FDesktop | |
//=========================================== | |
FDesktop::FDesktop(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QWidget(nullptr), | |
Fragment("FDesktop", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FDesktop::FDesktop\n"); | |
} | |
//=========================================== | |
// FDesktop::reload | |
//=========================================== | |
void FDesktop::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FDesktop::reload\n"); | |
auto& spec = dynamic_cast<const FDesktopSpec&>(spec_); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(this); | |
delete m_data.grid.release(); | |
m_data.grid = makeQtObjPtr<QGridLayout>(); | |
setLayout(m_data.grid.get()); | |
int cols = 6; | |
int rows = 5; | |
for (int i = 0; i < cols; ++i) { | |
for (int j = 0; j < rows; ++j) { | |
QSpacerItem* spacer = new QSpacerItem(ICON_WIDTH, ICON_HEIGHT); | |
// Grid takes ownership | |
m_data.grid->addItem(spacer, j, i); | |
} | |
} | |
int col = 0; | |
int row = 0; | |
int i = 0; | |
m_data.icons.clear(); | |
for (auto& icon : spec.icons) { | |
auto wgtIcon = makeQtObjPtr<DesktopIcon>(icon.eventName, icon.image, icon.text); | |
connect(wgtIcon.get(), SIGNAL(activated(const std::string&)), this, | |
SLOT(onIconActivate(const std::string&))); | |
m_data.grid->addWidget(wgtIcon.get(), row, col); | |
m_data.icons.push_back(std::move(wgtIcon)); | |
row = (row + 1) % rows; | |
col = i / rows; | |
++i; | |
} | |
} | |
//=========================================== | |
// FDesktop::onIconActivate | |
//=========================================== | |
void FDesktop::onIconActivate(const string& name) { | |
pEvent_t event(new Event(name)); | |
commonData.eventSystem.fire(std::move(event)); | |
} | |
//=========================================== | |
// FDesktop::cleanUp | |
//=========================================== | |
void FDesktop::cleanUp() { | |
DBG_PRINT("FDesktop::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(this); | |
} | |
//=========================================== | |
// FDesktop::~FDesktop | |
//=========================================== | |
FDesktop::~FDesktop() { | |
DBG_PRINT("FDesktop::~FDesktop\n"); | |
} | |
#include <QDateTime> | |
#include <QPixmap> | |
#include "fragments/f_main/f_desktop/desktop_icon.hpp" | |
using std::string; | |
using std::unique_ptr; | |
const long long DOUBLE_CLICK_DELAY = 300; | |
//=========================================== | |
// DesktopIcon::DesktopIcon | |
//=========================================== | |
DesktopIcon::DesktopIcon(const std::string& name, const string& image, const string& text) | |
: QWidget(nullptr), | |
m_name(name) { | |
QPixmap pix(image.c_str()); | |
wgtButton = makeQtObjPtr<QPushButton>(pix, ""); | |
wgtText = makeQtObjPtr<QLabel>(text.c_str()); | |
m_vbox = makeQtObjPtr<QVBoxLayout>(); | |
setLayout(m_vbox.get()); | |
wgtButton->setFlat(true); | |
wgtButton->setIconSize(pix.rect().size()); | |
wgtButton->setMinimumWidth(pix.width()); | |
wgtButton->setMinimumHeight(pix.height()); | |
wgtButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | |
m_vbox->setSpacing(0); | |
m_vbox->setMargin(0); | |
m_vbox->addWidget(wgtButton.get()); | |
m_vbox->addWidget(wgtText.get()); | |
m_vbox->addStretch(1); | |
connect(wgtButton.get(), SIGNAL(clicked()), this, SLOT(onButtonClick())); | |
} | |
//=========================================== | |
// DesktopIcon::onButtonClick | |
//=========================================== | |
void DesktopIcon::onButtonClick() { | |
long long now = QDateTime::currentMSecsSinceEpoch(); | |
if (now - m_lastClick <= DOUBLE_CLICK_DELAY) { | |
emit activated(m_name); | |
} | |
m_lastClick = now; | |
} | |
//=========================================== | |
// DesktopIcon::~DesktopIcon | |
//=========================================== | |
DesktopIcon::~DesktopIcon() {} | |
#include <QPixmap> | |
#include "fragments/f_main/f_desktop/f_desktop.hpp" | |
#include "fragments/f_main/f_desktop/f_server_room_init/f_server_room_init.hpp" | |
#include "fragments/f_main/f_desktop/f_server_room_init/f_server_room_init_spec.hpp" | |
#include "utils.hpp" | |
#include "app_config.hpp" | |
//=========================================== | |
// FServerRoomInit::FServerRoomInit | |
//=========================================== | |
FServerRoomInit::FServerRoomInit(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FServerRoomInit", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FServerRoomInit::FServerRoomInit\n"); | |
} | |
//=========================================== | |
// FServerRoomInit::reload | |
//=========================================== | |
void FServerRoomInit::reload(const FragmentSpec&) { | |
DBG_PRINT("FServerRoomInit::reload\n"); | |
m_hLaunch = commonData.eventSystem.listen("launchServerRoom", [this](const Event&) { | |
auto& parentData = parentFragData<FDesktopData>(); | |
QPixmap pix(commonData.appConfig.dataPath("youve_got_mail/procalc_dark.png").c_str()); | |
parentData.icons[0]->wgtButton->setIcon(pix); | |
}); | |
} | |
//=========================================== | |
// FServerRoomInit::cleanUp | |
//=========================================== | |
void FServerRoomInit::cleanUp() { | |
DBG_PRINT("FServerRoomInit::cleanUp\n"); | |
} | |
//=========================================== | |
// FServerRoomInit::~FServerRoomInit | |
//=========================================== | |
FServerRoomInit::~FServerRoomInit() { | |
DBG_PRINT("FServerRoomInit::~FServerRoomInit\n"); | |
} | |
#include "fragments/relocatable/f_glitch/f_glitch.hpp" | |
#include "fragments/relocatable/f_glitch/f_glitch_spec.hpp" | |
#include "effects.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// FGlitch::FGlitch | |
//=========================================== | |
FGlitch::FGlitch(Fragment& parent_, FragmentData& parentData_, const CommonFragData& commonData) | |
: QLabel(nullptr), | |
Fragment("FGlitch", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FGlitch::FGlitch\n"); | |
auto& parent = parentFrag<QWidget>(); | |
setParent(&parent); | |
setScaledContents(true); | |
setAttribute(Qt::WA_TransparentForMouseEvents); | |
m_glitchTimer = makeQtObjPtr<QTimer>(); | |
connect(m_glitchTimer.get(), SIGNAL(timeout()), this, SLOT(tick())); | |
show(); | |
} | |
//=========================================== | |
// FGlitch::reload | |
//=========================================== | |
void FGlitch::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FGlitch::reload\n"); | |
auto& spec = dynamic_cast<const FGlitchSpec&>(spec_); | |
auto& parent = parentFrag<QWidget>(); | |
move(0, 0); | |
resize(parent.size()); | |
m_glitchBuffer.reset(new QImage(parent.size(), QImage::Format_ARGB32)); | |
m_glitchFreqMin = spec.glitchFreqMin; | |
m_glitchFreqMax = spec.glitchFreqMax; | |
m_glitchDuration = spec.glitchDuration; | |
m_glitchTimer->start(m_glitchFreqMin * 1000); | |
} | |
//=========================================== | |
// FGlitch::tick | |
//=========================================== | |
void FGlitch::tick() { | |
auto& parent = parentFrag<QWidget>(); | |
if (!parent.isVisible()) { | |
return; | |
} | |
if (!isVisible()) { | |
QImage buf(m_glitchBuffer->size(), m_glitchBuffer->format()); | |
parent.render(&buf); | |
garbleImage(buf, *m_glitchBuffer); | |
setPixmap(QPixmap::fromImage(*m_glitchBuffer)); | |
setVisible(true); | |
raise(); | |
m_glitchTimer->setInterval(m_glitchDuration * 1000); | |
} | |
else { | |
setVisible(false); | |
std::uniform_int_distribution<int> dist(m_glitchFreqMin * 1000, m_glitchFreqMax * 1000); | |
m_glitchTimer->setInterval(dist(m_randEngine)); | |
} | |
} | |
//=========================================== | |
// FGlitch::cleanUp | |
//=========================================== | |
void FGlitch::cleanUp() { | |
DBG_PRINT("FGlitch::cleanUp\n"); | |
setParent(nullptr); | |
} | |
//=========================================== | |
// FGlitch::~FGlitch | |
//=========================================== | |
FGlitch::~FGlitch() { | |
DBG_PRINT("FGlitch::~FGlitch\n"); | |
} | |
#include <vector> | |
#include <array> | |
#include <random> | |
#include <QPainter> | |
#include "fragments/relocatable/f_tetrominos/f_tetrominos.hpp" | |
#include "fragments/relocatable/f_tetrominos/f_tetrominos_spec.hpp" | |
#include "utils.hpp" | |
using std::array; | |
using std::vector; | |
const int BLOCK_SIZE = 5; | |
const double FRAME_RATE = 15.0; | |
const double AVERAGE_ANGULAR_SPEED = 100.0; // Degrees per second | |
const double AVERAGE_SPEED = 35.0; // Pixels per second | |
const double SPEED_STD_DEVIATION = 7; // Average deviation from the average | |
static std::mt19937 randEngine(randomSeed()); | |
//=========================================== | |
// makeTetromino | |
//=========================================== | |
static void makeTetromino(const array<array<int, 4>, 4>& matrix, array<QPolygon, 4>& blocks) { | |
const int W = BLOCK_SIZE; | |
int idx = 0; | |
for (int i = 0; i < 4; ++i) { | |
for (int j = 0; j < 4; ++j) { | |
if (matrix[i][j]) { | |
QPolygon poly; | |
poly << QPoint(0, 0) | |
<< QPoint(W, 0) | |
<< QPoint(W, W) | |
<< QPoint(0, W); | |
QTransform t = QTransform().translate(i * W, j * W); | |
blocks[idx++] = t.map(poly); | |
} | |
} | |
} | |
} | |
//=========================================== | |
// FTetrominos::FTetrominos | |
//=========================================== | |
FTetrominos::FTetrominos(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: QLabel(nullptr), | |
Fragment("FTetrominos", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FTetrominos::FTetrominos\n"); | |
auto& parent = dynamic_cast<QWidget&>(parent_); | |
setParent(&parent); | |
setScaledContents(true); | |
setAttribute(Qt::WA_TransparentForMouseEvents); | |
m_data.timer = makeQtObjPtr<QTimer>(); | |
connect(m_data.timer.get(), SIGNAL(timeout()), this, SLOT(tick())); | |
constructTetrominos(1.0, 25.0); | |
m_hIncTetroRain = commonData.eventSystem.listen("increaseTetrominoRain", [this](const Event&) { | |
constructTetrominos(2.0, 50.0); | |
}); | |
show(); | |
} | |
//=========================================== | |
// FTetrominos::constructTetrominos | |
//=========================================== | |
void FTetrominos::constructTetrominos(double speedMultiplier, double percentageFill) { | |
auto& parent = parentFrag<QWidget>(); | |
m_tetrominos.clear(); | |
double winW = parent.size().width(); | |
double winH = parent.size().height(); | |
int spacing = 10; | |
int tetroSz = BLOCK_SIZE * 4 + spacing; | |
int rows = winH / tetroSz; | |
int cols = winW / tetroSz; | |
std::normal_distribution<double> randSpeed(AVERAGE_SPEED / FRAME_RATE, | |
SPEED_STD_DEVIATION / FRAME_RATE); | |
std::normal_distribution<double> randAngle(0, AVERAGE_ANGULAR_SPEED / FRAME_RATE); | |
std::uniform_real_distribution<double> randFloat(0, 100); | |
for (int i = 0; i < cols; ++i) { | |
double dy = randSpeed(randEngine) * speedMultiplier; | |
for (int j = 0; j < rows; ++j) { | |
if (randFloat(randEngine) > percentageFill) { | |
continue; | |
} | |
auto kind = static_cast<Tetromino::kind_t>(rand() % 7); | |
double da = randAngle(randEngine); | |
Tetromino t{kind, (i + 0.5) * tetroSz, winH - j * tetroSz, 0, dy, da, {{}}, QColor{}}; | |
switch (kind) { | |
case Tetromino::I: | |
t.colour = QColor(230, 50, 50); | |
makeTetromino({{ | |
{{0, 1, 0, 0}}, | |
{{0, 1, 0, 0}}, | |
{{0, 1, 0, 0}}, | |
{{0, 1, 0, 0}} | |
}}, t.blocks); | |
break; | |
case Tetromino::J: | |
t.colour = QColor(250, 40, 200); | |
makeTetromino({{ | |
{{0, 0, 0, 0}}, | |
{{0, 0, 1, 0}}, | |
{{0, 0, 1, 0}}, | |
{{0, 1, 1, 0}} | |
}}, t.blocks); | |
break; | |
case Tetromino::L: | |
t.colour = QColor(250, 250, 0); | |
makeTetromino({{ | |
{{0, 0, 0, 0}}, | |
{{0, 1, 0, 0}}, | |
{{0, 1, 0, 0}}, | |
{{0, 1, 1, 0}} | |
}}, t.blocks); | |
break; | |
case Tetromino::O: | |
t.colour = QColor(100, 220, 250); | |
makeTetromino({{ | |
{{0, 0, 0, 0}}, | |
{{0, 1, 1, 0}}, | |
{{0, 1, 1, 0}}, | |
{{0, 0, 0, 0}} | |
}}, t.blocks); | |
break; | |
case Tetromino::S: | |
t.colour = QColor(20, 20, 200); | |
makeTetromino({{ | |
{{0, 0, 0, 0}}, | |
{{0, 1, 1, 0}}, | |
{{1, 1, 0, 0}}, | |
{{0, 0, 0, 0}} | |
}}, t.blocks); | |
break; | |
case Tetromino::T: | |
t.colour = QColor(160, 160, 160); | |
makeTetromino({{ | |
{{0, 0, 0, 0}}, | |
{{1, 1, 1, 0}}, | |
{{0, 1, 0, 0}}, | |
{{0, 0, 0, 0}} | |
}}, t.blocks); | |
break; | |
case Tetromino::Z: | |
t.colour = QColor(50, 250, 50); | |
makeTetromino({{ | |
{{0, 0, 0, 0}}, | |
{{0, 1, 1, 0}}, | |
{{0, 0, 1, 1}}, | |
{{0, 0, 0, 0}} | |
}}, t.blocks); | |
break; | |
} | |
m_tetrominos.push_back(t); | |
} | |
} | |
} | |
//=========================================== | |
// FTetrominos::reload | |
//=========================================== | |
void FTetrominos::reload(const FragmentSpec&) { | |
DBG_PRINT("FTetrominos::reload\n"); | |
auto& parent = parentFrag<QWidget>(); | |
move(0, 0); | |
resize(parent.size()); | |
m_buffer.reset(new QImage(parent.size(), QImage::Format_ARGB32)); | |
m_data.timer->start(1000.0 / FRAME_RATE); | |
} | |
//=========================================== | |
// FTetrominos::drawTetrominos | |
//=========================================== | |
void FTetrominos::drawTetrominos(QImage& buffer) { | |
QPainter painter; | |
painter.begin(&buffer); | |
buffer.fill(Qt::GlobalColor::transparent); | |
for (auto it = m_tetrominos.begin(); it != m_tetrominos.end(); ++it) { | |
const Tetromino& t = *it; | |
int offset = -BLOCK_SIZE * 2.0; | |
QTransform transform = QTransform().translate(t.x, t.y).rotate(t.a).translate(offset, offset); | |
painter.setBrush(t.colour); | |
for (int i = 0; i < 4; ++i) { | |
painter.drawPolygon(transform.map(t.blocks[i])); | |
} | |
}; | |
painter.end(); | |
} | |
//=========================================== | |
// FTetrominos::moveTetrominos | |
//=========================================== | |
void FTetrominos::moveTetrominos() { | |
for (auto it = m_tetrominos.begin(); it != m_tetrominos.end(); ++it) { | |
Tetromino& t = *it; | |
t.y += t.dy; | |
t.a += t.da; | |
auto& parent = parentFrag<QWidget>(); | |
if (t.y > parent.size().height() + BLOCK_SIZE) { | |
t.y = -BLOCK_SIZE; | |
} | |
} | |
} | |
//=========================================== | |
// FTetrominos::tick | |
//=========================================== | |
void FTetrominos::tick() { | |
moveTetrominos(); | |
drawTetrominos(*m_buffer); | |
setPixmap(QPixmap::fromImage(*m_buffer)); | |
raise(); | |
} | |
//=========================================== | |
// FTetrominos::cleanUp | |
//=========================================== | |
void FTetrominos::cleanUp() { | |
DBG_PRINT("FTetrominos::cleanUp\n"); | |
setParent(nullptr); | |
} | |
//=========================================== | |
// FTetrominos::~FTetrominos | |
//=========================================== | |
FTetrominos::~FTetrominos() { | |
DBG_PRINT("FTetrominos::~FTetrominos\n"); | |
} | |
#include <cassert> | |
#include <cmath> | |
#include <QMainWindow> | |
#include <QPushButton> | |
#include <QApplication> | |
#include "fragments/relocatable/f_calculator/f_normal_calc_trigger/f_normal_calc_trigger.hpp" | |
#include "fragments/relocatable/f_calculator/f_normal_calc_trigger/f_normal_calc_trigger_spec.hpp" | |
#include "fragments/relocatable/f_calculator/f_calculator.hpp" | |
#include "state_ids.hpp" | |
#include "event_system.hpp" | |
#include "update_loop.hpp" | |
#include "effects.hpp" | |
#include "utils.hpp" | |
//=========================================== | |
// getMainWindow | |
//=========================================== | |
static QMainWindow* getMainWindow() { | |
foreach(QWidget* widget, qApp->topLevelWidgets()) { | |
if (QMainWindow* mainWindow = qobject_cast<QMainWindow*>(widget)) { | |
return mainWindow; | |
} | |
} | |
return nullptr; | |
} | |
//=========================================== | |
// FNormalCalcTrigger::FNormalCalcTrigger | |
//=========================================== | |
FNormalCalcTrigger::FNormalCalcTrigger(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FNormalCalcTrigger", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FNormalCalcTrigger::FNormalCalcTrigger\n"); | |
} | |
//=========================================== | |
// FNormalCalcTrigger::reload | |
//=========================================== | |
void FNormalCalcTrigger::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FNormalCalcTrigger::reload\n"); | |
auto& parentData = parentFragData<FCalculatorData>(); | |
disconnect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), | |
parentData.wgtCalculator.get(), SLOT(onButtonClick(int))); | |
connect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), this, | |
SLOT(onButtonClick(int))); | |
auto& spec = dynamic_cast<const FNormalCalcTriggerSpec&>(spec_); | |
m_targetWindowColour = spec.targetWindowColour; | |
m_targetDisplayColour = spec.targetDisplayColour; | |
m_symbols = spec.symbols; | |
} | |
//=========================================== | |
// FNormalCalcTrigger::cleanUp | |
//=========================================== | |
void FNormalCalcTrigger::cleanUp() { | |
DBG_PRINT("FNormalCalcTrigger::cleanUp\n"); | |
auto& parentData = parentFragData<FCalculatorData>(); | |
disconnect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), this, | |
SLOT(onButtonClick(int))); | |
connect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), | |
parentData.wgtCalculator.get(), SLOT(onButtonClick(int))); | |
} | |
//=========================================== | |
// FNormalCalcTrigger:onButtonClick | |
//=========================================== | |
void FNormalCalcTrigger::onButtonClick(int id) { | |
auto& data = parentFragData<FCalculatorData>(); | |
QWidget* window = getMainWindow(); | |
assert(window != nullptr); | |
if (id == BTN_EQUALS) { | |
double result = data.wgtCalculator->calculator.equals(); | |
data.wgtCalculator->wgtDigitDisplay->setText(data.wgtCalculator->calculator.display().c_str()); | |
if (std::isinf(result)) { | |
QColor origCol = window->palette().color(QPalette::Window); | |
int i = 0; | |
commonData.updateLoop.add([=,&data]() mutable { | |
auto& buttons = data.wgtCalculator->wgtButtonGrid->buttons; | |
++i; | |
int j = 0; | |
for (auto it = buttons.begin(); it != buttons.end(); ++it) { | |
QChar ch = m_symbols[(i + j) % m_symbols.length()]; | |
(*it)->setText(ch); | |
++j; | |
} | |
return i < commonData.updateLoop.fps() * 1.5; | |
}); | |
commonData.updateLoop.add([=]() mutable { | |
++i; | |
if (i % 2) { | |
setColour(*window, origCol, QPalette::Window); | |
} | |
else { | |
setColour(*window, Qt::white, QPalette::Window); | |
} | |
return i < commonData.updateLoop.fps(); | |
}, [&, window]() { | |
transitionColour(commonData.updateLoop, *window, m_targetWindowColour, | |
QPalette::Window, 0.5, [&]() { | |
commonData.eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_SHUFFLED_KEYS))); | |
}); | |
transitionColour(commonData.updateLoop, *data.wgtCalculator->wgtDigitDisplay, | |
m_targetDisplayColour, QPalette::Base, 0.5); | |
}); | |
} | |
} | |
else { | |
data.wgtCalculator->onButtonClick(id); | |
} | |
} | |
//=========================================== | |
// FNormalCalcTrigger::~FNormalCalcTrigger | |
//=========================================== | |
FNormalCalcTrigger::~FNormalCalcTrigger() { | |
DBG_PRINT("FNormalCalcTrigger::~FNormalCalcTrigger\n"); | |
} | |
#include <cassert> | |
#include <map> | |
#include <QMainWindow> | |
#include <QPushButton> | |
#include <QApplication> | |
#include <QButtonGroup> | |
#include "fragments/relocatable/f_calculator/f_partial_calc/f_partial_calc.hpp" | |
#include "fragments/relocatable/f_calculator/f_partial_calc/f_partial_calc_spec.hpp" | |
#include "fragments/relocatable/f_calculator/f_calculator.hpp" | |
#include "fragments/f_main/f_app_dialog/f_app_dialog.hpp" | |
#include "fragments/f_main/f_app_dialog/f_procalc_setup/events.hpp" | |
#include "state_ids.hpp" | |
#include "utils.hpp" | |
#include "evasive_button.hpp" | |
#include "exploding_button.hpp" | |
#include "state_ids.hpp" | |
using std::string; | |
using making_progress::SetupCompleteEvent; | |
struct BtnDesc { | |
QString text; | |
int idx; | |
int row; | |
int col; | |
}; | |
static const std::map<buttonId_t, BtnDesc> EVASIVE_BTNS = { | |
{ BTN_FIVE, { "5", 5, 2, 1 } } | |
}; | |
static const std::map<buttonId_t, BtnDesc> EXPLODING_BTNS = { | |
{ BTN_THREE, { "3", 3, 3, 2 } }, | |
{ BTN_MINUS, { "-", 11, 3, 3 } }, | |
{ BTN_POINT, { ".", 14, 4, 1 } }, | |
}; | |
//=========================================== | |
// FPartialCalc::FPartialCalc | |
//=========================================== | |
FPartialCalc::FPartialCalc(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FPartialCalc", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FPartialCalc::FPartialCalc\n"); | |
// TODO: Move this making_progress specific stuff out of this class | |
m_hSetupComplete = commonData.eventSystem.listen("makingProgress/setupComplete", | |
[this](const Event& event) { | |
const SetupCompleteEvent& e = dynamic_cast<const SetupCompleteEvent&>(event); | |
toggleFeatures(e.features); | |
}); | |
m_hDialogClosed = commonData.eventSystem.listen("dialogClosed", | |
[&commonData](const Event& event) { | |
const DialogClosedEvent& e = dynamic_cast<const DialogClosedEvent&>(event); | |
if (e.name == "procalcSetup") { | |
commonData.eventSystem.fire(pEvent_t(new RequestStateChangeEvent(ST_MAKING_PROGRESS, true))); | |
} | |
}); | |
} | |
//=========================================== | |
// FPartialCalc::toggleFeatures | |
//=========================================== | |
void FPartialCalc::toggleFeatures(const std::set<buttonId_t>& features) { | |
auto& parentData = parentFragData<FCalculatorData>(); | |
auto& group = *parentData.wgtCalculator->wgtButtonGrid->buttonGroup; | |
for (auto it = EVASIVE_BTNS.begin(); it != EVASIVE_BTNS.end(); ++it) { | |
int idx = it->second.idx; | |
EvasiveButton& btn = | |
dynamic_cast<EvasiveButton&>(*parentData.wgtCalculator->wgtButtonGrid->buttons[idx]); | |
btn.reset(); | |
} | |
parentData.wgtCalculator->wgtButtonGrid->grid->update(); | |
for (auto& button : parentData.wgtCalculator->wgtButtonGrid->buttons) { | |
buttonId_t id = static_cast<buttonId_t>(group.id(button.get())); | |
if (features.count(id) > 0) { | |
button->show(); | |
} | |
else { | |
button->hide(); | |
} | |
} | |
parentData.wgtCalculator->wgtDigitDisplay->clear(); | |
} | |
//=========================================== | |
// addButton | |
//=========================================== | |
static void addButton(QPushButton* btn, ButtonGrid& btnGrid, buttonId_t id, const BtnDesc& desc) { | |
QSizePolicy sp = btn->sizePolicy(); | |
sp.setRetainSizeWhenHidden(true); | |
btn->setMaximumHeight(60); | |
btn->setSizePolicy(sp); | |
btnGrid.grid->addWidget(btn, desc.row, desc.col); | |
btnGrid.buttonGroup->addButton(btn, id); | |
btnGrid.buttons[desc.idx] = makeQtObjPtrFromRawPtr<QPushButton>(btn); | |
} | |
//=========================================== | |
// FPartialCalc::reload | |
//=========================================== | |
void FPartialCalc::reload(const FragmentSpec&) { | |
DBG_PRINT("FPartialCalc::reload\n"); | |
auto& parentData = parentFragData<FCalculatorData>(); | |
auto& wgtButtonGrid = *parentData.wgtCalculator->wgtButtonGrid; | |
for (auto it = EXPLODING_BTNS.begin(); it != EXPLODING_BTNS.end(); ++it) { | |
addButton(new ExplodingButton(&wgtButtonGrid, it->second.text, commonData.appConfig, | |
commonData.updateLoop), wgtButtonGrid, it->first, it->second); | |
} | |
for (auto it = EVASIVE_BTNS.begin(); it != EVASIVE_BTNS.end(); ++it) { | |
addButton(new EvasiveButton(it->second.text), wgtButtonGrid, it->first, it->second); | |
} | |
disconnect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), | |
parentData.wgtCalculator.get(), SLOT(onButtonClick(int))); | |
connect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), this, | |
SLOT(onButtonClick(int))); | |
connect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonPressed(int)), this, | |
SLOT(onButtonPress(int))); | |
} | |
//=========================================== | |
// FPartialCalc::cleanUp | |
//=========================================== | |
void FPartialCalc::cleanUp() { | |
DBG_PRINT("FPartialCalc::cleanUp\n"); | |
auto& parentData = parentFragData<FCalculatorData>(); | |
for (auto& button : parentData.wgtCalculator->wgtButtonGrid->buttons) { | |
button->show(); | |
} | |
disconnect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonPressed(int)), this, | |
SLOT(onButtonPress(int))); | |
disconnect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), this, | |
SLOT(onButtonClick(int))); | |
connect(parentData.wgtCalculator->wgtButtonGrid.get(), SIGNAL(buttonClicked(int)), | |
parentData.wgtCalculator.get(), SLOT(onButtonClick(int))); | |
} | |
//=========================================== | |
// FPartialCalc:onButtonClick | |
//=========================================== | |
void FPartialCalc::onButtonClick(int id) { | |
if (EVASIVE_BTNS.count(static_cast<buttonId_t>(id)) > 0) { | |
return; | |
} | |
auto& parentData = parentFragData<FCalculatorData>(); | |
parentData.wgtCalculator->onButtonClick(id); | |
string display = parentData.wgtCalculator->wgtDigitDisplay->text().toStdString(); | |
commonData.eventSystem.fire(pEvent_t(new making_progress::ButtonPressEvent(display))); | |
} | |
//=========================================== | |
// FPartialCalc:onButtonPress | |
//=========================================== | |
void FPartialCalc::onButtonPress(int id) { | |
if (EVASIVE_BTNS.count(static_cast<buttonId_t>(id)) == 0) { | |
return; | |
} | |
auto& parentData = parentFragData<FCalculatorData>(); | |
parentData.wgtCalculator->onButtonClick(id); | |
string display = parentData.wgtCalculator->wgtDigitDisplay->text().toStdString(); | |
commonData.eventSystem.fire(pEvent_t(new making_progress::ButtonPressEvent(display))); | |
} | |
//=========================================== | |
// FPartialCalc::~FPartialCalc | |
//=========================================== | |
FPartialCalc::~FPartialCalc() { | |
DBG_PRINT("FPartialCalc::~FPartialCalc\n"); | |
} | |
#include "fragments/relocatable/f_calculator/f_calculator.hpp" | |
#include "fragments/relocatable/f_calculator/f_calculator_spec.hpp" | |
#include "fragments/relocatable/widget_frag_data.hpp" | |
#include "utils.hpp" | |
#include "effects.hpp" | |
//=========================================== | |
// FCalculator::FCalculator | |
//=========================================== | |
FCalculator::FCalculator(Fragment& parent_, FragmentData& parentData_, | |
const CommonFragData& commonData) | |
: Fragment("FCalculator", parent_, parentData_, m_data, commonData) { | |
DBG_PRINT("FCalculator::FCalculator\n"); | |
} | |
//=========================================== | |
// FCalculator::reload | |
//=========================================== | |
void FCalculator::reload(const FragmentSpec& spec_) { | |
DBG_PRINT("FCalculator::reload\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
m_origParentState.spacing = parentData.box->spacing(); | |
m_origParentState.margins = parentData.box->contentsMargins(); | |
m_data.wgtCalculator = makeQtObjPtr<CalculatorWidget>(commonData.eventSystem); | |
parentData.box->setSpacing(0); | |
parentData.box->setContentsMargins(0, 0, 0, 0); | |
parentData.box->addWidget(m_data.wgtCalculator.get()); | |
QFont f = m_data.wgtCalculator->font(); | |
f.setPixelSize(18); | |
m_data.wgtCalculator->setFont(f); | |
auto& spec = dynamic_cast<const FCalculatorSpec&>(spec_); | |
setColour(*m_data.wgtCalculator->wgtDigitDisplay, spec.displayColour, QPalette::Base); | |
} | |
//=========================================== | |
// FCalculator::cleanUp | |
//=========================================== | |
void FCalculator::cleanUp() { | |
DBG_PRINT("FCalculator::cleanUp\n"); | |
auto& parentData = parentFragData<WidgetFragData>(); | |
parentData.box->setSpacing(m_origParentState.spacing); | |
parentData.box->setContentsMargins(m_origParentState.margins); | |
parentData.box->removeWidget(m_data.wgtCalculator.get()); | |
} | |
//=========================================== | |
// FCalculator::~FCalculator | |
//=========================================== | |
FCalculator::~FCalculator() { | |
DBG_PRINT("FCalculator::~FCalculator\n"); | |
} | |
#include <random> | |
#include <cmath> | |
#include <limits> | |
#include <vector> | |
#include <sstream> | |
#include <QRect> | |
#include <QRectF> | |
#include <QSize> | |
#include "utils.hpp" | |
using std::string; | |
using std::stringstream; | |
using std::istream; | |
using std::ostream; | |
using std::vector; | |
#ifdef __APPLE__ | |
#include <chrono> | |
using std::chrono::system_clock; | |
using std::chrono::milliseconds; | |
using std::chrono::duration_cast; | |
long randomSeed() { | |
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); | |
} | |
#else | |
static std::random_device rd; | |
long randomSeed() { | |
return rd(); | |
} | |
#endif | |
#ifdef DEBUG | |
ostream& operator<<(ostream& os, const QRect& rect) { | |
os << rect.x() << ", " << rect.y() << ", " << rect.width() << ", " << rect.height(); | |
return os; | |
} | |
ostream& operator<<(ostream& os, const QRectF& rect) { | |
os << rect.x() << ", " << rect.y() << ", " << rect.width() << ", " << rect.height(); | |
return os; | |
} | |
ostream& operator<<(ostream& os, const QPoint& p) { | |
os << p.x() << ", " << p.y(); | |
return os; | |
} | |
ostream& operator<<(ostream& os, const QPointF& p) { | |
os << p.x() << ", " << p.y(); | |
return os; | |
} | |
std::ostream& operator<<(std::ostream& os, const QSize& sz) { | |
os << "(" << sz.width() << ", " << sz.height() << ")"; | |
return os; | |
} | |
#endif | |
string readString(istream& is) { | |
int nBytes = 0; | |
is.read(reinterpret_cast<char*>(&nBytes), sizeof(nBytes)); | |
vector<char> buf(nBytes); | |
is.read(buf.data(), nBytes); | |
return string(buf.data(), nBytes); | |
} | |
void writeString(ostream& os, const string& s) { | |
size_t nBytes = s.size(); | |
os.write(reinterpret_cast<const char*>(&nBytes), sizeof(nBytes)); | |
os.write(s.data(), nBytes); | |
} | |
vector<string> splitString(const string& s, char delim) { | |
vector<string> v; | |
stringstream ss(s); | |
string tok; | |
while (std::getline(ss, tok, delim)) { | |
v.push_back(tok); | |
} | |
return v; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment