Compare commits

...

20 Commits

Author SHA1 Message Date
Alex King 9477ff72ac [Release] 22.34.2 (#3709)
### Admin

* Update date in changelog ([#3698](https://github.com/EQEmu/Server/pull/3698)) @joligario 2023-11-19

### Code

* Fix typo in #giveitem ([#3704](https://github.com/EQEmu/Server/pull/3704)) @Kinglykrab 2023-11-22

### Fixes

* Add "IgnoreLevelBasedHasteCaps" rule to GetHaste() ([#3705](https://github.com/EQEmu/Server/pull/3705)) @jcr4990 2023-11-23
* Fix bots/Mercenaries being removed from hatelist ([#3708](https://github.com/EQEmu/Server/pull/3708)) @Kinglykrab 2023-11-23
* Fix some spell types failing IsValidSpellRange check ([#3707](https://github.com/EQEmu/Server/pull/3707)) @nytmyr 2023-11-23

### Loginserver

* Update ticket login table structure ([#3703](https://github.com/EQEmu/Server/pull/3703)) @KimLS 2023-11-22
2023-11-24 13:23:10 -05:00
Alex King 6d8e80b1e5 [Bug Fix] Fix bots/Mercenaries being removed from hatelist (#3708)
# Notes
- https://github.com/EQEmu/Server/pull/3595 caused bots, bot pets, and mercenaries to be removed from hate list because we were only checking for `IsClient()` not `IsOfClientBotMerc()`.
- Resolves an issue mentioned [here](https://discord.com/channels/212663220849213441/1177288302383079534) where NPCs would run past bots/mercenaries to attack the owner.
2023-11-23 12:36:54 -05:00
Joel ebeaef598b [Bug Fix] Add "IgnoreLevelBasedHasteCaps" rule to GetHaste() (#3705) 2023-11-22 22:45:53 -05:00
nytmyr 60b65da7f2 [Bug Fix] Fix some spell types failing IsValidSpellRange check (#3707)
Hate Reduction, Slow and DoT spells were failing IsValidSpellRange checks due to improper spell IDs being checked.

They were using the first spell in a bot's spell list by checking botSpell.id vs the proper spells in the loop.
2023-11-22 22:44:59 -05:00
Alex King 100e6698ea [Cleanup] Fix typo in #giveitem (#3704) 2023-11-22 08:26:54 -05:00
Alex 2bd94ab7a2 [Loginserver] Update ticket login table structure (#3703)
* Updates login table to support tickets in a way that makes more sense.

* Change to snake_case as requested by Akka

---------

Co-authored-by: KimLS <KimLS@peqtgc.com>
2023-11-22 02:56:47 -06:00
Akkadius 4c8d68c24b [Release] 22.34.1 2023-11-20 09:12:11 -06:00
Akkadius 1755678b1f [Release] 22.34.1 2023-11-20 09:10:22 -06:00
JJ 39e2763968 [Admin] Update date in changelog (#3698) 2023-11-19 13:22:56 -05:00
Fryguy f7780b0247 [Bug Fix] NPC Faction War prevention. (#3595)
* [Bug] NPC Faction War prevention.

This should assist with prevention of NPC Faction waring. I have been using this code on my server for over 5 years with no coredumps or any noted bugs. This was brought up as users have reported a few events where they can trigger NPC's to fight each other.

* Correct a few entries to line up with updated code.

* Re-add missing RestTimer functionality
2023-11-19 11:40:15 -05:00
JJ c0fe0f11f7 [Release] 22.34.0 (#3697)
* Update CHANGELOG.md

* Update package.json

* Update version.h

* Update CHANGELOG.md
2023-11-19 11:21:39 -05:00
nytmyr a1f1f11940 [Bots] Add ownerraid, byclass and byrace actionables and fix group-based arguments for raids. (#3680)
This adds raid support to actionables for bot commands.

This also addresses Issue #3567

If in a group, group based commands will function as normal, however if in a raid they will support raids and use the group of the bot that fits  the criteria.

This adds new actionables as well; namely, **ownerraid**, **byclass** and **byrace**.

**byclass** and **byrace** use the int of the chosen class/race. IE Shadowknight is 5, Barbarian is 2. **^create help** is an easy way for players to identify numbers associated with classes and races.

**targetgroup** will now select all bots that meet the criteria within a raid group in addition to the current functionality of groups.
**namesgroup** will now select all bots that meet the criteria within a raid group in addition to the current functionality of groups.
**ownerraid** will select all bots in the raid owned by the player.
**byclass** will selects all bots in the group or raid owned by the player that match the class.
**byrace** will selects all bots in the group or raid owned by the player that match the race

This adds actionables to **^casterrange** and **^camp** as well.
2023-11-18 23:24:49 -05:00
Alex King 4c5013e09e [Cleanup] Cleanup #giveitem and #summonitem (#3692)
# Notes
- Adds summoned messages for `#giveitem` and `#summonitem`.
- `#summonitem` did not stop you from summoning items beyond your status level.
2023-11-18 21:22:12 -05:00
Alex King 838ffbd8c7 [Commands] Add #show aa_points Command (#3695)
# Notes
- Adds a command to view a player's current, spent, and total AA Points.
2023-11-18 19:23:35 -05:00
JJ 42b41d973c [GM Commands] Remove duplicate comment (#3691) 2023-11-18 19:08:56 -05:00
JJ e7761133a9 [GM Commands] Add #takeplatinum (#3690)
* [GM Commands] Add `#takeplatinum`

* Revert database manifest change

* Revert database version change

* Remove duplicated messages

* Remove hint as to why `#takeplatinum` might fail.
2023-11-18 19:08:48 -05:00
Alex King 93f2bea96e [Cleanup] Cleanup #show currencies Command (#3693)
* [Cleanup] Cleanup #show currencies Command

# Notes
- Cleans up messages.
- Adds `AA Points` as a viewable currency.
- Adds an enum for `MoneyTypes` and `MoneySubtypes` so we're not using magic numbers for `GetMoney` anymore.
- Converts money amounts to `uint64`.

* Update currencies.cpp

* Update currencies.cpp
2023-11-18 19:05:04 -05:00
regneq ded82ac6d6 [Spawn] (imported from takp) Added min_time and max_time to spawnentry. This will prevent a NPC from… (#3685)
* Added mintime and maxtime to spawnentry. This will prevent a NPC from spawning outside of the times specified. NPCs spawned in this way will then behave like normal NPCs. They will not despawn on their own, unlike spawn_events/spawn_conditions. NPCs using this that are alone in their spawngroup will attempt to spawn after their respawn timer has expired if the time of day is outside their range. Otherwise, another NPC in the spawngroup will be chosen to spawn. The normal rules (chance, spawn_limit) still apply to these NPCs, this is just another rule added to the system.

mintime and maxtime both represent the in-game EQ Hour. Valid values are 1-24. If either or both of the values are 0, then the NPC will not have any time restriction.

Added a new rule World:BootHour. This allows server admins to specify the EQ hour the server will boot to. Valid options are 1-24. Setting this rule to 0 (default) disables it and world will use whatever time is specified in the DB.

* generated base_spawnentry_repository.h from script

* removed the rule insert from database_update_manifest.cpp.

* Add logging, initializers, minor cleanup

* Remove if/else branch

* Update eqtime.cpp

* Initializers, logging

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2023-11-18 14:23:04 -06:00
JJ 6ef182edfd [Database] Pull pet power from content database (#3689) 2023-11-18 14:19:33 -06:00
Paul Coene e466ca1c6d [Illusions] RandomizeFeastures erased texture. (#3686) 2023-11-12 16:02:54 -05:00
45 changed files with 1365 additions and 557 deletions
+58
View File
@@ -1,3 +1,61 @@
## [22.34.2] - 11/23/2023
### Admin
* Update date in changelog ([#3698](https://github.com/EQEmu/Server/pull/3698)) @joligario 2023-11-19
### Code
* Fix typo in #giveitem ([#3704](https://github.com/EQEmu/Server/pull/3704)) @Kinglykrab 2023-11-22
### Fixes
* Add "IgnoreLevelBasedHasteCaps" rule to GetHaste() ([#3705](https://github.com/EQEmu/Server/pull/3705)) @jcr4990 2023-11-23
* Fix bots/Mercenaries being removed from hatelist ([#3708](https://github.com/EQEmu/Server/pull/3708)) @Kinglykrab 2023-11-23
* Fix some spell types failing IsValidSpellRange check ([#3707](https://github.com/EQEmu/Server/pull/3707)) @nytmyr 2023-11-23
### Loginserver
* Update ticket login table structure ([#3703](https://github.com/EQEmu/Server/pull/3703)) @KimLS 2023-11-22
## [22.34.1] - 11/20/2023
### EQTime
Hotfix for world not spamming save messages by setting to detail level logging @Akkadius 2023-11-20
## [22.34.0] - 11/19/2023
### Bots
* Add ownerraid, byclass and byrace actionables and fix group-based arguments for raids. ([#3680](https://github.com/EQEmu/Server/pull/3680)) @nytmyr 2023-11-19
### Code
* Cleanup #giveitem and #summonitem ([#3692](https://github.com/EQEmu/Server/pull/3692)) @Kinglykrab 2023-11-19
* Cleanup #show currencies Command ([#3693](https://github.com/EQEmu/Server/pull/3693)) @Kinglykrab 2023-11-19
### Commands
* Add #show aa_points Command ([#3695](https://github.com/EQEmu/Server/pull/3695)) @Kinglykrab 2023-11-19
### Database
* Pull pet power from content database ([#3689](https://github.com/EQEmu/Server/pull/3689)) @joligario 2023-11-18
### GM Commands
* Add `#takeplatinum` ([#3690](https://github.com/EQEmu/Server/pull/3690)) @joligario 2023-11-19
* Remove duplicate comment ([#3691](https://github.com/EQEmu/Server/pull/3691)) @joligario 2023-11-19
### Illusions
* RandomizeFeastures erased texture. ([#3686](https://github.com/EQEmu/Server/pull/3686)) @noudess 2023-11-12
### Spawn
* (imported from takp) Added min_time and max_time to spawnentry. This will prevent a NPC from… ([#3685](https://github.com/EQEmu/Server/pull/3685)) @regneq 2023-11-18
## [22.33.0] - 11/11/2023
### Feature
+29 -21
View File
@@ -2097,37 +2097,45 @@ void Database::ClearInvSnapshots(bool from_now) {
struct TimeOfDay_Struct Database::LoadTime(time_t &realtime)
{
TimeOfDay_Struct eqTime;
std::string query = StringFormat("SELECT minute,hour,day,month,year,realtime FROM eqtime limit 1");
TimeOfDay_Struct t{};
std::string query = StringFormat("SELECT minute,hour,day,month,year,realtime FROM eqtime limit 1");
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount() == 0){
if (!results.Success() || results.RowCount() == 0) {
LogInfo("Loading EQ time of day failed. Using defaults");
eqTime.minute = 0;
eqTime.hour = 9;
eqTime.day = 1;
eqTime.month = 1;
eqTime.year = 3100;
t.minute = 0;
t.hour = 9;
t.day = 1;
t.month = 1;
t.year = 3100;
realtime = time(nullptr);
}
else{
auto row = results.begin();
eqTime.minute = Strings::ToUnsignedInt(row[0]);
eqTime.hour = Strings::ToUnsignedInt(row[1]);
eqTime.day = Strings::ToUnsignedInt(row[2]);
eqTime.month = Strings::ToUnsignedInt(row[3]);
eqTime.year = Strings::ToUnsignedInt(row[4]);
realtime = Strings::ToBigInt(row[5]);
return t;
}
return eqTime;
auto row = results.begin();
uint8 hour = Strings::ToUnsignedInt(row[1]);
time_t realtime_ = Strings::ToBigInt(row[5]);
if (RuleI(World, BootHour) > 0 && RuleI(World, BootHour) <= 24) {
hour = RuleI(World, BootHour);
realtime_ = time(nullptr);
}
t.minute = Strings::ToUnsignedInt(row[0]);
t.hour = hour;
t.day = Strings::ToUnsignedInt(row[2]);
t.month = Strings::ToUnsignedInt(row[3]);
t.year = Strings::ToUnsignedInt(row[4]);
realtime = realtime_;
LogEqTime("Setting hour to [{}]", hour);
return t;
}
bool Database::SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year)
{
std::string query = StringFormat("UPDATE eqtime set minute = %d, hour = %d, day = %d, month = %d, year = %d, realtime = %d limit 1", minute, hour, day, month, year, time(0));
std::string query = StringFormat("UPDATE eqtime set minute = %d, hour = %d, day = %d, month = %d, year = %d, realtime = %d limit 1", minute, hour, day, month, year, time(nullptr));
auto results = QueryDatabase(query);
return results.Success();
+12 -1
View File
@@ -5018,7 +5018,18 @@ INSERT INTO spawn2_disabled (spawn2_id, disabled) SELECT id, 1 FROM spawn2 WHERE
ALTER TABLE `spawn2` DROP COLUMN `enabled`;
)"
},
ManifestEntry{
.version = 9242,
.description = "2023_11_7_mintime_maxtime_spawnentry.sql",
.check = "SHOW COLUMNS FROM `spawnentry` LIKE 'min_time'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `spawnentry`
ADD COLUMN `min_time` smallint(4) NOT NULL DEFAULT 0 AFTER `condition_value_filter`,
ADD COLUMN `max_time` smallint(4) NOT NULL DEFAULT 0 AFTER `min_time`;
)"
},
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
// .version = 9228,
+16
View File
@@ -1059,4 +1059,20 @@ enum SpellTimeRestrictions
Night
};
enum MoneyTypes
{
Copper,
Silver,
Gold,
Platinum
};
enum MoneySubtypes
{
Personal,
Bank,
Cursor,
SharedBank // Platinum Only
};
#endif /*COMMON_EQ_CONSTANTS_H*/
+2
View File
@@ -100,6 +100,8 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults()
log_settings[Logs::Discord].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::QuestErrors].log_to_gmsay = static_cast<uint8>(Logs::General);
log_settings[Logs::QuestErrors].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::EqTime].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::EqTime].log_to_gmsay = static_cast<uint8>(Logs::General);
/**
* RFC 5424
+2
View File
@@ -139,6 +139,7 @@ namespace Logs {
PlayerEvents,
DataBuckets,
Zoning,
EqTime,
MaxCategoryID /* Don't Remove this */
};
@@ -237,6 +238,7 @@ namespace Logs {
"PlayerEvents",
"DataBuckets",
"Zoning",
"EqTime",
};
}
+10
View File
@@ -814,6 +814,16 @@
OutF(LogSys, Logs::Detail, Logs::Zoning, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogEqTime(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::EqTime))\
OutF(LogSys, Logs::General, Logs::EqTime, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogEqTimeDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::EqTime))\
OutF(LogSys, Logs::Detail, Logs::EqTime, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.IsLogEnabled(debug_level, log_category))\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
+31 -9
View File
@@ -46,16 +46,16 @@ EQTime::EQTime()
timezone = 0;
memset(&eqTime, 0, sizeof(eqTime));
//Defaults for time
TimeOfDay_Struct start;
start.day = 1;
start.hour = 9;
start.minute = 0;
start.month = 1;
start.year = 3100;
TimeOfDay_Struct t{};
t.day = 1;
t.hour = 9;
t.minute = 0;
t.month = 1;
t.year = 3100;
//Set default time zone
timezone = 0;
//Start EQTimer
SetCurrentEQTimeOfDay(start, time(0));
SetCurrentEQTimeOfDay(t, time(nullptr));
}
//getEQTimeOfDay - Reads timeConvert and writes the result to eqTimeOfDay
@@ -202,7 +202,7 @@ void EQTime::ToString(TimeOfDay_Struct *t, std::string &str) {
}
bool EQTime::IsDayTime() {
TimeOfDay_Struct tod; //Day time is 5am to 6:59pm (14 hours in-game)
TimeOfDay_Struct tod{}; //Day time is 5am to 6:59pm (14 hours in-game)
GetCurrentEQTimeOfDay(&tod); //TODO: what if it fails and returns zero?
if (tod.hour >= 5 || tod.hour < 19) {
@@ -213,7 +213,7 @@ bool EQTime::IsDayTime() {
}
bool EQTime::IsNightTime() {
TimeOfDay_Struct tod; //Night time is 7pm to 4:59am (10 hours in-game)
TimeOfDay_Struct tod{}; //Night time is 7pm to 4:59am (10 hours in-game)
GetCurrentEQTimeOfDay(&tod); //TODO: what if it fails and returns zero?
if (tod.hour >= 19 || tod.hour < 5) {
@@ -222,3 +222,25 @@ bool EQTime::IsNightTime() {
return false;
}
bool EQTime::IsInbetweenTime(uint8 min_time, uint8 max_time) {
TimeOfDay_Struct tod{};
GetCurrentEQTimeOfDay(&tod);
if (min_time == 0 || max_time == 0 || min_time > 24 || max_time > 24) {
return true;
}
if (max_time < min_time) {
if ((tod.hour >= min_time && tod.hour > max_time) || (tod.hour < min_time && tod.hour <= max_time)) {
return true;
}
}
else {
if (tod.hour >= min_time && tod.hour <= max_time) {
return true;
}
}
return false;
}
+1
View File
@@ -30,6 +30,7 @@ public:
uint32 getEQTimeZoneMin() { return timezone%60; }
bool IsDayTime();
bool IsNightTime();
bool IsInbetweenTime(uint8 min_time, uint8 max_time);
//Set functions
int SetCurrentEQTimeOfDay(TimeOfDay_Struct start_eq, time_t start_real);
@@ -16,6 +16,7 @@
#include "../../strings.h"
#include <ctime>
class BaseSpawnentryRepository {
public:
struct Spawnentry {
@@ -23,6 +24,8 @@ public:
int32_t npcID;
int16_t chance;
int32_t condition_value_filter;
int16_t min_time;
int16_t max_time;
int8_t min_expansion;
int8_t max_expansion;
std::string content_flags;
@@ -41,6 +44,8 @@ public:
"npcID",
"chance",
"condition_value_filter",
"min_time",
"max_time",
"min_expansion",
"max_expansion",
"content_flags",
@@ -55,6 +60,8 @@ public:
"npcID",
"chance",
"condition_value_filter",
"min_time",
"max_time",
"min_expansion",
"max_expansion",
"content_flags",
@@ -103,6 +110,8 @@ public:
e.npcID = 0;
e.chance = 0;
e.condition_value_filter = 1;
e.min_time = 0;
e.max_time = 0;
e.min_expansion = -1;
e.max_expansion = -1;
e.content_flags = "";
@@ -132,8 +141,9 @@ public:
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE id = {} LIMIT 1",
"{} WHERE {} = {} LIMIT 1",
BaseSelect(),
PrimaryKey(),
spawnentry_id
)
);
@@ -146,10 +156,12 @@ public:
e.npcID = static_cast<int32_t>(atoi(row[1]));
e.chance = static_cast<int16_t>(atoi(row[2]));
e.condition_value_filter = static_cast<int32_t>(atoi(row[3]));
e.min_expansion = static_cast<int8_t>(atoi(row[4]));
e.max_expansion = static_cast<int8_t>(atoi(row[5]));
e.content_flags = row[6] ? row[6] : "";
e.content_flags_disabled = row[7] ? row[7] : "";
e.min_time = static_cast<int16_t>(atoi(row[4]));
e.max_time = static_cast<int16_t>(atoi(row[5]));
e.min_expansion = static_cast<int8_t>(atoi(row[6]));
e.max_expansion = static_cast<int8_t>(atoi(row[7]));
e.content_flags = row[8] ? row[8] : "";
e.content_flags_disabled = row[9] ? row[9] : "";
return e;
}
@@ -187,10 +199,12 @@ public:
v.push_back(columns[1] + " = " + std::to_string(e.npcID));
v.push_back(columns[2] + " = " + std::to_string(e.chance));
v.push_back(columns[3] + " = " + std::to_string(e.condition_value_filter));
v.push_back(columns[4] + " = " + std::to_string(e.min_expansion));
v.push_back(columns[5] + " = " + std::to_string(e.max_expansion));
v.push_back(columns[6] + " = '" + Strings::Escape(e.content_flags) + "'");
v.push_back(columns[7] + " = '" + Strings::Escape(e.content_flags_disabled) + "'");
v.push_back(columns[4] + " = " + std::to_string(e.min_time));
v.push_back(columns[5] + " = " + std::to_string(e.max_time));
v.push_back(columns[6] + " = " + std::to_string(e.min_expansion));
v.push_back(columns[7] + " = " + std::to_string(e.max_expansion));
v.push_back(columns[8] + " = '" + Strings::Escape(e.content_flags) + "'");
v.push_back(columns[9] + " = '" + Strings::Escape(e.content_flags_disabled) + "'");
auto results = db.QueryDatabase(
fmt::format(
@@ -216,6 +230,8 @@ public:
v.push_back(std::to_string(e.npcID));
v.push_back(std::to_string(e.chance));
v.push_back(std::to_string(e.condition_value_filter));
v.push_back(std::to_string(e.min_time));
v.push_back(std::to_string(e.max_time));
v.push_back(std::to_string(e.min_expansion));
v.push_back(std::to_string(e.max_expansion));
v.push_back("'" + Strings::Escape(e.content_flags) + "'");
@@ -253,6 +269,8 @@ public:
v.push_back(std::to_string(e.npcID));
v.push_back(std::to_string(e.chance));
v.push_back(std::to_string(e.condition_value_filter));
v.push_back(std::to_string(e.min_time));
v.push_back(std::to_string(e.max_time));
v.push_back(std::to_string(e.min_expansion));
v.push_back(std::to_string(e.max_expansion));
v.push_back("'" + Strings::Escape(e.content_flags) + "'");
@@ -294,10 +312,12 @@ public:
e.npcID = static_cast<int32_t>(atoi(row[1]));
e.chance = static_cast<int16_t>(atoi(row[2]));
e.condition_value_filter = static_cast<int32_t>(atoi(row[3]));
e.min_expansion = static_cast<int8_t>(atoi(row[4]));
e.max_expansion = static_cast<int8_t>(atoi(row[5]));
e.content_flags = row[6] ? row[6] : "";
e.content_flags_disabled = row[7] ? row[7] : "";
e.min_time = static_cast<int16_t>(atoi(row[4]));
e.max_time = static_cast<int16_t>(atoi(row[5]));
e.min_expansion = static_cast<int8_t>(atoi(row[6]));
e.max_expansion = static_cast<int8_t>(atoi(row[7]));
e.content_flags = row[8] ? row[8] : "";
e.content_flags_disabled = row[9] ? row[9] : "";
all_entries.push_back(e);
}
@@ -326,10 +346,12 @@ public:
e.npcID = static_cast<int32_t>(atoi(row[1]));
e.chance = static_cast<int16_t>(atoi(row[2]));
e.condition_value_filter = static_cast<int32_t>(atoi(row[3]));
e.min_expansion = static_cast<int8_t>(atoi(row[4]));
e.max_expansion = static_cast<int8_t>(atoi(row[5]));
e.content_flags = row[6] ? row[6] : "";
e.content_flags_disabled = row[7] ? row[7] : "";
e.min_time = static_cast<int16_t>(atoi(row[4]));
e.max_time = static_cast<int16_t>(atoi(row[5]));
e.min_expansion = static_cast<int8_t>(atoi(row[6]));
e.max_expansion = static_cast<int8_t>(atoi(row[7]));
e.content_flags = row[8] ? row[8] : "";
e.content_flags_disabled = row[9] ? row[9] : "";
all_entries.push_back(e);
}
@@ -120,6 +120,7 @@ public:
{.parent_command = "set", .sub_command = "title_suffix", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "titlesuffix"},
{.parent_command = "set", .sub_command = "weather", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "weather"},
{.parent_command = "set", .sub_command = "zone", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "zclip|zcolor|zheader|zonelock|zsafecoords|zsky|zunderworld"},
{.parent_command = "show", .sub_command = "aa_points", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "showaapoints|showaapts"},
{.parent_command = "show", .sub_command = "aggro", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "aggro"},
{.parent_command = "show", .sub_command = "buffs", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "showbuffs"},
{.parent_command = "show", .sub_command = "buried_corpse_count", .access_level = AccountStatus::QuestTroupe, .top_level_aliases = "getplayerburiedcorpsecount"},
+1
View File
@@ -303,6 +303,7 @@ RULE_BOOL(World, EnforceCharacterLimitAtLogin, false, "Enforce the limit for cha
RULE_BOOL(World, EnableDevTools, true, "Enable or Disable the Developer Tools globally (Most of the time you want this enabled)")
RULE_BOOL(World, EnableChecksumVerification, false, "Enable or Disable the Checksum Verification for eqgame.exe and spells_us.txt")
RULE_INT(World, MaximumQuestErrors, 30, "Changes the maximum number of quest errors that can be displayed in #questerrors, default is 30")
RULE_INT(World, BootHour, 0, "Sets the in-game hour world will set when it first boots. 0-24 are valid options, where 0 disables this rule")
RULE_CATEGORY_END()
RULE_CATEGORY(Zone)
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "22.33.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "22.34.2-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9241
#define CURRENT_BINARY_DATABASE_VERSION 9242
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9040
+10 -28
View File
@@ -121,43 +121,25 @@ bool Database::GetLoginTokenDataFromToken(
std::string &user
)
{
auto query = fmt::format(
"SELECT tbllogintokens.Id, tbllogintokens.IpAddress, tbllogintokenclaims.Name, tbllogintokenclaims.Value FROM tbllogintokens "
"JOIN tbllogintokenclaims ON tbllogintokens.Id = tbllogintokenclaims.TokenId WHERE tbllogintokens.Expires > NOW() "
"AND tbllogintokens.Id='{0}' AND tbllogintokens.IpAddress='{1}'",
auto query = fmt::format("SELECT login_server, username, account_id FROM login_tickets WHERE expires > NOW()"
" AND id='{0}' AND ip_address='{1}' LIMIT 1",
Strings::Escape(token),
Strings::Escape(ip)
);
Strings::Escape(ip));
auto results = QueryDatabase(query);
if (results.RowCount() == 0 || !results.Success()) {
return false;
}
bool found_username = false;
bool found_login_id = false;
bool found_login_server_name = false;
for (auto row = results.begin(); row != results.end(); ++row) {
if (strcmp(row[2], "username") == 0) {
user = row[3];
found_username = true;
continue;
}
if (strcmp(row[2], "login_server_id") == 0) {
db_account_id = Strings::ToUnsignedInt(row[3]);
found_login_id = true;
continue;
}
if (strcmp(row[2], "login_server_name") == 0) {
db_loginserver = row[3];
found_login_server_name = true;
continue;
}
for (auto row = results.begin(); row != results.end(); ++row) {
db_loginserver = row[0];
user = row[1];
db_account_id = Strings::ToUnsignedInt(row[2]);
return true;
}
return found_username && found_login_id && found_login_server_name;
return false;
}
/**
+11
View File
@@ -0,0 +1,11 @@
DROP TABLE IF EXISTS `login_tickets`;
CREATE TABLE `login_tickets` (
`id` VARCHAR(128) NOT NULL,
`login_server` TEXT NOT NULL,
`username` TEXT NOT NULL,
`account_id` INT(10) UNSIGNED NOT NULL,
`ip_address` VARCHAR(45) NOT NULL,
`expires` DATETIME NOT NULL,
PRIMARY KEY (`id`) USING BTREE
)
ENGINE=InnoDB;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "22.33.0",
"version": "22.34.2",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+14 -6
View File
@@ -424,12 +424,20 @@ int main(int argc, char **argv)
}
if (EQTimeTimer.Check()) {
TimeOfDay_Struct tod;
zoneserver_list.worldclock.GetCurrentEQTimeOfDay(time(0), &tod);
if (!database.SaveTime(tod.minute, tod.hour, tod.day, tod.month, tod.year))
LogError("Failed to save eqtime");
else
LogDebug("EQTime successfully saved");
TimeOfDay_Struct tod{};
zoneserver_list.worldclock.GetCurrentEQTimeOfDay(time(nullptr), &tod);
if (!database.SaveTime(tod.minute, tod.hour, tod.day, tod.month, tod.year)) {
LogEqTime("Failed to save eqtime");
}
else {
LogEqTimeDetail("EQTime successfully saved - time is now year [{}] month [{}] day [{}] hour [{}] minute [{}]",
tod.year,
tod.month,
tod.day,
tod.hour,
tod.minute
);
}
}
zoneserver_list.Process();
+639 -201
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -183,7 +183,7 @@ bool Bot::BotCastHateReduction(Mob* tar, uint8 botLevel, const BotSpell& botSpel
if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0)
continue;
if (IsValidSpellRange(botSpell.SpellId, tar)) {
if (IsValidSpellRange(iter.SpellId, tar)) {
casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
}
if (casted_spell) {
@@ -328,7 +328,7 @@ bool Bot::BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe
continue;
}
if (IsValidSpellRange(botSpell.SpellId, tar)) {
if (IsValidSpellRange(iter.SpellId, tar)) {
casted_spell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost);
}
@@ -447,7 +447,7 @@ bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const b
uint32 TempDontDotMeBefore = tar->DontDotMeBefore();
if (IsValidSpellRange(botSpell.SpellId, tar)) {
if (IsValidSpellRange(s.SpellId, tar)) {
casted_spell = AIDoSpellCast(s.SpellIndex, tar, s.ManaCost, &TempDontDotMeBefore);
}
+23 -21
View File
@@ -2262,10 +2262,10 @@ void Client::QuestReadBook(const char* text, uint8 type) {
uint32 Client::GetCarriedPlatinum() {
return (
GetMoney(3, 0) +
(GetMoney(2, 0) / 10) +
(GetMoney(1, 0) / 100) +
(GetMoney(0, 0) / 1000)
GetMoney(MoneyTypes::Platinum, MoneySubtypes::Personal) +
(GetMoney(MoneyTypes::Gold, MoneySubtypes::Personal) / 10) +
(GetMoney(MoneyTypes::Silver, MoneySubtypes::Personal) / 100) +
(GetMoney(MoneyTypes::Copper, MoneySubtypes::Personal) / 1000)
);
}
@@ -8119,16 +8119,17 @@ void Client::SendHPUpdateMarquee(){
uint32 Client::GetMoney(uint8 type, uint8 subtype) {
uint32 value = 0;
switch (type) {
case 0: {
case MoneyTypes::Copper: {
switch (subtype) {
case 0:
case MoneySubtypes::Personal:
value = static_cast<uint32>(m_pp.copper);
break;
case 1:
case MoneySubtypes::Bank:
value = static_cast<uint32>(m_pp.copper_bank);
break;
case 2:
case MoneySubtypes::Cursor:
value = static_cast<uint32>(m_pp.copper_cursor);
break;
default:
@@ -8136,15 +8137,15 @@ uint32 Client::GetMoney(uint8 type, uint8 subtype) {
}
break;
}
case 1: {
case MoneyTypes::Silver: {
switch (subtype) {
case 0:
case MoneySubtypes::Personal:
value = static_cast<uint32>(m_pp.silver);
break;
case 1:
case MoneySubtypes::Bank:
value = static_cast<uint32>(m_pp.silver_bank);
break;
case 2:
case MoneySubtypes::Cursor:
value = static_cast<uint32>(m_pp.silver_cursor);
break;
default:
@@ -8152,15 +8153,15 @@ uint32 Client::GetMoney(uint8 type, uint8 subtype) {
}
break;
}
case 2: {
case MoneyTypes::Gold: {
switch (subtype) {
case 0:
case MoneySubtypes::Personal:
value = static_cast<uint32>(m_pp.gold);
break;
case 1:
case MoneySubtypes::Bank:
value = static_cast<uint32>(m_pp.gold_bank);
break;
case 2:
case MoneySubtypes::Cursor:
value = static_cast<uint32>(m_pp.gold_cursor);
break;
default:
@@ -8168,18 +8169,18 @@ uint32 Client::GetMoney(uint8 type, uint8 subtype) {
}
break;
}
case 3: {
case MoneyTypes::Platinum: {
switch (subtype) {
case 0:
case MoneySubtypes::Personal:
value = static_cast<uint32>(m_pp.platinum);
break;
case 1:
case MoneySubtypes::Bank:
value = static_cast<uint32>(m_pp.platinum_bank);
break;
case 2:
case MoneySubtypes::Cursor:
value = static_cast<uint32>(m_pp.platinum_cursor);
break;
case 3:
case MoneySubtypes::SharedBank:
value = static_cast<uint32>(m_pp.platinum_shared);
break;
default:
@@ -8190,6 +8191,7 @@ uint32 Client::GetMoney(uint8 type, uint8 subtype) {
default:
break;
}
return value;
}
+2
View File
@@ -217,6 +217,7 @@ int command_init(void)
command_add("summonitem", "[itemid] [charges] - Summon an item onto your cursor. Charges are optional.", AccountStatus::GMMgmt, command_summonitem) ||
command_add("suspend", "[name] [days] [reason] - Suspend by character name and for specificed number of days", AccountStatus::GMLeadAdmin, command_suspend) ||
command_add("suspendmulti", "[Character Name One|Character Name Two|etc] [Days] [Reason] - Suspend multiple characters by name for specified number of days", AccountStatus::GMLeadAdmin, command_suspendmulti) ||
command_add("takeplatinum", "[Platinum] - Takes specified amount of platinum from you or your player target", AccountStatus::GMMgmt, command_takeplatinum) ||
command_add("task", "(subcommand) - Task system commands", AccountStatus::GMLeadAdmin, command_task) ||
command_add("petname", "[newname] - Temporarily renames your pet. Leave name blank to restore the original name.", AccountStatus::GMAdmin, command_petname) ||
command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", AccountStatus::GMLeadAdmin, command_traindisc) ||
@@ -903,6 +904,7 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/summonitem.cpp"
#include "gm_commands/suspend.cpp"
#include "gm_commands/suspendmulti.cpp"
#include "gm_commands/takeplatinum.cpp"
#include "gm_commands/task.cpp"
#include "gm_commands/traindisc.cpp"
#include "gm_commands/tune.cpp"
+1
View File
@@ -168,6 +168,7 @@ void command_summonburiedplayercorpse(Client *c, const Seperator *sep);
void command_summonitem(Client *c, const Seperator *sep);
void command_suspend(Client *c, const Seperator *sep);
void command_suspendmulti(Client *c, const Seperator *sep);
void command_takeplatinum(Client* c, const Seperator* sep);
void command_task(Client *c, const Seperator *sep);
void command_petname(Client *c, const Seperator *sep);
void command_traindisc(Client *c, const Seperator *sep);
+1 -1
View File
@@ -1440,7 +1440,7 @@ void PerlembParser::ExportZoneVariables(std::string &package_name)
ExportVar(package_name.c_str(), "zonesn", zone->GetShortName());
ExportVar(package_name.c_str(), "instanceid", zone->GetInstanceID());
ExportVar(package_name.c_str(), "instanceversion", zone->GetInstanceVersion());
TimeOfDay_Struct eqTime;
TimeOfDay_Struct eqTime{};
zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eqTime);
ExportVar(package_name.c_str(), "zonehour", eqTime.hour - 1);
ExportVar(package_name.c_str(), "zonemin", eqTime.minute);
+134 -104
View File
@@ -2,110 +2,140 @@
void command_giveitem(Client *c, const Seperator *sep)
{
uint32 item_id = 0;
int16 charges = -1;
uint32 augment_one = 0;
uint32 augment_two = 0;
uint32 augment_three = 0;
uint32 augment_four = 0;
uint32 augment_five = 0;
uint32 augment_six = 0;
int arguments = sep->argnum;
std::string cmd_msg = sep->msg;
size_t link_open = cmd_msg.find('\x12');
size_t link_close = cmd_msg.find_last_of('\x12');
if (c->GetTarget()) {
if (!c->GetTarget()->IsClient()) {
c->Message(Chat::Red, "You can only give items to players with this command.");
return;
}
if (link_open != link_close && (cmd_msg.length() - link_open) > EQ::constants::SAY_LINK_BODY_SIZE) {
EQ::SayLinkBody_Struct link_body;
EQ::saylink::DegenerateLinkBody(
link_body,
cmd_msg.substr(link_open + 1, EQ::constants::SAY_LINK_BODY_SIZE));
item_id = link_body.item_id;
augment_one = link_body.augment_1;
augment_two = link_body.augment_2;
augment_three = link_body.augment_3;
augment_four = link_body.augment_4;
augment_five = link_body.augment_5;
augment_six = link_body.augment_6;
}
else if (sep->IsNumber(1)) {
item_id = Strings::ToInt(sep->arg[1]);
}
else if (!sep->IsNumber(1)) {
c->Message(
Chat::Red,
"Usage: #giveitem [item id | link] [charges] [augment_one_id] [augment_two_id] [augment_three_id] [augment_four_id] [augment_five_id] [augment_six_id] (Charges are optional.)"
);
return;
}
Client *client_target = c->GetTarget()->CastToClient();
uint8 item_status = 0;
uint8 current_status = c->Admin();
const EQ::ItemData *item = database.GetItem(item_id);
if (item) {
item_status = item->MinStatus;
}
if (item_status > current_status) {
c->Message(
Chat::White,
fmt::format(
"Insufficient status to summon this item, current status is {}, required status is {}.",
current_status,
item_status
).c_str()
);
return;
}
if (arguments >= 2 && sep->IsNumber(2)) {
charges = Strings::ToInt(sep->arg[2]);
}
if (arguments >= 3 && sep->IsNumber(3)) {
augment_one = Strings::ToInt(sep->arg[3]);
}
if (arguments >= 4 && sep->IsNumber(4)) {
augment_two = Strings::ToInt(sep->arg[4]);
}
if (arguments >= 5 && sep->IsNumber(5)) {
augment_three = Strings::ToInt(sep->arg[5]);
}
if (arguments >= 6 && sep->IsNumber(6)) {
augment_four = Strings::ToInt(sep->arg[6]);
}
if (arguments >= 7 && sep->IsNumber(7)) {
augment_five = Strings::ToInt(sep->arg[7]);
}
if (arguments == 8 && sep->IsNumber(8)) {
augment_six = Strings::ToInt(sep->arg[8]);
}
client_target->SummonItem(
item_id,
charges,
augment_one,
augment_two,
augment_three,
augment_four,
augment_five,
augment_six
);
}
else {
c->Message(Chat::Red, "You must target a client to give the item to.");
if (!c->GetTarget() || !c->GetTarget()->IsClient()) {
c->Message(Chat::White, "You must target a player to use this command.");
return;
}
}
uint32 item_id = 0;
int16 charges = -1;
uint32 augment_one = 0;
uint32 augment_two = 0;
uint32 augment_three = 0;
uint32 augment_four = 0;
uint32 augment_five = 0;
uint32 augment_six = 0;
const uint16 arguments = sep->argnum;
std::string cmd_msg = sep->msg;
size_t link_open = cmd_msg.find('\x12');
size_t link_close = cmd_msg.find_last_of('\x12');
if (link_open != link_close && (cmd_msg.length() - link_open) > EQ::constants::SAY_LINK_BODY_SIZE) {
EQ::SayLinkBody_Struct link_body;
EQ::saylink::DegenerateLinkBody(
link_body,
cmd_msg.substr(link_open + 1, EQ::constants::SAY_LINK_BODY_SIZE)
);
item_id = link_body.item_id;
augment_one = link_body.augment_1;
augment_two = link_body.augment_2;
augment_three = link_body.augment_3;
augment_four = link_body.augment_4;
augment_five = link_body.augment_5;
augment_six = link_body.augment_6;
} else if (sep->IsNumber(1)) {
item_id = Strings::ToUnsignedInt(sep->arg[1]);
} else if (!sep->IsNumber(1)) {
c->Message(
Chat::Red,
"Usage: #giveitem [item id | link] [charges] [augment_one_id] [augment_two_id] [augment_three_id] [augment_four_id] [augment_five_id] [augment_six_id] (Charges and augments are optional.)"
);
return;
}
Client* t = c->GetTarget()->CastToClient();
uint8 item_status = 0;
const uint8 current_status = c->Admin();
const auto *item = database.GetItem(item_id);
if (!item) {
c->Message(
Chat::White,
fmt::format(
"Item ID {} does not exist.",
item_id
).c_str()
);
return;
}
item_status = item->MinStatus;
if (item_status > current_status) {
c->Message(
Chat::White,
fmt::format(
"Insufficient status to summon this item, current status is {}, required status is {}.",
current_status,
item_status
).c_str()
);
return;
}
if (arguments >= 2 && sep->IsNumber(2)) {
charges = static_cast<int16>(Strings::ToInt(sep->arg[2]));
}
if (arguments >= 3 && sep->IsNumber(3)) {
augment_one = Strings::ToUnsignedInt(sep->arg[3]);
}
if (arguments >= 4 && sep->IsNumber(4)) {
augment_two = Strings::ToUnsignedInt(sep->arg[4]);
}
if (arguments >= 5 && sep->IsNumber(5)) {
augment_three = Strings::ToUnsignedInt(sep->arg[5]);
}
if (arguments >= 6 && sep->IsNumber(6)) {
augment_four = Strings::ToUnsignedInt(sep->arg[6]);
}
if (arguments >= 7 && sep->IsNumber(7)) {
augment_five = Strings::ToUnsignedInt(sep->arg[7]);
}
if (arguments == 8 && sep->IsNumber(8)) {
augment_six = Strings::ToUnsignedInt(sep->arg[8]);
}
t->SummonItem(
item_id,
charges,
augment_one,
augment_two,
augment_three,
augment_four,
augment_five,
augment_six
);
const auto *new_item = database.CreateItem(
item_id,
charges,
augment_one,
augment_two,
augment_three,
augment_four,
augment_five,
augment_six
);
EQ::SayLinkEngine linker;
linker.SetLinkType(EQ::saylink::SayLinkItemInst);
linker.SetItemInst(new_item);
const std::string &item_link = linker.GenerateLink();
c->Message(
Chat::White,
fmt::format(
"You have given {} to {}.",
item_link,
c->GetTargetDescription(t)
).c_str()
);
}
+2 -2
View File
@@ -4,7 +4,7 @@ void command_givemoney(Client *c, const Seperator *sep)
{
int arguments = sep->argnum;
if (!arguments || !sep->IsNumber(1)) { //as long as the first one is a number, we'll just let atoi convert the rest to 0 or a number
c->Message(Chat::Red, "Usage: #Usage: #givemoney [Platinum] [Gold] [Silver] [Copper]");
c->Message(Chat::Red, "Usage: #givemoney [Platinum] [Gold] [Silver] [Copper]");
return;
}
@@ -19,7 +19,7 @@ void command_givemoney(Client *c, const Seperator *sep)
uint32 silver = sep->IsNumber(3) ? Strings::ToUnsignedInt(sep->arg[3]) : 0;
uint32 copper = sep->IsNumber(4) ? Strings::ToUnsignedInt(sep->arg[4]) : 0;
if (!platinum && !gold && !silver && !copper) {
c->Message(Chat::Red, "Usage: #Usage: #givemoney [Platinum] [Gold] [Silver] [Copper]");
c->Message(Chat::Red, "Usage: #givemoney [Platinum] [Gold] [Silver] [Copper]");
return;
}
+4 -4
View File
@@ -14,14 +14,14 @@ void SetDate(Client *c, const Seperator *sep)
return;
}
TimeOfDay_Struct eq_time;
zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eq_time);
TimeOfDay_Struct t{};
zone->zone_time.GetCurrentEQTimeOfDay(time(0), &t);
const uint16 year = Strings::ToUnsignedInt(sep->arg[2]);
const uint8 month = Strings::ToUnsignedInt(sep->arg[3]);
const uint8 day = Strings::ToUnsignedInt(sep->arg[4]);
const uint8 hour = !sep->IsNumber(5) ? eq_time.hour : Strings::ToUnsignedInt(sep->arg[5]) + 1;
const uint8 minute = !sep->IsNumber(6) ? eq_time.minute : Strings::ToUnsignedInt(sep->arg[6]);
const uint8 hour = !sep->IsNumber(5) ? t.hour : Strings::ToUnsignedInt(sep->arg[5]) + 1;
const uint8 minute = !sep->IsNumber(6) ? t.minute : Strings::ToUnsignedInt(sep->arg[6]);
c->Message(
Chat::White,
+1 -1
View File
@@ -6,7 +6,7 @@ void SetTime(Client *c, const Seperator *sep)
if (arguments < 2 || !sep->IsNumber(2)) {
c->Message(Chat::White, "Usage: #set time [Hour] [Minute]");
TimeOfDay_Struct world_time;
TimeOfDay_Struct world_time{};
zone->zone_time.GetCurrentEQTimeOfDay(time(0), &world_time);
auto time_string = fmt::format(
+2
View File
@@ -1,4 +1,5 @@
#include "../client.h"
#include "show/aa_points.cpp"
#include "show/aggro.cpp"
#include "show/buffs.cpp"
#include "show/buried_corpse_count.cpp"
@@ -55,6 +56,7 @@ void command_show(Client *c, const Seperator *sep)
};
std::vector<Cmd> commands = {
Cmd{.cmd = "aa_points", .u = "aa_points", .fn = ShowAAPoints, .a = {"#showaapoints", "#showaapts"}},
Cmd{.cmd = "aggro", .u = "aggro [Distance] [-v] (-v is verbose Faction Information)", .fn = ShowAggro, .a = {"#aggro"}},
Cmd{.cmd = "buffs", .u = "buffs", .fn = ShowBuffs, .a = {"#showbuffs"}},
Cmd{.cmd = "buried_corpse_count", .u = "buried_corpse_count", .fn = ShowBuriedCorpseCount, .a = {"#getplayerburiedcorpsecount"}},
+37
View File
@@ -0,0 +1,37 @@
#include "../../client.h"
#include "../../dialogue_window.h"
void ShowAAPoints(Client *c, const Seperator *sep)
{
Client *t = c;
if (c->GetTarget() && c->GetTarget()->IsClient()) {
t = c->GetTarget()->CastToClient();
}
const int aa_points = t->GetAAPoints();
const int spent_aa_points = t->GetSpentAA();
const int total_aa_points = (aa_points + spent_aa_points);
if (!total_aa_points) {
c->Message(
Chat::White,
fmt::format(
"{} {} no AA Points.",
c->GetTargetDescription(t, TargetDescriptionType::UCYou),
c == t ? "have" : "has"
).c_str()
);
return;
}
c->Message(
Chat::White,
fmt::format(
"AA Points for {} | Current: {} Spent: {} Total: {}",
c->GetTargetDescription(t, TargetDescriptionType::UCSelf),
Strings::Commify(aa_points),
Strings::Commify(spent_aa_points),
Strings::Commify(total_aa_points)
).c_str()
);
}
+20 -20
View File
@@ -3,34 +3,34 @@
void ShowCurrencies(Client *c, const Seperator *sep)
{
auto t = c;
Client *t = c;
if (c->GetTarget() && c->GetTarget()->IsClient()) {
t = c->GetTarget()->CastToClient();
}
const uint32 platinum = (
t->GetMoney(3, 0) +
t->GetMoney(3, 1) +
t->GetMoney(3, 2) +
t->GetMoney(3, 3)
const uint64 platinum = (
t->GetMoney(MoneyTypes::Platinum, MoneySubtypes::Personal) +
t->GetMoney(MoneyTypes::Platinum, MoneySubtypes::Bank) +
t->GetMoney(MoneyTypes::Platinum, MoneySubtypes::Cursor) +
t->GetMoney(MoneyTypes::Platinum, MoneySubtypes::SharedBank)
);
const uint32 gold = (
t->GetMoney(2, 0) +
t->GetMoney(2, 1) +
t->GetMoney(2, 2)
const uint64 gold = (
t->GetMoney(MoneyTypes::Gold, MoneySubtypes::Personal) +
t->GetMoney(MoneyTypes::Gold, MoneySubtypes::Bank) +
t->GetMoney(MoneyTypes::Gold, MoneySubtypes::Cursor)
);
const uint32 silver = (
t->GetMoney(1, 0) +
t->GetMoney(1, 1) +
t->GetMoney(1, 2)
const uint64 silver = (
t->GetMoney(MoneyTypes::Silver, MoneySubtypes::Personal) +
t->GetMoney(MoneyTypes::Silver, MoneySubtypes::Bank) +
t->GetMoney(MoneyTypes::Silver, MoneySubtypes::Cursor)
);
const uint32 copper = (
t->GetMoney(0, 0) +
t->GetMoney(0, 1) +
t->GetMoney(0, 2)
const uint64 copper = (
t->GetMoney(MoneyTypes::Copper, MoneySubtypes::Personal) +
t->GetMoney(MoneyTypes::Copper, MoneySubtypes::Bank) +
t->GetMoney(MoneyTypes::Copper, MoneySubtypes::Cursor)
);
std::string currency_table;
@@ -79,9 +79,9 @@ void ShowCurrencies(Client *c, const Seperator *sep)
for (const auto& a : zone->AlternateCurrencies) {
const uint32 currency_value = t->GetAlternateCurrencyValue(a.id);
if (currency_value) {
const auto* d = database.GetItem(a.item_id);
const auto *item = database.GetItem(a.item_id);
currency_table += DialogueWindow::TableRow(
DialogueWindow::TableCell(d->Name) +
DialogueWindow::TableCell(item->Name) +
DialogueWindow::TableCell(Strings::Commify(currency_value))
);
+75 -32
View File
@@ -2,18 +2,19 @@
void command_summonitem(Client *c, const Seperator *sep)
{
uint32 item_id = 0;
int16 charges = -1;
uint32 augment_one = 0;
uint32 augment_two = 0;
uint32 augment_three = 0;
uint32 augment_four = 0;
uint32 augment_five = 0;
uint32 augment_six = 0;
int arguments = sep->argnum;
std::string cmd_msg = sep->msg;
size_t link_open = cmd_msg.find('\x12');
size_t link_close = cmd_msg.find_last_of('\x12');
uint32 item_id = 0;
int16 charges = -1;
uint32 augment_one = 0;
uint32 augment_two = 0;
uint32 augment_three = 0;
uint32 augment_four = 0;
uint32 augment_five = 0;
uint32 augment_six = 0;
const uint16 arguments = sep->argnum;
std::string cmd_msg = sep->msg;
size_t link_open = cmd_msg.find('\x12');
size_t link_close = cmd_msg.find_last_of('\x12');
if (link_open != link_close && (cmd_msg.length() - link_open) > EQ::constants::SAY_LINK_BODY_SIZE) {
EQ::SayLinkBody_Struct link_body;
EQ::saylink::DegenerateLinkBody(link_body, cmd_msg.substr(link_open + 1, EQ::constants::SAY_LINK_BODY_SIZE));
@@ -24,16 +25,14 @@ void command_summonitem(Client *c, const Seperator *sep)
augment_four = link_body.augment_4;
augment_five = link_body.augment_5;
augment_six = link_body.augment_6;
}
else if (!sep->IsNumber(1)) {
} else if (!sep->IsNumber(1)) {
c->Message(
Chat::White,
"Usage: #summonitem [item id | link] [charges] [augment_one_id] [augment_two_id] [augment_three_id] [augment_four_id] [augment_five_id] [augment_six_id] (Charges are optional.)"
"Usage: #summonitem [item id | link] [charges] [augment_one_id] [augment_two_id] [augment_three_id] [augment_four_id] [augment_five_id] [augment_six_id] (Charges and augments are optional.)"
);
return;
}
else {
item_id = Strings::ToInt(sep->arg[1]);
} else {
item_id = Strings::ToUnsignedInt(sep->arg[1]);
}
if (!item_id) {
@@ -41,13 +40,23 @@ void command_summonitem(Client *c, const Seperator *sep)
return;
}
uint8 item_status = 0;
uint8 current_status = c->Admin();
const EQ::ItemData *item = database.GetItem(item_id);
if (item) {
item_status = item->MinStatus;
uint8 item_status = 0;
const uint8 current_status = c->Admin();
const auto *item = database.GetItem(item_id);
if (!item) {
c->Message(
Chat::White,
fmt::format(
"Item ID {} does not exist.",
item_id
).c_str()
);
return;
}
item_status = item->MinStatus;
if (item_status > current_status) {
c->Message(
Chat::White,
@@ -57,36 +66,70 @@ void command_summonitem(Client *c, const Seperator *sep)
item_status
).c_str()
);
return;
}
if (arguments >= 2 && sep->IsNumber(2)) {
charges = Strings::ToInt(sep->arg[2]);
charges = static_cast<int16>(Strings::ToInt(sep->arg[2]));
}
if (arguments >= 3 && sep->IsNumber(3)) {
augment_one = Strings::ToInt(sep->arg[3]);
augment_one = Strings::ToUnsignedInt(sep->arg[3]);
}
if (arguments >= 4 && sep->IsNumber(4)) {
augment_two = Strings::ToInt(sep->arg[4]);
augment_two = Strings::ToUnsignedInt(sep->arg[4]);
}
if (arguments >= 5 && sep->IsNumber(5)) {
augment_three = Strings::ToInt(sep->arg[5]);
augment_three = Strings::ToUnsignedInt(sep->arg[5]);
}
if (arguments >= 6 && sep->IsNumber(6)) {
augment_four = Strings::ToInt(sep->arg[6]);
augment_four = Strings::ToUnsignedInt(sep->arg[6]);
}
if (arguments >= 7 && sep->IsNumber(7)) {
augment_five = Strings::ToInt(sep->arg[7]);
augment_five = Strings::ToUnsignedInt(sep->arg[7]);
}
if (arguments == 8 && sep->IsNumber(8)) {
augment_six = Strings::ToInt(sep->arg[8]);
augment_six = Strings::ToUnsignedInt(sep->arg[8]);
}
c->SummonItem(item_id, charges, augment_one, augment_two, augment_three, augment_four, augment_five, augment_six);
}
c->SummonItem(
item_id,
charges,
augment_one,
augment_two,
augment_three,
augment_four,
augment_five,
augment_six
);
const auto *new_item = database.CreateItem(
item_id,
charges,
augment_one,
augment_two,
augment_three,
augment_four,
augment_five,
augment_six
);
EQ::SayLinkEngine linker;
linker.SetLinkType(EQ::saylink::SayLinkItemInst);
linker.SetItemInst(new_item);
const std::string &item_link = linker.GenerateLink();
c->Message(
Chat::White,
fmt::format(
"You have summoned {}.",
item_link
).c_str()
);
}
+57
View File
@@ -0,0 +1,57 @@
#include "../client.h"
void command_takeplatinum(Client *c, const Seperator *sep)
{
int arguments = sep->argnum;
if (!arguments || !sep->IsNumber(1)) { //must be a number
c->Message(Chat::Red, "Usage: #takeplatinum [Platinum]");
return;
}
Client *target = c;
if (c->GetTarget() && c->GetTarget()->IsClient()) {
target = c->GetTarget()->CastToClient();
}
uint32 platinum = Strings::ToUnsignedInt(sep->arg[1]);
if (!platinum) {
c->Message(Chat::Red, "Usage: #takeplatinum [Platinum]");
return;
}
bool success = target->TakePlatinum(
platinum,
true
);
if (success) {
c->Message(
Chat::White,
fmt::format(
"Subtracted {} from {}.",
Strings::Money(
platinum,
0,
0,
0
),
c->GetTargetDescription(target)
).c_str()
);
}
else {
c->Message(
Chat::Red,
fmt::format(
"Unable to subtract {} from {}.",
Strings::Money(
platinum,
0,
0,
0
),
c->GetTargetDescription(target)
).c_str()
);
}
}
+24 -17
View File
@@ -42,27 +42,34 @@ HateList::~HateList()
{
}
void HateList::WipeHateList()
{
void HateList::WipeHateList(bool npc_only) {
auto iterator = list.begin();
while (iterator != list.end()) {
Mob *m = (*iterator)->entity_on_hatelist;
if (
m &&
(
m->IsOfClientBotMerc() ||
(m->IsPet() && m->GetOwner() && m->GetOwner()->IsOfClientBotMerc())
) &&
npc_only
) {
iterator++;
} else {
if (m) {
if (parse->HasQuestSub(hate_owner->GetNPCTypeID(), EVENT_HATE_LIST)) {
parse->EventNPC(EVENT_HATE_LIST, hate_owner->CastToNPC(), m, "0", 0);
}
while (iterator != list.end())
{
Mob* m = (*iterator)->entity_on_hatelist;
if (m)
{
if (parse->HasQuestSub(hate_owner->GetNPCTypeID(), EVENT_HATE_LIST)) {
parse->EventNPC(EVENT_HATE_LIST, hate_owner->CastToNPC(), m, "0", 0);
}
if (m->IsClient()) {
m->CastToClient()->DecrementAggroCount();
m->CastToClient()->RemoveXTarget(hate_owner, true);
}
if (m->IsClient()) {
m->CastToClient()->DecrementAggroCount();
m->CastToClient()->RemoveXTarget(hate_owner, true);
delete (*iterator);
iterator = list.erase(iterator);
}
}
delete (*iterator);
iterator = list.erase(iterator);
}
}
@@ -734,7 +741,7 @@ int HateList::AreaRampage(Mob *caster, Mob *target, int count, ExtraAttackOption
caster->CombatRange(h->entity_on_hatelist, 1.0, true, opts)) {
id_list.push_back(h->entity_on_hatelist->GetID());
}
if (count != -1 && id_list.size() > count) {
break;
}
+1 -1
View File
@@ -88,7 +88,7 @@ public:
void SetHateAmountOnEnt(Mob *other, int64 in_hate, uint64 in_damage);
void SetHateOwner(Mob *new_hate_owner) { hate_owner = new_hate_owner; }
void SpellCast(Mob *caster, uint32 spell_id, float range, Mob *ae_center = nullptr);
void WipeHateList();
void WipeHateList(bool npc_only = false);
void RemoveStaleEntries(int time_ms, float dist);
+1 -1
View File
@@ -1273,7 +1273,7 @@ int lua_get_zone_weather() {
}
luabind::adl::object lua_get_zone_time(lua_State *L) {
TimeOfDay_Struct eqTime;
TimeOfDay_Struct eqTime{};
zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eqTime);
luabind::adl::object ret = luabind::newtable(L);
+19 -14
View File
@@ -3586,10 +3586,17 @@ void Mob::SendIllusionPacket(const AppearanceStruct& a)
gender = new_gender;
hairstyle = new_hair;
haircolor = new_hair_color;
helmtexture = new_helmet_texture;
race = new_race;
size = new_size;
texture = new_texture;
// These two should not be modified in base data - it kills db texture
// when illusion is only for RandomizeFeatures...
if (new_helmet_texture != UINT8_MAX) {
helmtexture = new_helmet_texture;
}
if (new_texture != UINT8_MAX) {
texture = new_texture;
}
auto outapp = new EQApplicationPacket(OP_Illusion, sizeof(Illusion_Struct));
auto is = (Illusion_Struct *) outapp->pBuffer;
@@ -4881,16 +4888,14 @@ bool Mob::RemoveFromHateList(Mob* mob)
return bFound;
}
void Mob::WipeHateList()
{
if(IsEngaged())
{
hate_list.WipeHateList();
AI_Event_NoLongerEngaged();
}
else
{
hate_list.WipeHateList();
void Mob::WipeHateList(bool npc_only) {
if (IsEngaged()) {
hate_list.WipeHateList(npc_only);
if (hate_list.IsHateListEmpty()) {
AI_Event_NoLongerEngaged();
}
} else {
hate_list.WipeHateList(npc_only);
}
}
@@ -5310,7 +5315,7 @@ int Mob::GetHaste()
h += spellbonuses.hastetype2 > 10 ? 10 : spellbonuses.hastetype2;
// 26+ no cap, 1-25 10
if (level > 25) // 26+
if (level > 25 || (IsClient() && RuleB(Character, IgnoreLevelBasedHasteCaps))) // 26+
h += itembonuses.haste;
else // 1-25
h += itembonuses.haste > 10 ? 10 : itembonuses.haste;
@@ -5332,7 +5337,7 @@ int Mob::GetHaste()
h = cap;
// 51+ 25 (despite there being higher spells...), 1-50 10
if (level > 50) { // 51+
if (level > 50 || (IsClient() && RuleB(Character, IgnoreLevelBasedHasteCaps))) { // 51+
cap = RuleI(Character, Hastev3Cap);
if (spellbonuses.hastetype3 > cap) {
h += cap;
+1 -1
View File
@@ -767,7 +767,7 @@ public:
void SetAssistAggro(bool value) { AssistAggro = value; if (PrimaryAggro) AssistAggro = false; }
bool HateSummon();
void FaceTarget(Mob* mob_to_face = 0);
void WipeHateList();
void WipeHateList(bool npc_only = false);
void AddFeignMemory(Mob* attacker);
void RemoveFromFeignMemory(Mob* attacker);
void ClearFeignMemory();
+4
View File
@@ -1062,6 +1062,10 @@ void Mob::AI_Process() {
SetTarget(hate_list.GetClosestEntOnHateList(this));
else {
if (AI_target_check_timer->Check()) {
if (IsNPC() && (!IsPet() || (HasOwner() && GetOwner()->IsNPC())) && !CastToNPC()->WillAggroNPCs()) {
WipeHateList(true); // wipe NPCs from hate list to prevent faction war
}
if (IsFocused()) {
if (!target) {
SetTarget(hate_list.GetEntWithMostHateOnList(this));
+1 -1
View File
@@ -384,7 +384,7 @@ bool ZoneDatabase::GetPoweredPetEntry(const char *pet_type, int16 petpower, PetR
"FROM pets WHERE type='%s' AND petpower<=%d ORDER BY petpower DESC LIMIT 1",
pet_type, petpower);
auto results = QueryDatabase(query);
auto results = content_db.QueryDatabase(query);
if (!results.Success()) {
return false;
}
+3 -3
View File
@@ -692,7 +692,7 @@ void SpawnConditionManager::Process() {
//check each spawn event.
//get our current time
TimeOfDay_Struct tod;
TimeOfDay_Struct tod{};
zone->zone_time.GetCurrentEQTimeOfDay(&tod);
//see if time is past our nearest event.
@@ -745,7 +745,7 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) {
return; //unable to find the spawn condition to operate on
}
TimeOfDay_Struct tod;
TimeOfDay_Struct tod{};
zone->zone_time.GetCurrentEQTimeOfDay(&tod);
if(event.strict && (event.next.hour != tod.hour || event.next.day != tod.day || event.next.month != tod.month || event.next.year != tod.year))
{
@@ -956,7 +956,7 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in
//spawn points which get turned on. Im too lazy to figure out a
//better solution, and I just dont care thats much.
//get our current time
TimeOfDay_Struct tod;
TimeOfDay_Struct tod{};
zone->zone_time.GetCurrentEQTimeOfDay(&tod);
for(auto cur = spawn_events.begin(); cur != spawn_events.end(); ++cur) {
+27 -9
View File
@@ -29,12 +29,13 @@
extern EntityList entity_list;
extern Zone *zone;
SpawnEntry::SpawnEntry(uint32 in_NPCType, int in_chance, uint16 in_filter, uint8 in_npc_spawn_limit)
{
SpawnEntry::SpawnEntry(uint32 in_NPCType, int in_chance, uint16 in_filter, uint8 in_npc_spawn_limit, uint8 in_min_time, uint8 in_max_time) {
NPCType = in_NPCType;
chance = in_chance;
condition_value_filter = in_filter;
npc_spawn_limit = in_npc_spawn_limit;
min_time = in_min_time;
max_time = in_max_time;
}
SpawnGroup::SpawnGroup(
@@ -85,8 +86,15 @@ uint32 SpawnGroup::GetNPCType(uint16 in_filter)
continue;
}
if (se->condition_value_filter != in_filter)
if (se->min_time != 0 && se->max_time != 0 && se->min_time <= 24 && se->max_time <= 24) {
if (!zone->zone_time.IsInbetweenTime(se->min_time, se->max_time)) {
continue;
}
}
if (se->condition_value_filter != in_filter) {
continue;
}
totalchance += se->chance;
possible.push_back(se);
@@ -224,7 +232,9 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG
chance,
condition_value_filter,
npc_types.spawn_limit
AS sl
AS sl,
min_time,
max_time
FROM
spawnentry,
spawn2,
@@ -251,7 +261,9 @@ bool ZoneDatabase::LoadSpawnGroups(const char *zone_name, uint16 version, SpawnG
Strings::ToInt(row[1]),
Strings::ToInt(row[2]),
Strings::ToInt(row[3]),
(row[4] ? Strings::ToInt(row[4]) : 0)
(row[4] ? Strings::ToInt(row[4]) : 0),
Strings::ToInt(row[5]),
Strings::ToInt(row[6])
);
SpawnGroup *spawn_group = spawn_group_list->GetSpawnGroup(Strings::ToInt(row[0]));
@@ -340,7 +352,9 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn
spawnentry.npcid,
spawnentry.chance,
spawnentry.condition_value_filter,
spawngroup.spawn_limit
spawngroup.spawn_limit,
spawnentry.min_time,
spawnentry.max_time
FROM
spawnentry,
spawngroup
@@ -361,16 +375,20 @@ bool ZoneDatabase::LoadSpawnGroupsByID(int spawn_group_id, SpawnGroupList *spawn
Strings::ToInt(row[1]),
Strings::ToInt(row[2]),
Strings::ToInt(row[3]),
(row[4] ? Strings::ToInt(row[4]) : 0)
(row[4] ? Strings::ToInt(row[4]) : 0),
Strings::ToInt(row[5]),
Strings::ToInt(row[6])
);
LogSpawnsDetail(
"Loading spawn_entry spawn_group_id [{}] npc_id [{}] chance [{}] condition_value_filter [{}] spawn_limit [{}]",
"Loading spawn_entry spawn_group_id [{}] npc_id [{}] chance [{}] condition_value_filter [{}] spawn_limit [{}] min_time [{}] max_time [{}] ",
row[0],
row[1],
row[2],
row[3],
row[4]
row[4],
row[5],
row[6]
);
SpawnGroup *spawn_group = spawn_group_list->GetSpawnGroup(Strings::ToInt(row[0]));
+3 -1
View File
@@ -26,10 +26,12 @@
class SpawnEntry {
public:
SpawnEntry(uint32 in_NPCType, int in_chance, uint16 in_filter, uint8 in_npc_spawn_limit);
SpawnEntry(uint32 in_NPCType, int in_chance, uint16 in_filter, uint8 in_npc_spawn_limit, uint8 in_min_time, uint8 in_max_time);
~SpawnEntry() {}
uint32 NPCType;
int chance;
uint8 min_time;
uint8 max_time;
uint16 condition_value_filter;
//this is a cached value from npc_types, for speed
+10 -3
View File
@@ -3885,11 +3885,12 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
if (IsDetrimentalSpell(buff.spellid)) {
if (caster->IsClient()) {
if (!caster->CastToClient()->GetFeigned())
if (!caster->CastToClient()->GetFeigned()) {
AddToHateList(caster, -effect_value);
} else if (!IsClient()) // Allow NPC's to generate hate if casted on other
// NPC's.
}
} else if (!IsClient()) { // Allow NPC's to generate hate if casted on other NPC's
AddToHateList(caster, -effect_value);
}
}
effect_value = caster->GetActDoTDamage(buff.spellid, effect_value, this);
@@ -3990,6 +3991,12 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
case SE_Charm: {
if (!caster || !PassCharismaCheck(caster, buff.spellid)) {
BuffFadeByEffect(SE_Charm);
// Remove from hate list of any NPC's hate list and remove all NPCs this hate list
if (IsNPC()) {
entity_list.RemoveFromHateLists(this);
WipeHateList(true);
}
}
break;
+29 -31
View File
@@ -4244,41 +4244,39 @@ bool Mob::SpellOnTarget(
spelltar->DamageShield(this, true);
}
if (
spelltar->IsAIControlled() &&
IsDetrimentalSpell(spell_id) &&
!IsHarmonySpell(spell_id)
) {
auto aggro_amount = CheckAggroAmount(spell_id, spelltar, isproc);
LogSpells("Spell [{}] cast on [{}] generated [{}] hate", spell_id,
spelltar->GetName(), aggro_amount);
if (aggro_amount > 0) {
spelltar->AddToHateList(this, aggro_amount);
} else {
int64 newhate = spelltar->GetHateAmount(this) + aggro_amount;
spelltar->SetHateAmountOnEnt(this, std::max(newhate, static_cast<int64>(1)));
}
} else if (IsBeneficialSpell(spell_id) && !IsSummonPCSpell(spell_id)) {
if (this != spelltar && IsClient()){
if (spelltar->IsClient()) {
CastToClient()->UpdateRestTimer(spelltar->CastToClient()->GetRestTimer());
} else if (spelltar->IsPet()) {
auto* owner = spelltar->GetOwner();
if (owner && owner != this && owner->IsClient()) {
CastToClient()->UpdateRestTimer(owner->CastToClient()->GetRestTimer());
if (!(spelltar->IsNPC() && IsNPC() && !(IsPet() && GetOwner()->IsClient()))) {
if (spelltar->IsAIControlled() && IsDetrimentalSpell(spell_id) && !IsHarmonySpell(spell_id)) {
auto aggro_amount = CheckAggroAmount(spell_id, spelltar, isproc);
LogSpellsDetail("Spell {} cast on {} generated {} hate", spell_id,
spelltar->GetName(), aggro_amount);
if (aggro_amount > 0) {
spelltar->AddToHateList(this, aggro_amount);
} else {
int64 newhate = spelltar->GetHateAmount(this) + aggro_amount;
spelltar->SetHateAmountOnEnt(this, std::max(newhate, static_cast<int64>(1)));
}
} else if (IsBeneficialSpell(spell_id) && !IsSummonPCSpell(spell_id)) {
if (this != spelltar && IsClient()){
if (spelltar->IsClient()) {
CastToClient()->UpdateRestTimer(spelltar->CastToClient()->GetRestTimer());
} else if (spelltar->IsPet()) {
auto* owner = spelltar->GetOwner();
if (owner && owner != this && owner->IsClient()) {
CastToClient()->UpdateRestTimer(owner->CastToClient()->GetRestTimer());
}
}
}
}
entity_list.AddHealAggro(
spelltar,
this,
CheckHealAggroAmount(
spell_id,
entity_list.AddHealAggro(
spelltar,
(spelltar->GetMaxHP() - spelltar->GetHP())
)
);
this,
CheckHealAggroAmount(
spell_id,
spelltar,
(spelltar->GetMaxHP() - spelltar->GetHP())
)
);
}
}
// make sure spelltar is high enough level for the buff
+1 -1
View File
@@ -965,7 +965,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
char time_message[255];
time_t current_time = time(nullptr);
TimeOfDay_Struct eq_time;
TimeOfDay_Struct eq_time{};
zone->zone_time.GetCurrentEQTimeOfDay(current_time, &eq_time);
sprintf(time_message, "EQTime [%02d:%s%d %s]",