1
|
|
|
/* |
2
|
|
|
* This file is part of Araknemu. |
3
|
|
|
* |
4
|
|
|
* Araknemu is free software: you can redistribute it and/or modify |
5
|
|
|
* it under the terms of the GNU Lesser General Public License as published by |
6
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
7
|
|
|
* (at your option) any later version. |
8
|
|
|
* |
9
|
|
|
* Araknemu is distributed in the hope that it will be useful, |
10
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
11
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12
|
|
|
* GNU Lesser General Public License for more details. |
13
|
|
|
* |
14
|
|
|
* You should have received a copy of the GNU Lesser General Public License |
15
|
|
|
* along with Araknemu. If not, see <https://www.gnu.org/licenses/>. |
16
|
|
|
* |
17
|
|
|
* Copyright (c) 2017-2020 Vincent Quatrevieux |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
package fr.quatrevieux.araknemu.data.living.repository.implementation.sql; |
21
|
|
|
|
22
|
|
|
import fr.arakne.utils.value.Colors; |
23
|
|
|
import fr.arakne.utils.value.constant.Gender; |
24
|
|
|
import fr.arakne.utils.value.constant.Race; |
25
|
|
|
import fr.quatrevieux.araknemu.core.dbal.executor.QueryExecutor; |
26
|
|
|
import fr.quatrevieux.araknemu.core.dbal.repository.EntityNotFoundException; |
27
|
|
|
import fr.quatrevieux.araknemu.core.dbal.repository.Record; |
28
|
|
|
import fr.quatrevieux.araknemu.core.dbal.repository.RepositoryException; |
29
|
|
|
import fr.quatrevieux.araknemu.core.dbal.repository.RepositoryUtils; |
30
|
|
|
import fr.quatrevieux.araknemu.data.living.entity.player.Player; |
31
|
|
|
import fr.quatrevieux.araknemu.data.living.repository.player.PlayerRepository; |
32
|
|
|
import fr.quatrevieux.araknemu.data.transformer.Transformer; |
33
|
|
|
import fr.quatrevieux.araknemu.data.value.Position; |
34
|
|
|
import fr.quatrevieux.araknemu.data.value.ServerCharacters; |
35
|
|
|
import fr.quatrevieux.araknemu.game.chat.ChannelType; |
36
|
|
|
import fr.quatrevieux.araknemu.game.world.creature.characteristics.MutableCharacteristics; |
37
|
|
|
|
38
|
|
|
import java.sql.ResultSet; |
39
|
|
|
import java.sql.SQLException; |
40
|
|
|
import java.util.ArrayList; |
41
|
|
|
import java.util.Collection; |
42
|
|
|
import java.util.Set; |
43
|
|
|
|
44
|
|
|
final class SqlPlayerRepository implements PlayerRepository { |
45
|
|
|
private final QueryExecutor executor; |
46
|
|
|
private final Transformer<MutableCharacteristics> characteristicsTransformer; |
47
|
|
|
private final Transformer<Set<ChannelType>> channelsTransformer; |
48
|
|
|
|
49
|
|
|
private final RepositoryUtils<Player> utils; |
50
|
|
|
|
51
|
1 |
|
public SqlPlayerRepository(QueryExecutor executor, Transformer<MutableCharacteristics> characteristicsTransformer, Transformer<Set<ChannelType>> channelsTransformer) { |
52
|
1 |
|
this.executor = executor; |
53
|
1 |
|
this.characteristicsTransformer = characteristicsTransformer; |
54
|
1 |
|
this.channelsTransformer = channelsTransformer; |
55
|
1 |
|
this.utils = new RepositoryUtils<>(this.executor, new SqlPlayerRepository.Loader()); |
56
|
1 |
|
} |
57
|
|
|
|
58
|
|
|
@Override |
59
|
|
|
public void initialize() throws RepositoryException { |
60
|
|
|
try { |
61
|
1 |
|
executor.query( |
62
|
|
|
"CREATE TABLE PLAYER (" + |
63
|
|
|
"PLAYER_ID INTEGER PRIMARY KEY AUTOINCREMENT," + |
64
|
|
|
"ACCOUNT_ID INTEGER," + |
65
|
|
|
"SERVER_ID INTEGER," + |
66
|
|
|
"PLAYER_NAME VARCHAR(32)," + |
67
|
|
|
"RACE INTEGER(2)," + |
68
|
|
|
"SEX INTEGER(1)," + |
69
|
|
|
"COLOR1 INTEGER," + |
70
|
|
|
"COLOR2 INTEGER," + |
71
|
|
|
"COLOR3 INTEGER," + |
72
|
|
|
"PLAYER_LEVEL INTEGER," + |
73
|
|
|
"PLAYER_STATS TEXT," + |
74
|
|
|
"MAP_ID INTEGER," + |
75
|
|
|
"CELL_ID INTEGER," + |
76
|
|
|
"CHANNELS VARCHAR(16)," + |
77
|
|
|
"BOOST_POINTS INTEGER," + |
78
|
|
|
"SPELL_POINTS INTEGER," + |
79
|
|
|
"LIFE_POINTS INTEGER," + |
80
|
|
|
"PLAYER_EXPERIENCE BIGINT," + |
81
|
|
|
"SAVED_MAP_ID INTEGER," + |
82
|
|
|
"SAVED_CELL_ID INTEGER," + |
83
|
|
|
"PLAYER_KAMAS BIGINT," + |
84
|
|
|
"UNIQUE (PLAYER_NAME, SERVER_ID)" + |
85
|
|
|
")" |
86
|
|
|
); |
87
|
|
|
|
88
|
1 |
|
executor.query("CREATE INDEX IDX_ACC_SRV ON PLAYER (ACCOUNT_ID, SERVER_ID)"); |
89
|
|
|
} catch (SQLException e) { |
90
|
|
|
throw new RepositoryException(e); |
91
|
1 |
|
} |
92
|
1 |
|
} |
93
|
|
|
|
94
|
|
|
@Override |
95
|
|
|
public void destroy() throws RepositoryException { |
96
|
|
|
try { |
97
|
1 |
|
executor.query("DROP TABLE PLAYER"); |
98
|
|
|
} catch (SQLException e) { |
99
|
|
|
throw new RepositoryException(e); |
100
|
1 |
|
} |
101
|
1 |
|
} |
102
|
|
|
|
103
|
|
|
@Override |
104
|
|
|
public Player add(Player entity) throws RepositoryException { |
105
|
1 |
|
return utils.update( |
106
|
|
|
"INSERT INTO PLAYER " + |
107
|
|
|
"(ACCOUNT_ID, SERVER_ID, PLAYER_NAME, RACE, SEX, COLOR1, COLOR2, COLOR3, PLAYER_LEVEL, PLAYER_STATS, MAP_ID, CELL_ID, CHANNELS, BOOST_POINTS, SPELL_POINTS, LIFE_POINTS, PLAYER_EXPERIENCE, SAVED_MAP_ID, SAVED_CELL_ID, PLAYER_KAMAS) " + |
108
|
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", |
109
|
|
|
stmt -> { |
110
|
1 |
|
stmt.setInt(1, entity.accountId()); |
111
|
1 |
|
stmt.setInt(2, entity.serverId()); |
112
|
1 |
|
stmt.setString(3, entity.name()); |
113
|
1 |
|
stmt.setInt(4, entity.race().ordinal()); |
114
|
1 |
|
stmt.setInt(5, entity.gender().ordinal()); |
115
|
1 |
|
stmt.setInt(6, entity.colors().color1()); |
116
|
1 |
|
stmt.setInt(7, entity.colors().color2()); |
117
|
1 |
|
stmt.setInt(8, entity.colors().color3()); |
118
|
1 |
|
stmt.setInt(9, entity.level()); |
119
|
1 |
|
stmt.setString(10, characteristicsTransformer.serialize(entity.stats())); |
120
|
1 |
|
stmt.setInt(11, entity.position().map()); |
121
|
1 |
|
stmt.setInt(12, entity.position().cell()); |
122
|
1 |
|
stmt.setString(13, channelsTransformer.serialize(entity.channels())); |
123
|
1 |
|
stmt.setInt(14, entity.boostPoints()); |
124
|
1 |
|
stmt.setInt(15, entity.spellPoints()); |
125
|
1 |
|
stmt.setInt(16, entity.life()); |
126
|
1 |
|
stmt.setLong(17, entity.experience()); |
127
|
1 |
|
stmt.setInt(18, entity.savedPosition().map()); |
128
|
1 |
|
stmt.setInt(19, entity.savedPosition().cell()); |
129
|
1 |
|
stmt.setLong(20, entity.kamas()); |
130
|
1 |
|
}, |
131
|
|
|
entity |
132
|
|
|
); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
@Override |
136
|
|
|
public void delete(Player entity) throws RepositoryException { |
137
|
1 |
|
if (utils.update("DELETE FROM PLAYER WHERE PLAYER_ID = ?", rs -> rs.setInt(1, entity.id())) < 1) { |
138
|
1 |
|
throw new EntityNotFoundException(); |
139
|
|
|
} |
140
|
1 |
|
} |
141
|
|
|
|
142
|
|
|
@Override |
143
|
|
|
public Player get(Player entity) throws RepositoryException { |
144
|
1 |
|
return utils.findOne( |
145
|
|
|
"SELECT * FROM PLAYER WHERE PLAYER_ID = ?", |
146
|
1 |
|
stmt -> stmt.setInt(1, entity.id()) |
147
|
|
|
); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
@Override |
151
|
|
|
public boolean has(Player entity) throws RepositoryException { |
152
|
1 |
|
return utils.aggregate( |
153
|
|
|
"SELECT COUNT(*) FROM PLAYER WHERE PLAYER_ID = ?", |
154
|
1 |
|
stmt -> stmt.setInt(1, entity.id()) |
155
|
|
|
) > 0; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
@Override |
159
|
|
|
public Collection<Player> findByAccount(int accountId, int serverId) { |
160
|
1 |
|
return utils.findAll( |
161
|
|
|
"SELECT * FROM PLAYER WHERE ACCOUNT_ID = ? AND SERVER_ID = ?", |
162
|
|
|
stmt -> { |
163
|
1 |
|
stmt.setInt(1, accountId); |
164
|
1 |
|
stmt.setInt(2, serverId); |
165
|
1 |
|
} |
166
|
|
|
); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
@Override |
170
|
|
|
public boolean nameExists(int serverId, String name) { |
171
|
1 |
|
return utils.aggregate( |
172
|
|
|
"SELECT COUNT(*) FROM PLAYER WHERE PLAYER_NAME = ? AND SERVER_ID = ?", |
173
|
|
|
stmt -> { |
174
|
1 |
|
stmt.setString(1, name); |
175
|
1 |
|
stmt.setInt(2, serverId); |
176
|
1 |
|
} |
177
|
|
|
) > 0; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
@Override |
181
|
|
|
public int accountCharactersCount(Player player) { |
182
|
1 |
|
return utils.aggregate( |
183
|
|
|
"SELECT COUNT(*) FROM PLAYER WHERE ACCOUNT_ID = ? AND SERVER_ID = ?", |
184
|
|
|
stmt -> { |
185
|
1 |
|
stmt.setInt(1, player.accountId()); |
186
|
1 |
|
stmt.setInt(2, player.serverId()); |
187
|
1 |
|
} |
188
|
|
|
); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
@Override |
192
|
|
|
public Collection<ServerCharacters> accountCharactersCount(int accountId) { |
193
|
|
|
try { |
194
|
1 |
|
return executor.prepare( |
195
|
|
|
"SELECT SERVER_ID, COUNT(*) FROM PLAYER WHERE ACCOUNT_ID = ? GROUP BY SERVER_ID", |
196
|
|
|
stmt -> { |
197
|
1 |
|
stmt.setInt(1, accountId); |
198
|
|
|
|
199
|
1 |
|
try (ResultSet rs = stmt.executeQuery()) { |
200
|
1 |
|
final Collection<ServerCharacters> list = new ArrayList<>(); |
201
|
|
|
|
202
|
1 |
|
while (rs.next()) { |
203
|
1 |
|
list.add( |
204
|
|
|
new ServerCharacters( |
205
|
1 |
|
rs.getInt("SERVER_ID"), |
206
|
1 |
|
rs.getInt("COUNT(*)") |
207
|
|
|
) |
208
|
|
|
); |
209
|
|
|
} |
210
|
|
|
|
211
|
1 |
|
return list; |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
); |
215
|
|
|
} catch (SQLException e) { |
216
|
|
|
throw new RepositoryException("Cannot load characters count", e); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
@Override |
221
|
|
|
public Collection<ServerCharacters> serverCharactersCountByAccountPseudo(String accountPseudo) { |
222
|
|
|
try { |
223
|
1 |
|
return executor.prepare( |
224
|
|
|
"SELECT SERVER_ID, COUNT(*) FROM PLAYER P JOIN ACCOUNT A ON P.ACCOUNT_ID = A.ACCOUNT_ID WHERE A.PSEUDO = ? GROUP BY SERVER_ID", |
225
|
|
|
stmt -> { |
226
|
1 |
|
stmt.setString(1, accountPseudo); |
227
|
|
|
|
228
|
1 |
|
try (ResultSet rs = stmt.executeQuery()) { |
229
|
1 |
|
final Collection<ServerCharacters> list = new ArrayList<>(); |
230
|
|
|
|
231
|
1 |
|
while (rs.next()) { |
232
|
1 |
|
list.add(new ServerCharacters(rs.getInt("SERVER_ID"), rs.getInt("COUNT(*)"))); |
233
|
|
|
} |
234
|
|
|
|
235
|
1 |
|
return list; |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
); |
239
|
|
|
} catch (SQLException e) { |
240
|
|
|
throw new RepositoryException("Cannot load characters count", e); |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
@Override |
245
|
|
|
public Player getForGame(Player player) { |
246
|
1 |
|
return utils.findOne( |
247
|
|
|
"SELECT * FROM PLAYER WHERE PLAYER_ID = ? AND ACCOUNT_ID = ? AND SERVER_ID = ?", |
248
|
|
|
stmt -> { |
249
|
1 |
|
stmt.setInt(1, player.id()); |
250
|
1 |
|
stmt.setInt(2, player.accountId()); |
251
|
1 |
|
stmt.setInt(3, player.serverId()); |
252
|
1 |
|
} |
253
|
|
|
); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
@Override |
257
|
|
|
public void save(Player player) { |
258
|
1 |
|
final int rows = utils.update( |
259
|
|
|
"UPDATE PLAYER SET " + |
260
|
|
|
"PLAYER_LEVEL = ?, PLAYER_STATS = ?, MAP_ID = ?, CELL_ID = ?, CHANNELS = ?, BOOST_POINTS = ?, SPELL_POINTS = ?, LIFE_POINTS = ?, PLAYER_EXPERIENCE = ?, SAVED_MAP_ID = ?, SAVED_CELL_ID = ?, PLAYER_KAMAS = ? " + |
261
|
|
|
"WHERE PLAYER_ID = ?", |
262
|
|
|
stmt -> { |
263
|
1 |
|
stmt.setInt(1, player.level()); |
264
|
1 |
|
stmt.setString(2, characteristicsTransformer.serialize(player.stats())); |
265
|
1 |
|
stmt.setInt(3, player.position().map()); |
266
|
1 |
|
stmt.setInt(4, player.position().cell()); |
267
|
1 |
|
stmt.setString(5, channelsTransformer.serialize(player.channels())); |
268
|
1 |
|
stmt.setInt(6, player.boostPoints()); |
269
|
1 |
|
stmt.setInt(7, player.spellPoints()); |
270
|
1 |
|
stmt.setInt(8, player.life()); |
271
|
1 |
|
stmt.setLong(9, player.experience()); |
272
|
1 |
|
stmt.setInt(10, player.savedPosition().map()); |
273
|
1 |
|
stmt.setInt(11, player.savedPosition().cell()); |
274
|
1 |
|
stmt.setLong(12, player.kamas()); |
275
|
1 |
|
stmt.setInt(13, player.id()); |
276
|
1 |
|
} |
277
|
|
|
); |
278
|
|
|
|
279
|
1 |
|
if (rows != 1) { |
280
|
1 |
|
throw new EntityNotFoundException(); |
281
|
|
|
} |
282
|
1 |
|
} |
283
|
|
|
|
284
|
1 |
|
private class Loader implements RepositoryUtils.Loader<Player> { |
|
|
|
|
285
|
1 |
|
private final Gender[] genders = Gender.values(); |
286
|
|
|
|
287
|
|
|
@Override |
288
|
|
|
public Player create(Record record) throws SQLException { |
289
|
1 |
|
return new Player( |
290
|
1 |
|
record.getInt("PLAYER_ID"), |
291
|
1 |
|
record.getInt("ACCOUNT_ID"), |
292
|
1 |
|
record.getInt("SERVER_ID"), |
293
|
1 |
|
record.getString("PLAYER_NAME"), |
294
|
1 |
|
Race.byId(record.getPositiveInt("RACE")), |
295
|
1 |
|
record.getArrayValue("SEX", genders), |
296
|
1 |
|
createColors(record), |
297
|
1 |
|
record.getPositiveInt("PLAYER_LEVEL"), |
298
|
1 |
|
record.unserialize("PLAYER_STATS", characteristicsTransformer), |
299
|
|
|
new Position( |
300
|
1 |
|
record.getNonNegativeInt("MAP_ID"), |
301
|
1 |
|
record.getNonNegativeInt("CELL_ID") |
302
|
|
|
), |
303
|
1 |
|
record.unserialize("CHANNELS", channelsTransformer), |
304
|
1 |
|
record.getNonNegativeInt("BOOST_POINTS"), |
305
|
1 |
|
record.getNonNegativeInt("SPELL_POINTS"), |
306
|
1 |
|
record.getNonNegativeInt("LIFE_POINTS"), |
307
|
1 |
|
record.getNonNegativeLong("PLAYER_EXPERIENCE"), |
308
|
|
|
new Position( |
309
|
1 |
|
record.getNonNegativeInt("SAVED_MAP_ID"), |
310
|
1 |
|
record.getNonNegativeInt("SAVED_CELL_ID") |
311
|
|
|
), |
312
|
1 |
|
record.getNonNegativeLong("PLAYER_KAMAS") |
313
|
|
|
); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
@Override |
317
|
|
|
public Player fillKeys(Player entity, ResultSet keys) throws SQLException { |
318
|
1 |
|
return entity.withId(keys.getInt(1)); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
@SuppressWarnings("argument") // Ignore invalid colors error |
322
|
|
|
private Colors createColors(Record record) throws SQLException { |
323
|
1 |
|
return new Colors( |
324
|
1 |
|
record.getInt("COLOR1"), |
325
|
1 |
|
record.getInt("COLOR2"), |
326
|
1 |
|
record.getInt("COLOR3") |
327
|
|
|
); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
} |
331
|
|
|
|