forked from Nebuleon/ativayeban
-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathplayer.c
253 lines (229 loc) · 6.14 KB
/
player.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include "draw.h"
#include "game.h"
#include "init.h"
#include "main.h"
#include "particle.h"
#include "player.h"
#include "space.h"
#include "sound.h"
#include "utils.h"
#define PLAYER_SPRITESHEET_STRIDE 8
#define PLAYER_SPRITESHEET_COUNT 16
#define PLAYER_ROLL_SCALE 0.015f
#define PLAYER_BLINK_FRAME_OFFSET 16
#define PLAYER_BLINK_FRAMES 20
#define PLAYER_BLINK_INTERVAL_FRAMES ((rand() % 100) + 100)
#define PLAYER_BLINK_CHANCE 50
#define PLAYER_RESPAWN_COUNTER 0
#define PLAYER_TAIL_COUNTER 20
Tex PlayerSpritesheets[MAX_PLAYERS];
Animation Spark;
Animation SparkRed;
Animation Tail;
Mix_Chunk* SoundPlayerBounce = NULL;
Player players[MAX_PLAYERS];
int NumPlayers;
#ifndef __GCW0__
int NumJoysticks;
#endif
typedef struct
{
cpVect BounceForce;
int Num;
} OnArbiterData;
static void OnArbiter(cpBody *body, cpArbiter *arb, void *data);
void PlayerUpdate(Player *player, const Uint32 ms)
{
if (!player->Enabled) return;
if (player->RespawnCounter > 0)
{
player->RespawnCounter -= ms;
if (player->RespawnCounter < 0)
{
player->RespawnCounter = 0;
}
}
if (!player->Alive) return;
// Update the speed at which the player is going.
// Provide positive bonus for:
// - when the player is very slow
// - when the player is rolling
// And negative bonus for:
// - when the player is above max speed
float accel = ACCELERATION;
const cpVect vel = cpBodyGetVelocity(player->Body);
const float relVelX = (float)fabs(vel.x) * SIGN(vel.x * player->AccelX);
if (relVelX > MAX_SPEED) accel = ACCELERATION_MAX_SPEED;
if (relVelX < MIN_SPEED) accel += MIN_SPEED_ACCEL_BONUS;
if (player->WasOnSurface) accel += ROLL_ACCEL_BONUS;
cpBodySetForce(player->Body, cpv(player->AccelX / 32767.0f * accel, 0));
// Detect bounces
OnArbiterData o = { cpvzero, 0 };
cpBodyEachArbiter(player->Body, OnArbiter, &o);
if (o.Num > 0)
{
if (!cpveql(o.BounceForce, cpvzero))
{
SoundPlayBounce((float)cpvlength(o.BounceForce));
}
if (player->WasOnSurface)
{
float angularV = (float)cpBodyGetAngularVelocity(player->Body);
//printf("angularV %f\n", angularV);
SoundPlayRoll(player->Index, angularV);
}
player->WasOnSurface = true;
player->ScoredInAir = false;
}
else
{
SoundStopRoll(player->Index);
player->WasOnSurface = false;
}
player->Roll = (int)(-cpBodyGetAngle(player->Body) / (2 * M_PI) * PLAYER_SPRITESHEET_COUNT);
player->Roll = player->Roll % PLAYER_SPRITESHEET_COUNT;
if (player->Roll < 0) player->Roll += PLAYER_SPRITESHEET_COUNT;
// Randomly blink after not blinking for a while
player->BlinkCounter--;
player->NextBlinkCounter--;
if (player->NextBlinkCounter <= 0)
{
player->BlinkCounter = PLAYER_BLINK_FRAMES;
player->NextBlinkCounter = PLAYER_BLINK_INTERVAL_FRAMES;
}
// Leave a tail
player->TailCounter -= ms;
if (player->TailCounter <= 0)
{
player->TailCounter = PLAYER_TAIL_COUNTER;
ParticlesAdd(&Tail, player->x, player->y, 0, 0);
}
const cpVect pos = cpBodyGetPosition(player->Body);
player->x = (float)pos.x;
player->y = (float)pos.y;
}
static void OnArbiter(cpBody *body, cpArbiter *arb, void *data)
{
UNUSED(body);
OnArbiterData *o = data;
o->BounceForce = cpvadd(o->BounceForce, cpArbiterTotalImpulse(arb));
o->Num++;
}
void PlayerDraw(const Player *player, const float y)
{
if (!player->Enabled) return;
// Draw the character.
int rollFrame = player->Roll;
if (player->BlinkCounter > 0)
{
rollFrame += PLAYER_BLINK_FRAME_OFFSET;
}
SDL_Rect src = {
(rollFrame % PLAYER_SPRITESHEET_STRIDE) * PLAYER_SPRITESHEET_WIDTH,
(rollFrame / PLAYER_SPRITESHEET_STRIDE) * PLAYER_SPRITESHEET_HEIGHT,
PLAYER_SPRITESHEET_WIDTH, PLAYER_SPRITESHEET_HEIGHT
};
SDL_Rect dest = {
(int)SCREEN_X(player->x) - PLAYER_SPRITESHEET_WIDTH / 2,
(int)(SCREEN_Y(player->y) - PLAYER_SPRITESHEET_HEIGHT / 2 - y),
src.w, src.h
};
RenderTex(player->T.T, &src, &dest);
}
void PlayerInit(Player *player, const int i, const cpVect pos)
{
player->Index = i;
player->Enabled = true;
player->Alive = true;
player->RespawnCounter = 0;
player->Score = 0;
player->Body = cpSpaceAddBody(
space.Space,
cpBodyNew(10.0f, cpMomentForCircle(10.0f, 0.0f, PLAYER_RADIUS, cpvzero)));
cpBodySetPosition(player->Body, pos);
cpShape *shape = cpSpaceAddShape(
space.Space, cpCircleShapeNew(player->Body, PLAYER_RADIUS, cpvzero));
cpShapeSetElasticity(shape, PLAYER_ELASTICITY);
cpShapeSetFriction(shape, 0.9f);
player->AccelX = 0;
player->WasOnSurface = false;
player->ScoredInAir = false;
player->Roll = 0;
player->BlinkCounter = 0;
player->NextBlinkCounter = 1;
player->TailCounter = PLAYER_TAIL_COUNTER;
player->T = PlayerSpritesheets[i];
}
void PlayerReset(Player *player, const int i)
{
player->Score = 0;
if (!player->Enabled) return;
cpBody *body = player->Body;
cpBodySetPosition(body, cpv(
(i + 1) * FIELD_WIDTH / (PlayerAliveCount() + 1),
FIELD_HEIGHT * 0.75f));
cpBodySetVelocity(body, cpvzero);
}
void PlayerScore(Player *player, const bool air)
{
// Score extra if consecutive in air
player->Score += (air && player->ScoredInAir) ? 2 : 1;
// Add sparks at player position
ParticlesAddExplosion(
(air && player->ScoredInAir) ? &SparkRed : &Spark,
player->x, player->y, 100, 2.5f);
if (air)
{
player->ScoredInAir = true;
}
SoundPlay(SoundScore, 1.0);
}
void PlayerKill(Player *player)
{
if (player->Alive)
{
SoundPlay(SoundLose, 1.0);
}
player->Alive = false;
player->RespawnCounter = PLAYER_RESPAWN_COUNTER;
// This ensures drawing the player with eyes closed
player->BlinkCounter = 1;
SoundStopRoll(player->Index);
}
void PlayerRespawn(Player *player, const float x, const float y)
{
player->x = x;
player->y = y;
player->RespawnCounter = -1;
}
void PlayerRevive(Player *player)
{
player->Alive = true;
SoundPlay(SoundStart, 1.0);
// Reset body to cached position
cpBodySetPosition(player->Body, cpv(player->x, player->y));
cpBodySetVelocity(player->Body, cpvzero);
}
int PlayerAliveCount(void)
{
int num = 0;
for (int i = 0; i < NumPlayers; i++)
{
if (!players[i].Alive) continue;
num++;
}
return num;
}
int PlayerEnabledCount(void)
{
int num = 0;
for (int i = 0; i < NumPlayers; i++)
{
if (!players[i].Enabled) continue;
num++;
}
return num;
}