PVP

EQ2Emulator Development forum.

Moderator: Team Members

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

PVP

Post by bolly » Wed May 12, 2010 3:11 am

Hey,

Since there is 1-20 content i've decided to throw up a level 1-20 pvp server. I've got the combat working by commenting out the IsPlayer() checks in attackallowed but I'm not sure where spells decide if it's a player or not.

Anyone know the function involved?

Cheers

Mix

User avatar
Eradani
Posts: 192
Joined: Wed May 05, 2010 6:25 am
Location: Saskatchewan

Re: PVP

Post by Eradani » Wed May 12, 2010 6:29 am

cool

T1 Wardens are OP and T1 SKs suck
hehe
my client version: 12682L, 2016/06/06
last one that will run on XP cause i'm just a stick-in-the-mud

User avatar
John Adams
Retired
Posts: 9684
Joined: Thu Jul 26, 2007 6:27 am
EQ2Emu Server: EQ2Emulator Test Center
Characters: John
Location: Arizona
Contact:

Re: PVP

Post by John Adams » Wed May 12, 2010 7:11 am

I probably don't have to say this, but the code is nowhere near ready to support PVP, not properly. Sure you can slash at a player and cast a spell on them... but I don't think that's all there is to proper PVP.

Far as I know, the code decides if it's a player the same way it would decide if you can be attacked at all (not an enemy, or you cannot attack that?) maybe. Scat is gone for the week, so maybe when he gets back he can answer. Not sure if Zcoretri has messed with spells at a C++ level yet.

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

Re: PVP

Post by bolly » Wed May 12, 2010 12:07 pm

okies, no rush, i'm wondering whether its related to the spell type

User avatar
Scatman
Retired
Posts: 1688
Joined: Wed Apr 16, 2008 5:44 am
EQ2Emu Server: Scatman's Word
Characters: Scatman
Location: New Jersey

Re: PVP

Post by Scatman » Wed May 12, 2010 2:51 pm

I don't officially leave until tomorrow but I'm at a friend's now in Philly since we're leaving from philly airport. Spells and melee are different. Sounds like you got melee under control but spells would be in SpellProcess::ProcessSpell I think it is if I remember correctly. The logic for spells is much more advanced than melee but if you fool around with it you should be able to get it. Like John said, the server has 0 pvp support so you may be one-shotting players, or maybe doing 1 dmg, not sure :P May be interesting to find out.

Oh, also check out SpellProcess::GetSpellTargets. I can more easily guide you when I get back.

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

Re: PVP

Post by bolly » Thu May 13, 2010 12:44 am

<3!!!

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

Re: PVP

Post by bolly » Thu May 13, 2010 3:55 am

Woop, found it in SpellProcess like you said!

I guess I need to do the racial checks now and also remove exp for pvp :-) But this should be fun for now!

One thing I did wonder about was having pvp as an option in the variables table and wrapping an if around these checks - will need to find where you are doing that atm and pop that in!

Code: Select all

bool Combat::AttackAllowed(Entity* attacker, Spawn* victim, bool calculate_distance, bool range_attack){
	if(!attacker || !victim || attacker->IsMezzedOrStunned() || attacker->IsDazed())
		return false;
	/* bolly - for pvp
	if((attacker->IsPlayer() && victim->appearance.attackable == 0) || (attacker->IsPlayer() && victim->IsPlayer()))
		return false;
		*/
	else if(victim->GetHP() == 0)
		return false;
	else if(calculate_distance){
		float distance = attacker->GetDistance(victim);
		distance -= victim->appearance.pos.collision_radius/10;
		distance -= attacker->appearance.pos.collision_radius/10;
		if(range_attack){
			Item* weapon = 0;
			Item* ammo = 0;
			if(attacker->IsPlayer()){
				weapon = ((Player*)attacker)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT);
				ammo = ((Player*)attacker)->GetEquipmentList()->GetItem(EQ2_AMMO_SLOT);
			}
			if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()){		
				if(weapon->ranged_info->range_low <= distance && (weapon->ranged_info->range_high + ammo->thrown_info->range) >= distance)
					return true;
			}
		}
		else{
			if(distance <= MAX_COMBAT_RANGE)
				return true;
		}
	}
	//todo: add more calculations
	return true;
}

Code: Select all

void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell){
	if(spell && caster){
		Client* client = 0;
		int8 target_type = spell->GetSpellData()->target_type;
		if(caster->IsPlayer() && zone)
			client = zone->GetClientBySpawn(caster);
		if (caster->IsMezzedOrStunned() || caster->IsStifled()) {
			zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST);
			return;
		}
		if(caster->IsPlayer() && !IsReady(spell, caster)){
			CheckSpellQueue(spell, caster);
			return;
		}
		if (target_type != SPELL_TARGET_SELF && target_type != SPELL_TARGET_GROUP_AE && target_type != SPELL_TARGET_NONE && spell->GetSpellData()->max_aoe_targets == 0){
			if (!target) {
				zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
				return;
			}
			if(caster->GetDistance(target) > spell->GetSpellData()->range) {
				zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_FAR_AWAY);
				return;
			}
			if (caster->GetDistance(target) < spell->GetSpellData()->min_range) {
				zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_CLOSE);
				return;
			}
		}
		if(target_type == SPELL_TARGET_SELF && spell->GetSpellData()->max_aoe_targets == 0) {
			if (harvest_spell) {
				if (!target || !target->IsGroundSpawn()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
					return;
				}
			}
			else
				target = caster;
		}
		if (target_type == SPELL_TARGET_ENEMY && spell->GetSpellData()->max_aoe_targets == 0) {
			if (spell->GetSpellData()->friendly_spell) {
				if (!target->IsEntity()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
					return;
				}
				if (target->appearance.attackable) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND);
					return;
				}
			}
			else {
				if (!target->IsEntity()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
					return;
				}
				if (caster == target || (!target->IsPlayer() && !target->appearance.attackable)) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
					return;
				}
				if (!target->Alive()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ALIVE);
					return;
				}
				if (target->GetInvulnerable()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_TARGET_INVULNERABLE);
					return;
				}
				/* bolly - pvp
				if (target->IsPlayer() && caster->IsPlayer()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
					return;
				}
				*/
			}
		}
		LuaSpell* lua_spell = 0;
		if(lua_interface)
			lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str());
		if(!lua_spell){
			string lua_script = spell->GetSpellData()->lua_script;
			lua_script.append(".lua");
			lua_spell = 0;
			if(lua_interface)
				lua_spell = lua_interface->GetSpell(lua_script.c_str());
			if(!lua_spell)
				return;
			else
				spell->GetSpellData()->lua_script = lua_script;
		}
		lua_spell->caster = caster;
		lua_spell->spell = spell;
		lua_spell->initial_target = target;
		GetSpellTargets(lua_spell);
		if (lua_spell->targets.size() == 0 && spell->GetSpellData()->max_aoe_targets == 0) {
			LogFile->write(EQEMuLog::Error, "SpellProcess::ProcessSpell Unable to find any spell targets for spell '%s'.", spell->GetName());
			safe_delete(lua_spell);
			return;
		}
		if (spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) {
			bool ret_val = DeleteCasterSpell(lua_spell->caster, spell);
			if (ret_val) {
				int8 actual_concentration = spell->GetSpellData()->req_concentration / 256;
				if (actual_concentration > 0) {
					caster->GetInfoStruct()->cur_concentration -= actual_concentration;
					if (caster->IsPlayer())
						caster->GetZone()->TriggerCharSheetTimer();
				}
				CheckRecast(spell, caster);
				SendSpellBookUpdate(client);
				safe_delete(lua_spell);
				return;
			}
		}
		if(!CheckPower(lua_spell)) {
			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_POWER);
			safe_delete(lua_spell);
			return;
		}
		if (!CheckHP(lua_spell)) { 
			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_HEALTH);
			safe_delete(lua_spell);
			return; 
		}
		if (!CheckConcentration(lua_spell)) {
			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_CONC);
			safe_delete(lua_spell);
			return;
		}
		LockAllSpells(client);
		SendStartCast(lua_spell, client);
		if(spell->GetSpellData()->cast_time > 0){
			CastTimer* cast_timer = new CastTimer;
			cast_timer->entity_command = 0;
			cast_timer->spell = lua_spell;
			cast_timer->spell->caster = caster;
			cast_timer->delete_timer = false;
			cast_timer->timer = new Timer(spell->GetSpellData()->cast_time*10);
			cast_timer->zone = zone;
			cast_timers.Add(cast_timer);
			if(caster)
				caster->IsCasting(true);
		}
		else{
			if(!CastProcessedSpell(lua_spell)) {
				safe_delete(lua_spell);
				return;
			}
		}
		if(caster)
			caster->GetZone()->SendCastSpellPacket(lua_spell, caster);
	}
}

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

Re: PVP

Post by bolly » Thu May 13, 2010 6:16 am

go for an eqemu approach and use servertype?

I'm thinking:

0 - pve
1 - ffa pvp
2 - team pvp

User avatar
John Adams
Retired
Posts: 9684
Joined: Thu Jul 26, 2007 6:27 am
EQ2Emu Server: EQ2Emulator Test Center
Characters: John
Location: Arizona
Contact:

Re: PVP

Post by John Adams » Thu May 13, 2010 6:45 am

Yeah you can do that for now if you like. My long-term plan is for a Rules System like EQEmu has implemented. I think that'll be the easier solution down the road.

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

Re: PVP

Post by bolly » Fri May 14, 2010 10:16 am

Ok added the variable config - there wasn't a GetStartingCity/SetStartingCity so i added those to playerinfo and also WorldDatabase::loadCharacter but they are not provided here

Melee Checks

Code: Select all

bool Combat::AttackAllowed(Entity* attacker, Spawn* victim, bool calculate_distance, bool range_attack){
	Variable* var = variables.FindVariable("server_type");
	if (var)
	{
		sint16 ServerType = atoi(var->GetValue());
		switch(ServerType)
		{
			case 2:
				// can only attack different starting city players
				if (attacker->IsPlayer() && victim->IsPlayer())
				{
					if (((Player*)attacker)->GetPlayerInfo()->GetStartingCity() == ((Player*)victim)->GetPlayerInfo()->GetStartingCity())
					{
						// same starting city so can't attack
						return false;
					}
				}
				break;
			case 1:
				// can only attack diff race
				if (attacker->IsPlayer() && victim->IsPlayer() && (attacker->GetRace() == victim->GetRace()))
				{
					return false;
				}
				break;
				// normal operation
			default:
				if((attacker->IsPlayer() && victim->appearance.attackable == 0) || (attacker->IsPlayer() && victim->IsPlayer()))
				return false;
		}
	} else {
		// normal operation (variable table entry missing)
		if((attacker->IsPlayer() && victim->appearance.attackable == 0) || (attacker->IsPlayer() && victim->IsPlayer()))
			return false;
	}
	
	if(!attacker || !victim || attacker->IsMezzedOrStunned() || attacker->IsDazed())
		return false;
	else if(victim->GetHP() == 0)
		return false;
	else if(calculate_distance){
		float distance = attacker->GetDistance(victim);
		distance -= victim->appearance.pos.collision_radius/10;
		distance -= attacker->appearance.pos.collision_radius/10;
		if(range_attack){
			Item* weapon = 0;
			Item* ammo = 0;
			if(attacker->IsPlayer()){
				weapon = ((Player*)attacker)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT);
				ammo = ((Player*)attacker)->GetEquipmentList()->GetItem(EQ2_AMMO_SLOT);
			}
			if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()){		
				if(weapon->ranged_info->range_low <= distance && (weapon->ranged_info->range_high + ammo->thrown_info->range) >= distance)
					return true;
			}
		}
		else{
			if(distance <= MAX_COMBAT_RANGE)
				return true;
		}
	}
	//todo: add more calculations
	return true;
}
Spell Checks

Code: Select all

void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell){
	if(spell && caster){
		Client* client = 0;
		int8 target_type = spell->GetSpellData()->target_type;
		if(caster->IsPlayer() && zone)
			client = zone->GetClientBySpawn(caster);
		if (caster->IsMezzedOrStunned() || caster->IsStifled()) {
			zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST);
			return;
		}

		if(caster->IsPlayer() && !IsReady(spell, caster)){
			CheckSpellQueue(spell, caster);
			return;
		}
		if (target_type != SPELL_TARGET_SELF && target_type != SPELL_TARGET_GROUP_AE && target_type != SPELL_TARGET_NONE && spell->GetSpellData()->max_aoe_targets == 0){
			if (!target) {
				zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
				return;
			}
			if(caster->GetDistance(target) > spell->GetSpellData()->range) {
				zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_FAR_AWAY);
				return;
			}
			if (caster->GetDistance(target) < spell->GetSpellData()->min_range) {
				zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_CLOSE);
				return;
			}
		}
		if(target_type == SPELL_TARGET_SELF && spell->GetSpellData()->max_aoe_targets == 0) {
			if (harvest_spell) {
				if (!target || !target->IsGroundSpawn()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
					return;
				}
			}
			else
				target = caster;
		}
		if (target_type == SPELL_TARGET_ENEMY && spell->GetSpellData()->max_aoe_targets == 0) {
			if (spell->GetSpellData()->friendly_spell) {
				if (!target->IsEntity()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
					return;
				}
				if (target->appearance.attackable) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND);
					return;
				}
			}
			else {
				if (!target->IsEntity()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET);
					return;
				}
				if (caster == target || (!target->IsPlayer() && !target->appearance.attackable)) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
					return;
				}
				if (!target->Alive()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ALIVE);
					return;
				}
				if (target->GetInvulnerable()) {
					zone->SendSpellFailedPacket(client, SPELL_ERROR_TARGET_INVULNERABLE);
					return;
				}
								
				Variable* var = variables.FindVariable("server_type");
				if (var)
				{
					sint16 ServerType = atoi(var->GetValue());
					switch(ServerType)
					{
						case 2:
							// can only attack different starting city players
							if (caster->IsPlayer() && target->IsPlayer())
							{
								if (((Player*)caster)->GetPlayerInfo()->GetStartingCity() == ((Player*)target)->GetPlayerInfo()->GetStartingCity())
								{
									// same starting city so can't attack
									return;
								}
							}
							break;
						case 1:
							// can only attack diff race
							if (caster->IsPlayer() && target->IsPlayer() && (caster->GetRace() == target->GetRace()))
							{
								return;
							}
							break;
							// normal operation
						default:
							// normal operation (variable table entry missing)
							if (target->IsPlayer() && caster->IsPlayer()) {
								zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
								return;
							}
					}
				} else {
					// normal operation (variable table entry missing)
					if (target->IsPlayer() && caster->IsPlayer()) {
						zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY);
						return;
					}
				}
			}
		}
		LuaSpell* lua_spell = 0;
		if(lua_interface)
			lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str());
		if(!lua_spell){
			string lua_script = spell->GetSpellData()->lua_script;
			lua_script.append(".lua");
			lua_spell = 0;
			if(lua_interface)
				lua_spell = lua_interface->GetSpell(lua_script.c_str());
			if(!lua_spell)
				return;
			else
				spell->GetSpellData()->lua_script = lua_script;
		}
		lua_spell->caster = caster;
		lua_spell->spell = spell;
		lua_spell->initial_target = target;
		GetSpellTargets(lua_spell);
		if (lua_spell->targets.size() == 0 && spell->GetSpellData()->max_aoe_targets == 0) {
			LogFile->write(EQEMuLog::Error, "SpellProcess::ProcessSpell Unable to find any spell targets for spell '%s'.", spell->GetName());
			safe_delete(lua_spell);
			return;
		}
		if (spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) {
			bool ret_val = DeleteCasterSpell(lua_spell->caster, spell);
			if (ret_val) {
				int8 actual_concentration = spell->GetSpellData()->req_concentration / 256;
				if (actual_concentration > 0) {
					caster->GetInfoStruct()->cur_concentration -= actual_concentration;
					if (caster->IsPlayer())
						caster->GetZone()->TriggerCharSheetTimer();
				}
				CheckRecast(spell, caster);
				SendSpellBookUpdate(client);
				safe_delete(lua_spell);
				return;
			}
		}
		if(!CheckPower(lua_spell)) {
			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_POWER);
			safe_delete(lua_spell);
			return;
		}
		if (!CheckHP(lua_spell)) { 
			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_HEALTH);
			safe_delete(lua_spell);
			return; 
		}
		if (!CheckConcentration(lua_spell)) {
			zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_CONC);
			safe_delete(lua_spell);
			return;
		}
		LockAllSpells(client);
		SendStartCast(lua_spell, client);
		if(spell->GetSpellData()->cast_time > 0){
			CastTimer* cast_timer = new CastTimer;
			cast_timer->entity_command = 0;
			cast_timer->spell = lua_spell;
			cast_timer->spell->caster = caster;
			cast_timer->delete_timer = false;
			cast_timer->timer = new Timer(spell->GetSpellData()->cast_time*10);
			cast_timer->zone = zone;
			cast_timers.Add(cast_timer);
			if(caster)
				caster->IsCasting(true);
		}
		else{
			if(!CastProcessedSpell(lua_spell)) {
				safe_delete(lua_spell);
				return;
			}
		}
		if(caster)
			caster->GetZone()->SendCastSpellPacket(lua_spell, caster);
	}
}

User avatar
John Adams
Retired
Posts: 9684
Joined: Thu Jul 26, 2007 6:27 am
EQ2Emu Server: EQ2Emulator Test Center
Characters: John
Location: Arizona
Contact:

Re: PVP

Post by John Adams » Fri May 14, 2010 10:41 am

If this is something you want committed to the base code, please provide a DIFF file with your changes, and we'll test it out and commit.

If you're interested in becoming a C++ developer "officially" ;) let me know that, too.

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

Re: PVP

Post by bolly » Fri May 14, 2010 11:15 am

add starting_city to PlayerInfo::PlayerInfo(Player* in_player)

Player.h (playerinfo)
void SetStartingCity(int32 id);
int32 GetStartingCity();

Player.cpp (playerinfo)
int32 PlayerInfo::GetStartingCity(){
return starting_city;
}

void PlayerInfo::SetStartingCity(int32 id)
{
starting_city = id;
}

WorldDatabase.cpp (WorldDatabase::loadCharacter)
change query to
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, current_zone_id, x, y, z, heading, admin_status, race, model_type, class, deity, level, gender, tradeskill_level, wing_type, hair_type, chest_type, legs_type, soga_wing_type, soga_hair_type, soga_chest_type, soga_legs_type, 0xFFFFFFFF - crc32(name), facial_hair_type, soga_facial_hair_type,instance_id,last_saved, DATEDIFF(curdate(), created_date) as accage, starting_city from characters where name='%s' and account_id=%i", query.escaped_name, account_id);
and add
client->GetPlayer()->GetPlayerInfo()->SetStartingCity(atoi(row[28]));

User avatar
John Adams
Retired
Posts: 9684
Joined: Thu Jul 26, 2007 6:27 am
EQ2Emu Server: EQ2Emulator Test Center
Characters: John
Location: Arizona
Contact:

Re: PVP

Post by John Adams » Fri May 14, 2010 11:17 am

Sorry, maybe I should have asked first, do you know what a "diff" file is? Using TortoiseSVN, you can right-click on the World folder, and "Create Patch", and it will compare your work with SVN's current, making a "differential" file with precise changes.

That's what I'm after, so we do not copy/paste or fat-finger code changes by pulling them off a forum post.

bolly
Retired
Posts: 389
Joined: Mon Sep 21, 2009 3:03 pm
Location: Leeds, UK

Re: PVP

Post by bolly » Fri May 14, 2010 11:22 am

Using my own svn but I will run bash's diff against it 2 secs bud. I posted that before I read your reply!

User avatar
John Adams
Retired
Posts: 9684
Joined: Thu Jul 26, 2007 6:27 am
EQ2Emu Server: EQ2Emulator Test Center
Characters: John
Location: Arizona
Contact:

Re: PVP

Post by John Adams » Fri May 14, 2010 11:45 am

Hmm, your own SVN? Is it current with ours? We'll have a look, but that might present other problems since your Rev will not match ours. Post it, we'll see how it goes. At least we can see the exact changes that way clearly.

Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests