diff --git a/data-canary/scripts/lib/register_npc_type.lua b/data-canary/scripts/lib/register_npc_type.lua
index bf23e53951e..84c642b9c48 100644
--- a/data-canary/scripts/lib/register_npc_type.lua
+++ b/data-canary/scripts/lib/register_npc_type.lua
@@ -25,19 +25,6 @@ registerNpcType.description = function(npcType, mask)
end
end
-registerNpcType.speechBubble = function(npcType, mask)
- local speechBubble = npcType:speechBubble()
- if mask.speechBubble then
- npcType:speechBubble(mask.speechBubble)
- elseif speechBubble == 3 then
- npcType:speechBubble(4)
- elseif speechBubble < 1 then
- npcType:speechBubble(1)
- else
- npcType:speechBubble(2)
- end
-end
-
registerNpcType.outfit = function(npcType, mask)
if mask.outfit then
npcType:outfit(mask.outfit)
@@ -206,3 +193,9 @@ registerNpcType.currency = function(npcType, mask)
npcType:currency(mask.currency)
end
end
+
+registerNpcType.speechBubble = function(npcType, mask)
+ if mask.speechBubble then
+ npcType:speechBubble(mask.speechBubble)
+ end
+end
diff --git a/data-otservbr-global/scripts/lib/register_npc_type.lua b/data-otservbr-global/scripts/lib/register_npc_type.lua
index bf23e53951e..84c642b9c48 100644
--- a/data-otservbr-global/scripts/lib/register_npc_type.lua
+++ b/data-otservbr-global/scripts/lib/register_npc_type.lua
@@ -25,19 +25,6 @@ registerNpcType.description = function(npcType, mask)
end
end
-registerNpcType.speechBubble = function(npcType, mask)
- local speechBubble = npcType:speechBubble()
- if mask.speechBubble then
- npcType:speechBubble(mask.speechBubble)
- elseif speechBubble == 3 then
- npcType:speechBubble(4)
- elseif speechBubble < 1 then
- npcType:speechBubble(1)
- else
- npcType:speechBubble(2)
- end
-end
-
registerNpcType.outfit = function(npcType, mask)
if mask.outfit then
npcType:outfit(mask.outfit)
@@ -206,3 +193,9 @@ registerNpcType.currency = function(npcType, mask)
npcType:currency(mask.currency)
end
end
+
+registerNpcType.speechBubble = function(npcType, mask)
+ if mask.speechBubble then
+ npcType:speechBubble(mask.speechBubble)
+ end
+end
diff --git a/data/items/items.xml b/data/items/items.xml
index 0b12e55ca6e..0d1241cd6a7 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -10650,7 +10650,10 @@
-
+ -
+
+
+
@@ -13126,43 +13129,70 @@
-
- -
+
-
- -
+
-
+
+
+ -
- -
+
-
+
+
+ -
+ -
+
+
- -
+
-
- -
+
-
+
+
+ -
- -
+
-
+
+
+ -
- -
+
-
+
+
+ -
- -
+
-
+
+
+ -
- -
+
-
+
+
+ -
+ -
+
+
-
@@ -13561,7 +13591,11 @@
- -
+
-
+
+
+
+ -
@@ -13720,8 +13754,12 @@
- -
+
-
+
+
+ -
+
-
@@ -18442,6 +18480,7 @@
-
+
@@ -18450,6 +18489,7 @@
-
+
@@ -18468,10 +18508,13 @@
- -
+
-
+ -
+
+
-
@@ -18926,10 +18969,13 @@
- -
+
-
+ -
+
+
-
@@ -47740,8 +47786,12 @@
- -
+
-
+
+
+ -
+
-
@@ -47764,8 +47814,19 @@
-
- -
+
-
+
+
+ -
+
+
+ -
+
+
+ -
+
+
-
@@ -50208,6 +50269,7 @@
-
+
diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp
index 29ccdfa63e3..8faa76b6367 100644
--- a/src/creatures/combat/combat.cpp
+++ b/src/creatures/combat/combat.cpp
@@ -343,6 +343,8 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) {
}
}
}
+ } else if (target && target->getNpc()) {
+ return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE;
}
if (g_game().getWorldType() == WORLD_TYPE_NO_PVP) {
diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp
index 031d557f863..b032d42e9ee 100644
--- a/src/creatures/combat/condition.cpp
+++ b/src/creatures/combat/condition.cpp
@@ -1247,7 +1247,7 @@ bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) {
}
if (!creature->isAttackable() || Combat::canDoCombat(attacker, creature) != RETURNVALUE_NOERROR) {
- if (!creature->isInGhostMode()) {
+ if (!creature->isInGhostMode() && !creature->getNpc()) {
g_game().addMagicEffect(creature->getPosition(), CONST_ME_POFF);
}
return false;
diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp
index 6b4185970fc..e9ee0c46211 100644
--- a/src/creatures/creatures_definitions.hpp
+++ b/src/creatures/creatures_definitions.hpp
@@ -223,6 +223,7 @@ enum SpeechBubble_t {
SPEECHBUBBLE_TRADE = 2,
SPEECHBUBBLE_QUEST = 3,
SPEECHBUBBLE_QUESTTRADER = 4,
+ SPEECHBUBBLE_HIRELING = 7,
};
enum MarketAction_t {
diff --git a/src/creatures/npcs/npcs.cpp b/src/creatures/npcs/npcs.cpp
index 649ba6cd166..4f80f3b5c43 100644
--- a/src/creatures/npcs/npcs.cpp
+++ b/src/creatures/npcs/npcs.cpp
@@ -95,6 +95,8 @@ void NpcType::loadShop(NpcType* npcType, ShopBlock shopBlock) {
} else {
npcType->info.shopItemVector.push_back(shopBlock);
}
+
+ info.speechBubble = SPEECHBUBBLE_TRADE;
}
bool Npcs::load(bool loadLibs /* = true*/, bool loadNpcs /* = true*/, bool reloading /* = false*/) const {
diff --git a/src/creatures/npcs/npcs.h b/src/creatures/npcs/npcs.h
index e30cb4b23cc..92bf372f272 100644
--- a/src/creatures/npcs/npcs.h
+++ b/src/creatures/npcs/npcs.h
@@ -32,7 +32,7 @@ class NpcType {
LightInfo light = {};
- uint8_t speechBubble;
+ uint8_t speechBubble = SPEECHBUBBLE_NORMAL;
uint16_t currencyId = ITEM_GOLD_COIN;
diff --git a/src/items/items.cpp b/src/items/items.cpp
index c658e178b57..cc73998f1c0 100644
--- a/src/items/items.cpp
+++ b/src/items/items.cpp
@@ -169,25 +169,18 @@ bool Items::loadFromXml() {
}
for (auto itemNode : doc.child("items").children()) {
- pugi::xml_attribute idAttribute = itemNode.attribute("id");
- if (idAttribute) {
+ if (auto idAttribute = itemNode.attribute("id"); idAttribute) {
parseItemNode(itemNode, pugi::cast(idAttribute.value()));
continue;
}
- pugi::xml_attribute fromIdAttribute = itemNode.attribute("fromid");
+ auto fromIdAttribute = itemNode.attribute("fromid");
if (!fromIdAttribute) {
- if (idAttribute) {
- SPDLOG_WARN("[Items::loadFromXml] - "
- "No item id: {} found",
- idAttribute.value());
- } else {
- SPDLOG_WARN("[Items::loadFromXml] - No item id found");
- }
+ SPDLOG_WARN("[Items::loadFromXml] - No item id found, use id or fromid");
continue;
}
- pugi::xml_attribute toIdAttribute = itemNode.attribute("toid");
+ auto toIdAttribute = itemNode.attribute("toid");
if (!toIdAttribute) {
SPDLOG_WARN("[Items::loadFromXml] - "
"tag fromid: {} without toid",
diff --git a/src/items/tile.cpp b/src/items/tile.cpp
index 737cb46cff0..99e3bdd36fb 100644
--- a/src/items/tile.cpp
+++ b/src/items/tile.cpp
@@ -465,9 +465,17 @@ void Tile::onUpdateTile(const SpectatorHashSet &spectators) {
}
ReturnValue Tile::queryAdd(int32_t, const Thing &thing, uint32_t, uint32_t tileFlags, Creature*) const {
+ if (hasBitSet(FLAG_NOLIMIT, tileFlags)) {
+ return RETURNVALUE_NOERROR;
+ }
+
if (const Creature* creature = thing.getCreature()) {
- if (hasBitSet(FLAG_NOLIMIT, tileFlags)) {
- return RETURNVALUE_NOERROR;
+
+ if (creature->getNpc()) {
+ ReturnValue returnValue = checkNpcCanWalkIntoTile();
+ if (returnValue != RETURNVALUE_NOERROR) {
+ return returnValue;
+ }
}
if (hasBitSet(FLAG_PATHFINDING, tileFlags) && hasFlag(TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) {
@@ -525,9 +533,8 @@ ReturnValue Tile::queryAdd(int32_t, const Thing &thing, uint32_t, uint32_t tileF
}
}
- MagicField* field = getFieldItem();
- if (field && !field->isBlocking() && field->getDamage() != 0) {
- CombatType_t combatType = field->getCombatType();
+ if (hasHarmfulField()) {
+ CombatType_t combatType = getFieldItem()->getCombatType();
// There is 3 options for a monster to enter a magic field
// 1) Monster is immune
@@ -557,7 +564,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing &thing, uint32_t, uint32_t tileF
}
}
- if (hasBitSet(FLAG_PATHFINDING, tileFlags) && hasFlag(TILESTATE_MAGICFIELD) && !fieldIsUnharmable()) {
+ if (hasBitSet(FLAG_PATHFINDING, tileFlags) && hasHarmfulField()) {
return RETURNVALUE_NOTPOSSIBLE;
}
@@ -633,10 +640,6 @@ ReturnValue Tile::queryAdd(int32_t, const Thing &thing, uint32_t, uint32_t tileF
return RETURNVALUE_NOTPOSSIBLE;
}
- if (hasBitSet(FLAG_NOLIMIT, tileFlags)) {
- return RETURNVALUE_NOERROR;
- }
-
bool itemIsHangable = item->isHangable();
if (ground == nullptr && !itemIsHangable) {
return RETURNVALUE_NOTPOSSIBLE;
@@ -700,9 +703,16 @@ ReturnValue Tile::queryAdd(int32_t, const Thing &thing, uint32_t, uint32_t tileF
return RETURNVALUE_NOERROR;
}
-bool Tile::fieldIsUnharmable() const {
- uint16_t fieldId = getFieldItem()->getID();
- return fieldId == ITEM_FIREFIELD_PVP_SMALL || fieldId == ITEM_FIREFIELD_PERSISTENT_SMALL;
+ReturnValue Tile::checkNpcCanWalkIntoTile() const {
+ if (hasHarmfulField()) {
+ return RETURNVALUE_NOTPOSSIBLE;
+ } else {
+ return RETURNVALUE_NOERROR;
+ }
+}
+
+bool Tile::hasHarmfulField() const {
+ return hasFlag(TILESTATE_MAGICFIELD) && getFieldItem() && !getFieldItem()->isBlocking() && getFieldItem()->getDamage() > 0;
}
ReturnValue Tile::queryMaxCount(int32_t, const Thing &, uint32_t count, uint32_t &maxQueryCount, uint32_t) const {
diff --git a/src/items/tile.h b/src/items/tile.h
index d5127d809ac..65d2148f282 100644
--- a/src/items/tile.h
+++ b/src/items/tile.h
@@ -246,7 +246,8 @@ class Tile : public Cylinder {
void setTileFlags(const Item* item);
void resetTileFlags(const Item* item);
- bool fieldIsUnharmable() const;
+ bool hasHarmfulField() const;
+ ReturnValue checkNpcCanWalkIntoTile() const;
protected:
Item* ground = nullptr;
diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp
index 7d8591ac382..64a8aa56d08 100644
--- a/src/lua/functions/core/game/lua_enums.cpp
+++ b/src/lua/functions/core/game/lua_enums.cpp
@@ -955,6 +955,7 @@ void LuaEnums::initSpeechBubbleEnums(lua_State* L) {
registerEnum(L, SPEECHBUBBLE_TRADE);
registerEnum(L, SPEECHBUBBLE_QUEST);
registerEnum(L, SPEECHBUBBLE_QUESTTRADER);
+ registerEnum(L, SPEECHBUBBLE_HIRELING);
}
// Use with player:addMapMark
diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp
index e427488ef76..5c6fcb5763a 100644
--- a/src/lua/functions/creatures/player/player_functions.cpp
+++ b/src/lua/functions/creatures/player/player_functions.cpp
@@ -3208,7 +3208,7 @@ int PlayerFunctions::luaPlayerGetFaction(lua_State* L) {
return 1;
}
-int PlayerFunctions::luaPlayerIsUIExhausted(lua_State *L) {
+int PlayerFunctions::luaPlayerIsUIExhausted(lua_State* L) {
// player:isUIExhausted()
Player* player = getUserdata(L, 1);
if (!player) {
@@ -3222,7 +3222,7 @@ int PlayerFunctions::luaPlayerIsUIExhausted(lua_State *L) {
return 1;
}
-int PlayerFunctions::luaPlayerUpdateUIExhausted(lua_State *L) {
+int PlayerFunctions::luaPlayerUpdateUIExhausted(lua_State* L) {
// player:updateUIExhausted(exhaustionTime = 250)
Player* player = getUserdata(L, 1);
if (!player) {
diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp
index 67081defe6c..cf5a63e694f 100644
--- a/src/lua/functions/creatures/player/player_functions.hpp
+++ b/src/lua/functions/creatures/player/player_functions.hpp
@@ -562,8 +562,8 @@ class PlayerFunctions final : LuaScriptInterface {
static int luaPlayerGetForgeSlivers(lua_State* L);
static int luaPlayerGetForgeCores(lua_State* L);
- static int luaPlayerIsUIExhausted(lua_State* L);
- static int luaPlayerUpdateUIExhausted(lua_State* L);
+ static int luaPlayerIsUIExhausted(lua_State* L);
+ static int luaPlayerUpdateUIExhausted(lua_State* L);
static int luaPlayerSetFaction(lua_State* L);
static int luaPlayerGetFaction(lua_State* L);