Skip to content

Commit e671795

Browse files
committed
pa: training data upload and web service
1 parent f463e6a commit e671795

3 files changed

Lines changed: 203 additions & 34 deletions

File tree

propa_rank.cpp

Lines changed: 200 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
along with this program. If not, see <https://www.gnu.org/licenses/>.
1717
*/
1818
#include "propa_rank.h"
19+
#include "kage.h"
1920
#include <dcserver/json.hpp>
2021

2122
using namespace std::chrono_literals;
@@ -66,26 +67,118 @@ void RankConnection::onReceive(const std::error_code& ec, size_t len)
6667
socket.shutdown(asio::socket_base::shutdown_both, ignored);
6768
return;
6869
}
69-
if (len >= 0x34)
70+
if (len < 0x34) {
71+
ERROR_LOG(Game::PropellerA, "Packet too short: %zx", len);
72+
}
73+
else
7074
{
71-
std::string username = (const char *)&recvBuffer[0x14];
72-
Statement stmt(database, "SELECT kills, wins, games, flightTime, flightDistance, shotDown, points, rank from ranking where user_id = ?");
73-
stmt.bind(1, username);
74-
// total kills, number of wins, number of games, total flight time, total flight distance,
75-
// number of times shot down, total points, rank
76-
// ranks: 0: none, 1:general, 2:lieutenant general, 3:major general, 4:colonel
77-
// 5:lieutenant colonel, 6:major, 7:captain, 8:first lieutenant, 9:second lieutenant,
78-
// 10:sergeant, 11:senior airman, 12:airman first class, 13:airman, 14:airman basic, 15:none...
79-
// after game: 1st: 10 pts, 2nd: 5 pts, 3rd: 2 pts
80-
std::vector<uint32_t> data(8);
81-
if (!stmt.step()) {
82-
data[7] = ntohl(14);
75+
uint32_t op = read32(recvBuffer.data(), 0);
76+
if (op == 1)
77+
{
78+
// Get online ranking
79+
std::string username = (const char *)&recvBuffer[0x14];
80+
Statement stmt(database, "SELECT kills, wins, games, flightTime, flightDistance, shotDown, points, rank from ranking where user_id = ?");
81+
stmt.bind(1, username);
82+
// total kills, number of wins, number of games, total flight time, total flight distance,
83+
// number of times shot down, total points, rank
84+
// ranks: 0: none, 1:general, 2:lieutenant general, 3:major general, 4:colonel
85+
// 5:lieutenant colonel, 6:major, 7:captain, 8:first lieutenant, 9:second lieutenant,
86+
// 10:sergeant, 11:senior airman, 12:airman first class, 13:airman, 14:airman basic, 15:none...
87+
// after game: 1st: 10 pts, 2nd: 5 pts, 3rd: 2 pts
88+
std::vector<uint32_t> data(8);
89+
if (!stmt.step()) {
90+
data[7] = ntohl(14);
91+
}
92+
else {
93+
for (int i = 0; i < 8; i++)
94+
data[i] = ntohl(stmt.getIntColumn(i));
95+
}
96+
send(data);
8397
}
84-
else {
85-
for (int i = 0; i < 8; i++)
86-
data[i] = ntohl(stmt.getIntColumn(i));
98+
else if (op == 5)
99+
{
100+
// Update training data
101+
std::string username = (const char *)&recvBuffer[0x14];
102+
uint32_t size = read32(recvBuffer.data(), 0x38);
103+
if (size != 3 * (8 + 2 + 1) * sizeof(uint32_t)) {
104+
std::vector<uint32_t> data(1);
105+
send(data);
106+
}
107+
else
108+
{
109+
// 3 categories (Stunt, Challenge, Agility)
110+
// 8 scores
111+
// 2 bonus scores(?)
112+
// total score
113+
uint32_t scores[3][8];
114+
uint32_t bonuses[3][2];
115+
uint32_t totals[3];
116+
unsigned offset = 0x3c;
117+
for (unsigned cat = 0; cat < 3; cat++)
118+
{
119+
for (unsigned score = 0; score < 8; score++, offset += 4)
120+
scores[cat][score] = read32(recvBuffer.data(), offset);
121+
for (unsigned bonus = 0; bonus < 2; bonus++, offset += 4)
122+
bonuses[cat][bonus] = read32(recvBuffer.data(), offset);
123+
totals[cat] = read32(recvBuffer.data(), offset);
124+
offset += 4;
125+
}
126+
uint32_t resp = 0x10;
127+
Statement stmt(database, "SELECT cat1total, cat2total, cat3total from training where user_id = ?");
128+
stmt.bind(1, username);
129+
const char *sqlString;
130+
if (stmt.step())
131+
{
132+
if ((int)totals[0] > stmt.getIntColumn(0))
133+
resp |= 1;
134+
if ((int)totals[1] < stmt.getIntColumn(1))
135+
resp |= 2;
136+
if ((int)totals[2] < stmt.getIntColumn(2))
137+
resp |= 4;
138+
sqlString = "UPDATE training SET "
139+
"cat1score1=?, cat1score2=?, cat1score3=?, cat1score4=?, cat1score5=?, cat1score6=?, cat1score7=?, cat1score8=?, "
140+
"cat1bonus1=?, cat1bonus2=?, cat1total=?, "
141+
"cat2score1=?, cat2score2=?, cat2score3=?, cat2score4=?, cat2score5=?, cat2score6=?, cat2score7=?, cat2score8=?, "
142+
"cat2bonus1=?, cat2bonus2=?, cat2total=?, "
143+
"cat3score1=?, cat3score2=?, cat3score3=?, cat3score4=?, cat3score5=?, cat3score6=?, cat3score7=?, cat3score8=?, "
144+
"cat3bonus1=?, cat3bonus2=?, cat3total=? WHERE user_id=?";
145+
}
146+
else
147+
{
148+
resp |= 7;
149+
sqlString = "INSERT INTO training ("
150+
"cat1score1, cat1score2, cat1score3, cat1score4, cat1score5, cat1score6, cat1score7, cat1score8,"
151+
"cat1bonus1, cat1bonus2, cat1total,"
152+
"cat2score1, cat2score2, cat2score3, cat2score4, cat2score5, cat2score6, cat2score7, cat2score8,"
153+
"cat2bonus1, cat2bonus2, cat2total,"
154+
"cat3score1, cat3score2, cat3score3, cat3score4, cat3score5, cat3score6, cat3score7, cat3score8,"
155+
"cat3bonus1, cat3bonus2, cat3total,"
156+
"user_id) "
157+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
158+
}
159+
{
160+
Statement stmt(database, sqlString);
161+
int column = 1;
162+
for (unsigned cat = 0; cat < 3; cat++)
163+
{
164+
for (unsigned score = 0; score < 8; score++, offset += 4)
165+
stmt.bind(column++, scores[cat][score]);
166+
for (unsigned bonus = 0; bonus < 2; bonus++, offset += 4)
167+
stmt.bind(column++, bonuses[cat][bonus]);
168+
stmt.bind(column++, totals[cat]);
169+
}
170+
stmt.bind(column++, username);
171+
stmt.step();
172+
}
173+
174+
std::vector<uint32_t> data(4);
175+
write32((uint8_t *)data.data(), 0, resp);
176+
write32((uint8_t *)data.data(), 4, totals[0]);
177+
write32((uint8_t *)data.data(), 8, totals[1]);
178+
write32((uint8_t *)data.data(), 12, totals[2]);
179+
send(data);
180+
}
87181
}
88-
send(data);
89182
}
90183
receive();
91184
}
@@ -112,23 +205,44 @@ RankAcceptor::RankAcceptor(uint16_t httpPort, asio::io_context& io_context, cons
112205
asio::socket_base::reuse_address option(true);
113206
acceptor.set_option(option);
114207
database.open(dbpath);
115-
Statement stmt(database, "SELECT name FROM sqlite_master WHERE type='table' AND name='ranking'");
116-
if (!stmt.step()) {
117-
WARN_LOG(Game::PropellerA, "Table 'ranking' doesn't exist. Creating it");
118-
database.exec("CREATE TABLE ranking (id INTEGER PRIMARY KEY AUTOINCREMENT, "
119-
"user_id VARCHAR(32) UNIQUE, "
120-
"kills INTEGER DEFAULT 0, "
121-
"wins INTEGER DEFAULT 0, "
122-
"games INTEGER DEFAULT 0, "
123-
"flightTime INTEGER DEFAULT 0, "
124-
"flightDistance INTEGER DEFAULT 0, "
125-
"shotDown INTEGER DEFAULT 0, "
126-
"points INTEGER DEFAULT 0, "
127-
"rank INTEGER DEFAULT 14)");
208+
{
209+
Statement stmt(database, "SELECT name FROM sqlite_master WHERE type='table' AND name='ranking'");
210+
if (!stmt.step())
211+
{
212+
WARN_LOG(Game::PropellerA, "Table 'ranking' doesn't exist. Creating it");
213+
database.exec("CREATE TABLE ranking (id INTEGER PRIMARY KEY AUTOINCREMENT, "
214+
"user_id VARCHAR(32) UNIQUE, "
215+
"kills INTEGER DEFAULT 0, "
216+
"wins INTEGER DEFAULT 0, "
217+
"games INTEGER DEFAULT 0, "
218+
"flightTime INTEGER DEFAULT 0, "
219+
"flightDistance INTEGER DEFAULT 0, "
220+
"shotDown INTEGER DEFAULT 0, "
221+
"points INTEGER DEFAULT 0, "
222+
"rank INTEGER DEFAULT 14)");
223+
}
224+
}
225+
{
226+
Statement stmt(database, "SELECT name FROM sqlite_master WHERE type='table' AND name='training'");
227+
if (!stmt.step())
228+
{
229+
WARN_LOG(Game::PropellerA, "Table 'training' doesn't exist. Creating it");
230+
database.exec("CREATE TABLE training (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id VARCHAR(32) UNIQUE, "
231+
"cat1score1 INTEGER DEFAULT 0, cat1score2 INTEGER DEFAULT 0, cat1score3 INTEGER DEFAULT 0, cat1score4 INTEGER DEFAULT 0, "
232+
"cat1score5 INTEGER DEFAULT 0, cat1score6 INTEGER DEFAULT 0, cat1score7 INTEGER DEFAULT 0, cat1score8 INTEGER DEFAULT 0,"
233+
"cat1bonus1 INTEGER DEFAULT 0, cat1bonus2 INTEGER DEFAULT 0, cat1total INTEGER DEFAULT 0,"
234+
"cat2score1 INTEGER DEFAULT 0, cat2score2 INTEGER DEFAULT 0, cat2score3 INTEGER DEFAULT 0, cat2score4 INTEGER DEFAULT 0, "
235+
"cat2score5 INTEGER DEFAULT 0, cat2score6 INTEGER DEFAULT 0, cat2score7 INTEGER DEFAULT 0, cat2score8 INTEGER DEFAULT 0,"
236+
"cat2bonus1 INTEGER DEFAULT 0, cat2bonus2 INTEGER DEFAULT 0, cat2total INTEGER DEFAULT 0,"
237+
"cat3score1 INTEGER DEFAULT 0, cat3score2 INTEGER DEFAULT 0, cat3score3 INTEGER DEFAULT 0, cat3score4 INTEGER DEFAULT 0, "
238+
"cat3score5 INTEGER DEFAULT 0, cat3score6 INTEGER DEFAULT 0, cat3score7 INTEGER DEFAULT 0, cat3score8 INTEGER DEFAULT 0,"
239+
"cat3bonus1 INTEGER DEFAULT 0, cat3bonus2 INTEGER DEFAULT 0, cat3total INTEGER DEFAULT 0)");
240+
}
128241
}
129242

130243
using namespace std::placeholders;
131-
http.addCgiHandler("/get_rank", std::bind(&RankAcceptor::handleHttp, this, _1, _2));
244+
http.addCgiHandler("/get_rank", std::bind(&RankAcceptor::handleRankHttp, this, _1, _2));
245+
http.addCgiHandler("/get_training", std::bind(&RankAcceptor::handleTrainingHttp, this, _1, _2));
132246

133247
Instance = this;
134248
}
@@ -178,7 +292,7 @@ void RankAcceptor::updateRank(const std::string& name, int kills, int wins, int
178292
}
179293
}
180294

181-
void RankAcceptor::handleHttp(const Request& request, Reply& reply)
295+
void RankAcceptor::handleRankHttp(const Request& request, Reply& reply)
182296
{
183297
try {
184298
std::string sortBy = "kills";
@@ -229,3 +343,57 @@ void RankAcceptor::handleHttp(const Request& request, Reply& reply)
229343
reply = Reply::stockReply(Reply::internal_server_error);
230344
}
231345
}
346+
347+
static std::string formatTime(unsigned time)
348+
{
349+
char buf[16];
350+
sprintf(buf, "%d'%02d\"%02d", time / 60 / 100, (time / 100) % 60, time % 100);
351+
return buf;
352+
}
353+
354+
void RankAcceptor::handleTrainingHttp(const Request& request, Reply& reply)
355+
{
356+
try {
357+
std::string sortBy = "cat1total";
358+
if (!request.content.empty())
359+
{
360+
try {
361+
json query = json::parse(request.content);
362+
sortBy = query.at("sortBy").get<std::string>();
363+
} catch (const json::exception& e) {
364+
DEBUG_LOG(Game::PropellerA, "Invalid json: %s", e.what());
365+
}
366+
}
367+
std::string select = "SELECT user_id, cat1total, cat2total, cat3total FROM training";
368+
if (sortBy == "userName") {
369+
select += " ORDER BY user_id";
370+
}
371+
else if (sortBy == "cat1total") {
372+
select += " ORDER BY " + sortBy + " DESC, user_id";
373+
}
374+
else if (sortBy == "cat2total" || sortBy == "cat3total") {
375+
select += " ORDER BY " + sortBy + ", user_id";
376+
}
377+
else {
378+
reply = Reply::stockReply(Reply::bad_request);
379+
return;
380+
}
381+
Statement stmt(database, select.c_str());
382+
383+
json result;
384+
while (stmt.step())
385+
{
386+
json entry;
387+
entry["userName"] = stmt.getStringColumn(0);
388+
entry["cat1total"] = stmt.getIntColumn(1);
389+
entry["cat2total"] = formatTime(stmt.getIntColumn(2));
390+
entry["cat3total"] = formatTime(stmt.getIntColumn(3));
391+
result.push_back(entry);
392+
}
393+
reply.setContent(result.dump(-1, ' ', false, json::error_handler_t::replace), "application/json");
394+
395+
} catch (const std::exception& e) {
396+
ERROR_LOG(Game::PropellerA, "HTTP handler exception: %s", e.what());
397+
reply = Reply::stockReply(Reply::internal_server_error);
398+
}
399+
}

propa_rank.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ class RankAcceptor
7373
static RankAcceptor *Instance;
7474

7575
private:
76-
void handleHttp(const Request& request, Reply& reply);
76+
void handleRankHttp(const Request& request, Reply& reply);
77+
void handleTrainingHttp(const Request& request, Reply& reply);
7778

7879
asio::io_context& io_context;
7980
asio::ip::tcp::acceptor acceptor;

propeller.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ bool PropellerServer::handlePacket(Player *player, const uint8_t *data, size_t l
709709
room->setStateData(data[0x11], data + 0x18);
710710
break;
711711

712-
case IN_GAME_HDATA2: // in game data for human planes (with audio? doesn't look like it), len 6c
712+
case IN_GAME_HDATA2: // in game data for human planes (with audio), len 6c
713713
//DEBUG_LOG(game, "[%s] gamedata[B] flags %4x slot %d", player->getName().c_str(), (data[0] >> 2) << 10, data[0x11]);
714714
//dumpData(data + 0x10, len - 0x10);
715715
//dumpGameData(player, data[0x11], data + 0x40);

0 commit comments

Comments
 (0)