eluna-scripts-ornfelt/lua/WowEmulationScriptPack/C++/Endurance/Endurance.cpp
2024-09-23 07:59:23 +02:00

487 lines
18 KiB
C++

#include "Endurance.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "ObjectExtension.h"
#include "ObjectExtension.cpp"
#include "WorldSession.h"
#include "DatabaseEnv.h"
#include "Map.h"
#include "Creature.h"
#include "GameEventCallbacks.h"
#include "GossipDef.h"
#include "GameTime.h"
#include "PersonalInstance\PersonalInstance.h"
#define CREATURE_ENTRY_TO_FIGHT 35465 // Zhaagrym -> should have diminishing returns enabled
#define CREATURE_FACTION_TO_FIGHT 189 // something that is neutral to everyone yet can eb attacked
#define EnduranceScalePerLevel 1.10f // 10% per level ? So it will take ages to fully scale to your potential. So what ?
#define CREATURE_DMG_BASE 10
#define CREATURE_HEALTH_BASE 8000
#define CREAURE_DMG_SCALE_SCALER 100000 // for the sake of float to into conversion. This will truncate precission
#define CREATURE_RESPAWN_COOLDOWN 2000 // do not allow macro to spam spawns
//static uint32 RandomEntry[] = { 46,126,127,171,285,422,456,458,513,515,517,544,545,548,578,732,735,747,1024,1083,1541,4359,8796,15356,15357,15359,15360,15668,17102,17190 };
bool CheckValidClientCommand(const char *cmsg, int32 type, const char * channel);
EnduranceStore::EnduranceStore(ObjectGuid pg, int32 pAccountID, int32 pClass)
{
PlayerGUID = pg;
AccountID = pAccountID;
PlayerClass = pClass;
HighestDefeated = 1;
LoadFromDB();
SpawnStamp = 0;
}
void EnduranceStore::LoadFromDB()
{
char Query[5000];
sprintf_s(Query, sizeof(Query), "SELECT MAX(HighestDefeated) FROM account_Endurance where CharId=%d", (uint32)PlayerGUID);
QueryResult result = CharacterDatabase.Query(Query);
if (result)
{
Field* fields = result->Fetch();
HighestDefeated = fields[0].GetUInt32();
}
}
void EnduranceStore::SaveToDB()
{
char Query[5000];
sprintf_s(Query, sizeof(Query), "REPLACE INTO account_Endurance (CharId, Classid, HighestDefeated, FightDuration) VALUES (%d, %d, %d, %d)", (uint32)PlayerGUID, PlayerClass, HighestDefeated, (uint32)(FightEndedAt - FightStartedAt));
CharacterDatabase.Execute(Query);
}
uint64 EnduranceStore::GetMSSincePrevSpawn()
{
return GameTime::GetGameTimeMS() - SpawnStamp;
}
void EnduranceStore::FightStarted(bool ForceReset, uint64 pCreatureGUID)
{
if(FightStartedAt == 0)
FightStartedAt = GameTime::GetGameTime();
if (ForceReset)
FightStartedAt = 0;
if (pCreatureGUID != 0)
CreatureGUID = pCreatureGUID;
}
void EnduranceStore::FightEnded()
{
FightEndedAt = GameTime::GetGameTime();
HighestDefeated++;
SaveToDB();
FightStartedAt = 0;
}
// 1.01 , 1.02, 1.03.....Level 10 means 10%, level 100 means 100% ?
// 1.05 , 1.10, 1.15.....Level 10 means 50%, level 100 means 500% ?
// 1.10 , 1.20, 1.30.....Level 10 means 10x, level 100 means 10x ?
float EnduranceStore::GetNextEndurancePCTScaler()
{
float ret = 1;
for (int32 i = 0; i < HighestDefeated; i++)
ret *= EnduranceScalePerLevel;
return ret;
}
EnduranceStore *GetES(Player *player)
{
EnduranceStore *pi = player->GetExtension<EnduranceStore>(OE_PLAYER_ENDURANCE_STATUS_STORE);
if (pi == NULL)
{
pi = new EnduranceStore(player->GetGUID(),player->GetSession()->GetAccountId(),player->getClass());
player->SetExtension<EnduranceStore>(OE_PLAYER_ENDURANCE_STATUS_STORE, pi);
}
if (pi == NULL)
{
printf("Error, could not allocate memory\n");
return NULL;
}
return pi;
}
bool IsEnduranceOngoind(Player *player)
{
EnduranceStore *pi = player->GetExtension<EnduranceStore>(OE_PLAYER_ENDURANCE_STATUS_STORE);
if (pi == NULL)
return false;
Creature *et = player->GetMap()->GetCreature(ObjectGuid(pi->GetCreatureGUID()));
if (et == NULL)
return false;
if (et->IsAlive() == false)
return false;
return true;
}
void OnEnduranceMobDmgReceived(void *p, void *)
{
CP_DMG_DONE_RECEIVED *params = PointerCast(CP_DMG_DONE_RECEIVED, p);
//in case this is called more than once
if (params->OriDamage == 0 || params->Attacker == NULL || params->Victim == NULL)
{
// printf("No attacker or victim\n");
return;
}
//is the attacker the one who created the endurance mob ? Or is the mob an endurance target ?
bool IsAttackerEnduranceMob = false;
Player *plr = params->Attacker->GetCharmerOrOwnerPlayerOrPlayerItself();
if (plr == NULL)
{
//if this is the mob attacking the player. Scale it's damage
int64 *Scaler10000 = params->Attacker->GetCreateIn64Extension(OE_CREAURE_ENDURANCE_SCALER, true);
if (Scaler10000 != NULL)
{
params->FlatMods += CREATURE_DMG_BASE * (*Scaler10000) / CREAURE_DMG_SCALE_SCALER;
// printf("Damage is scaled\n");
return;
}
else
{
// printf("Attacker is not a player or summon of the endurance mob. Exiting\n");
params->ForceDamageAbsorb();
return;
}
}
//if attacker player is not the one who created us. Deny dmg
int64 *MobSpawner = params->Victim->GetCreateIn64Extension(OE_CREAURE_ENDURANCE_OWNER);
if (MobSpawner == NULL || (int64)(plr->GetGUID().GetRawValue()) != *MobSpawner)
{
// printf("Mob is either not an endurance mob anymore or attacker is not the one who is doung endurance\n");
params->ForceDamageAbsorb();
return;
}
//mod damage based on map setting
if (params->Attacker->GetMap() != NULL)
{
int64 *InstanceCreator = params->Attacker->GetMap()->GetCreateIn64Extension(OE_MAP_IS_CUSTOM_INSTANCE_CREATOR, true);
if (InstanceCreator != NULL && (int64)(plr->GetGUID().GetRawValue()) != *InstanceCreator)
{
// printf("Instance is a custom map, but player is not the owner. Deny dmg\n");
params->ForceDamageAbsorb();
return;
}
}
//in case this is the first hit, mark it as the start of the fight
EnduranceStore *es = GetES(plr);
es->FightStarted();
// printf("Try to trigger fight on hit. Player hit the mob\n");
}
void ScaleEnduranceMobHealthOnSpawn(void *p, void *)
{
CP_CREATURE_SPAWN *params = PointerCast(CP_CREATURE_SPAWN, p);
//safety first
if (params->Spawn == NULL || params->map == NULL)
{
// printf("Invalid params. Early exiting\n");
return;
}
//get the creator of us
uint64 *MobSpawner = (uint64 *)params->Spawn->GetCreateIn64Extension(OE_CREAURE_ENDURANCE_OWNER,true);
if (MobSpawner == NULL)
{
// printf("ScaleEnduranceMobHealthOnSpawn: Mob is not an endurance mob anymore\n");
return;
}
Player *plr = params->map->GetPlayer(ObjectGuid(*MobSpawner));
if(plr==NULL)
{
// printf("Could not find the player who spawned us\n");
return;
}
//check hes endurance status
EnduranceStore *es = GetES(plr);
if ( es )
{
float Scaler = es->GetNextEndurancePCTScaler();
//set HP based on instance difficulty
uint32 MaxHealth = (uint32)( CREATURE_HEALTH_BASE * Scaler);
if (MaxHealth > 0x0FFFFFFF)
MaxHealth = 0x0FFFFFFF;
*params->Spawn->GetCreateExtension<uint32>(OE_CREATURE_ENDURANCE_HEALTH) = MaxHealth;
params->Spawn->SetCreateHealth(MaxHealth);
params->Spawn->SetMaxHealth(MaxHealth);
params->Spawn->SetHealth(MaxHealth);
//always have level of player to reduce hit / miss chances
params->Spawn->SetLevel( plr->getLevel() );
//scale the dmg also
*params->Spawn->GetCreateIn64Extension(OE_CREAURE_ENDURANCE_SCALER, false, (int64)(Scaler*CREAURE_DMG_SCALE_SCALER));
// printf("Scaled mob health,lvl,dmg based on endurance level\n");
}
//make them run fast to catch up with ranged kiting players
params->Spawn->SetSpeed(UnitMoveType::MOVE_RUN, plr->GetSpeed(UnitMoveType::MOVE_RUN) * 2.0f);
params->Spawn->SetSpeed(UnitMoveType::MOVE_WALK, plr->GetSpeed(UnitMoveType::MOVE_WALK) * 2.0f);
//allow it to fly
params->Spawn->SetCanFly(true);
params->Spawn->SetWaterWalking(true);
params->Spawn->SetHover(true);
params->Spawn->SetSwim(true);
//make him PVP enabled
params->Spawn->SetPvP(true);
//disable agro range
params->Spawn->SetFaction(CREATURE_FACTION_TO_FIGHT);
//it would be enough to have diminishing return on these
/* {
static uint32 const placeholderSpellId = std::numeric_limits<uint32>::max();
for (uint32 i = MECHANIC_NONE + 1; i < MAX_MECHANIC; ++i)
params->Spawn->ApplySpellImmune(placeholderSpellId, IMMUNITY_MECHANIC, i, true);
}/**/
//instead full immunity, grant him the ability for diminishing returns
// params->Spawn->GetCreatureTemplate()->flags_extra |= CREATURE_FLAG_EXTRA_ALL_DIMINISH;
}
void EnduranceStore::EventSpawnedCreature(Creature *c)
{
SpawnStamp = GameTime::GetGameTimeMS();
}
void SpawnEnduranceTarget(Player *player)
{
//if player is not ingame than abandon the plan
if (player->FindMap() == NULL)
{
return;
}
//it needs to be a custom instance to summon endurance mode
if (player->GetMap()->GetExtension<int64>(OE_MAP_IS_CUSTOM_INSTANCE_CREATOR) == NULL)
{
player->BroadcastMessage("This command can only be used in your own instance");
return;
}
//it needs to be our own custom instance to summon endurance mode
if (*player->GetMap()->GetCreateIn64Extension(OE_MAP_IS_CUSTOM_INSTANCE_CREATOR) != player->GetGUID().GetRawValue())
{
player->BroadcastMessage("This command can only be used in your own instance");
return;
}
if (player->GetMap()->GetPlayers().getSize() != 1)
{
player->BroadcastMessage("Too many players present on the map to start endurance");
return;
}
EnduranceStore *es = GetES(player);
//if there is already a spawn, try to remove it from the map
if (es->GetCreatureGUID())
{
Creature *c = player->GetMap()->GetCreature(ObjectGuid(es->GetCreatureGUID()));
if (c)
{
if (es->GetMSSincePrevSpawn() < CREATURE_RESPAWN_COOLDOWN)
return; //pretend nothing happened if player is trying to crash the server with a macro
else
player->GetMap()->AddObjectToRemoveList(c);
}
}
//uint32 ListSize = sizeof(RandomEntry) / sizeof(uint32);
//uint32 RandomIndex = rand32() % ListSize;
//spawn monster
Creature* creature = new Creature();
if (!creature->Create(player->GetMap()->GenerateLowGuid<HighGuid::Unit>(), player->GetMap(), player->GetPhaseMaskForSpawn(), CREATURE_ENTRY_TO_FIGHT, *player, NULL, 0, true))
//if (!creature->Create(player->GetMap()->GenerateLowGuid<HighGuid::Unit>(), player->GetMap(), player->GetPhaseMaskForSpawn(), RandomEntry[RandomIndex], *player, NULL, 0, true))
{
delete creature;
player->BroadcastMessage("Could not spawn creature");
return;
}
//handle internal states
es->EventSpawnedCreature(creature);
//mark for this player that our endurance mob exists
creature->GetCreateIn64Extension(OE_CREAURE_ENDURANCE_OWNER, false, player->GetGUID().GetRawValue());
//scale mob hp
//TriggerCallbackFunctions(CALLBACK_TYPE_CREATURE_SPAWN, &CP_CREATURE_SPAWN(creature, player->GetMap()));
//try tp push it to the map
if (!player->GetMap()->AddToMap(creature))
{
delete creature;
player->BroadcastMessage("Could not spawn creature");
return;
}
creature->SetHomePosition(creature->GetPosition());
//register dmg monitor callback. Deny all dmg not comming from player who spawned us. Not like we
creature->RegisterCallbackFunc(CALLBACK_TYPE_DMG_DONE, &OnEnduranceMobDmgReceived);
creature->RegisterCallbackFunc(CALLBACK_TYPE_DMG_RECEIVED, &OnEnduranceMobDmgReceived);
player->BroadcastMessage("Spawned endurance target %d. You need to be alone in the instance or you will not get your score recorded", es->GetBestLevel());
// force the fight timer to reset
es->FightStarted(true, creature->GetGUID().GetRawValue());
}
class TC_GAME_API EnduranceChatListnerScript : public PlayerScript
{
public:
EnduranceChatListnerScript() : PlayerScript("EnduranceChatListnerScript") {}
void OnCreatureKill(Player* killer, Creature* killed)
{
// printf("Player killed a creature\n");
if (killed == NULL)
{
// printf("Killer is missing\n");
return;
}
if (killed->GetExtension<int64>(OE_CREAURE_ENDURANCE_OWNER) == NULL)
{
// printf("Mob is not endurance mob\n");
return;
}
if (*killed->GetCreateIn64Extension(OE_CREAURE_ENDURANCE_OWNER) != (int64)killer->GetGUID().GetRawValue())
{
// printf("Endurance mob owner is not the killer\n");
return;
}
if (killer == NULL)
{
// printf("Killer is null\n");
return;
}
if (killed->GetThreatManager().GetThreatListSize() > 2)
{
// printf("More than 2 attackers. Consider this an unfair fight\n");
return;
}
//enslaved creature ?
if (killed->GetOwner() != NULL)
{
killer->BroadcastMessage("Endurance:Something went fishy. No score awarded. Error 1");
return;
}
//maybe other ways to seduce it ?
if (killed->GetFaction() != CREATURE_FACTION_TO_FIGHT)
{
killer->BroadcastMessage("Endurance:Something went fishy. No score awarded. Error 2");
return;
}
if (*killed->GetCreateExtension<uint32>(OE_CREATURE_ENDURANCE_HEALTH) > killed->GetMaxHealth())
{
killer->BroadcastMessage("Endurance:Something went fishy. No score awarded. Error 3");
return;
}
if(killer->GetMap()->GetPlayersCountExceptGMs() > 1)
{
killer->BroadcastMessage("Endurance:Something went fishy. No score awarded. Error 4");
return;
}
EnduranceStore *es = GetES(killer);
if (es->GetCreatureGUID() != killed->GetGUID().GetRawValue())
{
// printf("Not the same endurance creature we spawned earlier\n");
return;
}
// printf("Mark end of the endurance fight\n");
killer->BroadcastMessage("Congratulations on killing endurance target level %d", es->GetBestLevel());
es->FightEnded();
killer->ModifyMoney((int32)(es->GetBestLevel() + 1) * 50000);
killer->BroadcastMessage("You won %d Gold, keep going!", (int32)((es->GetBestLevel() + 1))*5);
}
void OnPlayerKilledByCreature(Creature* killer, Player* killed)
{
if (killer->GetExtension<int64>(OE_CREAURE_ENDURANCE_OWNER) != NULL)
{
//destoying object. Eeeeek
killer->AddObjectToRemoveList();
return;
}
}
//when we teleport to the map, we will spawn an NPC that you can interract with. He will help you spawn endurance targets ( just in case you did not know about the command )
void OnMapChanged(Player* player)
{
//sanity checks
if (player == NULL || player->FindMap() == NULL)
return;
//we will only spawn this creature in personal instances
if (player->GetMap()->GetExtension<int64>(OE_MAP_IS_CUSTOM_INSTANCE) == NULL)
return;
//in theory there is already an assistant waiting for us that has the endurance menu
}
void OnDelete(ObjectGuid guid, uint32 accountId)
{
char Query[5000];
sprintf_s(Query, sizeof(Query), "delete from account_endurance where CharId=%d", (uint32)guid.GetRawValue());
CharacterDatabase.Execute(Query);
}
};
void RBAC_StartEndurance(Player* player) {
if(player->GetMap()->GetId() == ENDURANCE_MAP)
SpawnEnduranceTarget(player);
else
player->BroadcastMessage("You may not start Endurance here, first enter Endurance Map.");
}
void EnParseClientUserCommand(Player* player, uint32 type, const char *cmsg)
{
// printf("got command %s\n", cmsg);
if (CheckValidClientCommand(cmsg, type, NULL) == false)
{
return;
}
if (player->GetGroup())
{
player->BroadcastMessage("You can not use this command while in a group");
return;
}
//do we want to set the difficulty ?
if (strstr(cmsg, "#csStartEndurance") == cmsg)
{
SpawnEnduranceTarget(player);
}
}
void EnOnChatMessageReceived(void *p, void *)
{
CP_CHAT_RECEIVED *params = PointerCast(CP_CHAT_RECEIVED, p);
//check for strings that might be our commands
EnParseClientUserCommand(params->SenderPlayer, params->MsgType, params->Msg->c_str());
}
void AddEnduranceScripts()
{
//store endurance state, in case server restarts
//CREATE TABLE `account_endurance` (`AcctId` INT NULL, `CharId` INT NULL, `ClassId` INT NULL, `HighestDefeated` INT NULL, `FightDuration` INT NULL);
//ALTER TABLE `account_endurance` ADD INDEX `AcctId` (`AcctId`, `CharId`) USING BTREE;
//CREATE UNIQUE INDEX relation ON account_endurance(AcctId, CharId);
//alter table `account_endurance` drop column `AcctId`,change `CharId` `CharId` int(12) UNSIGNED NOT NULL, change `Classid` `Classid` int(12) UNSIGNED NULL , change `HighestDefeated` `HighestDefeated` int(12) UNSIGNED NULL , change `FightDuration` `FightDuration` int(12) UNSIGNED NULL , add primary key(`CharId`)
//some creature that anyone can attack. Murlocks ?
//make the NPC be able to fly to the player attacking it
//insert into creature_template_movement values(35465,1,1,1,0);
//make the NPC attack from far to avoid people abusing terrain can't reach issues
//update creature_model_info set CombatReach=15 where displayid=1913
new EnduranceChatListnerScript();
RegisterCallbackFunction(CALLBACK_TYPE_CREATURE_SPAWN, ScaleEnduranceMobHealthOnSpawn, NULL);
RegisterCallbackFunction(CALLBACK_TYPE_CHAT_RECEIVED, EnOnChatMessageReceived, NULL);
}