Jump to content

Fenril

Members
  • Posts

    10
  • Joined

  • Last visited

Posts posted by Fenril

  1. 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! ?

  2. Hi all,

    I have two questions.

    1. How to replace these special characters?
    image.thumb.png.0bddff321b6a979aa0c17b8a0e3ee83f.png

    2. How to increment this achievement by speaking in the city of Prontera for example? A particular npc must be present? A particular function?

    This is my information :

    achievement_db.yml

    Header:
      Type: ACHIEVEMENT_DB
      Version: 1
    
    Body:
      - ID: 100000
        Group: "AG_CHATTING"
        Name: "Test Speaking"
        Map: "prontera"
        Target:
          - Id: 0
            Count: 1
        Score: 10


    achievement_list.lub

    achievement_tbl = {
    	[100000] = {
    		UI_Type = 1,
    		group = "CHATTING",
    		major = 4,
    		minor = 1,
    		title = "Prontera Test",
    		content = {
    			summary = "Test Prontera",
    			details = "Test Speaking in Prontera",
    		},
    		resource = {
    			[1] = {
    				text = "Test 1",
    				count = 150,
    			},
    			[2] = {
    				text = "Test 2",
    				count = 300,
    			},
    			[3] = {
    				text = "Test 3",
    				count = 600,
    			},
    			[4] = {
    				text = "Test 4",
    				count = 1200,
    			},
    			[5] = {
    				text = "Test 5",
    				count = 2400,
    			},
    		},
    		reward = {
    			title = 1000,
    		},
    		score = 20,
    	}
    }
    
    function main()
    	for achieveID, achieveInfo in pairs(achievement_tbl) do
    		result, msg = InsertAchieveInfo(achieveID, achieveInfo.title, achieveInfo.content.summary, achieveInfo.content.details, achieveInfo.score)
    		if not result then
    			return false, msg
    		end
    		if nil ~= achieveInfo.resource then
    			for index, resource in ipairs(achieveInfo.resource) do
    				if nil ~= resource.shortcut then
    					shortcut = resource.shortcut
    				else
    					shortcut = -1
    				end
    				if 0 == achieveInfo.UI_Type then
    					result, msg = InsertAchieveResource(achieveID, resource.text, 0, shortcut)
    				elseif 1 == achieveInfo.UI_Type then
    					result, msg = InsertAchieveResource(achieveID, resource.text, resource.count, shortcut)
    				end
    				if not result then
    					return false, msg
    				end
    			end
    		end
    		if nil ~= achieveInfo.reward then
    			if nil ~= achieveInfo.reward.item then
    				result, msg = InsertAchieveRewardItem(achieveID, achieveInfo.reward.item)
    			end
    			if nil ~= achieveInfo.reward.title then
    				result, msg = InsertAchieveRewardTitle(achieveID, achieveInfo.reward.title)
    				if not result then
    					return false, msg
    				end
    			end
    			if nil ~= achieveInfo.reward.buff then
    				result, msg = InsertAchieveRewardBuff(achieveID, achieveInfo.reward.buff)
    				if not result then
    					return false, msg
    				end
    			end
    		end
    		result, msg = InsertAchieveUIType(achieveID, achieveInfo.UI_Type, achieveInfo.group)
    		if not result then
    			return false, msg
    		end
    		result, msg = SetAchieveIDByTab(achieveID, achieveInfo.major, achieveInfo.minor)
    		if not result then
    			return false, msg
    		end
    	end
    	return true, "good"
    end

     

    Thanks for reply.

  3. Hi everyone,

    Based on the src mod "storeit" ( @Akinari ), I would like to add a command that stores all inventory cards, including those present in inventory equipment, except those equipped.

    Currently, the command "storecard" stores the cards present in inventory equipment only...

    ACMD_FUNC(storecard) {
    
    	int i;
    	nullpo_retr(-1, sd);
    
    	if (sd->npc_id)
    	{
    		clif_displaymessage(fd, "You cannot be talking to an NPC and use this command.");
    		return -1;
    	}
    
    	if (sd->state.storage_flag != 1)
      	{	//Open storage.
    		switch (storage_storageopen(sd)) {
    		case 2: //Try again
    			clif_displaymessage(fd, "Run this command again..");
    			return 0;
    		case 1: //Failure
    			clif_displaymessage(fd, "You can't open the storage currently.");
    			return -1;
    		}
    	}
    	for (i = 0; i < MAX_INVENTORY; i++) {
    		if (sd->inventory.u.items_inventory[i].amount) {
    			if(sd->inventory.u.items_inventory[i].equip == 0)
    				storage_storageadd(sd, &sd->storage, i, sd->inventory.u.items_inventory[i].amount && sd->inventory.u.items_inventory[i].card[0]);
    		}
    	}
    	storage_storageclose(sd);
    
    	clif_displaymessage(fd, "All cards stored");
    	return 0;
    
    }

    Thanks for reply.

  4. Hi everyone,

    With the latest emulator, how can you change the max ASPD for a specifie class ?

    Example, max aspd for everyone = 190 except for Sniper / SinX, as well LK with skill "Frenzy" ?

    I saw this and i test: 

    status->amotion = ((sd->class_&MAPID_UPPERMASK) == MAPID_ASSASSIN)? 198:190)

    But after recompile, it's not functional.

    Have any ideas ?

    Thanks for all.

    Sry for my bad English.

×
×
  • Create New...