Page 1 of 2

PVP

Posted: Wed May 12, 2010 3:11 am
by bolly
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

Re: PVP

Posted: Wed May 12, 2010 6:29 am
by Eradani
cool

T1 Wardens are OP and T1 SKs suck
hehe

Re: PVP

Posted: Wed May 12, 2010 7:11 am
by John Adams
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.

Re: PVP

Posted: Wed May 12, 2010 12:07 pm
by bolly
okies, no rush, i'm wondering whether its related to the spell type

Re: PVP

Posted: Wed May 12, 2010 2:51 pm
by Scatman
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.

Re: PVP

Posted: Thu May 13, 2010 12:44 am
by bolly
<3!!!

Re: PVP

Posted: Thu May 13, 2010 3:55 am
by bolly
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);
	}
}

Re: PVP

Posted: Thu May 13, 2010 6:16 am
by bolly
go for an eqemu approach and use servertype?

I'm thinking:

0 - pve
1 - ffa pvp
2 - team pvp

Re: PVP

Posted: Thu May 13, 2010 6:45 am
by John Adams
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.

Re: PVP

Posted: Fri May 14, 2010 10:16 am
by bolly
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);
	}
}

Re: PVP

Posted: Fri May 14, 2010 10:41 am
by John Adams
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.

Re: PVP

Posted: Fri May 14, 2010 11:15 am
by bolly
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]));

Re: PVP

Posted: Fri May 14, 2010 11:17 am
by John Adams
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.

Re: PVP

Posted: Fri May 14, 2010 11:22 am
by bolly
Using my own svn but I will run bash's diff against it 2 secs bud. I posted that before I read your reply!

Re: PVP

Posted: Fri May 14, 2010 11:45 am
by John Adams
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.