diff --git a/common/string_util.cpp b/common/string_util.cpp index df3790def..6a4555ccf 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -527,3 +527,51 @@ bool isAlphaNumeric(const char *text) return true; } + +// Function to convert single digit or two digit number into words +std::string convert2digit(int n, std::string suffix) +{ + // if n is zero + if (n == 0) { + return ""; + } + + // split n if it is more than 19 + if (n > 19) { + return NUM_TO_ENGLISH_Y[n / 10] + NUM_TO_ENGLISH_X[n % 10] + suffix; + } + else { + return NUM_TO_ENGLISH_X[n] + suffix; + } +} + +// Function to convert a given number (max 9-digits) into words +std::string numberToWords(unsigned long long int n) +{ + // string to store word representation of given number + std::string res; + + // this handles digits at ones & tens place + res = convert2digit((n % 100), ""); + + if (n > 100 && n % 100) { + res = "and " + res; + } + + // this handles digit at hundreds place + res = convert2digit(((n / 100) % 10), "Hundred ") + res; + + // this handles digits at thousands & tens thousands place + res = convert2digit(((n / 1000) % 100), "Thousand ") + res; + + // this handles digits at hundred thousands & one millions place + res = convert2digit(((n / 100000) % 100), "Lakh, ") + res; + + // this handles digits at ten millions & hundred millions place + res = convert2digit((n / 10000000) % 100, "Crore, ") + res; + + // this handles digits at ten millions & hundred millions place + res = convert2digit((n / 1000000000) % 100, "Billion, ") + res; + + return res; +} diff --git a/common/string_util.h b/common/string_util.h index 037d6a2d8..3fb5e3ec9 100644 --- a/common/string_util.h +++ b/common/string_util.h @@ -43,6 +43,20 @@ std::vector split(std::string str_to_split, char delimiter); const std::string StringFormat(const char* format, ...); const std::string vStringFormat(const char* format, va_list args); std::string implode(std::string glue, std::vector src); +std::string convert2digit(int n, std::string suffix); +std::string numberToWords(unsigned long long int n); + +// For converstion of numerics into English +// Used for grid nodes, as NPC names remove numerals. +// But general purpose + +const std::string NUM_TO_ENGLISH_X[] = { "", "One ", "Two ", "Three ", "Four ", + "Five ", "Six ", "Seven ", "Eight ", "Nine ", "Ten ", "Eleven ", + "Twelve ", "Thirteen ", "Fourteen ", "Fifteen ", + "Sixteen ", "Seventeen ", "Eighteen ", "Nineteen " }; + +const std::string NUM_TO_ENGLISH_Y[] = { "", "", "Twenty ", "Thirty ", "Forty ", + "Fifty ", "Sixty ", "Seventy ", "Eighty ", "Ninety " }; /** * @param str @@ -189,5 +203,8 @@ uint32 hextoi(const char* num); uint64 hextoi64(const char* num); void MakeLowerString(const char *source, char *target); void RemoveApostrophes(std::string &s); +std::string convert2digit(int n, std::string suffix); +std::string numberToWords(unsigned long long int n); + #endif diff --git a/zone/command.cpp b/zone/command.cpp index 09aa704b1..3cb8b6558 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -204,9 +204,10 @@ int command_init(void) command_add("equipitem", "[slotid(0-21)] - Equip the item on your cursor into the specified slot", 50, command_equipitem) || command_add("face", "- Change the face of your target", 80, command_face) || command_add("faction", "[Find (criteria | all ) | Review (criteria | all) | Reset (id)] - Resets Player's Faction", 80, command_faction) || - command_add("findaliases", "[search term]- Searches for available command aliases, by alias or command", 0, command_findaliases) || + command_add("findaliases", "[search criteria]- Searches for available command aliases, by alias or command", 0, command_findaliases) || command_add("findnpctype", "[search criteria] - Search database NPC types", 100, command_findnpctype) || - command_add("findspell", "[searchstring] - Search for a spell", 50, command_findspell) || + command_add("findrace", "[search criteria] - Search for a race", 50, command_findrace) || + command_add("findspell", "[search criteria] - Search for a spell", 50, command_findspell) || command_add("findzone", "[search criteria] - Search database zones", 100, command_findzone) || command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", 80, command_fixmob) || command_add("flag", "[status] [acctname] - Refresh your admin status, or set an account's admin status if arguments provided", 0, command_flag) || @@ -2438,10 +2439,10 @@ void command_grid(Client *c, const Seperator *sep) } std::string query = StringFormat( - "SELECT `x`, `y`, `z`, `heading`, `number`, `pause` " + "SELECT `x`, `y`, `z`, `heading`, `number` " "FROM `grid_entries` " "WHERE `zoneid` = %u and `gridid` = %i " - "ORDER BY `number` ", + "ORDER BY `number`", zone->GetZoneID(), target->CastToNPC()->GetGrid() ); @@ -2471,18 +2472,31 @@ void command_grid(Client *c, const Seperator *sep) /** * Spawn grid nodes */ - for (auto row = results.begin(); row != results.end(); ++row) { - auto node_position = glm::vec4(atof(row[0]), atof(row[1]), atof(row[2]), atof(row[3])); + std::map, int32> zoffset; - NPC *npc = NPC::SpawnGridNodeNPC( - target->GetCleanName(), - node_position, - static_cast(target->CastToNPC()->GetGrid()), - static_cast(atoi(row[4])), - static_cast(atoi(row[5])) - ); - npc->SetFlyMode(GravityBehavior::Flying); - npc->GMMove(node_position.x, node_position.y, node_position.z, node_position.w); + for (auto row = results.begin(); row != results.end(); ++row) { + glm::vec4 node_position = glm::vec4(atof(row[0]), atof(row[1]), atof(row[2]), atof(row[3])); + + std::vector node_loc { + node_position.x, + node_position.y, + node_position.z + }; + + // If we already have a node at this location, set the z offset + // higher from the existing one so we can see it. Adjust so if + // there is another at the same spot we adjust again. + auto search = zoffset.find(node_loc); + if (search != zoffset.end()) { + search->second = search->second + 3; + } + else { + zoffset[node_loc] = 0.0; + } + + node_position.z += zoffset[node_loc]; + + NPC::SpawnGridNodeNPC(node_position,atoi(row[4]),zoffset[node_loc]); } } else if (strcasecmp("delete", sep->arg[1]) == 0) { @@ -2628,6 +2642,46 @@ void command_showskills(Client *c, const Seperator *sep) c->Message(Chat::White, "Skill [%d] is at [%d] - %u", i, t->GetSkill(i), t->GetRawSkill(i)); } +void command_findrace(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == 0) { + c->Message(Chat::White, "Usage: #findrace [race name]"); + } else if (Seperator::IsNumber(sep->argplus[1])) { + int search_id = atoi(sep->argplus[1]); + std::string race_name = GetRaceIDName(search_id); + if (race_name != std::string("")) { + c->Message(Chat::White, "Race %d: %s", search_id, race_name.c_str()); + return; + } + } else { + const char *search_criteria = sep->argplus[1]; + int found_count = 0; + char race_name[64]; + char search_string[65]; + strn0cpy(search_string, search_criteria, sizeof(search_string)); + strupr(search_string); + char *string_location; + for (int race_id = RACE_HUMAN_1; race_id <= RT_PEGASUS_3; race_id++) { + strn0cpy(race_name, GetRaceIDName(race_id), sizeof(race_name)); + strupr(race_name); + string_location = strstr(race_name, search_string); + if (string_location != nullptr) { + c->Message(Chat::White, "Race %d: %s", race_id, GetRaceIDName(race_id)); + found_count++; + } + + if (found_count == 20) { + break; + } + } + if (found_count == 20) { + c->Message(Chat::White, "20 Races found... max reached."); + } else { + c->Message(Chat::White, "%i Race(s) found.", found_count); + } + } +} + void command_findspell(Client *c, const Seperator *sep) { if (sep->arg[1][0] == 0) @@ -4169,10 +4223,11 @@ void command_findzone(Client *c, const Seperator *sep) c->Message( Chat::White, fmt::format( - "[{}] [{}] [{}] Version ({}) [{}]", + "[{}] [{}] [{}] ID ({}) Version ({}) [{}]", (version == 0 ? command_zone : "zone"), command_gmzone, short_name, + zone_id, version, long_name ).c_str() diff --git a/zone/command.h b/zone/command.h index 7c1af0edf..05c92eac4 100644 --- a/zone/command.h +++ b/zone/command.h @@ -101,6 +101,7 @@ void command_face(Client *c, const Seperator *sep); void command_faction(Client *c, const Seperator *sep); void command_findaliases(Client *c, const Seperator *sep); void command_findnpctype(Client *c, const Seperator *sep); +void command_findrace(Client *c, const Seperator *sep); void command_findspell(Client *c, const Seperator *sep); void command_findzone(Client *c, const Seperator *sep); void command_fixmob(Client *c, const Seperator *sep); diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index f95730eb0..0251f990f 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -530,6 +530,32 @@ XS(XS__zone) { XSRETURN_EMPTY; } +XS(XS__zonegroup); +XS(XS__zonegroup) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::zonegroup(string zone_name)"); + + char *zone_name = (char *) SvPV_nolen(ST(0)); + + quest_manager.ZoneGroup(zone_name); + + XSRETURN_EMPTY; +} + +XS(XS__zoneraid); +XS(XS__zoneraid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::zoneraid(string zone_name)"); + + char *zone_name = (char *) SvPV_nolen(ST(0)); + + quest_manager.ZoneRaid(zone_name); + + XSRETURN_EMPTY; +} + XS(XS__settimer); XS(XS__settimer) { dXSARGS; @@ -4520,6 +4546,8 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "write"), XS__write, file); newXS(strcpy(buf, "ze"), XS__ze, file); newXS(strcpy(buf, "zone"), XS__zone, file); + newXS(strcpy(buf, "zonegroup"), XS__zonegroup, file); + newXS(strcpy(buf, "zoneraid"), XS__zoneraid, file); XSRETURN_YES; } diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index b7b9ca6bb..15c059423 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -1146,6 +1146,18 @@ Lua_EntityList lua_get_entity_list() { return Lua_EntityList(&entity_list); } +void lua_zone(const char* zone_name) { + quest_manager.Zone(zone_name); +} + +void lua_zone_group(const char* zone_name) { + quest_manager.ZoneGroup(zone_name); +} + +void lua_zone_raid(const char* zone_name) { + quest_manager.ZoneRaid(zone_name); +} + int lua_get_zone_id() { if(!zone) return 0; @@ -1893,6 +1905,9 @@ luabind::scope lua_register_general() { luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*,Lua_NPC))&lua_get_qglobals), luabind::def("get_qglobals", (luabind::adl::object(*)(lua_State*))&lua_get_qglobals), luabind::def("get_entity_list", &lua_get_entity_list), + luabind::def("zone", &lua_zone), + luabind::def("zone_group", &lua_zone_group), + luabind::def("zone_raid", &lua_zone_raid), luabind::def("get_zone_id", &lua_get_zone_id), luabind::def("get_zone_long_name", &lua_get_zone_long_name), luabind::def("get_zone_short_name", &lua_get_zone_short_name), diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index f37d70292..b7f69ac14 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1319,7 +1319,10 @@ void Mob::AI_Process() { FaceTarget(); } } - else if (AI_movement_timer->Check() && target) { + // mob/npc waits until call for help complete, others can move + else if (AI_movement_timer->Check() && target && + (GetOwnerID() || IsBot() || + CastToNPC()->GetCombatEvent())) { if (!IsRooted()) { LogAI("Pursuing [{}] while engaged", target->GetName()); RunTo(target->GetX(), target->GetY(), target->GetZ()); diff --git a/zone/npc.cpp b/zone/npc.cpp index 1794c6d8c..15b0ce765 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -1068,14 +1068,20 @@ bool NPC::SpawnZoneController() return true; } -NPC * NPC::SpawnGridNodeNPC(std::string name, const glm::vec4 &position, uint32 grid_id, uint32 grid_number, uint32 pause) { +void NPC::SpawnGridNodeNPC(const glm::vec4 &position, int32 grid_number, int32 zoffset) { + auto npc_type = new NPCType; memset(npc_type, 0, sizeof(NPCType)); - sprintf(npc_type->name, "%u_%u", grid_id, grid_number); - sprintf(npc_type->lastname, "Number: %u Grid: %u Pause: %u", grid_number, grid_id, pause); + std::string str_zoffset = numberToWords(zoffset); + std::string str_number = numberToWords(grid_number); - npc_type->current_hp = 4000000; + strcpy(npc_type->name, str_number.c_str()); + if (zoffset != 0) { + strcat(npc_type->name, "(Stacked)"); + } + + npc_type->current_hp = 4000000; npc_type->max_hp = 4000000; npc_type->race = 2254; npc_type->gender = 2; @@ -1095,11 +1101,12 @@ NPC * NPC::SpawnGridNodeNPC(std::string name, const glm::vec4 &position, uint32 auto node_position = glm::vec4(position.x, position.y, position.z, position.w); auto npc = new NPC(npc_type, nullptr, node_position, GravityBehavior::Flying); + + npc->name[strlen(npc->name)-3] = (char) NULL; + npc->GiveNPCTypeData(npc_type); - entity_list.AddNPC(npc, true, true); - - return npc; + entity_list.AddNPC(npc); } NPC * NPC::SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4 &position) { diff --git a/zone/npc.h b/zone/npc.h index d9f98cfc6..79a98ae94 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -112,7 +112,7 @@ public: virtual ~NPC(); static NPC *SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4 &position); - static NPC *SpawnGridNodeNPC(std::string name, const glm::vec4 &position, uint32 grid_id, uint32 grid_number, uint32 pause); + static void SpawnGridNodeNPC(const glm::vec4 &position, int32 grid_number, int32 zoffset); //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQ::skills::SkillType attack_skill); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 969fab6eb..5b830544a 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -407,6 +407,84 @@ void QuestManager::Zone(const char *zone_name) { } } +void QuestManager::ZoneGroup(const char *zone_name) { + QuestManagerCurrentQuestVars(); + if (initiator && initiator->IsClient()) { + if (!initiator->GetGroup()) { + auto pack = new ServerPacket(ServerOP_ZoneToZoneRequest, sizeof(ZoneToZone_Struct)); + ZoneToZone_Struct* ztz = (ZoneToZone_Struct*) pack->pBuffer; + ztz->response = 0; + ztz->current_zone_id = zone->GetZoneID(); + ztz->current_instance_id = zone->GetInstanceID(); + ztz->requested_zone_id = database.GetZoneID(zone_name); + ztz->admin = initiator->Admin(); + strcpy(ztz->name, initiator->GetName()); + ztz->guild_id = initiator->GuildID(); + ztz->ignorerestrictions = 3; + worldserver.SendPacket(pack); + safe_delete(pack); + } else { + auto client_group = initiator->GetGroup(); + for (int member_index = 0; member_index < MAX_GROUP_MEMBERS; member_index++) { + if (client_group->members[member_index] && client_group->members[member_index]->IsClient()) { + auto group_member = client_group->members[member_index]->CastToClient(); + auto pack = new ServerPacket(ServerOP_ZoneToZoneRequest, sizeof(ZoneToZone_Struct)); + ZoneToZone_Struct* ztz = (ZoneToZone_Struct*) pack->pBuffer; + ztz->response = 0; + ztz->current_zone_id = zone->GetZoneID(); + ztz->current_instance_id = zone->GetInstanceID(); + ztz->requested_zone_id = database.GetZoneID(zone_name); + ztz->admin = group_member->Admin(); + strcpy(ztz->name, group_member->GetName()); + ztz->guild_id = group_member->GuildID(); + ztz->ignorerestrictions = 3; + worldserver.SendPacket(pack); + safe_delete(pack); + } + } + } + } +} + +void QuestManager::ZoneRaid(const char *zone_name) { + QuestManagerCurrentQuestVars(); + if (initiator && initiator->IsClient()) { + if (!initiator->GetRaid()) { + auto pack = new ServerPacket(ServerOP_ZoneToZoneRequest, sizeof(ZoneToZone_Struct)); + ZoneToZone_Struct* ztz = (ZoneToZone_Struct*) pack->pBuffer; + ztz->response = 0; + ztz->current_zone_id = zone->GetZoneID(); + ztz->current_instance_id = zone->GetInstanceID(); + ztz->requested_zone_id = database.GetZoneID(zone_name); + ztz->admin = initiator->Admin(); + strcpy(ztz->name, initiator->GetName()); + ztz->guild_id = initiator->GuildID(); + ztz->ignorerestrictions = 3; + worldserver.SendPacket(pack); + safe_delete(pack); + } else { + auto client_raid = initiator->GetRaid(); + for (int member_index = 0; member_index < MAX_RAID_MEMBERS; member_index++) { + if (client_raid->members[member_index].member && client_raid->members[member_index].member->IsClient()) { + auto raid_member = client_raid->members[member_index].member->CastToClient(); + auto pack = new ServerPacket(ServerOP_ZoneToZoneRequest, sizeof(ZoneToZone_Struct)); + ZoneToZone_Struct* ztz = (ZoneToZone_Struct*) pack->pBuffer; + ztz->response = 0; + ztz->current_zone_id = zone->GetZoneID(); + ztz->current_instance_id = zone->GetInstanceID(); + ztz->requested_zone_id = database.GetZoneID(zone_name); + ztz->admin = raid_member->Admin(); + strcpy(ztz->name, raid_member->GetName()); + ztz->guild_id = raid_member->GuildID(); + ztz->ignorerestrictions = 3; + worldserver.SendPacket(pack); + safe_delete(pack); + } + } + } + } +} + void QuestManager::settimer(const char *timer_name, int seconds) { QuestManagerCurrentQuestVars(); diff --git a/zone/questmgr.h b/zone/questmgr.h index fb27ca002..c01dafd70 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -77,6 +77,8 @@ public: void selfcast(int spell_id); void addloot(int item_id, int charges = 0, bool equipitem = true, int aug1 = 0, int aug2 = 0, int aug3 = 0, int aug4 = 0, int aug5 = 0, int aug6 = 0); void Zone(const char *zone_name); + void ZoneGroup(const char *zone_name); + void ZoneRaid(const char *zone_name); void settimer(const char *timer_name, int seconds); void settimerMS(const char *timer_name, int milliseconds); void settimerMS(const char *timer_name, int milliseconds, EQ::ItemInstance *inst); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 831580e8f..717bc4fbf 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -192,7 +192,7 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 bas DoAttack(who, my_hit); - who->AddToHateList(this, hate, 0, false); + who->AddToHateList(this, hate, 0); if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; @@ -870,7 +870,7 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co } if (IsClient() && !CastToClient()->GetFeigned()) - other->AddToHateList(this, hate, 0, false); + other->AddToHateList(this, hate, 0); other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQ::skills::SkillArchery); @@ -1200,9 +1200,9 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha if (TotalDmg > 0) { TotalDmg += TotalDmg * damage_mod / 100; - other->AddToHateList(this, TotalDmg, 0, false); + other->AddToHateList(this, TotalDmg, 0); } else { - other->AddToHateList(this, 0, 0, false); + other->AddToHateList(this, 0, 0); } other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillInUse); @@ -1384,7 +1384,7 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c } if (IsClient() && !CastToClient()->GetFeigned()) - other->AddToHateList(this, WDmg, 0, false); + other->AddToHateList(this, WDmg, 0); other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQ::skills::SkillThrowing); @@ -2158,7 +2158,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob *other, uint16 weapon_damage, EQ::skills::Sk CanSkillProc = false; // Disable skill procs } - other->AddToHateList(this, hate, 0, false); + other->AddToHateList(this, hate, 0); if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skillinuse && IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f;