Jump to content
  • 0

[Solved] Achievement Account System


Fenril

Question


  • Group:  Members
  • Topic Count:  5
  • Topics Per Day:  0.00
  • Content Count:  10
  • Reputation:   0
  • Joined:  11/26/13
  • Last Seen:  

Hi all,

I'm working about achievement account, based on @Aeclis released : Account Bound Achievement.
Thanks to him to help me, but now, i have 2 issues..
 

Git Hash: '21e7a35b729be8a9edafa6eb49e2e52060e09ff4'
 

The function is OK, BUT:

  1. When i select "Bound: "Account"" or "Bound: "Player"", i can't claim rewards... The button is not working, have no mail, and no error  in the console.
      - ID: 200023
        Bound: "Account"
        Group: "AG_GOAL_STATUS"
        Name: "Dragonlike Power!"
        Condition: " readparam(bStr) >= 125 "
        Reward:
            ItemID: 2285
    image.png.b63c2d8386518549b6dbe90698db6a82.png
     
  2. When I select the "Bound: "Account"" function for an achievement with a counter, if I disconnect with the started counter, reconnect, continue the quest, I disconnect again, reconnect, the quest will have remained at the same increment as during the 1st disconnection. The SQL table does not increment.
      - ID: 100001
        Bound: "Account"
        Group: "AG_BATTLE"
        Name: "Test Kill Poring"
        Target:
          - Id: 0
            MobID: 1002
            Count: 50
        Score: 10


    - I kill 1 poring and I log out.
    image.png.a59c9923d0514c7c58d2f9f1dba6ae8c.png
    image.thumb.png.efc9e549d9f98d05faf72227159d02f6.png

    - After reconnecting, I continue the quest, and I kill 19 more poring and I log out again :
    image.png.0a0934bdf423c4e3f140fda55da51d83.png
    image.thumb.png.efc9e549d9f98d05faf72227159d02f6.png

    - After reconnecting, the database did not increment and the quest restarts on the 1st disconnection :
    image.png.a59c9923d0514c7c58d2f9f1dba6ae8c.png
     

If you have an idea, I'm interested.
You will find below the src content of the int_achievement.cpp file :

Spoiler

// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder

#include "int_achievement.hpp"

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

#include "../common/db.hpp"
#include "../common/malloc.hpp"
#include "../common/mmo.hpp"
#include "../common/showmsg.hpp"
#include "../common/socket.hpp"
#include "../common/sql.hpp"
#include "../common/strlib.hpp"

#include "char.hpp"
#include "inter.hpp"
#include "int_mail.hpp"

/**
 * Load achievements for a character.
 * @param char_id: Character ID
 * @param count: Pointer to return the number of found entries.
 * @return Array of found entries. It has *count entries, and it is care of the caller to aFree() it afterwards.
 */
struct achievement *mapif_achievements_fromsql(uint32 char_id, int *count)
{
	struct achievement *achievelog = NULL;
	struct achievement tmp_achieve;
	SqlStmt *stmt;
	StringBuf buf;
	int i;

	if (!count)
		return NULL;

	memset(&tmp_achieve, 0, sizeof(tmp_achieve));

	StringBuf_Init(&buf);
	StringBuf_AppendStr(&buf, "SELECT `id`, COALESCE(UNIX_TIMESTAMP(`completed`),0), COALESCE(UNIX_TIMESTAMP(`rewarded`),0)");
	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
		StringBuf_Printf(&buf, ", `count%d`", i + 1);
	StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id` = '%u' OR (`char_id` = '0' AND `account_id` = (SELECT `account_id` FROM `char` WHERE `char_id` = '%u'))", schema_config.achievement_table, char_id, char_id);
	/*StringBuf_Printf(&buf, " FROM `%s` WHERE `char_id` = '%u'", schema_config.achievement_table, char_id);*/

	stmt = SqlStmt_Malloc(sql_handle);
	if( SQL_ERROR == SqlStmt_PrepareStr(stmt, StringBuf_Value(&buf))
	||  SQL_ERROR == SqlStmt_Execute(stmt) )
	{
		SqlStmt_ShowDebug(stmt);
		SqlStmt_Free(stmt);
		StringBuf_Destroy(&buf);
		*count = 0;
		return NULL;
	}

	SqlStmt_BindColumn(stmt, 0, SQLDT_INT,  &tmp_achieve.achievement_id, 0, NULL, NULL);
	SqlStmt_BindColumn(stmt, 1, SQLDT_INT,  &tmp_achieve.completed, 0, NULL, NULL);
	SqlStmt_BindColumn(stmt, 2, SQLDT_INT,  &tmp_achieve.rewarded, 0, NULL, NULL);
	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
		SqlStmt_BindColumn(stmt, 3 + i, SQLDT_INT, &tmp_achieve.count[i], 0, NULL, NULL);

	*count = (int)SqlStmt_NumRows(stmt);
	if (*count > 0) {
		i = 0;

		achievelog = (struct achievement *)aCalloc(*count, sizeof(struct achievement));
		while (SQL_SUCCESS == SqlStmt_NextRow(stmt)) {
			if (i >= *count) // Sanity check, should never happen
				break;
			memcpy(&achievelog[i++], &tmp_achieve, sizeof(tmp_achieve));
		}
		if (i < *count) {
			// Should never happen. Compact array
			*count = i;
			achievelog = (struct achievement *)aRealloc(achievelog, sizeof(struct achievement) * i);
		}
	}

	SqlStmt_Free(stmt);
	StringBuf_Destroy(&buf);

	ShowInfo("achievement load complete from DB - id: %d (total: %d)\n", char_id, *count);

	return achievelog;
}

/**
 * Deletes an achievement from a character's achievementlog.
 * @param char_id: Character ID
 * @param achievement_id: Achievement ID
 * @return false in case of errors, true otherwise
 */
bool mapif_achievement_delete(uint32 char_id, int achievement_id)
{
	if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d' AND `char_id` = '%u' OR `account_id` = (SELECT `account_id` FROM `char` WHERE `char_id` = '%u')", schema_config.achievement_table, achievement_id, char_id)) {
		Sql_ShowDebug(sql_handle);
		return false;
	}

	return true;
}

/**
 * Adds an achievement to a character's achievementlog.
 * @param char_id: Character ID
 * @param ad: Achievement data
 * @return false in case of errors, true otherwise
 */
bool mapif_achievement_add(uint32 char_id, struct achievement* ad)
{
	enum e_achievement_bound bound = ad->bound;
	StringBuf buf;
	int i;

	StringBuf_Init(&buf);
	StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `account_id`, `id`, `completed`, `rewarded`", schema_config.achievement_table);
	/*StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `id`, `completed`, `rewarded`", schema_config.achievement_table);*/
	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
		StringBuf_Printf(&buf, ", `count%d`", i + 1);
	StringBuf_AppendStr(&buf, ")");
	/*StringBuf_Printf(&buf, " VALUES ('%u', '%d',", char_id, ad->achievement_id, (uint32)ad->completed, (uint32)ad->rewarded);*/
	if (bound == ACCOUNT)
	{
		StringBuf_Printf(&buf, " VALUES ('0', (SELECT `account_id` FROM `char` WHERE `char_id` = '%u'), '%d',", char_id, ad->achievement_id, (uint32)ad->completed, (uint32)ad->rewarded);
	}
	else
	{
		StringBuf_Printf(&buf, " VALUES ('%u', '0','%d',", char_id, ad->achievement_id, (uint32)ad->completed, (uint32)ad->rewarded);
	}
	if( ad->completed ){
		StringBuf_Printf(&buf, "FROM_UNIXTIME('%u'),", (uint32)ad->completed);
	}
	else{
		StringBuf_AppendStr(&buf, "NULL,");
	}
	if( ad->rewarded ){
		StringBuf_Printf(&buf, "FROM_UNIXTIME('%u')", (uint32)ad->rewarded);
	}
	else{
		StringBuf_AppendStr(&buf, "NULL");
	}
	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
		StringBuf_Printf(&buf, ", '%d'", ad->count[i]);
	StringBuf_AppendStr(&buf, ")");

	if (SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf))) {
		Sql_ShowDebug(sql_handle);
		StringBuf_Destroy(&buf);
		return false;
	}

	StringBuf_Destroy(&buf);

	return true;
}

/**
 * Updates an achievement in a character's achievementlog.
 * @param char_id: Character ID
 * @param ad: Achievement data
 * @return false in case of errors, true otherwise
 */
bool mapif_achievement_update(uint32 char_id, struct achievement* ad)
{
	enum e_achievement_bound bound = ad->bound;
	StringBuf buf;
	int i;

	StringBuf_Init(&buf);
	StringBuf_Printf(&buf, "UPDATE `%s` SET ", schema_config.achievement_table);
	if( ad->completed ){
		StringBuf_Printf(&buf, "`completed` = FROM_UNIXTIME('%u'),", (uint32)ad->completed);
	}else{
		StringBuf_AppendStr(&buf, "`completed` = NULL,");
	}
	if( ad->rewarded ){
		StringBuf_Printf(&buf, "`rewarded` = FROM_UNIXTIME('%u')", (uint32)ad->rewarded);
	}else{
		StringBuf_AppendStr(&buf, "`rewarded` = NULL");
	}
	for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; ++i)
		StringBuf_Printf(&buf, ", `count%d` = '%d'", i + 1, ad->count[i]);
	{
	//StringBuf_Printf(&buf, " WHERE `id` = %d AND `char_id` = %u", ad->achievement_id, char_id);
	StringBuf_Printf(&buf, " WHERE `id` = %d AND `char_id` = %u OR (`char_id` = '0' AND `account_id` = (SELECT `account_id` FROM `char` WHERE `char_id` = '%u'))", ad->achievement_id, char_id);
	}
	if (SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf))) {
		Sql_ShowDebug(sql_handle);
		StringBuf_Destroy(&buf);
		return false;
	}

	StringBuf_Destroy(&buf);

	return true;
}

/**
 * Notifies the map-server of the result of saving a character's achievementlog.
 */
void mapif_achievement_save( int fd, uint32 char_id, bool success ){
	WFIFOHEAD(fd, 7);
	WFIFOW(fd, 0) = 0x3863;
	WFIFOL(fd, 2) = char_id;
	WFIFOB(fd, 6) = success;
	WFIFOSET(fd, 7);
}

/**
 * Handles the save request from mapserver for a character's achievementlog.
 * Received achievements are saved, and an ack is sent back to the map server.
 * @see inter_parse_frommap
 */
int mapif_parse_achievement_save(int fd)
{
	int i, j, k, old_n, new_n = (RFIFOW(fd, 2) - 8) / sizeof(struct achievement);
	uint32 char_id = RFIFOL(fd, 4);
	struct achievement *old_ad = NULL, *new_ad = NULL;
	bool success = true;

	if (new_n > 0)
		new_ad = (struct achievement *)RFIFOP(fd, 8);

	old_ad = mapif_achievements_fromsql(char_id, &old_n);

	for (i = 0; i < new_n; i++) {
		ARR_FIND(0, old_n, j, new_ad[i].achievement_id == old_ad[j].achievement_id);
		if (j < old_n) { // Update existing achievements
			// Only counts, complete, and reward are changable.
			ARR_FIND(0, MAX_ACHIEVEMENT_OBJECTIVES, k, new_ad[i].count[k] != old_ad[j].count[k]);
			if (k != MAX_ACHIEVEMENT_OBJECTIVES || new_ad[i].completed != old_ad[j].completed || new_ad[i].rewarded != old_ad[j].rewarded) {
				if ((success = mapif_achievement_update(char_id, &new_ad[i])) == false)
					break;
			}

			if (j < (--old_n)) {
				// Compact array
				memmove(&old_ad[j], &old_ad[j + 1], sizeof(struct achievement) * (old_n - j));
				memset(&old_ad[old_n], 0, sizeof(struct achievement));
			}
		} else { // Add new achievements
			if (new_ad[i].achievement_id) {
				if ((success = mapif_achievement_add(char_id, &new_ad[i])) == false)
					break;
			}
		}
	}

	for (i = 0; i < old_n; i++) { // Achievements not in new_ad but in old_ad are to be erased.
		if ((success = mapif_achievement_delete(char_id, old_ad[i].achievement_id)) == false)
			break;
	}

	if (old_ad)
		aFree(old_ad);

	mapif_achievement_save(fd, char_id, success);

	return 0;
}

/**
 * Sends the achievementlog of a character to the map-server.
 */
void mapif_achievement_load( int fd, uint32 char_id ){
	struct achievement *tmp_achievementlog = NULL;
	int num_achievements = 0;

	tmp_achievementlog = mapif_achievements_fromsql(char_id, &num_achievements);

	WFIFOHEAD(fd, num_achievements * sizeof(struct achievement) + 8);
	WFIFOW(fd, 0) = 0x3862;
	WFIFOW(fd, 2) = num_achievements * sizeof(struct achievement) + 8;
	WFIFOL(fd, 4) = char_id;

	if (num_achievements > 0)
		memcpy(WFIFOP(fd, 8), tmp_achievementlog, sizeof(struct achievement) * num_achievements);

	WFIFOSET(fd, num_achievements * sizeof(struct achievement) + 8);

	if (tmp_achievementlog)
		aFree(tmp_achievementlog);
}

/**
 * Sends achievementlog to the map server
 * NOTE: Achievements sent to the player are only completed ones
 * @see inter_parse_frommap
 */
int mapif_parse_achievement_load(int fd)
{
	mapif_achievement_load( fd, RFIFOL(fd, 2) );

	return 0;
}

/**
 * Notify the map-server if claiming the reward has succeeded.
 */
void mapif_achievement_reward( int fd, uint32 char_id, int32 achievement_id, time_t rewarded ){
	WFIFOHEAD(fd, 14);
	WFIFOW(fd, 0) = 0x3864;
	WFIFOL(fd, 2) = char_id;
	WFIFOL(fd, 6) = achievement_id;
	WFIFOL(fd, 10) = (uint32)rewarded;
	WFIFOSET(fd, 14);
}

/**
 * Request of the map-server that a player claimed his achievement rewards.
 * @see inter_parse_frommap
 */
int mapif_parse_achievement_reward(int fd){
	time_t current = time(NULL);
	uint32 char_id = RFIFOL(fd, 2);
	int32 achievement_id = RFIFOL(fd, 6);

	/*if( Sql_Query( sql_handle, "UPDATE `%s` SET `rewarded` = FROM_UNIXTIME('%u') WHERE `char_id`='%u' AND `id` = '%d' AND `completed` IS NOT NULL AND `rewarded` IS NULL", schema_config.achievement_table, (uint32)current, char_id, achievement_id ) == SQL_ERROR ||*/
	if( Sql_Query( sql_handle, "UPDATE `%s` SET `rewarded` = FROM_UNIXTIME('%u') WHERE (`char_id`='%u' OR `account_id` = (SELECT `account_id` FROM `char` WHERE `char_id` = '%u')) AND `id` = '%d' AND `completed` IS NOT NULL AND `rewarded` IS NULL", schema_config.achievement_table, (uint32)current, char_id, achievement_id ) == SQL_ERROR ||
		Sql_NumRowsAffected(sql_handle) <= 0 ){
		current = 0;
	}else if( RFIFOW(fd,10) > 0 ){ // Do not send a mail if no item reward
		char mail_sender[NAME_LENGTH];
		char mail_receiver[NAME_LENGTH];
		char mail_title[MAIL_TITLE_LENGTH];
		char mail_text[MAIL_BODY_LENGTH];
		struct item item;

		memset(&item, 0, sizeof(struct item));
		item.nameid = RFIFOW(fd, 10);
		item.amount = RFIFOL(fd, 12);
		item.identify = 1;

		safesnprintf(mail_sender, NAME_LENGTH, char_msg_txt(227)); // 227: GM
		safestrncpy(mail_receiver, RFIFOCP(fd,16), NAME_LENGTH);
		safesnprintf(mail_title, MAIL_TITLE_LENGTH, char_msg_txt(228)); // 228: Achievement Reward Mail
		safesnprintf(mail_text, MAIL_BODY_LENGTH, char_msg_txt(229), RFIFOCP(fd,16+NAME_LENGTH) ); // 229: [%s] Achievement Reward.

		if( !mail_sendmail(0, mail_sender, char_id, mail_receiver, mail_title, mail_text, 0, &item, 1) ){
			current = 0;
		}
	}

	mapif_achievement_reward(fd, char_id, achievement_id, current);

	return 0;
}

/**
 * Parses achievementlog related packets from the map server.
 * @see inter_parse_frommap
 */
int inter_achievement_parse_frommap(int fd)
{
	switch (RFIFOW(fd, 0)) {
		case 0x3062: mapif_parse_achievement_load(fd); break;
		case 0x3063: mapif_parse_achievement_save(fd); break;
		case 0x3064: mapif_parse_achievement_reward(fd); break;
		default:
			return 0;
	}
	return 1;
}

 


I really need this feature, and I'm ready to pay the price.


Thanks!

 

 

Edit : 

Ok it's solved by @Technoken

Thanks to him! ?

Edited by Fenril
Solved
Link to comment
Share on other sites

0 answers to this question

Recommended Posts

There have been no answers to this question yet

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...