summary refs log tree commit diff stats
path: root/src/systems
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-05-17 15:55:37 -0400
committerGitHub <noreply@github.com>2018-05-17 15:55:37 -0400
commit90aadf3844386824140a20d7fbb847bc16009a94 (patch)
tree6f83fce90e71abb22b1a8f3e09c79963b2a34d5d /src/systems
parentbc63fa57ced1c7329f7fdcfd168eaf7e290158bc (diff)
parent86f0106d0523825549f1e74b835688c78a10cf6c (diff)
downloadtherapy-90aadf3844386824140a20d7fbb847bc16009a94.tar.gz
therapy-90aadf3844386824140a20d7fbb847bc16009a94.tar.bz2
therapy-90aadf3844386824140a20d7fbb847bc16009a94.zip
Merge pull request #7 from hatkirby/es-rewrite
The ECS rewrite exceeds the original branch in functionality, so it is time to merge it in.
Diffstat (limited to 'src/systems')
-rw-r--r--src/systems/animating.cpp103
-rw-r--r--src/systems/animating.h25
-rw-r--r--src/systems/controlling.cpp136
-rw-r--r--src/systems/controlling.h27
-rw-r--r--src/systems/mapping.cpp187
-rw-r--r--src/systems/mapping.h19
-rw-r--r--src/systems/orienting.cpp230
-rw-r--r--src/systems/orienting.h35
-rw-r--r--src/systems/playing.cpp144
-rw-r--r--src/systems/playing.h25
-rw-r--r--src/systems/pondering.cpp916
-rw-r--r--src/systems/pondering.h81
-rw-r--r--src/systems/realizing.cpp438
-rw-r--r--src/systems/realizing.h86
-rw-r--r--src/systems/scheduling.cpp54
-rw-r--r--src/systems/scheduling.h22
-rw-r--r--src/systems/scripting.cpp257
-rw-r--r--src/systems/scripting.h34
18 files changed, 2819 insertions, 0 deletions
diff --git a/src/systems/animating.cpp b/src/systems/animating.cpp new file mode 100644 index 0000000..50a32fc --- /dev/null +++ b/src/systems/animating.cpp
@@ -0,0 +1,103 @@
1#include "animating.h"
2#include "game.h"
3#include "components/animatable.h"
4#include "components/transformable.h"
5
6void AnimatingSystem::tick(double)
7{
8 std::set<id_type> spriteEntities =
9 game_.getEntityManager().getEntitiesWithComponents<AnimatableComponent>();
10
11 for (id_type entity : spriteEntities)
12 {
13 auto& sprite = game_.getEntityManager().
14 getComponent<AnimatableComponent>(entity);
15
16 if (sprite.active)
17 {
18 if (!sprite.frozen)
19 {
20 sprite.countdown++;
21 }
22
23 const Animation& anim = sprite.getAnimation();
24 if (sprite.countdown >= anim.getDelay())
25 {
26 sprite.frame++;
27 sprite.countdown = 0;
28
29 if (sprite.frame >= anim.getFirstFrame() + anim.getNumFrames())
30 {
31 sprite.frame = anim.getFirstFrame();
32 }
33 }
34
35 if (sprite.flickering)
36 {
37 sprite.flickerTimer = (sprite.flickerTimer + 1) % 6;
38 }
39 }
40 }
41}
42
43void AnimatingSystem::render(Texture& texture)
44{
45 std::set<id_type> spriteEntities =
46 game_.getEntityManager().getEntitiesWithComponents<
47 AnimatableComponent,
48 TransformableComponent>();
49
50 for (id_type entity : spriteEntities)
51 {
52 auto& sprite = game_.getEntityManager().
53 getComponent<AnimatableComponent>(entity);
54
55 if (sprite.active)
56 {
57 auto& transform = game_.getEntityManager().
58 getComponent<TransformableComponent>(entity);
59
60 double alpha = 1.0;
61 if (sprite.flickering && (sprite.flickerTimer < 3))
62 {
63 alpha = 0.0;
64 }
65
66 Rectangle dstrect {
67 static_cast<int>(transform.pos.x()),
68 static_cast<int>(transform.pos.y()),
69 transform.size.w(),
70 transform.size.h()};
71
72 const AnimationSet& aset = sprite.animationSet;
73 game_.getRenderer().blit(
74 aset.getTexture(),
75 texture,
76 aset.getFrameRect(sprite.frame),
77 dstrect,
78 alpha);
79 }
80 }
81}
82
83void AnimatingSystem::initPrototype(id_type entity)
84{
85 auto& sprite = game_.getEntityManager().
86 getComponent<AnimatableComponent>(entity);
87
88 startAnimation(entity, sprite.origAnimation);
89
90 sprite.countdown = 0;
91 sprite.flickering = false;
92 sprite.flickerTimer = 0;
93 sprite.frozen = false;
94}
95
96void AnimatingSystem::startAnimation(id_type entity, std::string animation)
97{
98 auto& sprite = game_.getEntityManager().
99 getComponent<AnimatableComponent>(entity);
100
101 sprite.animation = std::move(animation);
102 sprite.frame = sprite.getAnimation().getFirstFrame();
103}
diff --git a/src/systems/animating.h b/src/systems/animating.h new file mode 100644 index 0000000..acc6191 --- /dev/null +++ b/src/systems/animating.h
@@ -0,0 +1,25 @@
1#ifndef ANIMATING_H_5BBF0094
2#define ANIMATING_H_5BBF0094
3
4#include "system.h"
5#include <string>
6#include "renderer/texture.h"
7
8class AnimatingSystem : public System {
9public:
10
11 AnimatingSystem(Game& game) : System(game)
12 {
13 }
14
15 void tick(double dt);
16
17 void render(Texture& texture);
18
19 void initPrototype(id_type entity);
20
21 void startAnimation(id_type entity, std::string animation);
22
23};
24
25#endif /* end of include guard: ANIMATING_H_5BBF0094 */
diff --git a/src/systems/controlling.cpp b/src/systems/controlling.cpp new file mode 100644 index 0000000..e65c09a --- /dev/null +++ b/src/systems/controlling.cpp
@@ -0,0 +1,136 @@
1#include "controlling.h"
2#include "game.h"
3#include "components/controllable.h"
4#include "components/orientable.h"
5#include "systems/orienting.h"
6
7void ControllingSystem::tick(double)
8{
9 while (!actions_.empty())
10 {
11 int key = actions_.front().first;
12 int action = actions_.front().second;
13
14 auto entities = game_.getEntityManager().getEntitiesWithComponents<
15 ControllableComponent,
16 OrientableComponent>();
17
18 for (auto entity : entities)
19 {
20 auto& controllable = game_.getEntityManager().
21 getComponent<ControllableComponent>(entity);
22
23 auto& orienting = game_.getSystemManager().getSystem<OrientingSystem>();
24
25 if (action == GLFW_PRESS)
26 {
27 if (key == controllable.getLeftKey())
28 {
29 controllable.setHoldingLeft(true);
30
31 if (!controllable.isFrozen())
32 {
33 orienting.moveLeft(entity);
34 }
35 } else if (key == controllable.getRightKey())
36 {
37 controllable.setHoldingRight(true);
38
39 if (!controllable.isFrozen())
40 {
41 orienting.moveRight(entity);
42 }
43 } else if (key == controllable.getJumpKey())
44 {
45 if (!controllable.isFrozen())
46 {
47 orienting.jump(entity);
48 }
49 } else if (key == controllable.getDropKey())
50 {
51 if (!controllable.isFrozen())
52 {
53 orienting.drop(entity);
54 }
55 }
56 } else if (action == GLFW_RELEASE)
57 {
58 if (key == controllable.getLeftKey())
59 {
60 controllable.setHoldingLeft(false);
61
62 if (!controllable.isFrozen())
63 {
64 if (controllable.isHoldingRight())
65 {
66 orienting.moveRight(entity);
67 } else {
68 orienting.stopWalking(entity);
69 }
70 }
71 } else if (key == controllable.getRightKey())
72 {
73 controllable.setHoldingRight(false);
74
75 if (!controllable.isFrozen())
76 {
77 if (controllable.isHoldingLeft())
78 {
79 orienting.moveLeft(entity);
80 } else {
81 orienting.stopWalking(entity);
82 }
83 }
84 } else if (key == controllable.getDropKey())
85 {
86 if (!controllable.isFrozen())
87 {
88 orienting.stopDropping(entity);
89 }
90 } else if (key == controllable.getJumpKey())
91 {
92 if (!controllable.isFrozen())
93 {
94 orienting.stopJumping(entity);
95 }
96 }
97 }
98 }
99
100 actions_.pop();
101 }
102}
103
104void ControllingSystem::input(int key, int action)
105{
106 actions_.push(std::make_pair(key, action));
107}
108
109void ControllingSystem::freeze(id_type entity)
110{
111 auto& controllable = game_.getEntityManager().
112 getComponent<ControllableComponent>(entity);
113
114 controllable.setFrozen(true);
115}
116
117void ControllingSystem::unfreeze(id_type entity)
118{
119 auto& controllable = game_.getEntityManager().
120 getComponent<ControllableComponent>(entity);
121
122 if (controllable.isFrozen())
123 {
124 controllable.setFrozen(false);
125
126 auto& orienting = game_.getSystemManager().getSystem<OrientingSystem>();
127
128 if (controllable.isHoldingLeft())
129 {
130 orienting.moveLeft(entity);
131 } else if (controllable.isHoldingRight())
132 {
133 orienting.moveRight(entity);
134 }
135 }
136}
diff --git a/src/systems/controlling.h b/src/systems/controlling.h new file mode 100644 index 0000000..d6f0789 --- /dev/null +++ b/src/systems/controlling.h
@@ -0,0 +1,27 @@
1#ifndef CONTROLLING_H_80B1BB8D
2#define CONTROLLING_H_80B1BB8D
3
4#include "system.h"
5#include <queue>
6
7class ControllingSystem : public System {
8public:
9
10 ControllingSystem(Game& game) : System(game)
11 {
12 }
13
14 void tick(double dt);
15
16 void input(int key, int action);
17
18 void freeze(id_type entity);
19
20 void unfreeze(id_type entity);
21
22private:
23
24 std::queue<std::pair<int,int>> actions_;
25};
26
27#endif /* end of include guard: CONTROLLING_H_80B1BB8D */
diff --git a/src/systems/mapping.cpp b/src/systems/mapping.cpp new file mode 100644 index 0000000..1275e11 --- /dev/null +++ b/src/systems/mapping.cpp
@@ -0,0 +1,187 @@
1#include "mapping.h"
2#include "components/mappable.h"
3#include "systems/realizing.h"
4#include "game.h"
5#include "consts.h"
6
7template <typename Storage>
8inline void addBoundary(
9 Storage& boundaries,
10 int axis,
11 int lower,
12 int upper,
13 MappableComponent::Boundary::Type type)
14{
15 boundaries.emplace(std::piecewise_construct,
16 std::tie(axis),
17 std::tie(axis, lower, upper, type));
18}
19
20void MappingSystem::render(Texture& texture)
21{
22 id_type map =
23 game_.getSystemManager().getSystem<RealizingSystem>().getActiveMap();
24
25 auto& mappable = game_.getEntityManager().
26 getComponent<MappableComponent>(map);
27
28 for (int i = 0; i < MAP_WIDTH * MAP_HEIGHT; i++)
29 {
30 int x = i % MAP_WIDTH;
31 int y = i / MAP_WIDTH;
32 int tile = mappable.tiles[i];
33
34 if (tile > 0)
35 {
36 Rectangle dst {
37 x * TILE_WIDTH,
38 y * TILE_HEIGHT,
39 TILE_WIDTH,
40 TILE_HEIGHT};
41
42 Rectangle src {
43 (tile % TILESET_COLS) * TILE_WIDTH,
44 (tile / TILESET_COLS) * TILE_HEIGHT,
45 TILE_WIDTH,
46 TILE_HEIGHT};
47
48 game_.getRenderer().blit(
49 mappable.tileset,
50 texture,
51 std::move(src),
52 std::move(dst));
53 }
54 }
55
56 int startX = ((GAME_WIDTH / TILE_WIDTH) / 2) - (mappable.title.size() / 2);
57
58 for (size_t i = 0; i < mappable.title.size(); i++)
59 {
60 Rectangle src {
61 (mappable.title[i] % FONT_COLS) * TILE_WIDTH,
62 (mappable.title[i] / FONT_COLS) * TILE_HEIGHT,
63 TILE_WIDTH,
64 TILE_HEIGHT};
65
66 Rectangle dst {
67 (startX + static_cast<int>(i)) * TILE_WIDTH,
68 24 * TILE_HEIGHT,
69 TILE_WIDTH,
70 TILE_HEIGHT};
71
72 game_.getRenderer().blit(
73 mappable.font,
74 texture,
75 std::move(src),
76 std::move(dst));
77 }
78}
79
80void MappingSystem::generateBoundaries(id_type mapEntity)
81{
82 auto& mappable = game_.getEntityManager().
83 getComponent<MappableComponent>(mapEntity);
84
85 addBoundary(
86 mappable.leftBoundaries,
87 -WALL_GAP,
88 0,
89 MAP_HEIGHT * TILE_HEIGHT,
90 MappableComponent::Boundary::Type::adjacency);
91
92 addBoundary(
93 mappable.rightBoundaries,
94 GAME_WIDTH + WALL_GAP,
95 0,
96 MAP_HEIGHT * TILE_HEIGHT,
97 MappableComponent::Boundary::Type::adjacency);
98
99 addBoundary(
100 mappable.upBoundaries,
101 -WALL_GAP,
102 0,
103 GAME_WIDTH,
104 MappableComponent::Boundary::Type::adjacency);
105
106 addBoundary(
107 mappable.downBoundaries,
108 MAP_HEIGHT * TILE_HEIGHT + WALL_GAP,
109 0,
110 GAME_WIDTH,
111 MappableComponent::Boundary::Type::adjacency);
112
113 for (size_t i = 0; i < MAP_WIDTH * MAP_HEIGHT; i++)
114 {
115 size_t x = i % MAP_WIDTH;
116 size_t y = i / MAP_WIDTH;
117 int tile = mappable.tiles[i];
118
119 if ((tile >= 5) && (tile <= 7))
120 {
121 addBoundary(
122 mappable.downBoundaries,
123 y * TILE_HEIGHT,
124 x * TILE_WIDTH,
125 (x + 1) * TILE_WIDTH,
126 MappableComponent::Boundary::Type::platform);
127 } else if ((tile > 0) && (tile < 28))
128 {
129 addBoundary(
130 mappable.rightBoundaries,
131 x * TILE_WIDTH,
132 y * TILE_HEIGHT,
133 (y+1) * TILE_HEIGHT,
134 MappableComponent::Boundary::Type::wall);
135
136 addBoundary(
137 mappable.leftBoundaries,
138 (x+1) * TILE_WIDTH,
139 y * TILE_HEIGHT,
140 (y+1) * TILE_HEIGHT,
141 MappableComponent::Boundary::Type::wall);
142
143 addBoundary(
144 mappable.downBoundaries,
145 y * TILE_HEIGHT,
146 x * TILE_WIDTH,
147 (x+1) * TILE_WIDTH,
148 MappableComponent::Boundary::Type::wall);
149
150 addBoundary(
151 mappable.upBoundaries,
152 (y+1) * TILE_HEIGHT,
153 x * TILE_WIDTH,
154 (x+1) * TILE_WIDTH,
155 MappableComponent::Boundary::Type::wall);
156 } else if (tile == 42)
157 {
158 addBoundary(
159 mappable.rightBoundaries,
160 x * TILE_WIDTH,
161 y * TILE_HEIGHT,
162 (y+1) * TILE_HEIGHT,
163 MappableComponent::Boundary::Type::danger);
164
165 addBoundary(
166 mappable.leftBoundaries,
167 (x+1) * TILE_WIDTH,
168 y * TILE_HEIGHT,
169 (y+1) * TILE_HEIGHT,
170 MappableComponent::Boundary::Type::danger);
171
172 addBoundary(
173 mappable.downBoundaries,
174 y * TILE_HEIGHT + 1,
175 x * TILE_WIDTH,
176 (x+1) * TILE_WIDTH,
177 MappableComponent::Boundary::Type::danger);
178
179 addBoundary(
180 mappable.upBoundaries,
181 (y+1) * TILE_HEIGHT,
182 x * TILE_WIDTH,
183 (x+1) * TILE_WIDTH,
184 MappableComponent::Boundary::Type::danger);
185 }
186 }
187}
diff --git a/src/systems/mapping.h b/src/systems/mapping.h new file mode 100644 index 0000000..3c47419 --- /dev/null +++ b/src/systems/mapping.h
@@ -0,0 +1,19 @@
1#ifndef MAPPING_H_33FC2294
2#define MAPPING_H_33FC2294
3
4#include "system.h"
5
6class MappingSystem : public System {
7public:
8
9 MappingSystem(Game& game) : System(game)
10 {
11 }
12
13 void render(Texture& texture);
14
15 void generateBoundaries(id_type mapEntity);
16
17};
18
19#endif /* end of include guard: MAPPING_H_33FC2294 */
diff --git a/src/systems/orienting.cpp b/src/systems/orienting.cpp new file mode 100644 index 0000000..d73ddd2 --- /dev/null +++ b/src/systems/orienting.cpp
@@ -0,0 +1,230 @@
1#include "orienting.h"
2#include "game.h"
3#include "components/orientable.h"
4#include "components/ponderable.h"
5#include "systems/animating.h"
6#include "consts.h"
7#include "muxer.h"
8
9void OrientingSystem::tick(double)
10{
11 auto entities = game_.getEntityManager().getEntitiesWithComponents<
12 OrientableComponent,
13 PonderableComponent>();
14
15 for (id_type entity : entities)
16 {
17 auto& orientable = game_.getEntityManager().
18 getComponent<OrientableComponent>(entity);
19
20 auto& ponderable = game_.getEntityManager().
21 getComponent<PonderableComponent>(entity);
22
23 switch (orientable.getWalkState())
24 {
25 case OrientableComponent::WalkState::still:
26 {
27 ponderable.vel.x() = 0.0;
28
29 break;
30 }
31
32 case OrientableComponent::WalkState::left:
33 {
34 ponderable.vel.x() = -WALK_SPEED;
35
36 break;
37 }
38
39 case OrientableComponent::WalkState::right:
40 {
41 ponderable.vel.x() = WALK_SPEED;
42
43 break;
44 }
45 }
46
47 if (orientable.isJumping() && (ponderable.vel.y() > 0))
48 {
49 orientable.setJumping(false);
50 }
51 }
52}
53
54void OrientingSystem::moveLeft(id_type entity)
55{
56 auto& ponderable = game_.getEntityManager().
57 getComponent<PonderableComponent>(entity);
58
59 auto& orientable = game_.getEntityManager().
60 getComponent<OrientableComponent>(entity);
61
62 orientable.setFacingRight(false);
63 orientable.setWalkState(OrientableComponent::WalkState::left);
64
65 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
66 if (ponderable.grounded)
67 {
68 animating.startAnimation(entity, "walkingLeft");
69 } else {
70 animating.startAnimation(entity, "stillLeft");
71 }
72}
73
74void OrientingSystem::moveRight(id_type entity)
75{
76 auto& ponderable = game_.getEntityManager().
77 getComponent<PonderableComponent>(entity);
78
79 auto& orientable = game_.getEntityManager().
80 getComponent<OrientableComponent>(entity);
81
82 orientable.setFacingRight(true);
83 orientable.setWalkState(OrientableComponent::WalkState::right);
84
85 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
86 if (ponderable.grounded)
87 {
88 animating.startAnimation(entity, "walkingRight");
89 } else {
90 animating.startAnimation(entity, "stillRight");
91 }
92}
93
94void OrientingSystem::stopWalking(id_type entity)
95{
96 auto& orientable = game_.getEntityManager().
97 getComponent<OrientableComponent>(entity);
98
99 orientable.setWalkState(OrientableComponent::WalkState::still);
100
101 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
102
103 if (orientable.isFacingRight())
104 {
105 animating.startAnimation(entity, "stillRight");
106 } else {
107 animating.startAnimation(entity, "stillLeft");
108 }
109}
110
111void OrientingSystem::jump(id_type entity)
112{
113 auto& ponderable = game_.getEntityManager().
114 getComponent<PonderableComponent>(entity);
115
116 if (ponderable.grounded)
117 {
118 auto& orientable = game_.getEntityManager().
119 getComponent<OrientableComponent>(entity);
120
121 orientable.setJumping(true);
122
123 playSound("res/Randomize87.wav", 0.25);
124
125 ponderable.vel.y() = JUMP_VELOCITY;
126 ponderable.accel.y() = JUMP_GRAVITY;
127
128 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
129 if (orientable.isFacingRight())
130 {
131 animating.startAnimation(entity, "stillRight");
132 } else {
133 animating.startAnimation(entity, "stillLeft");
134 }
135 }
136}
137
138void OrientingSystem::stopJumping(id_type entity)
139{
140 auto& orientable = game_.getEntityManager().
141 getComponent<OrientableComponent>(entity);
142
143 if (orientable.isJumping())
144 {
145 orientable.setJumping(false);
146
147 auto& ponderable = game_.getEntityManager().
148 getComponent<PonderableComponent>(entity);
149
150 ponderable.accel.y() = NORMAL_GRAVITY;
151 }
152}
153
154void OrientingSystem::land(id_type entity)
155{
156 auto& orientable = game_.getEntityManager().
157 getComponent<OrientableComponent>(entity);
158
159 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
160
161 switch (orientable.getWalkState())
162 {
163 case OrientableComponent::WalkState::still:
164 {
165 if (orientable.isFacingRight())
166 {
167 animating.startAnimation(entity, "stillRight");
168 } else {
169 animating.startAnimation(entity, "stillLeft");
170 }
171
172 break;
173 }
174
175 case OrientableComponent::WalkState::left:
176 {
177 animating.startAnimation(entity, "walkingLeft");
178
179 break;
180 }
181
182 case OrientableComponent::WalkState::right:
183 {
184 animating.startAnimation(entity, "walkingRight");
185
186 break;
187 }
188 }
189}
190
191void OrientingSystem::startFalling(id_type entity)
192{
193 auto& orientable = game_.getEntityManager().
194 getComponent<OrientableComponent>(entity);
195
196 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
197
198 if (orientable.isFacingRight())
199 {
200 animating.startAnimation(entity, "stillRight");
201 } else {
202 animating.startAnimation(entity, "stillLeft");
203 }
204}
205
206void OrientingSystem::drop(id_type entity)
207{
208 auto& orientable = game_.getEntityManager().
209 getComponent<OrientableComponent>(entity);
210
211 auto& ponderable = game_.getEntityManager().
212 getComponent<PonderableComponent>(entity);
213
214 if (ponderable.grounded
215 && (orientable.getDropState() == OrientableComponent::DropState::none))
216 {
217 orientable.setDropState(OrientableComponent::DropState::ready);
218 }
219}
220
221void OrientingSystem::stopDropping(id_type entity)
222{
223 auto& orientable = game_.getEntityManager().
224 getComponent<OrientableComponent>(entity);
225
226 if (orientable.getDropState() == OrientableComponent::DropState::ready)
227 {
228 orientable.setDropState(OrientableComponent::DropState::none);
229 }
230}
diff --git a/src/systems/orienting.h b/src/systems/orienting.h new file mode 100644 index 0000000..4ded612 --- /dev/null +++ b/src/systems/orienting.h
@@ -0,0 +1,35 @@
1#ifndef ORIENTING_H_099F0C23
2#define ORIENTING_H_099F0C23
3
4#include "system.h"
5
6class OrientingSystem : public System {
7public:
8
9 OrientingSystem(Game& game) : System(game)
10 {
11 }
12
13 void tick(double dt);
14
15 void moveLeft(id_type entity);
16
17 void moveRight(id_type entity);
18
19 void stopWalking(id_type entity);
20
21 void jump(id_type entity);
22
23 void stopJumping(id_type entity);
24
25 void land(id_type entity);
26
27 void startFalling(id_type entity);
28
29 void drop(id_type entity);
30
31 void stopDropping(id_type entity);
32
33};
34
35#endif /* end of include guard: ORIENTING_H_099F0C23 */
diff --git a/src/systems/playing.cpp b/src/systems/playing.cpp new file mode 100644 index 0000000..6652099 --- /dev/null +++ b/src/systems/playing.cpp
@@ -0,0 +1,144 @@
1#include "playing.h"
2#include "game.h"
3#include "components/transformable.h"
4#include "components/animatable.h"
5#include "components/playable.h"
6#include "components/controllable.h"
7#include "components/orientable.h"
8#include "systems/mapping.h"
9#include "systems/pondering.h"
10#include "systems/orienting.h"
11#include "systems/scheduling.h"
12#include "systems/controlling.h"
13#include "systems/animating.h"
14#include "systems/realizing.h"
15#include "animation.h"
16#include "muxer.h"
17
18void PlayingSystem::initPlayer()
19{
20 id_type player = game_.getEntityManager().emplaceEntity();
21
22 AnimationSet playerGraphics {"res/Starla.png", 10, 12, 6};
23 playerGraphics.emplaceAnimation("stillLeft", 3, 1, 1);
24 playerGraphics.emplaceAnimation("stillRight", 0, 1, 1);
25 playerGraphics.emplaceAnimation("walkingLeft", 4, 2, 10);
26 playerGraphics.emplaceAnimation("walkingRight", 1, 2, 10);
27
28 game_.getEntityManager().emplaceComponent<AnimatableComponent>(
29 player,
30 std::move(playerGraphics));
31
32 game_.getSystemManager().getSystem<AnimatingSystem>().startAnimation(
33 player,
34 "stillLeft");
35
36 auto& realizing = game_.getSystemManager().getSystem<RealizingSystem>();
37
38 auto& transformable = game_.getEntityManager().
39 emplaceComponent<TransformableComponent>(player);
40
41 transformable.pos = realizing.getStartingPos();
42 transformable.size.w() = 10;
43 transformable.size.h() = 12;
44
45 game_.getSystemManager().getSystem<PonderingSystem>().initializeBody(
46 player,
47 PonderableComponent::Type::freefalling);
48
49 game_.getEntityManager().emplaceComponent<ControllableComponent>(player);
50 game_.getEntityManager().emplaceComponent<OrientableComponent>(player);
51
52 auto& playable = game_.getEntityManager().
53 emplaceComponent<PlayableComponent>(player);
54
55 playable.mapId = realizing.getActiveMap();
56 playable.checkpointMapId = realizing.getStartingMapId();
57 playable.checkpointPos = realizing.getStartingPos();
58
59 realizing.enterActiveMap(player);
60
61 realizing.setActivePlayer(player);
62}
63
64void PlayingSystem::changeMap(
65 id_type player,
66 size_t mapId,
67 vec2d warpPos)
68{
69 auto& playable = game_.getEntityManager().
70 getComponent<PlayableComponent>(player);
71
72 auto& transformable = game_.getEntityManager().
73 getComponent<TransformableComponent>(player);
74
75 auto& pondering = game_.getSystemManager().getSystem<PonderingSystem>();
76 auto& realizing = game_.getSystemManager().getSystem<RealizingSystem>();
77
78 id_type newMapEntity = realizing.getEntityByMapId(mapId);
79
80 if (playable.mapId != newMapEntity)
81 {
82 if (playable.mapId == realizing.getActiveMap())
83 {
84 realizing.leaveActiveMap(player);
85 } else if (newMapEntity == realizing.getActiveMap())
86 {
87 realizing.enterActiveMap(player);
88 }
89
90 playable.mapId = newMapEntity;
91 }
92
93 pondering.unferry(player);
94
95 transformable.pos = warpPos;
96
97 if (realizing.getActivePlayer() == player)
98 {
99 realizing.loadMap(newMapEntity);
100 }
101}
102
103void PlayingSystem::die(id_type player)
104{
105 playSound("res/Hit_Hurt5.wav", 0.25);
106
107 auto& animatable = game_.getEntityManager().
108 getComponent<AnimatableComponent>(player);
109
110 auto& ponderable = game_.getEntityManager().
111 getComponent<PonderableComponent>(player);
112
113 auto& controlling = game_.getSystemManager().getSystem<ControllingSystem>();
114 controlling.freeze(player);
115
116 animatable.frozen = true;
117 animatable.flickering = true;
118 ponderable.frozen = true;
119 ponderable.collidable = false;
120
121 auto& scheduling = game_.getSystemManager().getSystem<SchedulingSystem>();
122
123 scheduling.schedule(player, 0.75, [&] (id_type player) {
124 auto& playable = game_.getEntityManager().
125 getComponent<PlayableComponent>(player);
126
127 changeMap(
128 player,
129 playable.checkpointMapId,
130 playable.checkpointPos);
131
132 animatable.frozen = false;
133 animatable.flickering = false;
134 ponderable.frozen = false;
135 ponderable.collidable = true;
136
137 // Reset the walk state, and then potentially let the
138 // ControllingSystem set it again.
139 auto& orienting = game_.getSystemManager().getSystem<OrientingSystem>();
140 orienting.stopWalking(player);
141
142 controlling.unfreeze(player);
143 });
144}
diff --git a/src/systems/playing.h b/src/systems/playing.h new file mode 100644 index 0000000..31f79ab --- /dev/null +++ b/src/systems/playing.h
@@ -0,0 +1,25 @@
1#ifndef PLAYING_H_70A54F7D
2#define PLAYING_H_70A54F7D
3
4#include "system.h"
5#include "vector.h"
6
7class PlayingSystem : public System {
8public:
9
10 PlayingSystem(Game& game) : System(game)
11 {
12 }
13
14 void initPlayer();
15
16 void changeMap(
17 id_type player,
18 size_t mapId,
19 vec2d warpPos);
20
21 void die(id_type player);
22
23};
24
25#endif /* end of include guard: PLAYING_H_70A54F7D */
diff --git a/src/systems/pondering.cpp b/src/systems/pondering.cpp new file mode 100644 index 0000000..d841679 --- /dev/null +++ b/src/systems/pondering.cpp
@@ -0,0 +1,916 @@
1#include "pondering.h"
2#include <queue>
3#include <algorithm>
4#include "game.h"
5#include "components/ponderable.h"
6#include "components/transformable.h"
7#include "components/orientable.h"
8#include "components/mappable.h"
9#include "components/playable.h"
10#include "systems/orienting.h"
11#include "systems/playing.h"
12#include "systems/realizing.h"
13#include "systems/scripting.h"
14#include "consts.h"
15
16void PonderingSystem::tick(double dt)
17{
18 auto entities = game_.getEntityManager().getEntitiesWithComponents<
19 PonderableComponent,
20 TransformableComponent>();
21
22 for (id_type entity : entities)
23 {
24 auto& ponderable = game_.getEntityManager().
25 getComponent<PonderableComponent>(entity);
26
27 // We will recursively process ferried bodies after their ferries have been
28 // processed, so hold off on processing ferried bodies at the top level.
29 if (ponderable.ferried)
30 {
31 continue;
32 }
33
34 tickBody(entity, dt);
35 }
36}
37
38void PonderingSystem::initializeBody(
39 id_type entity,
40 PonderableComponent::Type type)
41{
42 auto& ponderable = game_.getEntityManager().
43 emplaceComponent<PonderableComponent>(entity, type);
44
45 if (type == PonderableComponent::Type::freefalling)
46 {
47 ponderable.accel.y() = NORMAL_GRAVITY;
48 }
49}
50
51void PonderingSystem::initPrototype(id_type prototype)
52{
53 auto& ponderable = game_.getEntityManager().
54 getComponent<PonderableComponent>(prototype);
55
56 ponderable.vel.x() = 0.0;
57 ponderable.vel.y() = 0.0;
58 ponderable.accel.x() = 0.0;
59 ponderable.accel.y() = 0.0;
60 ponderable.grounded = false;
61 ponderable.frozen = false;
62 ponderable.collidable = true;
63 ponderable.ferried = false;
64 ponderable.passengers.clear();
65}
66
67void PonderingSystem::unferry(id_type entity)
68{
69 auto& ponderable = game_.getEntityManager().
70 getComponent<PonderableComponent>(entity);
71
72 if (ponderable.ferried)
73 {
74 ponderable.ferried = false;
75
76 auto& ferryPonder = game_.getEntityManager().
77 getComponent<PonderableComponent>(ponderable.ferry);
78
79 ferryPonder.passengers.erase(entity);
80 }
81}
82
83void PonderingSystem::tickBody(
84 id_type entity,
85 double dt)
86{
87 auto& ponderable = game_.getEntityManager().
88 getComponent<PonderableComponent>(entity);
89
90 if (!ponderable.active)
91 {
92 return;
93 }
94
95 auto& transformable = game_.getEntityManager().
96 getComponent<TransformableComponent>(entity);
97
98 // Accelerate
99 if (!ponderable.frozen)
100 {
101 ponderable.vel += ponderable.accel * dt;
102
103 if ((ponderable.type == PonderableComponent::Type::freefalling)
104 && (ponderable.vel.y() > TERMINAL_VELOCITY))
105 {
106 ponderable.vel.y() = TERMINAL_VELOCITY;
107 }
108 }
109
110 // Move
111 vec2d newPos = transformable.pos;
112
113 if (!ponderable.frozen)
114 {
115 newPos += ponderable.vel * dt;
116 }
117
118 CollisionResult result = moveBody(entity, newPos);
119
120 // Perform cleanup for orientable entites
121 bool groundedChanged = (ponderable.grounded != result.grounded);
122 ponderable.grounded = result.grounded;
123
124 if (game_.getEntityManager().hasComponent<OrientableComponent>(entity))
125 {
126 auto& orientable = game_.getEntityManager().
127 getComponent<OrientableComponent>(entity);
128
129 // Handle changes in groundedness
130 if (groundedChanged)
131 {
132 if (ponderable.grounded)
133 {
134 game_.getSystemManager().getSystem<OrientingSystem>().land(entity);
135 } else {
136 game_.getSystemManager().
137 getSystem<OrientingSystem>().startFalling(entity);
138 }
139 }
140
141 // Complete dropping, if necessary
142 if (orientable.getDropState() == OrientableComponent::DropState::active)
143 {
144 orientable.setDropState(OrientableComponent::DropState::none);
145 }
146 }
147
148 // Ferry or unferry as necessary
149 if ((ponderable.type == PonderableComponent::Type::freefalling) &&
150 groundedChanged)
151 {
152 if (ponderable.grounded &&
153 game_.getEntityManager().
154 hasComponent<PonderableComponent>(result.groundEntity))
155 {
156 // The body is now being ferried
157 auto& ferryPonder = game_.getEntityManager().
158 getComponent<PonderableComponent>(result.groundEntity);
159
160 ponderable.ferried = true;
161 ponderable.ferry = result.groundEntity;
162 ponderable.ferrySide = Direction::up;
163
164 ferryPonder.passengers.insert(entity);
165 } else if (ponderable.ferried)
166 {
167 // The body is no longer being ferried
168 unferry(entity);
169 }
170 }
171
172 // Handle ferry passengers
173 std::set<id_type> passengers = ponderable.passengers;
174
175 for (id_type passenger : passengers)
176 {
177 tickBody(passenger, dt);
178 }
179}
180
181PonderingSystem::CollisionResult PonderingSystem::moveBody(
182 id_type entity,
183 vec2d newPos)
184{
185 auto& ponderable = game_.getEntityManager().
186 getComponent<PonderableComponent>(entity);
187
188 CollisionResult result;
189
190 if (ponderable.collidable)
191 {
192 result = detectCollisions(entity, newPos);
193 } else {
194 result.pos = newPos;
195 }
196
197 if (!ponderable.frozen)
198 {
199 auto& transformable = game_.getEntityManager().
200 getComponent<TransformableComponent>(entity);
201
202 vec2d delta = result.pos - transformable.pos;
203
204 // Move.
205 transformable.pos = result.pos;
206
207 // Stop if the entity hit a wall.
208 if (result.blockedHoriz)
209 {
210 ponderable.vel.x() = 0.0;
211 }
212
213 if (result.blockedVert)
214 {
215 ponderable.vel.y() = 0.0;
216 }
217
218 // Move ferry passengers by the appropriate amount.
219 auto passengers = ponderable.passengers;
220
221 for (id_type passenger : passengers)
222 {
223 auto& passTrans = game_.getEntityManager().
224 getComponent<TransformableComponent>(passenger);
225
226 moveBody(passenger, passTrans.pos + delta);
227 }
228
229 // Move to an adjacent map, if necessary
230 if (result.adjacentlyWarping)
231 {
232 vec2d warpPos = result.pos;
233
234 switch (result.adjWarpDir)
235 {
236 case Direction::left:
237 {
238 warpPos.x() = GAME_WIDTH + WALL_GAP - transformable.size.w();
239
240 break;
241 }
242
243 case Direction::right:
244 {
245 warpPos.x() = -WALL_GAP;
246
247 break;
248 }
249
250 case Direction::up:
251 {
252 warpPos.y() = MAP_HEIGHT * TILE_HEIGHT - transformable.size.h();
253
254 break;
255 }
256
257 case Direction::down:
258 {
259 warpPos.y() = -WALL_GAP;
260
261 break;
262 }
263 }
264
265 game_.getSystemManager().getSystem<PlayingSystem>().
266 changeMap(
267 entity,
268 result.adjWarpMapId,
269 warpPos);
270 }
271 }
272
273 return result;
274}
275
276namespace CollisionParams {
277
278 template <typename HorizVert>
279 class Desc : public HorizVert {
280 public:
281
282 inline static bool AtLeastInAxisSweep(
283 double boundaryAxis,
284 double entityAxis)
285 {
286 return (boundaryAxis >= entityAxis);
287 }
288
289 inline static bool IsPastAxis(
290 double colliderAxis,
291 double entityAxis)
292 {
293 return (colliderAxis > entityAxis);
294 }
295
296 inline static double EntityAxis(const vec2d& pos, const vec2i& size)
297 {
298 return HorizVert::AxisLower(pos);
299 }
300
301 inline static double ObjectAxis(const vec2d& pos, const vec2i& size)
302 {
303 return HorizVert::AxisUpper(pos, size);
304 }
305
306 inline static bool Closer(double left, double right)
307 {
308 return right < left;
309 }
310 };
311
312 template <typename HorizVert>
313 class Asc : public HorizVert {
314 public:
315
316 inline static bool AtLeastInAxisSweep(
317 double boundaryAxis,
318 double entityAxis)
319 {
320 return (boundaryAxis <= entityAxis);
321 }
322
323 inline static bool IsPastAxis(
324 double colliderAxis,
325 double entityAxis)
326 {
327 return (colliderAxis < entityAxis);
328 }
329
330 inline static double EntityAxis(const vec2d& pos, const vec2i& size)
331 {
332 return HorizVert::AxisUpper(pos, size);
333 }
334
335 inline static double ObjectAxis(const vec2d& pos, const vec2i& size)
336 {
337 return HorizVert::AxisLower(pos);
338 }
339
340 inline static bool Closer(double left, double right)
341 {
342 return left < right;
343 }
344 };
345
346 template <size_t Axis, size_t NonAxis>
347 class HorizVert {
348 public:
349
350 inline static double AxisLower(const vec2d& pos)
351 {
352 return pos.coords[Axis];
353 }
354
355 inline static double AxisUpper(const vec2d& pos, const vec2i& size)
356 {
357 return pos.coords[Axis] + size.coords[Axis];
358 }
359
360 inline static double NonAxisLower(const vec2d& pos)
361 {
362 return pos.coords[NonAxis];
363 }
364
365 inline static double NonAxisUpper(const vec2d& pos, const vec2i& size)
366 {
367 return pos.coords[NonAxis] + size.coords[NonAxis];
368 }
369
370 };
371
372 using Horizontal = HorizVert<0, 1>;
373 using Vertical = HorizVert<1, 0>;
374
375 template <Direction dir, typename AscDesc>
376 class DetectCollisions : public AscDesc {
377 public:
378
379 static const Direction Dir = dir;
380
381 inline static double EntityAxis(const vec2d& pos, const vec2i& size)
382 {
383 return AscDesc::EntityAxis(pos, size);
384 }
385
386 inline static double ObjectAxis(const vec2d& pos, const vec2i& size)
387 {
388 return AscDesc::ObjectAxis(pos, size);
389 }
390
391 inline static double EntityAxis(const TransformableComponent& transformable)
392 {
393 return AscDesc::EntityAxis(transformable.pos, transformable.size);
394 }
395
396 inline static double ObjectAxis(const TransformableComponent& transformable)
397 {
398 return AscDesc::ObjectAxis(transformable.pos, transformable.size);
399 }
400 };
401
402 class Left : public DetectCollisions<Direction::left, Desc<Horizontal>> {
403 public:
404
405 inline static const MappableComponent::desc_boundaries_type& MapBoundaries(
406 const MappableComponent& mappable)
407 {
408 return mappable.leftBoundaries;
409 }
410 };
411
412 class Right : public DetectCollisions<Direction::right, Asc<Horizontal>> {
413 public:
414
415 inline static const MappableComponent::asc_boundaries_type& MapBoundaries(
416 const MappableComponent& mappable)
417 {
418 return mappable.rightBoundaries;
419 }
420 };
421
422 class Up : public DetectCollisions<Direction::up, Desc<Vertical>> {
423 public:
424
425 inline static const MappableComponent::desc_boundaries_type& MapBoundaries(
426 const MappableComponent& mappable)
427 {
428 return mappable.upBoundaries;
429 }
430 };
431
432 class Down : public DetectCollisions<Direction::down, Asc<Vertical>> {
433 public:
434
435 inline static const MappableComponent::asc_boundaries_type& MapBoundaries(
436 const MappableComponent& mappable)
437 {
438 return mappable.downBoundaries;
439 }
440 };
441};
442
443PonderingSystem::CollisionResult PonderingSystem::detectCollisions(
444 id_type entity,
445 vec2d newPos)
446{
447 auto& transformable = game_.getEntityManager().
448 getComponent<TransformableComponent>(entity);
449
450 CollisionResult result;
451 result.pos.x() = newPos.x();
452 result.pos.y() = transformable.pos.y();
453
454 // Find horizontal collisions.
455 if (result.pos.x() < transformable.pos.x())
456 {
457 detectCollisionsInDirection<CollisionParams::Left>(entity, result);
458 } else if (result.pos.x() > transformable.pos.x())
459 {
460 detectCollisionsInDirection<CollisionParams::Right>(entity, result);
461 }
462
463 // Find vertical collisions
464 if (!result.stopProcessing)
465 {
466 result.pos.y() = newPos.y();
467 result.touchedWall = false;
468
469 if (result.pos.y() < transformable.pos.y())
470 {
471 detectCollisionsInDirection<CollisionParams::Up>(entity, result);
472 } else if (result.pos.y() > transformable.pos.y())
473 {
474 detectCollisionsInDirection<CollisionParams::Down>(entity, result);
475 }
476 }
477
478 return result;
479}
480
481template <typename Param>
482void PonderingSystem::detectCollisionsInDirection(
483 id_type entity,
484 CollisionResult& result)
485{
486 // Get map data.
487 id_type mapEntity =
488 game_.getSystemManager().getSystem<RealizingSystem>().getActiveMap();
489
490 auto& mappable = game_.getEntityManager().
491 getComponent<MappableComponent>(mapEntity);
492
493 // Get old location.
494 auto& transform = game_.getEntityManager().
495 getComponent<TransformableComponent>(entity);
496
497 auto& ponderable = game_.getEntityManager().
498 getComponent<PonderableComponent>(entity);
499
500 bool boundaryCollision = false;
501
502 auto boundaries = Param::MapBoundaries(mappable);
503 auto it = boundaries.lower_bound(Param::EntityAxis(transform));
504
505 // Find the axis distance of the closest environmental boundary.
506 for (;
507 (it != std::end(boundaries)) &&
508 Param::AtLeastInAxisSweep(
509 it->first,
510 Param::EntityAxis(result.pos, transform.size));
511 it++)
512 {
513 // Check that the boundary is in range for the other axis.
514 if ((Param::NonAxisUpper(result.pos, transform.size) > it->second.lower) &&
515 (Param::NonAxisLower(result.pos) < it->second.upper))
516 {
517 // We have a collision!
518 boundaryCollision = true;
519
520 break;
521 }
522 }
523
524 // Find the results of pretending to move the entity's passengers, if there
525 // are any.
526 vec2d delta = result.pos - transform.pos;
527 std::map<id_type, CollisionResult> passResults;
528
529 for (id_type passenger : ponderable.passengers)
530 {
531 auto& passPonder = game_.getEntityManager().
532 getComponent<PonderableComponent>(passenger);
533
534 if (passPonder.ferrySide == Param::Dir)
535 {
536 auto& passTrans = game_.getEntityManager().
537 getComponent<TransformableComponent>(passenger);
538
539 passResults[passenger] =
540 detectCollisions(passenger, passTrans.pos + delta);
541 }
542 }
543
544 // Find a list of potential colliders, sorted so that the closest is
545 // first.
546 std::vector<id_type> colliders;
547
548 auto entities = game_.getEntityManager().getEntitiesWithComponents<
549 PonderableComponent,
550 TransformableComponent>();
551
552 for (id_type collider : entities)
553 {
554 // Can't collide with self.
555 if (collider == entity)
556 {
557 continue;
558 }
559
560 auto& colliderPonder = game_.getEntityManager().
561 getComponent<PonderableComponent>(collider);
562
563 // Only check objects that are active and collidable.
564 if (!colliderPonder.active || !colliderPonder.collidable)
565 {
566 continue;
567 }
568
569 // If the collider is a passenger of the entity, pretend that it has already
570 // moved.
571 auto& colliderTrans = game_.getEntityManager().
572 getComponent<TransformableComponent>(collider);
573
574 vec2d colliderPos = colliderTrans.pos;
575 vec2i colliderSize = colliderTrans.size;
576
577 if (passResults.count(collider))
578 {
579 colliderPos = passResults[collider].pos;
580 }
581
582 // Check if the entity would move into the potential collider,
583 if (Param::IsPastAxis(
584 Param::ObjectAxis(colliderPos, colliderSize),
585 Param::EntityAxis(result.pos, transform.size)) &&
586 // that it wasn't already colliding,
587 !Param::IsPastAxis(
588 Param::ObjectAxis(colliderPos, colliderSize),
589 Param::EntityAxis(transform)) &&
590 // that the position on the non-axis is in range,
591 (Param::NonAxisUpper(colliderPos, colliderSize) >
592 Param::NonAxisLower(result.pos)) &&
593 (Param::NonAxisLower(colliderPos) <
594 Param::NonAxisUpper(result.pos, transform.size)) &&
595 // and that the collider is not farther away than the environmental
596 // boundary.
597 (!boundaryCollision ||
598 Param::AtLeastInAxisSweep(
599 Param::ObjectAxis(colliderPos, colliderSize),
600 it->first)))
601 {
602 colliders.push_back(collider);
603 }
604 }
605
606 // Sort the potential colliders such that the closest to the axis of movement
607 // is first. When sorting, treat passengers of the entity as having already
608 // moved.
609 std::sort(
610 std::begin(colliders),
611 std::end(colliders),
612 [&] (id_type left, id_type right) {
613 auto& leftTrans = game_.getEntityManager().
614 getComponent<TransformableComponent>(left);
615
616 vec2d leftPos = leftTrans.pos;
617
618 if (passResults.count(left))
619 {
620 leftPos = passResults[left].pos;
621 }
622
623 auto& rightTrans = game_.getEntityManager().
624 getComponent<TransformableComponent>(right);
625
626 vec2d rightPos = rightTrans.pos;
627
628 if (passResults.count(right))
629 {
630 rightPos = passResults[right].pos;
631 }
632
633 return Param::Closer(
634 Param::ObjectAxis(leftPos, leftTrans.size),
635 Param::ObjectAxis(rightPos, rightTrans.size));
636 });
637
638 for (id_type collider : colliders)
639 {
640 auto& colliderTrans = game_.getEntityManager().
641 getComponent<TransformableComponent>(collider);
642
643 // If the collider is a passenger of the entity, pretend that it has already
644 // moved.
645 vec2d colliderPos = colliderTrans.pos;
646 vec2i colliderSize = colliderTrans.size;
647
648 if (passResults.count(collider))
649 {
650 colliderPos = passResults[collider].pos;
651 }
652
653 // Check if the entity would still move into the potential collider.
654 if (!Param::IsPastAxis(
655 Param::ObjectAxis(colliderPos, colliderSize),
656 Param::EntityAxis(result.pos, transform.size)))
657 {
658 break;
659 }
660
661 // TODO: Check if the entity is moving into one of its passengers.
662 auto& colliderPonder = game_.getEntityManager().
663 getComponent<PonderableComponent>(collider);
664
665 processCollision(
666 entity,
667 collider,
668 Param::Dir,
669 colliderPonder.colliderType,
670 Param::ObjectAxis(colliderPos, colliderSize),
671 Param::NonAxisLower(colliderPos),
672 Param::NonAxisUpper(colliderPos, colliderSize),
673 result);
674
675 if (result.stopProcessing)
676 {
677 break;
678 }
679 }
680
681 // If movement hasn't been stopped by an intermediary object, and
682 // collision checking hasn't been stopped, process the environmental
683 // boundaries closest to the entity.
684 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
685 {
686 double boundaryAxis = it->first;
687
688 for (;
689 (it != std::end(boundaries)) &&
690 (it->first == boundaryAxis);
691 it++)
692 {
693 if ((Param::NonAxisLower(result.pos) < it->second.upper) &&
694 (Param::NonAxisUpper(result.pos, transform.size) > it->second.lower))
695 {
696 processCollision(
697 entity,
698 mapEntity,
699 Param::Dir,
700 it->second.type,
701 it->first,
702 it->second.lower,
703 it->second.upper,
704 result);
705
706 if (result.stopProcessing)
707 {
708 break;
709 }
710 }
711 }
712 }
713}
714
715void PonderingSystem::processCollision(
716 id_type entity,
717 id_type collider,
718 Direction dir,
719 PonderableComponent::Collision type,
720 double axis,
721 double lower,
722 double upper,
723 CollisionResult& result)
724{
725 auto& transformable = game_.getEntityManager().
726 getComponent<TransformableComponent>(entity);
727
728 switch (type)
729 {
730 case PonderableComponent::Collision::wall:
731 {
732 result.touchedWall = true;
733
734 break;
735 }
736
737 case PonderableComponent::Collision::platform:
738 {
739 if (game_.getEntityManager().
740 hasComponent<OrientableComponent>(entity))
741 {
742 auto& orientable = game_.getEntityManager().
743 getComponent<OrientableComponent>(entity);
744
745 if (orientable.getDropState() !=
746 OrientableComponent::DropState::none)
747 {
748 orientable.setDropState(OrientableComponent::DropState::active);
749 } else {
750 result.touchedWall = true;
751 }
752 } else {
753 result.touchedWall = true;
754 }
755
756 break;
757 }
758
759 case PonderableComponent::Collision::adjacency:
760 {
761 auto& mappable = game_.getEntityManager().
762 getComponent<MappableComponent>(collider);
763
764 auto& adj = [&] () -> const MappableComponent::Adjacent& {
765 switch (dir)
766 {
767 case Direction::left: return mappable.leftAdjacent;
768 case Direction::right: return mappable.rightAdjacent;
769 case Direction::up: return mappable.upAdjacent;
770 case Direction::down: return mappable.downAdjacent;
771 }
772 }();
773
774 switch (adj.type)
775 {
776 case MappableComponent::Adjacent::Type::wall:
777 {
778 result.touchedWall = true;
779
780 break;
781 }
782
783 case MappableComponent::Adjacent::Type::wrap:
784 {
785 switch (dir)
786 {
787 case Direction::left:
788 {
789 result.pos.x() = GAME_WIDTH + WALL_GAP - transformable.size.w();
790
791 break;
792 }
793
794 case Direction::right:
795 {
796 result.pos.x() = -WALL_GAP;
797
798 break;
799 }
800
801 case Direction::up:
802 {
803 result.pos.y() =
804 MAP_HEIGHT * TILE_HEIGHT + WALL_GAP - transformable.pos.h();
805
806 break;
807 }
808
809 case Direction::down:
810 {
811 result.pos.y() = -WALL_GAP;
812
813 break;
814 }
815 }
816 }
817
818 case MappableComponent::Adjacent::Type::warp:
819 {
820 if (game_.getEntityManager().
821 hasComponent<PlayableComponent>(entity))
822 {
823 result.adjacentlyWarping = true;
824 result.adjWarpDir = dir;
825 result.adjWarpMapId = adj.mapId;
826 }
827
828 break;
829 }
830
831 case MappableComponent::Adjacent::Type::reverse:
832 {
833 // TODO: not yet implemented.
834
835 break;
836 }
837 }
838
839 break;
840 }
841
842 case PonderableComponent::Collision::danger:
843 {
844 if (game_.getEntityManager().
845 hasComponent<PlayableComponent>(entity))
846 {
847 game_.getSystemManager().getSystem<PlayingSystem>().die(entity);
848
849 result.adjacentlyWarping = false;
850 }
851
852 result.stopProcessing = true;
853
854 break;
855 }
856
857 case PonderableComponent::Collision::event:
858 {
859 if (game_.getEntityManager().
860 hasComponent<PlayableComponent>(entity))
861 {
862 game_.getSystemManager().getSystem<ScriptingSystem>().
863 onTouch(collider, entity);
864 }
865
866 break;
867 }
868
869 default:
870 {
871 // Not yet implemented.
872
873 break;
874 }
875 }
876
877 if (!result.stopProcessing && result.touchedWall)
878 {
879 switch (dir)
880 {
881 case Direction::left:
882 {
883 result.pos.x() = axis;
884 result.blockedHoriz = true;
885
886 break;
887 }
888
889 case Direction::right:
890 {
891 result.pos.x() = axis - transformable.size.w();
892 result.blockedHoriz = true;
893
894 break;
895 }
896
897 case Direction::up:
898 {
899 result.pos.y() = axis;
900 result.blockedVert = true;
901
902 break;
903 }
904
905 case Direction::down:
906 {
907 result.pos.y() = axis - transformable.size.h();
908 result.blockedVert = true;
909 result.groundEntity = collider;
910 result.grounded = true;
911
912 break;
913 }
914 }
915 }
916}
diff --git a/src/systems/pondering.h b/src/systems/pondering.h new file mode 100644 index 0000000..c79bbf6 --- /dev/null +++ b/src/systems/pondering.h
@@ -0,0 +1,81 @@
1#ifndef PONDERING_H_F2530E0E
2#define PONDERING_H_F2530E0E
3
4#include "system.h"
5#include "components/ponderable.h"
6#include "direction.h"
7#include "vector.h"
8
9class PonderingSystem : public System {
10public:
11
12 PonderingSystem(Game& game) : System(game)
13 {
14 }
15
16 void tick(double dt);
17
18 void initializeBody(id_type entity, PonderableComponent::Type type);
19
20 /**
21 * Initializes a ponderable map object from its prototype data.
22 *
23 * @requires entity is ponderable
24 * @requires entity is a map object
25 */
26 void initPrototype(id_type prototype);
27
28 /**
29 * Unferries an entity if it is a passenger of another entity. Use before
30 * moving a ponderable entity outside the PonderingSystem.
31 *
32 * @requires entity is ponderable
33 */
34 void unferry(id_type entity);
35
36private:
37
38 struct CollisionResult
39 {
40 vec2d pos;
41 bool stopProcessing = false;
42 bool touchedWall = false;
43 bool blockedHoriz = false;
44 bool blockedVert = false;
45 bool adjacentlyWarping = false;
46 Direction adjWarpDir;
47 size_t adjWarpMapId;
48 bool grounded = false;
49 id_type groundEntity;
50 };
51
52 void tickBody(
53 id_type entity,
54 double dt);
55
56 CollisionResult moveBody(
57 id_type entity,
58 vec2d newPos);
59
60 CollisionResult detectCollisions(
61 id_type entity,
62 vec2d newPos);
63
64 template <typename Param>
65 void detectCollisionsInDirection(
66 id_type entity,
67 CollisionResult& result);
68
69 void processCollision(
70 id_type entity,
71 id_type collider,
72 Direction dir,
73 PonderableComponent::Collision type,
74 double axis,
75 double lower,
76 double upper,
77 CollisionResult& result);
78
79};
80
81#endif /* end of include guard: PONDERING_H_F2530E0E */
diff --git a/src/systems/realizing.cpp b/src/systems/realizing.cpp new file mode 100644 index 0000000..baacf5a --- /dev/null +++ b/src/systems/realizing.cpp
@@ -0,0 +1,438 @@
1#include "realizing.h"
2#include <stdexcept>
3#include <libxml/parser.h>
4#include <cstring>
5#include <map>
6#include "game.h"
7#include "consts.h"
8#include "animation.h"
9#include "components/mappable.h"
10#include "components/animatable.h"
11#include "components/playable.h"
12#include "components/ponderable.h"
13#include "components/transformable.h"
14#include "components/prototypable.h"
15#include "components/automatable.h"
16#include "systems/mapping.h"
17#include "systems/animating.h"
18#include "systems/pondering.h"
19#include "systems/scripting.h"
20
21inline xmlChar* getProp(xmlNodePtr node, const char* attr)
22{
23 xmlChar* key = xmlGetProp(node, reinterpret_cast<const xmlChar*>(attr));
24 if (key == nullptr)
25 {
26 throw std::invalid_argument("Error parsing world file");
27 }
28
29 return key;
30}
31
32// TODO: neither the XML doc nor any of the emplaced entities are properly
33// destroyed if this constructor throws an exception.
34RealizingSystem::RealizingSystem(
35 Game& game,
36 std::string worldFile,
37 std::string prototypeFile) :
38 System(game),
39 worldFile_(std::move(worldFile)),
40 prototypeFile_(std::move(prototypeFile))
41{
42 auto& mapping = game_.getSystemManager().getSystem<MappingSystem>();
43
44 xmlChar* key = nullptr;
45
46 // Create a mapping between prototype names and the XML trees defining them.
47 xmlDocPtr protoXml = xmlParseFile(prototypeFile_.c_str());
48 if (protoXml == nullptr)
49 {
50 throw std::invalid_argument("Cannot find prototypes file");
51 }
52
53 xmlNodePtr protoTop = xmlDocGetRootElement(protoXml);
54 if (protoTop == nullptr)
55 {
56 throw std::invalid_argument("Error parsing prototypes file");
57 }
58
59 if (xmlStrcmp(protoTop->name, reinterpret_cast<const xmlChar*>("entities")))
60 {
61 throw std::invalid_argument("Error parsing prototypes file");
62 }
63
64 std::map<std::string, xmlNodePtr> prototypes;
65
66 for (xmlNodePtr node = protoTop->xmlChildrenNode;
67 node != nullptr;
68 node = node->next)
69 {
70 if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>("entity")))
71 {
72 key = getProp(node, "id");
73 std::string prototypeId = reinterpret_cast<char*>(key);
74 xmlFree(key);
75
76 prototypes[prototypeId] = node;
77 }
78 }
79
80 // Create entities from the world definition.
81 xmlDocPtr doc = xmlParseFile(worldFile_.c_str());
82 if (doc == nullptr)
83 {
84 throw std::invalid_argument("Cannot find world file");
85 }
86
87 xmlNodePtr top = xmlDocGetRootElement(doc);
88 if (top == nullptr)
89 {
90 throw std::invalid_argument("Error parsing world file");
91 }
92
93 if (xmlStrcmp(top->name, reinterpret_cast<const xmlChar*>("world")))
94 {
95 throw std::invalid_argument("Error parsing world file");
96 }
97
98 key = getProp(top, "startx");
99 startingPos_.x() = atoi(reinterpret_cast<char*>(key));
100 xmlFree(key);
101
102 key = getProp(top, "starty");
103 startingPos_.y() = atoi(reinterpret_cast<char*>(key));
104 xmlFree(key);
105
106 key = getProp(top, "startmap");
107 startingMapId_ = atoi(reinterpret_cast<char*>(key));
108 xmlFree(key);
109
110 for (xmlNodePtr node = top->xmlChildrenNode;
111 node != nullptr;
112 node = node->next)
113 {
114 if (!xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>("map")))
115 {
116 id_type map = game_.getEntityManager().emplaceEntity();
117
118 auto& mappable = game_.getEntityManager().
119 emplaceComponent<MappableComponent>(map,
120 Texture("res/tiles.png"),
121 Texture("res/font.bmp"));
122
123 key = getProp(node, "id");
124 mappable.mapId = atoi(reinterpret_cast<char*>(key));
125 xmlFree(key);
126
127 key = getProp(node, "title");
128 mappable.title = reinterpret_cast<char*>(key);
129 xmlFree(key);
130
131 for (xmlNodePtr mapNode = node->xmlChildrenNode;
132 mapNode != nullptr;
133 mapNode = mapNode->next)
134 {
135 if (!xmlStrcmp(
136 mapNode->name,
137 reinterpret_cast<const xmlChar*>("environment")))
138 {
139 key = xmlNodeGetContent(mapNode);
140 if (key == nullptr)
141 {
142 throw std::invalid_argument("Error parsing world file");
143 }
144
145 mappable.tiles.clear();
146 mappable.tiles.push_back(atoi(strtok(
147 reinterpret_cast<char*>(key),
148 ",\n")));
149
150 for (size_t i = 1; i < (MAP_WIDTH * MAP_HEIGHT); i++)
151 {
152 mappable.tiles.push_back(atoi(strtok(nullptr, ",\n")));
153 }
154
155 xmlFree(key);
156 } else if (!xmlStrcmp(
157 mapNode->name,
158 reinterpret_cast<const xmlChar*>("entity")))
159 {
160 id_type mapObject = game_.getEntityManager().emplaceEntity();
161
162 key = getProp(mapNode, "type");
163 std::string prototypeId = reinterpret_cast<char*>(key);
164 xmlFree(key);
165
166 xmlNodePtr prototypeNode = prototypes[prototypeId];
167
168 // Set the coordinates from the object definition.
169 auto& transformable = game_.getEntityManager().
170 emplaceComponent<TransformableComponent>(mapObject);
171
172 key = getProp(mapNode, "x");
173 transformable.origPos.x() = atoi(reinterpret_cast<char*>(key));
174 xmlFree(key);
175
176 key = getProp(mapNode, "y");
177 transformable.origPos.y() = atoi(reinterpret_cast<char*>(key));
178 xmlFree(key);
179
180 // Set the sprite and size using the prototype definition.
181 key = getProp(prototypeNode, "sprite");
182 std::string spritePath = reinterpret_cast<char*>(key);
183 xmlFree(key);
184
185 key = getProp(prototypeNode, "width");
186 transformable.origSize.w() = atoi(reinterpret_cast<char*>(key));
187 xmlFree(key);
188
189 key = getProp(prototypeNode, "height");
190 transformable.origSize.h() = atoi(reinterpret_cast<char*>(key));
191 xmlFree(key);
192
193 AnimationSet objectAnim(
194 spritePath.c_str(),
195 transformable.origSize.w(),
196 transformable.origSize.h(),
197 1);
198
199 objectAnim.emplaceAnimation("static", 0, 1, 1);
200
201 auto& animatable = game_.getEntityManager().
202 emplaceComponent<AnimatableComponent>(
203 mapObject,
204 std::move(objectAnim));
205
206 animatable.origAnimation = "static";
207
208 // Create a physics body.
209 game_.getSystemManager().getSystem<PonderingSystem>().
210 initializeBody(mapObject, PonderableComponent::Type::vacuumed);
211
212
213
214
215
216 auto& prototypable = game_.getEntityManager().
217 emplaceComponent<PrototypableComponent>(mapObject);
218
219 prototypable.prototypeId = prototypeId;
220
221 key = getProp(mapNode, "index");
222 prototypable.mapObjectIndex = atoi(reinterpret_cast<char*>(key));
223 xmlFree(key);
224
225 if (prototypeId == "movplat")
226 {
227 auto& automatable = game_.getEntityManager().
228 emplaceComponent<AutomatableComponent>(mapObject);
229
230 automatable.table = prototypeId;
231 } else if (prototypeId == "checkpoint")
232 {
233 auto& ponderable = game_.getEntityManager().
234 getComponent<PonderableComponent>(mapObject);
235
236 ponderable.colliderType = PonderableComponent::Collision::event;
237 }
238
239 mappable.objects.push_back(mapObject);
240 } else if (!xmlStrcmp(
241 mapNode->name,
242 reinterpret_cast<const xmlChar*>("adjacent")))
243 {
244 key = getProp(mapNode, "type");
245 std::string adjTypeStr(reinterpret_cast<char*>(key));
246 xmlFree(key);
247
248 MappableComponent::Adjacent::Type adjType;
249 if (adjTypeStr == "wall")
250 {
251 adjType = MappableComponent::Adjacent::Type::wall;
252 } else if (adjTypeStr == "wrap")
253 {
254 adjType = MappableComponent::Adjacent::Type::wrap;
255 } else if (adjTypeStr == "warp")
256 {
257 adjType = MappableComponent::Adjacent::Type::warp;
258 } else if (adjTypeStr == "reverseWarp")
259 {
260 adjType = MappableComponent::Adjacent::Type::reverse;
261 } else {
262 throw std::logic_error("Invalid adjacency type");
263 }
264
265 key = getProp(mapNode, "map");
266 size_t adjMapId = atoi(reinterpret_cast<char*>(key));
267 xmlFree(key);
268
269 key = getProp(mapNode, "dir");
270 std::string adjDir(reinterpret_cast<char*>(key));
271 xmlFree(key);
272
273 if (adjDir == "left")
274 {
275 mappable.leftAdjacent = {adjType, adjMapId};
276 } else if (adjDir == "right")
277 {
278 mappable.rightAdjacent = {adjType, adjMapId};
279 } else if (adjDir == "up")
280 {
281 mappable.upAdjacent = {adjType, adjMapId};
282 } else if (adjDir == "down")
283 {
284 mappable.downAdjacent = {adjType, adjMapId};
285 } else {
286 throw std::logic_error("Invalid adjacency direction");
287 }
288 }
289 }
290
291 mapping.generateBoundaries(map);
292
293 entityByMapId_[mappable.mapId] = map;
294 }
295 }
296
297 xmlFreeDoc(doc);
298 xmlFreeDoc(protoXml);
299
300 activateMap(entityByMapId_[startingMapId_]);
301}
302
303void RealizingSystem::loadMap(id_type mapEntity)
304{
305 deactivateMap();
306 activateMap(mapEntity);
307}
308
309void RealizingSystem::deactivateMap()
310{
311 id_type oldMap = activeMap_;
312
313 auto& oldMappable = game_.getEntityManager().
314 getComponent<MappableComponent>(oldMap);
315
316 // Deactivate any map objects from the old map.
317 for (id_type prototype : oldMappable.objects)
318 {
319 leaveActiveMap(prototype);
320 }
321
322 // Deactivate players that were on the old map.
323 std::set<id_type> players =
324 game_.getEntityManager().getEntitiesWithComponents<
325 PlayableComponent>();
326
327 for (id_type player : players)
328 {
329 auto& playable = game_.getEntityManager().
330 getComponent<PlayableComponent>(player);
331
332 if (playable.mapId == oldMap)
333 {
334 leaveActiveMap(player);
335 }
336 }
337}
338
339void RealizingSystem::activateMap(id_type mapEntity)
340{
341 auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>();
342 auto& pondering = game_.getSystemManager().getSystem<PonderingSystem>();
343
344 std::set<id_type> players =
345 game_.getEntityManager().getEntitiesWithComponents<
346 PlayableComponent>();
347
348 activeMap_ = mapEntity;
349
350 auto& mappable = game_.getEntityManager().
351 getComponent<MappableComponent>(mapEntity);
352
353 // Initialize the new map's objects.
354 for (id_type prototype : mappable.objects)
355 {
356 if (game_.getEntityManager().
357 hasComponent<TransformableComponent>(prototype))
358 {
359 auto& transformable = game_.getEntityManager().
360 getComponent<TransformableComponent>(prototype);
361
362 transformable.pos = transformable.origPos;
363 transformable.size = transformable.origSize;
364 }
365
366 if (game_.getEntityManager().hasComponent<AnimatableComponent>(prototype))
367 {
368 animating.initPrototype(prototype);
369 }
370
371 if (game_.getEntityManager().hasComponent<PonderableComponent>(prototype))
372 {
373 pondering.initPrototype(prototype);
374 }
375
376 enterActiveMap(prototype);
377 }
378
379 // Activate any players on the map.
380 for (id_type player : players)
381 {
382 auto& playable = game_.getEntityManager().
383 getComponent<PlayableComponent>(player);
384
385 if (playable.mapId == mapEntity)
386 {
387 enterActiveMap(player);
388 }
389 }
390}
391
392void RealizingSystem::enterActiveMap(id_type entity)
393{
394 if (game_.getEntityManager().hasComponent<AnimatableComponent>(entity))
395 {
396 auto& animatable = game_.getEntityManager().
397 getComponent<AnimatableComponent>(entity);
398
399 animatable.active = true;
400 }
401
402 if (game_.getEntityManager().hasComponent<PonderableComponent>(entity))
403 {
404 auto& ponderable = game_.getEntityManager().
405 getComponent<PonderableComponent>(entity);
406
407 ponderable.active = true;
408 }
409
410 if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity))
411 {
412 game_.getSystemManager().getSystem<ScriptingSystem>().startBehavior(entity);
413 }
414}
415
416void RealizingSystem::leaveActiveMap(id_type entity)
417{
418 if (game_.getEntityManager().hasComponent<AnimatableComponent>(entity))
419 {
420 auto& animatable = game_.getEntityManager().
421 getComponent<AnimatableComponent>(entity);
422
423 animatable.active = false;
424 }
425
426 if (game_.getEntityManager().hasComponent<PonderableComponent>(entity))
427 {
428 auto& ponderable = game_.getEntityManager().
429 getComponent<PonderableComponent>(entity);
430
431 ponderable.active = false;
432 }
433
434 if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity))
435 {
436 game_.getSystemManager().getSystem<ScriptingSystem>().stopBehavior(entity);
437 }
438}
diff --git a/src/systems/realizing.h b/src/systems/realizing.h new file mode 100644 index 0000000..ab5a150 --- /dev/null +++ b/src/systems/realizing.h
@@ -0,0 +1,86 @@
1#ifndef REALIZING_H_6853748C
2#define REALIZING_H_6853748C
3
4#include <string>
5#include <map>
6#include "system.h"
7#include "vector.h"
8
9class RealizingSystem : public System {
10public:
11
12 /**
13 * Constructs the realizing system.
14 *
15 * Note that this must be constructed after the following system:
16 * - Mapping
17 * - Animating
18 * - Pondering
19 * - Scripting
20 */
21 RealizingSystem(
22 Game& game,
23 std::string worldFile,
24 std::string prototypeFile);
25
26 id_type getActiveMap() const
27 {
28 return activeMap_;
29 }
30
31 int getStartingMapId() const
32 {
33 return startingMapId_;
34 }
35
36 vec2i getStartingPos() const
37 {
38 return startingPos_;
39 }
40
41 id_type getEntityByMapId(size_t mapId) const
42 {
43 return entityByMapId_.at(mapId);
44 }
45
46 id_type getActivePlayer() const
47 {
48 return activePlayer_;
49 }
50
51 void setActivePlayer(id_type entity)
52 {
53 activePlayer_ = entity;
54 }
55
56 /**
57 * Loads the given map.
58 */
59 void loadMap(id_type mapEntity);
60
61 /**
62 * Treats the given entity as part of the active map.
63 */
64 void enterActiveMap(id_type entity);
65
66 /**
67 * Stops treating the given entity as part of the active map.
68 */
69 void leaveActiveMap(id_type entity);
70
71private:
72
73 void deactivateMap();
74
75 void activateMap(id_type mapEntity);
76
77 std::string worldFile_;
78 std::string prototypeFile_;
79 int startingMapId_;
80 vec2i startingPos_;
81 std::map<size_t, id_type> entityByMapId_;
82 id_type activeMap_;
83 id_type activePlayer_;
84};
85
86#endif /* end of include guard: REALIZING_H_6853748C */
diff --git a/src/systems/scheduling.cpp b/src/systems/scheduling.cpp new file mode 100644 index 0000000..220efae --- /dev/null +++ b/src/systems/scheduling.cpp
@@ -0,0 +1,54 @@
1#include "scheduling.h"
2#include "game.h"
3#include "components/schedulable.h"
4#include "util.h"
5
6void SchedulingSystem::tick(double dt)
7{
8 auto entities = game_.getEntityManager().getEntitiesWithComponents<
9 SchedulableComponent>();
10
11 for (id_type entity : entities)
12 {
13 auto& schedulable = game_.getEntityManager().
14 getComponent<SchedulableComponent>(entity);
15
16 for (auto& action : schedulable.actions)
17 {
18 std::get<0>(action) -= dt;
19
20 if (std::get<0>(action) < 0)
21 {
22 std::get<1>(action)(entity);
23 }
24 }
25
26 erase_if(schedulable.actions,
27 [] (const SchedulableComponent::Action& action) {
28 return (std::get<0>(action) < 0);
29 });
30
31 if (schedulable.actions.empty())
32 {
33 game_.getEntityManager().removeComponent<SchedulableComponent>(entity);
34 }
35 }
36}
37
38void SchedulingSystem::schedule(
39 id_type entity,
40 double length,
41 std::function<void(id_type)> action)
42{
43 if (!game_.getEntityManager().hasComponent<SchedulableComponent>(entity))
44 {
45 game_.getEntityManager().emplaceComponent<SchedulableComponent>(entity);
46 }
47
48 auto& schedulable = game_.getEntityManager().
49 getComponent<SchedulableComponent>(entity);
50
51 schedulable.actions.emplace_back(
52 length,
53 std::move(action));
54}
diff --git a/src/systems/scheduling.h b/src/systems/scheduling.h new file mode 100644 index 0000000..7950d62 --- /dev/null +++ b/src/systems/scheduling.h
@@ -0,0 +1,22 @@
1#ifndef SCHEDULING_H_7B02E3E3
2#define SCHEDULING_H_7B02E3E3
3
4#include "system.h"
5
6class SchedulingSystem : public System {
7public:
8
9 SchedulingSystem(Game& game) : System(game)
10 {
11 }
12
13 void tick(double dt);
14
15 void schedule(
16 id_type entity,
17 double length,
18 std::function<void(id_type)> action);
19
20};
21
22#endif /* end of include guard: SCHEDULING_H_7B02E3E3 */
diff --git a/src/systems/scripting.cpp b/src/systems/scripting.cpp new file mode 100644 index 0000000..504224c --- /dev/null +++ b/src/systems/scripting.cpp
@@ -0,0 +1,257 @@
1#include "scripting.h"
2#include "game.h"
3#include "components/runnable.h"
4#include "components/ponderable.h"
5#include "components/transformable.h"
6#include "components/playable.h"
7#include "components/mappable.h"
8#include "components/prototypable.h"
9#include "components/automatable.h"
10#include "systems/realizing.h"
11#include "vector.h"
12#include "muxer.h"
13
14struct script_entity {
15 using id_type = EntityManager::id_type;
16
17 id_type id;
18
19 script_entity(id_type id) : id(id)
20 {
21 }
22};
23
24ScriptingSystem::ScriptingSystem(Game& game) : System(game)
25{
26 id_type entity = game_.getEntityManager().emplaceEntity();
27
28 engine_.open_libraries(sol::lib::base, sol::lib::coroutine);
29
30 engine_.new_usertype<vec2d>(
31 "vec2d",
32 sol::constructors<vec2d(), vec2d(double, double)>(),
33 "x", sol::property(
34 [] (vec2d& v) -> double { return v.x(); },
35 [] (vec2d& v, double x) { v.x() = x; }),
36 "y", sol::property(
37 [] (vec2d& v) -> double { return v.y(); },
38 [] (vec2d& v, double y) { v.y() = y; }));
39
40 engine_.new_usertype<vec2i>(
41 "vec2i",
42 sol::constructors<vec2i(), vec2i(int, int)>(),
43 "x", [] (vec2i& v) -> int& { return v.x(); },
44 "y", [] (vec2i& v) -> int& { return v.y(); });
45
46 engine_.new_usertype<script_entity>(
47 "entity",
48 sol::constructors<script_entity(id_type)>(),
49 "id", &script_entity::id,
50 "transformable",
51 [&] (script_entity& entity) -> TransformableComponent& {
52 return game_.getEntityManager().
53 getComponent<TransformableComponent>(entity.id);
54 },
55 "ponderable",
56 [&] (script_entity& entity) -> PonderableComponent& {
57 return game_.getEntityManager().
58 getComponent<PonderableComponent>(entity.id);
59 },
60 "mappable",
61 [&] (script_entity& entity) -> MappableComponent& {
62 return game_.getEntityManager().
63 getComponent<MappableComponent>(entity.id);
64 },
65 "playable",
66 [&] (script_entity& entity) -> PlayableComponent& {
67 return game_.getEntityManager().
68 getComponent<PlayableComponent>(entity.id);
69 },
70 "prototypable",
71 [&] (script_entity& entity) -> PrototypableComponent& {
72 return game_.getEntityManager().
73 getComponent<PrototypableComponent>(entity.id);
74 });
75
76 engine_.new_usertype<TransformableComponent>(
77 "transformable",
78 "pos", &TransformableComponent::pos);
79
80 engine_.new_usertype<PonderableComponent>(
81 "ponderable",
82 "vel", &PonderableComponent::vel,
83 "accel", &PonderableComponent::accel);
84
85 engine_.new_usertype<MappableComponent>(
86 "mappable",
87 "mapId", &MappableComponent::mapId);
88
89 engine_.new_usertype<PlayableComponent>(
90 "playable",
91 "checkpointPos", &PlayableComponent::checkpointPos,
92 "checkpointMapId", &PlayableComponent::checkpointMapId,
93 "checkpointMapObject", &PlayableComponent::checkpointMapObject,
94 "checkpointMapObjectIndex", &PlayableComponent::checkpointMapObjectIndex);
95
96 engine_.new_usertype<PrototypableComponent>(
97 "prototypable",
98 "mapObjectIndex", &PrototypableComponent::mapObjectIndex,
99 "prototypeId", &PrototypableComponent::prototypeId);
100
101 engine_.new_usertype<RealizingSystem>(
102 "realizing",
103 "activeMap", sol::property(&RealizingSystem::getActiveMap));
104
105 engine_.set_function(
106 "realizing",
107 [&] () -> RealizingSystem& {
108 return game_.getSystemManager().getSystem<RealizingSystem>();
109 });
110
111 engine_.set_function("playSound", playSound);
112
113 engine_.script_file("scripts/common.lua");
114 engine_.script_file("scripts/movplat.lua");
115 engine_.script_file("scripts/checkpoint.lua");
116}
117
118void ScriptingSystem::tick(double dt)
119{
120 auto entities = game_.getEntityManager().getEntitiesWithComponents<
121 RunnableComponent>();
122
123 for (id_type entity : entities)
124 {
125 auto& runnable = game_.getEntityManager().
126 getComponent<RunnableComponent>(entity);
127
128 if (*runnable.callable)
129 {
130 auto result = (*runnable.callable)(dt);
131 if (!result.valid())
132 {
133 sol::error e = result;
134 throw std::runtime_error(e.what());
135 }
136 }
137
138 if (!*runnable.callable)
139 {
140 killScript(entity);
141 }
142 }
143}
144
145void ScriptingSystem::killScript(id_type entity)
146{
147 auto& runnable = game_.getEntityManager().
148 getComponent<RunnableComponent>(entity);
149
150 if (runnable.behavior)
151 {
152 auto& automatable = game_.getEntityManager().
153 getComponent<AutomatableComponent>(runnable.actor);
154
155 automatable.running = false;
156 }
157
158 game_.getEntityManager().deleteEntity(entity);
159}
160
161template <typename... Args>
162sol::optional<EntityManager::id_type> ScriptingSystem::runScript(
163 std::string table,
164 std::string event,
165 id_type entity,
166 Args&&... args)
167{
168 id_type script = game_.getEntityManager().emplaceEntity();
169
170 auto& runnable = game_.getEntityManager().
171 emplaceComponent<RunnableComponent>(script);
172
173 runnable.runner =
174 std::unique_ptr<sol::thread>(
175 new sol::thread(
176 sol::thread::create(
177 engine_.lua_state())));
178
179 runnable.callable =
180 std::unique_ptr<sol::coroutine>(
181 new sol::coroutine(
182 runnable.runner->state().
183 traverse_get<sol::function>(
184 table,
185 event)));
186
187 if (!*runnable.callable)
188 {
189 throw std::runtime_error("Error running script");
190 }
191
192 auto result = (*runnable.callable)(
193 script_entity(entity),
194 std::forward<Args>(args)...);
195
196 if (!result.valid())
197 {
198 sol::error e = result;
199 throw std::runtime_error(e.what());
200 }
201
202 if (*runnable.callable)
203 {
204 return { script };
205 } else {
206 killScript(script);
207
208 return {};
209 }
210}
211
212void ScriptingSystem::startBehavior(id_type entity)
213{
214 auto& automatable = game_.getEntityManager().
215 getComponent<AutomatableComponent>(entity);
216
217 sol::optional<id_type> script =
218 runScript(
219 automatable.table,
220 "Behavior",
221 entity);
222
223 if (script)
224 {
225 automatable.script = *script;
226 automatable.running = true;
227
228 auto& runnable = game_.getEntityManager().
229 getComponent<RunnableComponent>(automatable.script);
230
231 runnable.behavior = true;
232 runnable.actor = entity;
233 }
234}
235
236void ScriptingSystem::stopBehavior(id_type entity)
237{
238 auto& automatable = game_.getEntityManager().
239 getComponent<AutomatableComponent>(entity);
240
241 if (automatable.running)
242 {
243 killScript(automatable.script);
244 }
245}
246
247void ScriptingSystem::onTouch(id_type entity, id_type player)
248{
249 auto& prototypable = game_.getEntityManager().
250 getComponent<PrototypableComponent>(entity);
251
252 runScript(
253 prototypable.prototypeId,
254 "OnTouch",
255 entity,
256 script_entity(player));
257}
diff --git a/src/systems/scripting.h b/src/systems/scripting.h new file mode 100644 index 0000000..b119c3f --- /dev/null +++ b/src/systems/scripting.h
@@ -0,0 +1,34 @@
1#ifndef AUTOMATING_H_E6E5D76E
2#define AUTOMATING_H_E6E5D76E
3
4#include "system.h"
5#include <sol.hpp>
6
7class ScriptingSystem : public System {
8public:
9
10 ScriptingSystem(Game& game);
11
12 void tick(double dt);
13
14 void killScript(id_type entity);
15
16 void startBehavior(id_type entity);
17
18 void stopBehavior(id_type entity);
19
20 void onTouch(id_type entity, id_type player);
21
22private:
23
24 template <typename... Args>
25 sol::optional<id_type> runScript(
26 std::string table,
27 std::string event,
28 id_type entity,
29 Args&&... args);
30
31 sol::state engine_;
32};
33
34#endif /* end of include guard: AUTOMATING_H_E6E5D76E */