Browse Source

V1 of /claim functionality. See issue #127 for further notes REQUIRES sql update!

Devn00b 1 year ago
parent
commit
f73dab429b

+ 17 - 7
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -5832,19 +5832,29 @@ void Commands::Command_Appearance(Client* client, Seperator* sep, int handler)
 
 /* 
 	Function: Command_Claim()
-	Purpose	: 
-	Params	: 
-	Dev		: 
-	Example	: 
+	Purpose	: Summon veteran rewards
+	Params	: nothing = show claim window, any number claims that item.
+	Dev		: devn00b
+	Example	: /claim 0 (claims the 1st item added to the list claim[0])
 */ 
 void Commands::Command_Claim(Client* client, Seperator* sep)
 {
-	if (sep && sep->arg[0] && sep->IsNumber(0)) 
+	//if we were passed a claim id
+	if (sep && sep->argplus[0] && sep->IsNumber(0)) 
 	{
-		LogWrite(MISC__TODO, 1, "TODO", "TODO: Command_Claim to be completed\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
+		int32 char_id = client->GetCharacterID();
+		int8 my_claim_id = atoi(sep->argplus[0]);
+		vector<ClaimItems> claim = database.LoadCharacterClaimItems(char_id);
+		Item* item = master_item_list.GetItem(claim[my_claim_id].item_id);
+		database.ClaimItem(char_id, item->details.item_id, client);
+		return;
 	}
-	else
+	else {
+		//if no arg just send the window.
 		client->ShowClaimWindow();
+		return;
+	}
+	return;
 }
 
 /* 

+ 5 - 2
EQ2/source/WorldServer/Entity.cpp

@@ -228,7 +228,7 @@ void Entity::MapInfoStruct()
 	get_int8_funcs["tradeskill_class1"] = l::bind(&InfoStruct::get_tradeskill_class1, &info_struct);
 	get_int8_funcs["tradeskill_class2"] = l::bind(&InfoStruct::get_tradeskill_class2, &info_struct);
 	get_int8_funcs["tradeskill_class3"] = l::bind(&InfoStruct::get_tradeskill_class3, &info_struct);
-	get_int16_funcs["account_age_base"] = l::bind(&InfoStruct::get_account_age_base, &info_struct);
+	get_int32_funcs["account_age_base"] = l::bind(&InfoStruct::get_account_age_base, &info_struct);
 	//	int8			account_age_bonus_[19];
 	get_int16_funcs["absorb"] = l::bind(&InfoStruct::get_absorb, &info_struct);
 	get_int32_funcs["xp"] = l::bind(&InfoStruct::get_xp, &info_struct);
@@ -337,6 +337,7 @@ void Entity::MapInfoStruct()
 	get_int8_funcs["override_ranged_weapon"] = l::bind(&InfoStruct::get_override_ranged_weapon, &info_struct);
 	
 	get_int8_funcs["friendly_target_npc"] = l::bind(&InfoStruct::get_friendly_target_npc, &info_struct);
+	get_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::get_last_claim_time, &info_struct);
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -410,7 +411,7 @@ void Entity::MapInfoStruct()
 	set_int8_funcs["tradeskill_class1"] = l::bind(&InfoStruct::set_tradeskill_class1, &info_struct, l::_1);
 	set_int8_funcs["tradeskill_class2"] = l::bind(&InfoStruct::set_tradeskill_class2, &info_struct, l::_1);
 	set_int8_funcs["tradeskill_class3"] = l::bind(&InfoStruct::set_tradeskill_class3, &info_struct, l::_1);
-	set_int16_funcs["account_age_base"] = l::bind(&InfoStruct::set_account_age_base, &info_struct, l::_1);
+	set_int32_funcs["account_age_base"] = l::bind(&InfoStruct::set_account_age_base, &info_struct, l::_1);
 	//	int8			account_age_bonus_[19];
 	set_int16_funcs["absorb"] = l::bind(&InfoStruct::set_absorb, &info_struct, l::_1);
 	set_int32_funcs["xp"] = l::bind(&InfoStruct::set_xp, &info_struct, l::_1);
@@ -519,6 +520,8 @@ void Entity::MapInfoStruct()
 	set_int8_funcs["override_ranged_weapon"] = l::bind(&InfoStruct::set_override_ranged_weapon, &info_struct, l::_1);
 	
 	set_int8_funcs["friendly_target_npc"] = l::bind(&InfoStruct::set_friendly_target_npc, &info_struct, l::_1);
+	set_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::set_last_claim_time, &info_struct, l::_1);
+
 }
 
 bool Entity::HasMoved(bool include_heading){

+ 9 - 3
EQ2/source/WorldServer/Entity.h

@@ -272,6 +272,7 @@ struct InfoStruct{
 		override_ranged_weapon_ = 0;
 		
 		friendly_target_npc_ = 0;
+		last_claim_time_ = 0;
 	}
 
 
@@ -455,6 +456,7 @@ struct InfoStruct{
 		override_secondary_weapon_ = oldStruct->get_override_secondary_weapon();
 		override_ranged_weapon_ = oldStruct->get_override_ranged_weapon();
 		friendly_target_npc_ = oldStruct->get_friendly_target_npc();
+		last_claim_time_ = oldStruct->get_last_claim_time();
 
 	}
 	//mutable std::shared_mutex mutex_;
@@ -541,7 +543,7 @@ struct InfoStruct{
 	int8	 get_tradeskill_class2() { std::lock_guard<std::mutex> lk(classMutex); return tradeskill_class2_; }
 	int8	 get_tradeskill_class3() { std::lock_guard<std::mutex> lk(classMutex); return tradeskill_class3_; }
 
-	int16	 get_account_age_base() { std::lock_guard<std::mutex> lk(classMutex); return account_age_base_; }
+	int32	 get_account_age_base() { std::lock_guard<std::mutex> lk(classMutex); return account_age_base_; }
 
 	int8	 get_account_age_bonus(int8 field) { std::lock_guard<std::mutex> lk(classMutex); return account_age_bonus_[field]; }
 	int16	 get_absorb() { std::lock_guard<std::mutex> lk(classMutex); return absorb_; }
@@ -655,6 +657,7 @@ struct InfoStruct{
 	int8	get_override_ranged_weapon() { std::lock_guard<std::mutex> lk(classMutex); return override_ranged_weapon_; }
 	
 	int8	get_friendly_target_npc() { std::lock_guard<std::mutex> lk(classMutex); return friendly_target_npc_; }
+	int32	get_last_claim_time() { std::lock_guard<std::mutex> lk(classMutex); return last_claim_time_; }
 	
 	
 	void	set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
@@ -748,7 +751,7 @@ struct InfoStruct{
 	void	set_tradeskill_class2(int8 value) { std::lock_guard<std::mutex> lk(classMutex); tradeskill_class2_ = value; }
 	void	set_tradeskill_class3(int8 value) { std::lock_guard<std::mutex> lk(classMutex); tradeskill_class3_ = value; }
 
-	void	set_account_age_base(int16 value) { std::lock_guard<std::mutex> lk(classMutex); account_age_base_ = value; }
+	void	set_account_age_base(int32 value) { std::lock_guard<std::mutex> lk(classMutex); account_age_base_ = value; }
 
 	void	set_xp_vitality(float value) { std::lock_guard<std::mutex> lk(classMutex); xp_vitality_ = value; }
 
@@ -939,6 +942,8 @@ struct InfoStruct{
 	void	set_override_secondary_weapon(int8 value) { std::lock_guard<std::mutex> lk(classMutex); override_secondary_weapon_ = value; }
 	void	set_override_ranged_weapon(int8 value) { std::lock_guard<std::mutex> lk(classMutex); override_ranged_weapon_ = value; }
 	void	set_friendly_target_npc(int8 value) { std::lock_guard<std::mutex> lk(classMutex); friendly_target_npc_ = value; }
+	void	set_last_claim_time(int32 value) { std::lock_guard<std::mutex> lk(classMutex); last_claim_time_ = value; }
+
 	void	ResetEffects(Spawn* spawn)
 	{
 		for(int i=0;i<45;i++){
@@ -1032,7 +1037,7 @@ private:
 	int8			tradeskill_class1_;
 	int8			tradeskill_class2_;
 	int8			tradeskill_class3_;
-	int16			account_age_base_;
+	int32			account_age_base_;
 	int8			account_age_bonus_[19];
 	int16			absorb_;
 	int32			xp_;
@@ -1139,6 +1144,7 @@ private:
 	int8			override_ranged_weapon_;
 	
 	int8			friendly_target_npc_;
+	int32			last_claim_time_;
 	
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;

+ 163 - 0
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -8072,3 +8072,166 @@ void WorldDatabase::UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 s
 		}
 	}
 }
+
+
+//devn00b: load the items the server has into a character_ db for easy access. Should be done on char create.
+void WorldDatabase::LoadClaimItems(int32 char_id)
+{
+	int16 total = 0;
+	Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, item_id, max_claim, one_per_char, veteran_reward_time from claim_items");
+
+	if (result)
+	{
+		if (mysql_num_rows(result) > 0)
+		{
+
+			while (result && (row = mysql_fetch_row(result)))
+			{
+				MYSQL_RES* res = query.RunQuery2(Q_INSERT, "insert into character_claim_items (char_id, item_id, max_claim, curr_claim, one_per_char, veteran_reward_time) values  (%i, %i, %i, %i, %i)", char_id, atoul(row[1]), atoul(row[2]), atoul(row[2]), atoul(row[3]), atoi64(row[4]));
+				total++;
+			}
+		}
+	}
+	LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Claim Item(s)", total);
+}
+
+int16 WorldDatabase::CountCharClaimItems(int32 char_id) {
+	Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT count(*) from character_claim_items where char_id=%i", char_id);
+
+	if (result && mysql_num_rows(result) > 0) {
+		MYSQL_ROW row;
+		row = mysql_fetch_row(result);
+		return atoi(row[0]); // Return count
+	}
+
+	return 0;
+}
+
+vector<ClaimItems> WorldDatabase::LoadCharacterClaimItems(int32 char_id) {
+	vector<ClaimItems> claim;
+	Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, max_claim, curr_claim, one_per_char, last_claim, veteran_reward_time from character_claim_items where char_id=%u", char_id);
+	int16 i = 0;
+
+	if (result)
+	{
+		if (mysql_num_rows(result) > 0)
+		{
+
+			while (result && (row = mysql_fetch_row(result)))
+			{
+				ClaimItems c;
+				int8 max_claim = atoi(row[1]);
+				int8 curr_claim = atoi(row[2]);
+
+				//if one per char is set set max/cur to 1.
+				if (atoi(row[3]) == 1) {
+					int8 max_claim = 1;
+					int8 curr_claim = 1;
+				}
+
+				c.item_id = atoi(row[0]);
+				c.max_claim = max_claim;
+				c.curr_claim = curr_claim;
+				c.one_per_char = atoi(row[3]);
+				c.vet_reward_time = atoi(row[5]);
+				claim.push_back(c);
+				i++;
+			}
+			return claim;
+		}
+	}
+	return claim;
+}
+
+bool WorldDatabase::ClaimItem(int32 char_id, int32 item_id, Client* client) {
+
+	if (!client || !item_id || !char_id) {
+		return 0;
+	}
+
+	Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, max_claim, curr_claim, one_per_char, last_claim, veteran_reward_time from character_claim_items where char_id=%u and item_id=%u", char_id, item_id);
+
+	if (result)
+	{
+		if (mysql_num_rows(result) > 0)
+		{
+			while (result && (row = mysql_fetch_row(result)))
+			{
+				Item* item = master_item_list.GetItem(item_id);
+				Player* player = client->GetPlayer();
+				InfoStruct* info = player->GetInfoStruct();
+
+				int32 item_id = atoi(row[0]);
+				int8 max_claim = atoi(row[1]);
+				int8 curr_claim = atoi(row[2]);
+				int32 vet_reward_req = atoi(row[5]);
+
+				//no claims left.
+				if (curr_claim == 0) {
+					client->Message(CHANNEL_COLOR_RED, "You have nothing to claim.");
+					return 0;
+				}
+
+
+				//On live, characters must wait 6 seconds between claims. So..Lots of stuff here.
+				uint32 last_claim = info->get_last_claim_time(); //load our last claim
+				time_t now = time(0); //get the current epoc time
+				uint32 curr_time = now; // current time.
+				uint32 total_time = curr_time - last_claim; //Time since last claim (Current time - last claim)
+
+				if (total_time < 6) {
+					uint32 ttw = 6 - total_time;
+					char tmp[64] = { 0 };
+					sprintf(tmp, "You must wait %u more seconds.", ttw);
+					client->Message(CHANNEL_COLOR_RED, tmp);
+					return 0;
+				}
+
+				//remove 1 from claim
+				int8 tmp = curr_claim - 1;
+				Query query2;
+				query2.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET `curr_claim`=%u , `last_claim`=%u WHERE char_id=%i and item_id=%i", tmp, curr_time, char_id, item_id);
+				//give the item to the player, and update last claim time.
+				client->AddItem(item_id);
+				info->set_last_claim_time(curr_time);
+
+				char tmpx[256] = { 0 };
+				client->Message(CHANNEL_COLOR_YELLOW, "You have consumed the item."); //message from live.
+				sprintf(tmpx, "You have claimed your %s! Please look for the item in your inventory. ", item->name.c_str()); //live uses different messages for diff things. Will figure this out later.
+				client->Message(CHANNEL_COLOR_YELLOW, tmpx);
+				//reload the window to show new count.
+				client->ShowClaimWindow();
+				return 0;
+			}
+		}
+	}
+	return 0;
+}
+
+int32	WorldDatabase::GetAccountAge(int32 account_id) {
+	int32 acct_age = 0;
+	int32 acct_created = 0;
+	time_t now = time(0); //get the current epoc time
+	uint32 curr_time = now; // current time.
+
+	Query query;
+	//order by created date and grab the oldest one.
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT UNIX_TIMESTAMP(created_date) FROM characters WHERE account_id=%i ORDER BY created_date LIMIT 1", account_id);
+	if (result && mysql_num_rows(result) > 0) {
+		MYSQL_ROW row = mysql_fetch_row(result);
+		acct_created = atoi(row[0]);
+	}
+	if (!curr_time || !acct_created)
+		return 0;
+
+	acct_age = curr_time - acct_created;
+	return acct_age;
+}

+ 16 - 0
EQ2/source/WorldServer/WorldDatabase.h

@@ -125,6 +125,15 @@ struct StartingItem{
 	int16	count;
 };
 
+struct ClaimItems {
+	int32 char_id;
+	int32 item_id;
+	int8  max_claim;
+	int8  curr_claim;
+	int8  one_per_char;
+	int32 vet_reward_time;
+};
+
 class Bot;
 
 class WorldDatabase : public Database {
@@ -531,6 +540,13 @@ public:
 	void				LoadHOStarters();
 	void				LoadHOWheel();
 
+	/* Claim Items */
+	void				LoadClaimItems(int32 char_id);
+	int16				CountCharClaimItems(int32 char_id);
+	vector<ClaimItems>  LoadCharacterClaimItems(int32 char_id);
+	bool				ClaimItem(int32 char_id, int32 item_id, Client* client);
+	int32				GetAccountAge(int32 account_id);
+
 	/* Race Types */
 	void				LoadRaceTypes();
 

+ 48 - 21
EQ2/source/WorldServer/client.cpp

@@ -9503,36 +9503,63 @@ void Client::SetPendingGuildInvite(Guild* guild, Player* invited_by) {
 void Client::ShowClaimWindow() {
 	PacketStruct* packet = configReader.getStruct("WS_PromoFlagsDetails", GetVersion());
 	if (packet) {
-		map<int32, Item*>* items = &master_item_list.items;
-		map<int32, Item*>::iterator itr;
-		int32 i = 0;
-		if (items->size() > 10)
-			packet->setArrayLengthByName("num_claim_items", 10);
-		else
-			packet->setArrayLengthByName("num_claim_items", items->size());
-		for (itr = items->begin(); itr != items->end(); itr++) {
-			if (i == 10)
-				break;
-			Item* item = itr->second;
+
+		int16 loaded = database.CountCharClaimItems(GetCharacterID());
+		vector<ClaimItems> claim = database.LoadCharacterClaimItems(GetCharacterID());
+		int32 account_age = database.GetAccountAge(GetAccountID());
+
+		//not sure if there is a message or not, but adding this and a return, so if we have nothing do nothing rather than display an empty window.
+		if (loaded == 0 || claim.empty()) {
+			Message(CHANNEL_COLOR_RED, "You have nothing to claim.");
+			return;
+		}
+
+		packet->setArrayLengthByName("num_claim_items", loaded);
+
+		int j = 0; //use this to track skipped vet items.
+		for (int i = 0; i < claim.size(); i++)
+		{
+			if (j == claim.size()) {
+				Message(CHANNEL_COLOR_RED, "You have nothing to claim.");
+				return;
+			}
+
+			Item* item = master_item_list.GetItem(claim[i].item_id);
+			int16 claimed = 0;
+
+			if (claim[i].curr_claim < claim[i].max_claim) {
+				claimed = claim[i].max_claim - claim[i].curr_claim;
+			}
+			else {
+				claimed = 0;
+			}
+
+			//dont display vet rewards until they reach the age required
+			if (account_age < claim[i].vet_reward_time) {
+				j++;
+				continue;
+			}
+
 			packet->setArrayDataByName("id", i, i);
 			packet->setArrayDataByName("not_yet_claimed", 1, i);
-			packet->setArrayDataByName("num_remaining", 5, i);
-			packet->setArrayDataByName("one_per_character", 1, i);
-			packet->setArrayDataByName("claimed_on_this_char", 0, i);
+			packet->setArrayDataByName("num_remaining", claim[i].curr_claim, i);
+			packet->setArrayDataByName("one_per_character", claim[i].one_per_char, i);
+			packet->setArrayDataByName("claimed_on_this_char", claimed, i);
+			//packet->setArrayDataByName("unknown2", 1, 2); // 1:already claimed tab 2: broken commenting out until implemented.
 			packet->setArrayDataByName("item_name", item->name.c_str(), i);
-			packet->setArrayDataByName("text", "If you ever see this text, let Scatman know, thanks! :)", i);
-			packet->setArrayDataByName("category", "Scott's Shit", i);
+			packet->setArrayDataByName("text", "If you ever see this text, let a developer know!", i); //I've not seen this!
+			//packet->setArrayDataByName("category", "Scott's Shit", i); //devn00b: not using so commenting out, leaving in case we ever do implement categories.
 			packet->setArrayDataByName("icon", item->details.icon, i);
 			packet->setArrayDataByName("item_id", item->details.item_id, i);
-			i++;
+			j++;
 		}
-		packet->setDataByName("unknown3", 1);
-		QueuePacket(packet->serialize());
-		safe_delete(packet);
 	}
-
+	packet->setDataByName("unknown3", 1);
+	QueuePacket(packet->serialize());
+	safe_delete(packet);
 }
 
+
 void Client::ShowGuildSearchWindow() {
 	PacketStruct* packet = configReader.getStruct("WS_GuildRecruiting", GetVersion());
 	if (packet) {