diff --git a/common/version.h b/common/version.h index 019060043..690983e3f 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9178 +#define CURRENT_BINARY_DATABASE_VERSION 9179 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9028 diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 4c70d0f1f..2a734f866 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -432,6 +432,7 @@ 9176|2022_01_10_checksum_verification.sql|SHOW COLUMNS FROM `account` LIKE 'crc_eqgame'|empty| 9177|2022_03_06_table_structure_changes.sql|SHOW COLUMNS FROM `pets` LIKE 'id'|empty| 9178|2022_03_07_saylink_collation.sql|SELECT * FROM db_version WHERE version >= 9178|empty| +9179|2022_04_30_hp_regen_per_second.sql|SHOW COLUMNS FROM `npc_types` LIKE 'hp_regen_per_second'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2022_04_30_hp_regen_per_second.sql b/utils/sql/git/required/2022_04_30_hp_regen_per_second.sql new file mode 100644 index 000000000..e36fc4d45 --- /dev/null +++ b/utils/sql/git/required/2022_04_30_hp_regen_per_second.sql @@ -0,0 +1 @@ +ALTER TABLE npc_types ADD COLUMN hp_regen_per_second bigint(11) DEFAULT 0 AFTER hp_regen_rate; diff --git a/zone/gm_commands/npcedit.cpp b/zone/gm_commands/npcedit.cpp index 66b483384..50d15b0bc 100755 --- a/zone/gm_commands/npcedit.cpp +++ b/zone/gm_commands/npcedit.cpp @@ -28,6 +28,7 @@ void command_npcedit(Client *c, const Seperator *sep) c->Message(Chat::White, "#npcedit herosforgemodel - Sets an NPC's Hero's Forge Model"); c->Message(Chat::White, "#npcedit size - Sets an NPC's Size"); c->Message(Chat::White, "#npcedit hpregen - Sets an NPC's Hitpoints Regeneration Rate Per Tick"); + c->Message(Chat::White, "#npcedit hp_regen_per_second - Sets an NPC's HP regeneration per second"); c->Message(Chat::White, "#npcedit manaregen - Sets an NPC's Mana Regeneration Rate Per Tick"); c->Message(Chat::White, "#npcedit loottable - Sets an NPC's Loottable ID"); c->Message(Chat::White, "#npcedit merchantid - Sets an NPC's Merchant ID"); @@ -308,6 +309,23 @@ void command_npcedit(Client *c, const Seperator *sep) return; } + if (strcasecmp(sep->arg[1], "hp_regen_per_second") == 0) { + c->Message( + Chat::Yellow, + fmt::format( + "NPC ID {} now regenerates {} HP per second.", + npc_id, + atoi(sep->arg[2])).c_str() + ); + std::string query = fmt::format( + "UPDATE npc_types SET hp_regen_per_second = {} WHERE id = {}", + atoi(sep->arg[2]), + npc_id + ); + content_db.QueryDatabase(query); + return; + } + if (strcasecmp(sep->arg[1], "manaregen") == 0) { c->Message( Chat::Yellow, diff --git a/zone/merc.cpp b/zone/merc.cpp index 63c3d7f35..a87c1a04b 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4880,6 +4880,7 @@ void Merc::UpdateMercStats(Client *c, bool setmax) max_end = npc_type->max_hp; // Hack since Endurance does not exist for NPCType yet base_end = npc_type->max_hp; // Hack since Endurance does not exist for NPCType yet hp_regen = npc_type->hp_regen; + hp_regen_per_second = npc_type->hp_regen_per_second; mana_regen = npc_type->mana_regen; max_dmg = npc_type->max_dmg; min_dmg = npc_type->min_dmg; diff --git a/zone/mob.cpp b/zone/mob.cpp index 738462335..c1f8d670c 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -96,11 +96,13 @@ Mob::Mob( uint8 in_legtexture, uint8 in_feettexture, uint16 in_usemodel, - bool in_always_aggro + bool in_always_aggro, + int64 in_hp_regen_per_second ) : attack_timer(2000), attack_dw_timer(2000), ranged_timer(2000), + hp_regen_per_second_timer(1000), tic_timer(6000), mana_timer(2000), spellend_timer(0), @@ -249,37 +251,38 @@ Mob::Mob( aa_title = 0xFF; } - AC = in_ac; - ATK = in_atk; - STR = in_str; - STA = in_sta; - DEX = in_dex; - AGI = in_agi; - INT = in_int; - WIS = in_wis; - CHA = in_cha; - MR = CR = FR = DR = PR = Corrup = PhR = 0; - ExtraHaste = 0; - bEnraged = false; - current_mana = 0; - max_mana = 0; - hp_regen = in_hp_regen; - mana_regen = in_mana_regen; - ooc_regen = RuleI(NPC, OOCRegen); //default Out of Combat Regen - maxlevel = in_maxlevel; - scalerate = in_scalerate; - invisible = 0; - invisible_undead = 0; - invisible_animals = 0; - sneaking = false; - hidden = false; - improved_hidden = false; - invulnerable = false; - IsFullHP = (current_hp == max_hp); - qglobal = 0; - spawned = false; - rare_spawn = false; - always_aggro = in_always_aggro; + AC = in_ac; + ATK = in_atk; + STR = in_str; + STA = in_sta; + DEX = in_dex; + AGI = in_agi; + INT = in_int; + WIS = in_wis; + CHA = in_cha; + MR = CR = FR = DR = PR = Corrup = PhR = 0; + ExtraHaste = 0; + bEnraged = false; + current_mana = 0; + max_mana = 0; + hp_regen = in_hp_regen; + hp_regen_per_second = in_hp_regen_per_second; + mana_regen = in_mana_regen; + ooc_regen = RuleI(NPC, OOCRegen); //default Out of Combat Regen + maxlevel = in_maxlevel; + scalerate = in_scalerate; + invisible = 0; + invisible_undead = 0; + invisible_animals = 0; + sneaking = false; + hidden = false; + improved_hidden = false; + invulnerable = false; + IsFullHP = (current_hp == max_hp); + qglobal = 0; + spawned = false; + rare_spawn = false; + always_aggro = in_always_aggro; InitializeBuffSlots(); diff --git a/zone/mob.h b/zone/mob.h index 9b82937a9..25b6c870b 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -162,7 +162,8 @@ public: uint8 in_legtexture, uint8 in_feettexture, uint16 in_usemodel, - bool in_always_aggros_foes + bool in_always_aggros_foes, + int64 in_hp_regen_per_second = 0 ); virtual ~Mob(); @@ -245,11 +246,11 @@ public: //Invisible bool IsInvisible(Mob* other = 0) const; void SetInvisible(uint8 state, bool set_on_bonus_calc = false); - + void CalcSeeInvisibleLevel(); void CalcInvisibleLevel(); void ZeroInvisibleVars(uint8 invisible_type); - + inline uint8 GetSeeInvisibleLevelFromNPCStat(uint16 in_see_invis); void BreakInvisibleSpells(); @@ -269,7 +270,7 @@ public: inline void SetSeeInvisibleUndead(uint8 val) { see_invis_undead = val; } uint32 tmHidden; // timestamp of hide, only valid while hidden == true - uint8 invisible, nobuff_invisible, invisible_undead, invisible_animals; + uint8 invisible, nobuff_invisible, invisible_undead, invisible_animals; uint8 see_invis, innate_see_invis, see_invis_undead; //TODO: do we need a see_invis_animal ? bool sneaking, hidden, improved_hidden; @@ -298,7 +299,7 @@ public: void ChangeSize(float in_size, bool bNoRestriction = false); void DoAnim(const int animnum, int type=0, bool ackreq = true, eqFilterType filter = FilterNone); void ProjectileAnimation(Mob* to, int item_id, bool IsArrow = false, float speed = 0, float angle = 0, float tilt = 0, float arc = 0, const char *IDFile = nullptr, EQ::skills::SkillType skillInUse = EQ::skills::SkillArchery); - void SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target=nullptr, uint32 value1slot = 1, uint32 value1ground = 1, uint32 value2slot = 1, uint32 value2ground = 1, + void SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target=nullptr, uint32 value1slot = 1, uint32 value1ground = 1, uint32 value2slot = 1, uint32 value2ground = 1, uint32 value3slot = 1, uint32 value3ground = 1, uint32 value4slot = 1, uint32 value4ground = 1, uint32 value5slot = 1, uint32 value5ground = 1); void SendLevelAppearance(); void SendStunAppearance(); @@ -380,13 +381,13 @@ public: int16 GetItemSlotToConsumeCharge(int32 spell_id, uint32 inventory_slot); bool CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot); bool IsFromTriggeredSpell(EQ::spells::CastingSlot slot, uint32 item_slot = 0xFFFFFFFF); - - //Bard + + //Bard bool ApplyBardPulse(int32 spell_id, Mob *spell_target, EQ::spells::CastingSlot slot); bool IsActiveBardSong(int32 spell_id); bool HasActiveSong() const { return(bardsong != 0); } void ZeroBardPulseVars(); - void DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time, int32 spell_id, uint16 target_id, EQ::spells::CastingSlot slot, uint32 item_slot, + void DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time, int32 spell_id, uint16 target_id, EQ::spells::CastingSlot slot, uint32 item_slot, uint32 recast_type , uint32 recast_delay); bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1); @@ -1424,6 +1425,7 @@ protected: int32 current_mana; int32 max_mana; int32 hp_regen; + int64 hp_regen_per_second; int32 mana_regen; int32 ooc_regen; uint8 maxlevel; @@ -1542,6 +1544,7 @@ protected: int attack_delay; //delay between attacks in 10ths of seconds bool always_aggro; int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) + Timer hp_regen_per_second_timer; Timer tic_timer; Timer mana_timer; int32 dw_same_delay; @@ -1558,7 +1561,7 @@ protected: int32 appearance_effects_id[MAX_APPEARANCE_EFFECTS]; int32 appearance_effects_slot[MAX_APPEARANCE_EFFECTS]; - + int queue_wearchange_slot; Timer shield_timer; @@ -1766,7 +1769,7 @@ protected: private: Mob* target; - + #ifdef BOTS std::shared_ptr m_target_of_heal_rotation; diff --git a/zone/npc.cpp b/zone/npc.cpp index a36e22e18..73a8542bc 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -114,7 +114,8 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi npc_type_data->legtexture, npc_type_data->feettexture, npc_type_data->use_model, - npc_type_data->always_aggro + npc_type_data->always_aggro, + npc_type_data->hp_regen_per_second ), attacked_timer(CombatEventTimer_expire), swarm_timer(100), @@ -870,6 +871,12 @@ bool NPC::Process() } } + if (hp_regen_per_second > 0 && hp_regen_per_second_timer.Check()) { + if (GetHP() < GetMaxHP()) { + SetHP(GetHP() + hp_regen_per_second); + } + } + if (tic_timer.Check()) { parse->EventNPC(EVENT_TICK, this, nullptr, "", 0); BuffProcess(); @@ -2594,6 +2601,10 @@ void NPC::ModifyNPCStat(const char *identifier, const char *new_value) hp_regen = atoi(val.c_str()); return; } + else if (id == "hp_regen_per_second") { + hp_regen_per_second = strtoll(val.c_str(), nullptr, 10); + return; + } else if (id == "mana_regen") { mana_regen = atoi(val.c_str()); return; @@ -2741,6 +2752,9 @@ float NPC::GetNPCStat(const char *identifier) else if (id == "hp_regen") { return hp_regen; } + else if (id == "hp_regen_per_second") { + return hp_regen_per_second; + } else if (id == "mana_regen") { return mana_regen; } diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 12bcb378d..7cc89f579 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -2476,7 +2476,9 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load "npc_types.model, " "npc_types.flymode, " "npc_types.always_aggro, " - "npc_types.exp_mod " + "npc_types.exp_mod, " + "npc_types.hp_regen_per_second " + "FROM npc_types %s", where_condition.c_str() ); @@ -2542,14 +2544,14 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->d_melee_texture1 = atoi(row[38]); temp_npctype_data->d_melee_texture2 = atoi(row[39]); strn0cpy(temp_npctype_data->ammo_idfile, row[40], 30); - temp_npctype_data->prim_melee_type = atoi(row[41]); - temp_npctype_data->sec_melee_type = atoi(row[42]); - temp_npctype_data->ranged_type = atoi(row[43]); - temp_npctype_data->runspeed = atof(row[44]); - temp_npctype_data->findable = atoi(row[45]) == 0 ? false : true; - temp_npctype_data->trackable = atoi(row[46]) == 0 ? false : true; - temp_npctype_data->hp_regen = atoi(row[47]); - temp_npctype_data->mana_regen = atoi(row[48]); + temp_npctype_data->prim_melee_type = atoi(row[41]); + temp_npctype_data->sec_melee_type = atoi(row[42]); + temp_npctype_data->ranged_type = atoi(row[43]); + temp_npctype_data->runspeed = atof(row[44]); + temp_npctype_data->findable = atoi(row[45]) == 0 ? false : true; + temp_npctype_data->trackable = atoi(row[46]) == 0 ? false : true; + temp_npctype_data->hp_regen = atoi(row[47]); + temp_npctype_data->mana_regen = atoi(row[48]); // set default value for aggroradius temp_npctype_data->aggroradius = (int32) atoi(row[49]); @@ -2673,13 +2675,14 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->charm_avoidance_rating = atoi(row[105]); temp_npctype_data->charm_atk = atoi(row[106]); - temp_npctype_data->skip_global_loot = atoi(row[107]) != 0; - temp_npctype_data->rare_spawn = atoi(row[108]) != 0; - temp_npctype_data->stuck_behavior = atoi(row[109]); - temp_npctype_data->use_model = atoi(row[110]); - temp_npctype_data->flymode = atoi(row[111]); - temp_npctype_data->always_aggro = atoi(row[112]); - temp_npctype_data->exp_mod = atoi(row[113]); + temp_npctype_data->skip_global_loot = atoi(row[107]) != 0; + temp_npctype_data->rare_spawn = atoi(row[108]) != 0; + temp_npctype_data->stuck_behavior = atoi(row[109]); + temp_npctype_data->use_model = atoi(row[110]); + temp_npctype_data->flymode = atoi(row[111]); + temp_npctype_data->always_aggro = atoi(row[112]); + temp_npctype_data->exp_mod = atoi(row[113]); + temp_npctype_data->hp_regen_per_second = strtoll(row[114], nullptr, 10); temp_npctype_data->skip_auto_scale = false; // hardcoded here for now @@ -3588,7 +3591,7 @@ void ZoneDatabase::ListAllInstances(Client* client, uint32 character_id) remaining_time_string = "Already Expired"; } } - + client->Message( Chat::White, fmt::format("Instance {} | Zone: {} ({}){}", @@ -3860,7 +3863,7 @@ void ZoneDatabase::SavePetInfo(Client *client) "ON DUPLICATE KEY UPDATE `petname` = '%s', `petpower` = %i, `spell_id` = %u, " "`hp` = %u, `mana` = %u, `size` = %f, `taunting` = %u", client->CharacterID(), pet, petinfo->Name, petinfo->petpower, petinfo->SpellID, - petinfo->HP, petinfo->Mana, petinfo->size, (petinfo->taunting) ? 1 : 0, + petinfo->HP, petinfo->Mana, petinfo->size, (petinfo->taunting) ? 1 : 0, // and now the ON DUPLICATE ENTRIES petinfo->Name, petinfo->petpower, petinfo->SpellID, petinfo->HP, petinfo->Mana, petinfo->size, (petinfo->taunting) ? 1 : 0); results = database.QueryDatabase(query); diff --git a/zone/zonedump.h b/zone/zonedump.h index 56a89b920..a3705eba0 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -35,9 +35,9 @@ spawn2 mediumblob, npcs mediumblob, npc_loot mediumblob, gmspawntype mediumblob, struct NPCType { char name[64]; - char lastname[70]; + char lastname[70]; int32 current_hp; - int32 max_hp; + int32 max_hp; float size; float runspeed; uint8 gender; @@ -105,6 +105,7 @@ struct NPCType uint8 sec_melee_type; uint8 ranged_type; int32 hp_regen; + int64 hp_regen_per_second; int32 mana_regen; int32 aggroradius; // added for AI improvement - neotokyo int32 assistradius; // assist radius, defaults to aggroradis if not set @@ -122,7 +123,7 @@ struct NPCType int avoidance_rating; // flat bonus before mods bool findable; //can be found with find command bool trackable; - int16 slow_mitigation; + int16 slow_mitigation; uint8 maxlevel; uint32 scalerate; bool private_corpse; @@ -164,8 +165,8 @@ namespace player_lootitem { uint32 aug_5; uint32 aug_6; int8 attuned; - uint8 min_level; // - uint8 max_level; // + uint8 min_level; // + uint8 max_level; // }; }