summaryrefslogtreecommitdiff
path: root/src/echar/char.c
blob: cbaa884bbfc094521dfb0f24414b55e967601859 (plain) (blame)
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
// Copyright (c) Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// Copyright (c) 2014 - 2015 Evol developers

#include "common/hercules.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common/HPMi.h"
#include "common/memmgr.h"
#include "common/mapindex.h"
#include "common/mmo.h"
#include "common/socket.h"
#include "common/strlib.h"
#include "common/sql.h"
#include "common/utils.h"
#include "common/timer.h"
#include "char/char.h"
#include "char/inter.h"

#include "plugins/HPMHooking.h"

#include "ecommon/enum/gender.h"

#include "echar/char.h"
#include "echar/config.h"

void echar_parse_char_create_new_char(int *fdPtr, struct char_session_data **sdPtr)
{
    // ignore char creation disable option
    const int fd = *fdPtr;
    struct char_session_data *sd = *sdPtr;
    uint16 race = 0;
    uint16 look = 0;
    uint8 sex = 0;

    if (!sd)
        return;

    race = RFIFOW(fd, 31);
    if (race < min_char_class || race > max_char_class)
    {
        chr->creation_failed(fd, -10);
        RFIFOSKIP(fd, 31 + 5);
        hookStop();
        return;
    }
    sex = RFIFOB(fd, 33);
    if (sex != 0 && sex != 1 && sex != 3 && sex != 99)
    {
        chr->creation_failed(fd, -11);
        RFIFOSKIP(fd, 31 + 5);
        hookStop();
        return;
    }
    look = RFIFOW(fd, 34);
    if (look < min_look || look > max_look)
    {
        chr->creation_failed(fd, -12);
        RFIFOSKIP(fd, 31 + 5);
        hookStop();
        return;
    }

    // +++ need remove addition sql query after this line for set sex
    const int result = chr->make_new_char_sql(sd, RFIFOP(fd, 2), 1, 1, 1, 1, 1, 1, RFIFOB(fd, 26), RFIFOW(fd, 27), RFIFOW(fd, 29), JOB_NOVICE, 'U');
    if (result < 0)
    {
        chr->creation_failed(fd, result);
    }
    else
    {
        // retrieve data
        struct mmo_charstatus char_dat;
        chr->mmo_char_fromsql(result, &char_dat, false); //Only the short data is needed.

        char_dat.class = race;
        char_dat.sex = sex;
        char_dat.clothes_color = look;

        chr->mmo_char_tosql(result, &char_dat);
        char cSex = 'U';
        if (sex == SEX_MALE)
            cSex = 'M';
        else if (sex == SEX_FEMALE)
            cSex = 'F';

        if (SQL_ERROR == SQL->Query(inter->sql_handle, "UPDATE `%s` SET `sex` = '%c' WHERE `char_id` = '%d'", "char", cSex, char_dat.char_id))
        {
            Sql_ShowDebug(inter->sql_handle);
        }
        chr->creation_ok(fd, &char_dat);

        // add new entry to the chars list
        sd->found_char[char_dat.slot] = result; // the char_id of the new char
    }
    RFIFOSKIP(fd, 31 + 5);
    hookStop();
}

static int tmpVersion = 0;

void echar_parse_char_connect_pre(int *fdPtr,
                                  struct char_session_data **sd __attribute__ ((unused)),
                                  uint32 *ipl __attribute__ ((unused)))
{
    tmpVersion = RFIFOW(*fdPtr, 14);
}

void echar_parse_char_connect_post(int fd,
                                   struct char_session_data *sd,
                                   uint32 ipl __attribute__ ((unused)))
{
    sd = (struct char_session_data*)sockt->session[fd]->session_data;
    if (sd)
        sd->version = tmpVersion;
}

void echar_creation_failed(int *fdPtr, int *result)
{
    const int fd = *fdPtr;
    WFIFOHEAD(fd, 3);
    WFIFOW(fd, 0) = 0x6e;
    /* Others I found [Ind] */
    /* 0x02 = Symbols in Character Names are forbidden */
    /* 0x03 = You are not eligible to open the Character Slot. */
    /* 0x0B = This service is only available for premium users.  */
    /* 0x0C = Character name is invalid. */
    switch (*result)
    {
        case -1: WFIFOB(fd, 2) = 0x00; break; // 'Charname already exists'
        case -2: WFIFOB(fd, 2) = 0xFF; break; // 'Char creation denied'
        case -3: WFIFOB(fd, 2) = 0x01; break; // 'You are underaged'
        case -4: WFIFOB(fd, 2) = 0x03; break; // 'You are not eligible to open the Character Slot.'
        case -5: WFIFOB(fd, 2) = 0x02; break; // 'Symbols in Character Names are forbidden'
        case -10: WFIFOB(fd, 2) = 0x50; break; // Wrong class
        case -11: WFIFOB(fd, 2) = 0x51; break; // Wrong sex
        case -12: WFIFOB(fd, 2) = 0x52; break; // Wrong look

        default:
            ShowWarning("chr->parse_char: Unknown result received from chr->make_new_char_sql: %d!\n", *result);
            WFIFOB(fd,2) = 0xFF;
            break;
    }
    WFIFOSET(fd,3);
    hookStop();
}

void echar_parse_change_paassword(int fd)
{
    if (chr->login_fd < 0)
        return;
    struct char_session_data* sd = (struct char_session_data*)sockt->session[fd]->session_data;
    if (!sd)
        return;
    WFIFOHEAD(chr->login_fd, 54);
    WFIFOW(chr->login_fd, 0) = 0x5000;
    WFIFOL(chr->login_fd, 2) = sd->account_id;
    memcpy (WFIFOP (chr->login_fd, 6), RFIFOP (fd, 2), 24);
    memcpy (WFIFOP (chr->login_fd, 30), RFIFOP (fd, 26), 24);
    WFIFOSET(chr->login_fd, 54);
}

void echar_parse_login_password_change_ack(int charFd)
{
    struct char_session_data* sd = NULL;
    const int accountId = RFIFOL(charFd, 2);
    const int status = RFIFOB(charFd, 6);

    int fd = -1;
    ARR_FIND( 0, sockt->fd_max, fd, sockt->session[fd] && (sd = (struct char_session_data*)sockt->session[fd]->session_data) && sd->auth && sd->account_id == accountId );
    if (fd < sockt->fd_max && fd >= 0)
    {
        WFIFOHEAD(fd, 3);
        WFIFOW(fd, 0) = 0x62;
        WFIFOB(fd, 2) = status;
        WFIFOSET(fd, 3);
    }
}

void echar_send_HC_ACK_CHARINFO_PER_PAGE_post(int fd,
                                              struct char_session_data *sd)
{
    send_additional_slots(fd, sd);
}

void echar_send_HC_ACK_CHARINFO_PER_PAGE_tail_pre(int *fdPtr __attribute__ ((unused)),
                                                  struct char_session_data **sdPtr __attribute__ ((unused)))
{
    hookStop();
}

int echar_mmo_char_send_characters_post(int retVal,
                                        int fd,
                                        struct char_session_data* sd)
{
    send_additional_slots(fd, sd);
    return retVal;
}

void send_additional_slots(int fd, struct char_session_data* sd)
{
    int char_id;
    int name_id;
    int slot;
    int card0;
    int card1;
    int card2;
    int card3;

    if (!sd)
        return;

    struct SqlStmt* stmt = SQL->StmtMalloc(inter->sql_handle);
    if (stmt == NULL)
    {
        SqlStmt_ShowDebug(stmt);
        return;
    }

    if (SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT "
        "inventory.char_id, inventory.nameid, inventory.equip, "
        "inventory.card0, inventory.card1, inventory.card2, inventory.card3 "
        "FROM `char` "
        "LEFT JOIN inventory ON inventory.char_id = `char`.char_id "
        "WHERE account_id = '%d' AND equip <> 0 AND amount > 0 ORDER BY inventory.char_id", sd->account_id)
        || SQL_ERROR == SQL->StmtExecute(stmt)
        || SQL_ERROR == SQL->StmtBindColumn(stmt, 0,  SQLDT_INT, &char_id, sizeof char_id, NULL, NULL)
        || SQL_ERROR == SQL->StmtBindColumn(stmt, 1,  SQLDT_INT, &name_id, sizeof name_id, NULL, NULL)
        || SQL_ERROR == SQL->StmtBindColumn(stmt, 2,  SQLDT_INT, &slot, sizeof slot, NULL, NULL)
        || SQL_ERROR == SQL->StmtBindColumn(stmt, 3,  SQLDT_INT, &card0, sizeof card0, NULL, NULL)
        || SQL_ERROR == SQL->StmtBindColumn(stmt, 4,  SQLDT_INT, &card1, sizeof card1, NULL, NULL)
        || SQL_ERROR == SQL->StmtBindColumn(stmt, 5,  SQLDT_INT, &card2, sizeof card2, NULL, NULL)
        || SQL_ERROR == SQL->StmtBindColumn(stmt, 6,  SQLDT_INT, &card3, sizeof card3, NULL, NULL))
    {
        SqlStmt_ShowDebug(stmt);
        SQL->StmtFree(stmt);
        return;
    }

    while (SQL_SUCCESS == SQL->StmtNextRow(stmt))
    {
        int type = 2;
        switch (slot)
        {
            case 0:
                type = 0;
                break;
            case EQP_HEAD_LOW:
                type = 3;
                break;
            case EQP_HAND_R:
                type = 2;
                break;
            case EQP_GARMENT:
                type = 12;
                break;
            case EQP_ACC_L:
                type = 19;
                break;
            case EQP_ARMOR:
                type = 17;
                break;
            case EQP_HAND_L:
                type = 8;
                break;
            case EQP_SHOES:
                type = 9;
                break;
            case EQP_ACC_R:
                type = 18;
                break;
            case EQP_HEAD_TOP:
                type = 4;
                break;
            case EQP_HEAD_MID:
                type = 5;
                break;
            case EQP_COSTUME_HEAD_TOP:
                type = 13;
                break;
            case EQP_COSTUME_HEAD_MID:
                type = 14;
                break;
            case EQP_COSTUME_HEAD_LOW:
                type = 15;
                break;
            case EQP_COSTUME_GARMENT:
                type = 16;
                break;
            dafault:
                ShowWarning("unknown equip for char %d, item %d, slot %d, cards %d,%d,%d,%d\n", char_id, name_id, slot, (int)card0, (int)card1, (int)card2, (int)card3);
                type = 0;
                break;
        }

        if (type == 0)
            continue;
        WFIFOHEAD (fd, 19);
        WFIFOW (fd, 0) = 0xb17 + evolPacketOffset;
        WFIFOL (fd, 2) = char_id;
        WFIFOB (fd, 6) = (unsigned char)type;
        WFIFOW (fd, 7) = name_id;
        WFIFOW (fd, 9) = 0;
        WFIFOW (fd, 11) = card0;
        WFIFOW (fd, 13) = card1;
        WFIFOW (fd, 15) = card2;
        WFIFOW (fd, 17) = card3;
        WFIFOSET (fd, 19);

        //ShowWarning("char %d, item %d, slot %d->%d, cards %d,%d,%d,%d\n", char_id, name_id, slot, type, (int)card0, (int)card1, (int)card2, (int)card3);
    }

    SQL->StmtFree(stmt);
}

void echar_parse_map_serverexit(int mapFd)
{
    const int code = RFIFOW(mapFd, 2);
    switch (code)
    {
        case 100:  // all exit
        case 101:  // all restart
        case 102:  // restart char and map server
        case 104:  // git pull and restart all restart
        case 105:  // build all
        case 106:  // rebuild all
        case 107:  // git pull and build all
        case 108:  // git pull and rebuild all
        case 109:  // build plugin
        case 110:  // git pull and build plugin
            echat_send_login_serverexit(code);
            HSleep(1);
            core->shutdown_callback();
            break;
        case 103:  // restart map server
            break;
        default:
            ShowWarning("Unknown termination code: %d\n", code);
    }
}

void echat_send_login_serverexit(const int code)
{
    WFIFOHEAD(chr->login_fd, 4);
    WFIFOW(chr->login_fd, 0) = 0x5003;
    WFIFOW(chr->login_fd, 2) = code;
    WFIFOSET(chr->login_fd, 4);
}

// send non-binary gender to map server
int echar_mmo_gender(const struct char_session_data **sd __attribute__ ((unused)),
                     const struct mmo_charstatus **p __attribute__ ((unused)), char *sex)
{
    hookStop();

    switch (*sex) {
        case 'M':
            return GENDER_MALE;
        case 'F':
            return GENDER_FEMALE;
        default:
            return GENDER_NONBINARY;
    }
}

// update sql from map to char with the new gender
// XXX: this whole hook is only to change one line; we might want to change it
//      upstream in hercules
int echar_changecharsex(int *char_idPtr, int *sexPtr)
{
    int char_id = *char_idPtr;
    int sex = *sexPtr;

    int account_id = 0;
    char *data;

    // get character data
    if (SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `account_id` FROM `char` WHERE `char_id` = '%d'", char_id)) {
        Sql_ShowDebug(inter->sql_handle);
        hookStop();
        return 1;
    }

    if (SQL->NumRows(inter->sql_handle) != 1 || SQL_ERROR == SQL->NextRow(inter->sql_handle)) {
        SQL->FreeResult(inter->sql_handle);
        hookStop();
        return 1;
    }

    SQL->GetData(inter->sql_handle, 0, &data, NULL); account_id = atoi(data);
    SQL->FreeResult(inter->sql_handle);

    if (SQL_ERROR == SQL->Query(inter->sql_handle, "UPDATE `char` SET `sex` = '%c' WHERE `char_id` = '%d'", sex == SEX_MALE ? 'M' : (sex == SEX_FEMALE ? 'F' : 'U'), char_id)) {
        Sql_ShowDebug(inter->sql_handle);
        hookStop();
        return 1;
    }

    // disconnect player if online on char-server
    chr->disconnect_player(account_id);

    // notify all mapservers about this change
    chr->changesex(account_id, sex);
    hookStop();
    return 0;
}