Skip to content

Instantly share code, notes, and snippets.

@robjinman
Created August 11, 2018 22:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robjinman/f5326ee40e99181b71553bf82083fa2e to your computer and use it in GitHub Desktop.
Save robjinman/f5326ee40e99181b71553bf82083fa2e to your computer and use it in GitHub Desktop.
Code for Pro Office Calculator
#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 = &region;
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 = &region;
}
}
}
++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 == &current ? 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