From 3e02cedeaae8a06c96df149a257af68da07aba49 Mon Sep 17 00:00:00 2001
From: Matt Gomez <martin_jojo@hotmail.com>
Date: Thu, 11 Jul 2024 20:34:36 -0600
Subject: [PATCH] 11072014

---
 .../creaturescripts/others/login_events.lua   |   3 -
 data/libs/functions/revscriptsys.lua          |   4 +
 .../creaturescripts/familiar/on_login.lua     |   1 +
 data/scripts/creaturescripts/player/login.lua |   1 +
 src/creatures/combat/combat.cpp               |  29 +--
 src/creatures/combat/combat.hpp               |   8 +-
 src/creatures/creature.cpp                    |   4 +
 .../monsters/spawns/spawn_monster.cpp         |   3 +-
 .../monsters/spawns/spawn_monster.hpp         |  27 ++-
 src/creatures/npcs/spawns/spawn_npc.cpp       |   2 +-
 src/creatures/npcs/spawns/spawn_npc.hpp       |   4 +-
 src/creatures/players/player.cpp              |  20 ++-
 src/creatures/players/player.hpp              |  10 +-
 src/game/game.cpp                             |  30 ++--
 src/io/functions/iologindata_load_player.cpp  |   4 +-
 src/io/iologindata.cpp                        |  25 +--
 src/io/iologindata.hpp                        |   4 +-
 src/io/iomapserialize.cpp                     |   8 +-
 .../functions/core/game/global_functions.cpp  |   6 +-
 .../creatures/monster/monster_functions.cpp   |   3 +-
 .../events/global_event_functions.cpp         |   2 +
 .../events/global_event_functions.hpp         |   1 +
 src/lua/global/globalevent.cpp                |  13 +-
 src/lua/global/globalevent.hpp                |   2 +
 src/lua/lua_definitions.hpp                   |   1 +
 src/map/map.cpp                               | 169 ++++++++++++++----
 src/map/map.hpp                               |   2 +-
 src/server/network/protocol/protocolgame.cpp  |   2 +-
 src/server/signals.cpp                        |   2 +
 29 files changed, 266 insertions(+), 124 deletions(-)

diff --git a/data-otxserver/scripts/creaturescripts/others/login_events.lua b/data-otxserver/scripts/creaturescripts/others/login_events.lua
index f88095e47..2223f095f 100644
--- a/data-otxserver/scripts/creaturescripts/others/login_events.lua
+++ b/data-otxserver/scripts/creaturescripts/others/login_events.lua
@@ -1,10 +1,7 @@
 local loginEvents = CreatureEvent("LoginEvents")
 function loginEvents.onLogin(player)
 	local events = {
-		"AdvanceSave",
 		"RookgaardAdvance",
-		"FamiliarLogin",
-		"FamiliarAdvance",
 		--Quests
 		--Cults Of Tibia Quest
 		"HealthPillar",
diff --git a/data/libs/functions/revscriptsys.lua b/data/libs/functions/revscriptsys.lua
index 84b8275e3..515522d64 100644
--- a/data/libs/functions/revscriptsys.lua
+++ b/data/libs/functions/revscriptsys.lua
@@ -233,6 +233,10 @@ do
 			self:type("periodchange")
 			self:onPeriodChange(value)
 			return
+		elseif key == "onSave" then
+			self:type("save")
+			self:onSave(value)
+			return
 		end
 		rawset(self, key, value)
 	end
diff --git a/data/scripts/creaturescripts/familiar/on_login.lua b/data/scripts/creaturescripts/familiar/on_login.lua
index e264040db..dd4a1928e 100644
--- a/data/scripts/creaturescripts/familiar/on_login.lua
+++ b/data/scripts/creaturescripts/familiar/on_login.lua
@@ -5,6 +5,7 @@ function familiarOnLogin.onLogin(player)
 		return false
 	end
 
+	player:registerEvent("FamiliarAdvance")
 	local vocation = FAMILIAR_ID[player:getVocation():getBaseId()]
 
 	local familiarName
diff --git a/data/scripts/creaturescripts/player/login.lua b/data/scripts/creaturescripts/player/login.lua
index fbe4f9e63..416760669 100644
--- a/data/scripts/creaturescripts/player/login.lua
+++ b/data/scripts/creaturescripts/player/login.lua
@@ -173,6 +173,7 @@ function playerLoginGlobal.onLogin(player)
 	player:registerEvent("PlayerDeath")
 	player:registerEvent("DropLoot")
 	player:registerEvent("BossParticipation")
+	player:registerEvent("UpdatePlayerOnAdvancedLevel")
 	return true
 end
 
diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp
index 87f7500d0..91ff712de 100644
--- a/src/creatures/combat/combat.cpp
+++ b/src/creatures/combat/combat.cpp
@@ -108,7 +108,7 @@ CombatDamage Combat::getCombatDamage(std::shared_ptr<Creature> creature, std::sh
 	return damage;
 }
 
-void Combat::getCombatArea(const Position &centerPos, const Position &targetPos, const std::unique_ptr<AreaCombat> &area, std::forward_list<std::shared_ptr<Tile>> &list) {
+void Combat::getCombatArea(const Position &centerPos, const Position &targetPos, const std::unique_ptr<AreaCombat> &area, std::vector<std::shared_ptr<Tile>> &list) {
 	if (targetPos.z >= MAP_MAX_LAYERS) {
 		return;
 	}
@@ -116,7 +116,7 @@ void Combat::getCombatArea(const Position &centerPos, const Position &targetPos,
 	if (area) {
 		area->getList(centerPos, targetPos, list);
 	} else {
-		list.push_front(g_game().map.getOrCreateTile(targetPos));
+		list.emplace_back(g_game().map.getOrCreateTile(targetPos));
 	}
 }
 
@@ -1119,7 +1119,7 @@ bool Combat::doCombat(std::shared_ptr<Creature> caster, const Position &position
 }
 
 void Combat::CombatFunc(std::shared_ptr<Creature> caster, const Position &origin, const Position &pos, const std::unique_ptr<AreaCombat> &area, const CombatParams &params, CombatFunction func, CombatDamage* data) {
-	std::forward_list<std::shared_ptr<Tile>> tileList;
+	std::vector<std::shared_ptr<Tile>> tileList;
 
 	if (caster) {
 		getCombatArea(caster->getPosition(), pos, area, tileList);
@@ -1827,26 +1827,29 @@ AreaCombat::AreaCombat(const AreaCombat &rhs) {
 	}
 }
 
-void AreaCombat::getList(const Position &centerPos, const Position &targetPos, std::forward_list<std::shared_ptr<Tile>> &list) const {
+void AreaCombat::getList(const Position &centerPos, const Position &targetPos, std::vector<std::shared_ptr<Tile>> &list) const {
 	const std::unique_ptr<MatrixArea> &area = getArea(centerPos, targetPos);
 	if (!area) {
 		return;
 	}
 
-	uint32_t centerY, centerX;
+	uint32_t centerY;
+	uint32_t centerX;
 	area->getCenter(centerY, centerX);
 
+	const uint32_t rows = area->getRows();
+	const uint32_t cols = area->getCols();
+	list.reserve(rows * cols);
+
 	Position tmpPos(targetPos.x - centerX, targetPos.y - centerY, targetPos.z);
-	uint32_t cols = area->getCols();
-	for (uint32_t y = 0, rows = area->getRows(); y < rows; ++y) {
-		for (uint32_t x = 0; x < cols; ++x) {
-			if (area->getValue(y, x) != 0 && g_game().isSightClear(targetPos, tmpPos, true)) {
-				list.push_front(g_game().map.getOrCreateTile(tmpPos));
+	for (uint32_t y = 0; y < rows; ++y, ++tmpPos.y, tmpPos.x -= cols) {
+		for (uint32_t x = 0; x < cols; ++x, ++tmpPos.x) {
+			if (area->getValue(y, x) != 0) {
+				if (g_game().isSightClear(targetPos, tmpPos, true)) {
+					list.emplace_back(g_game().map.getOrCreateTile(tmpPos));
+				}
 			}
-			tmpPos.x++;
 		}
-		tmpPos.x -= cols;
-		tmpPos.y++;
 	}
 }
 
diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp
index 93b02502f..304a70913 100644
--- a/src/creatures/combat/combat.hpp
+++ b/src/creatures/combat/combat.hpp
@@ -84,7 +84,7 @@ class ChainPickerCallback final : public CallBack {
 };
 
 struct CombatParams {
-	std::forward_list<std::shared_ptr<Condition>> conditionList;
+	std::vector<std::shared_ptr<Condition>> conditionList;
 
 	std::unique_ptr<ValueCallback> valueCallback;
 	std::unique_ptr<TileCallback> tileCallback;
@@ -218,7 +218,7 @@ class AreaCombat {
 	// non-assignable
 	AreaCombat &operator=(const AreaCombat &) = delete;
 
-	void getList(const Position &centerPos, const Position &targetPos, std::forward_list<std::shared_ptr<Tile>> &list) const;
+	void getList(const Position &centerPos, const Position &targetPos, std::vector<std::shared_ptr<Tile>> &list) const;
 
 	void setupArea(const std::list<uint32_t> &list, uint32_t rows);
 	void setupArea(int32_t length, int32_t spread);
@@ -290,7 +290,7 @@ class Combat {
 	static void doCombatDispel(std::shared_ptr<Creature> caster, std::shared_ptr<Creature> target, const CombatParams &params);
 	static void doCombatDispel(std::shared_ptr<Creature> caster, const Position &position, const std::unique_ptr<AreaCombat> &area, const CombatParams &params);
 
-	static void getCombatArea(const Position &centerPos, const Position &targetPos, const std::unique_ptr<AreaCombat> &area, std::forward_list<std::shared_ptr<Tile>> &list);
+	static void getCombatArea(const Position &centerPos, const Position &targetPos, const std::unique_ptr<AreaCombat> &area, std::vector<std::shared_ptr<Tile>> &list);
 
 	static bool isInPvpZone(std::shared_ptr<Creature> attacker, std::shared_ptr<Creature> target);
 	static bool isProtected(std::shared_ptr<Player> attacker, std::shared_ptr<Player> target);
@@ -320,7 +320,7 @@ class Combat {
 		return area != nullptr;
 	}
 	void addCondition(const std::shared_ptr<Condition> condition) {
-		params.conditionList.emplace_front(condition);
+		params.conditionList.emplace_back(condition);
 	}
 	void setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb);
 	void postCombatEffects(std::shared_ptr<Creature> caster, const Position &origin, const Position &pos) const {
diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp
index c68ab4f35..e3474ab24 100644
--- a/src/creatures/creature.cpp
+++ b/src/creatures/creature.cpp
@@ -1050,6 +1050,10 @@ void Creature::goToFollowCreature_async(std::function<void()> &&onComplete) {
 
 	pathfinderRunning.store(true);
 	g_dispatcher().asyncEvent([self = getCreature()] {
+		if (!self || self->isRemoved()) {
+			return;
+		}
+
 		self->goToFollowCreature();
 		self->pathfinderRunning.store(false);
 	});
diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp
index 968b90d8c..ca8f45847 100644
--- a/src/creatures/monsters/spawns/spawn_monster.cpp
+++ b/src/creatures/monsters/spawns/spawn_monster.cpp
@@ -61,8 +61,7 @@ bool SpawnsMonster::loadFromXML(const std::string &filemonstername) {
 			continue;
 		}
 
-		spawnMonsterList.emplace_front(centerPos, radius);
-		SpawnMonster &spawnMonster = spawnMonsterList.front();
+		SpawnMonster &spawnMonster = spawnMonsterList.emplace_back(centerPos, radius);
 
 		for (auto childMonsterNode : spawnMonsterNode.children()) {
 			if (strcasecmp(childMonsterNode.name(), "monster") == 0) {
diff --git a/src/creatures/monsters/spawns/spawn_monster.hpp b/src/creatures/monsters/spawns/spawn_monster.hpp
index 35b856d23..81a08973c 100644
--- a/src/creatures/monsters/spawns/spawn_monster.hpp
+++ b/src/creatures/monsters/spawns/spawn_monster.hpp
@@ -36,6 +36,25 @@ class SpawnMonster {
 	SpawnMonster(const SpawnMonster &) = delete;
 	SpawnMonster &operator=(const SpawnMonster &) = delete;
 
+	// moveable
+	SpawnMonster(SpawnMonster &&rhs) noexcept :
+		spawnMonsterMap(std::move(rhs.spawnMonsterMap)),
+		spawnedMonsterMap(std::move(rhs.spawnedMonsterMap)),
+		checkSpawnMonsterEvent(rhs.checkSpawnMonsterEvent), centerPos(rhs.centerPos), radius(rhs.radius), interval(rhs.interval) { }
+
+	SpawnMonster &operator=(SpawnMonster &&rhs) noexcept {
+		if (this != &rhs) {
+			spawnMonsterMap = std::move(rhs.spawnMonsterMap);
+			spawnedMonsterMap = std::move(rhs.spawnedMonsterMap);
+
+			checkSpawnMonsterEvent = rhs.checkSpawnMonsterEvent;
+			centerPos = rhs.centerPos;
+			radius = rhs.radius;
+			interval = rhs.interval;
+		}
+		return *this;
+	}
+
 	bool addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t interval, uint32_t weight = 1);
 	void removeMonster(std::shared_ptr<Monster> monster);
 	void removeMonsters();
@@ -83,10 +102,6 @@ class SpawnsMonster {
 	bool loadFromXML(const std::string &filemonstername);
 	void startup();
 	void clear();
-	SpawnMonster &addSpawnMonster(const Position &pos, int32_t radius) {
-		spawnMonsterList.emplace_front(pos, radius);
-		return spawnMonsterList.front();
-	}
 
 	bool isStarted() const {
 		return started;
@@ -94,12 +109,12 @@ class SpawnsMonster {
 	bool isLoaded() const {
 		return loaded;
 	}
-	std::forward_list<SpawnMonster> &getspawnMonsterList() {
+	std::vector<SpawnMonster> &getspawnMonsterList() {
 		return spawnMonsterList;
 	}
 
 private:
-	std::forward_list<SpawnMonster> spawnMonsterList;
+	std::vector<SpawnMonster> spawnMonsterList;
 	std::string filemonstername;
 	bool loaded = false;
 	bool started = false;
diff --git a/src/creatures/npcs/spawns/spawn_npc.cpp b/src/creatures/npcs/spawns/spawn_npc.cpp
index a8be411dc..694822c23 100644
--- a/src/creatures/npcs/spawns/spawn_npc.cpp
+++ b/src/creatures/npcs/spawns/spawn_npc.cpp
@@ -57,7 +57,7 @@ bool SpawnsNpc::loadFromXml(const std::string &fileNpcName) {
 			continue;
 		}
 
-		const auto &spawnNpc = spawnNpcList.emplace_front(std::make_shared<SpawnNpc>(centerPos, radius));
+		const auto &spawnNpc = spawnNpcList.emplace_back(std::make_shared<SpawnNpc>(centerPos, radius));
 
 		for (auto childNode : spawnNode.children()) {
 			if (strcasecmp(childNode.name(), "npc") == 0) {
diff --git a/src/creatures/npcs/spawns/spawn_npc.hpp b/src/creatures/npcs/spawns/spawn_npc.hpp
index 49eb3bc6f..b0d0ab862 100644
--- a/src/creatures/npcs/spawns/spawn_npc.hpp
+++ b/src/creatures/npcs/spawns/spawn_npc.hpp
@@ -94,12 +94,12 @@ class SpawnsNpc {
 		return fileName = std::move(setName);
 	}
 
-	std::forward_list<std::shared_ptr<SpawnNpc>> &getSpawnNpcList() {
+	std::vector<std::shared_ptr<SpawnNpc>> &getSpawnNpcList() {
 		return spawnNpcList;
 	}
 
 private:
-	std::forward_list<std::shared_ptr<SpawnNpc>> spawnNpcList;
+	std::vector<std::shared_ptr<SpawnNpc>> spawnNpcList;
 	std::string fileName;
 	bool loaded = false;
 	bool started = false;
diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp
index 273079873..2660c0391 100644
--- a/src/creatures/players/player.cpp
+++ b/src/creatures/players/player.cpp
@@ -5289,12 +5289,12 @@ double Player::getLostPercent() const {
 
 void Player::learnInstantSpell(const std::string &spellName) {
 	if (!hasLearnedInstantSpell(spellName)) {
-		learnedInstantSpellList.push_front(spellName);
+		learnedInstantSpellList.emplace_back(spellName);
 	}
 }
 
 void Player::forgetInstantSpell(const std::string &spellName) {
-	learnedInstantSpellList.remove(spellName);
+	std::erase(learnedInstantSpellList, spellName);
 }
 
 bool Player::hasLearnedInstantSpell(const std::string &spellName) const {
@@ -5700,12 +5700,12 @@ bool Player::addPartyInvitation(std::shared_ptr<Party> newParty) {
 		return false;
 	}
 
-	invitePartyList.push_front(newParty);
+	invitePartyList.emplace_back(newParty);
 	return true;
 }
 
 void Player::removePartyInvitation(std::shared_ptr<Party> remParty) {
-	invitePartyList.remove(remParty);
+	std::erase(invitePartyList, remParty);
 }
 
 void Player::clearPartyInvitations() {
@@ -6124,7 +6124,7 @@ bool Player::hasModalWindowOpen(uint32_t modalWindowId) const {
 }
 
 void Player::onModalWindowHandled(uint32_t modalWindowId) {
-	modalWindows.remove(modalWindowId);
+	std::erase(modalWindows, modalWindowId);
 }
 
 void Player::sendModalWindow(const ModalWindow &modalWindow) {
@@ -6132,7 +6132,7 @@ void Player::sendModalWindow(const ModalWindow &modalWindow) {
 		return;
 	}
 
-	modalWindows.push_front(modalWindow.id);
+	modalWindows.emplace_back(modalWindow.id);
 	client->sendModalWindow(modalWindow);
 }
 
@@ -6251,8 +6251,10 @@ size_t Player::getMaxDepotItems() const {
 	return g_configManager().getNumber(FREE_DEPOT_LIMIT, __FUNCTION__);
 }
 
-std::forward_list<std::shared_ptr<Condition>> Player::getMuteConditions() const {
-	std::forward_list<std::shared_ptr<Condition>> muteConditions;
+std::vector<std::shared_ptr<Condition>> Player::getMuteConditions() const {
+	std::vector<std::shared_ptr<Condition>> muteConditions;
+	muteConditions.reserve(conditions.size());
+
 	for (const std::shared_ptr<Condition> &condition : conditions) {
 		if (condition->getTicks() <= 0) {
 			continue;
@@ -6263,7 +6265,7 @@ std::forward_list<std::shared_ptr<Condition>> Player::getMuteConditions() const
 			continue;
 		}
 
-		muteConditions.push_front(condition);
+		muteConditions.emplace_back(condition);
 	}
 	return muteConditions;
 }
diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp
index 2c765850f..bfaad14cf 100644
--- a/src/creatures/players/player.hpp
+++ b/src/creatures/players/player.hpp
@@ -2664,7 +2664,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
 	static uint32_t playerFirstID;
 	static uint32_t playerLastID;
 
-	std::forward_list<std::shared_ptr<Condition>> getMuteConditions() const;
+	std::vector<std::shared_ptr<Condition>> getMuteConditions() const;
 
 	void checkTradeState(std::shared_ptr<Item> item);
 	bool hasCapacity(std::shared_ptr<Item> item, uint32_t count) const;
@@ -2760,11 +2760,11 @@ class Player final : public Creature, public Cylinder, public Bankable {
 
 	GuildWarVector guildWarVector;
 
-	std::forward_list<std::shared_ptr<Party>> invitePartyList;
-	std::forward_list<uint32_t> modalWindows;
-	std::forward_list<std::string> learnedInstantSpellList;
+	std::vector<std::shared_ptr<Party>> invitePartyList;
+	std::vector<uint32_t> modalWindows;
+	std::vector<std::string> learnedInstantSpellList;
 	// TODO: This variable is only temporarily used when logging in, get rid of it somehow.
-	std::forward_list<std::shared_ptr<Condition>> storedConditionList;
+	std::vector<std::shared_ptr<Condition>> storedConditionList;
 
 	std::unordered_set<std::shared_ptr<MonsterType>> m_bestiaryMonsterTracker;
 	std::unordered_set<std::shared_ptr<MonsterType>> m_bosstiaryMonsterTracker;
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 7ef6b7a90..2348d3845 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -593,7 +593,8 @@ void Game::setGameState(GameState_t newState) {
 		}
 
 		case GAME_STATE_SHUTDOWN: {
-			g_globalEvents().execute(GLOBALEVENT_SHUTDOWN);
+			g_globalEvents().save();
+			g_globalEvents().shutdown();
 
 			// kick all players that are still online
 			auto it = players.begin();
@@ -611,6 +612,8 @@ void Game::setGameState(GameState_t newState) {
 		}
 
 		case GAME_STATE_CLOSED: {
+			g_globalEvents().save();
+
 			/* kick all players without the CanAlwaysLogin flag */
 			auto it = players.begin();
 			while (it != players.end()) {
@@ -6935,14 +6938,6 @@ int32_t Game::applyHealthChange(CombatDamage &damage, std::shared_ptr<Creature>
 			}
 		}
 	}
-
-	if (damage.primary.value >= targetHealth) {
-		damage.primary.value = targetHealth;
-		damage.secondary.value = 0;
-	} else if (damage.secondary.value) {
-		damage.secondary.value = std::min<int32_t>(damage.secondary.value, targetHealth - damage.primary.value);
-	}
-
 	return targetHealth;
 }
 
@@ -7300,19 +7295,12 @@ bool Game::combatChangeHealth(std::shared_ptr<Creature> attacker, std::shared_pt
 			}
 		}
 
-		auto targetHealth = applyHealthChange(damage, target);
-		if (damage.primary.value >= targetHealth) {
-			damage.primary.value = targetHealth;
-			damage.secondary.value = 0;
-		} else if (damage.secondary.value) {
-			damage.secondary.value = std::min<int32_t>(damage.secondary.value, targetHealth - damage.primary.value);
-		}
-
 		// Apply Custom PvP Damage (must be placed here to avoid recursive calls)
 		if (attackerPlayer && targetPlayer) {
 			applyPvPDamage(damage, attackerPlayer, targetPlayer);
 		}
 
+		auto targetHealth = target->getHealth();
 		realDamage = damage.primary.value + damage.secondary.value;
 		if (realDamage == 0) {
 			return true;
@@ -7324,6 +7312,14 @@ bool Game::combatChangeHealth(std::shared_ptr<Creature> attacker, std::shared_pt
 			}
 		}
 
+		targetHealth = applyHealthChange(damage, target);
+		if (damage.primary.value >= targetHealth) {
+			damage.primary.value = targetHealth;
+			damage.secondary.value = 0;
+		} else if (damage.secondary.value) {
+			damage.secondary.value = std::min<int32_t>(damage.secondary.value, targetHealth - damage.primary.value);
+		}
+
 		target->drainHealth(attacker, realDamage);
 		if (realDamage > 0 && targetMonster) {
 			if (attackerPlayer && attackerPlayer->getPlayer()) {
diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp
index 8406cf110..764d46414 100644
--- a/src/io/functions/iologindata_load_player.cpp
+++ b/src/io/functions/iologindata_load_player.cpp
@@ -235,7 +235,7 @@ void IOLoginDataLoad::loadPlayerConditions(std::shared_ptr<Player> player, DBRes
 	auto condition = Condition::createCondition(propStream);
 	while (condition) {
 		if (condition->unserialize(propStream)) {
-			player->storedConditionList.push_front(condition);
+			player->storedConditionList.emplace_back(condition);
 		}
 		condition = Condition::createCondition(propStream);
 	}
@@ -465,7 +465,7 @@ void IOLoginDataLoad::loadPlayerInstantSpellList(std::shared_ptr<Player> player,
 	query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID();
 	if ((result = db.storeQuery(query.str()))) {
 		do {
-			player->learnedInstantSpellList.emplace_front(result->getString("name"));
+			player->learnedInstantSpellList.emplace_back(result->getString("name"));
 		} while (result->next());
 	}
 }
diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp
index 122ade42c..132cead64 100644
--- a/src/io/iologindata.cpp
+++ b/src/io/iologindata.cpp
@@ -349,15 +349,14 @@ bool IOLoginData::hasBiddedOnHouse(uint32_t guid) {
 	return db.storeQuery(query.str()).get() != nullptr;
 }
 
-std::forward_list<VIPEntry> IOLoginData::getVIPEntries(uint32_t accountId) {
-	std::forward_list<VIPEntry> entries;
-
+std::vector<VIPEntry> IOLoginData::getVIPEntries(uint32_t accountId) {
 	std::string query = fmt::format("SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = {}", accountId);
+	std::vector<VIPEntry> entries;
 
-	DBResult_ptr result = Database::getInstance().storeQuery(query);
-	if (result) {
+	if (const auto &result = Database::getInstance().storeQuery(query)) {
+		entries.reserve(result->countResults());
 		do {
-			entries.emplace_front(
+			entries.emplace_back(
 				result->getNumber<uint32_t>("player_id"),
 				result->getString("name"),
 				result->getString("description"),
@@ -366,6 +365,7 @@ std::forward_list<VIPEntry> IOLoginData::getVIPEntries(uint32_t accountId) {
 			);
 		} while (result->next());
 	}
+
 	return entries;
 }
 
@@ -388,15 +388,16 @@ void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) {
 	g_database().executeQuery(query);
 }
 
-std::forward_list<VIPGroupEntry> IOLoginData::getVIPGroupEntries(uint32_t accountId, uint32_t guid) {
-	std::forward_list<VIPGroupEntry> entries;
-
+std::vector<VIPGroupEntry> IOLoginData::getVIPGroupEntries(uint32_t accountId, uint32_t guid) {
 	std::string query = fmt::format("SELECT `id`, `name`, `customizable` FROM `account_vipgroups` WHERE `account_id` = {}", accountId);
 
-	DBResult_ptr result = g_database().storeQuery(query);
-	if (result) {
+	std::vector<VIPGroupEntry> entries;
+
+	if (const auto &result = g_database().storeQuery(query)) {
+		entries.reserve(result->countResults());
+
 		do {
-			entries.emplace_front(
+			entries.emplace_back(
 				result->getNumber<uint8_t>("id"),
 				result->getString("name"),
 				result->getNumber<uint8_t>("customizable") == 0 ? false : true
diff --git a/src/io/iologindata.hpp b/src/io/iologindata.hpp
index be4147398..1451cf897 100644
--- a/src/io/iologindata.hpp
+++ b/src/io/iologindata.hpp
@@ -31,12 +31,12 @@ class IOLoginData {
 	static void increaseBankBalance(uint32_t guid, uint64_t bankBalance);
 	static bool hasBiddedOnHouse(uint32_t guid);
 
-	static std::forward_list<VIPEntry> getVIPEntries(uint32_t accountId);
+	static std::vector<VIPEntry> getVIPEntries(uint32_t accountId);
 	static void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string &description, uint32_t icon, bool notify);
 	static void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string &description, uint32_t icon, bool notify);
 	static void removeVIPEntry(uint32_t accountId, uint32_t guid);
 
-	static std::forward_list<VIPGroupEntry> getVIPGroupEntries(uint32_t accountId, uint32_t guid);
+	static std::vector<VIPGroupEntry> getVIPGroupEntries(uint32_t accountId, uint32_t guid);
 	static void addVIPGroupEntry(uint8_t groupId, uint32_t accountId, const std::string &groupName, bool customizable);
 	static void editVIPGroupEntry(uint8_t groupId, uint32_t accountId, const std::string &groupName, bool customizable);
 	static void removeVIPGroupEntry(uint8_t groupId, uint32_t accountId);
diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp
index 475a210a7..e2a367c4e 100644
--- a/src/io/iomapserialize.cpp
+++ b/src/io/iomapserialize.cpp
@@ -241,19 +241,21 @@ void IOMapSerialize::saveTile(PropWriteStream &stream, std::shared_ptr<Tile> til
 		return;
 	}
 
-	std::forward_list<std::shared_ptr<Item>> items;
+	std::vector<std::shared_ptr<Item>> items;
+	items.reserve(32);
+
 	uint16_t count = 0;
 	for (auto &item : *tileItems) {
 		if (item->getID() == ITEM_BATHTUB_FILLED_NOTMOVABLE) {
 			std::shared_ptr<Item> tub = Item::CreateItem(ITEM_BATHTUB_FILLED);
-			items.push_front(tub);
+			items.emplace_back(tub);
 			++count;
 			continue;
 		} else if (!item->isSavedToHouses()) {
 			continue;
 		}
 
-		items.push_front(item);
+		items.emplace_back(item);
 		++count;
 	}
 
diff --git a/src/lua/functions/core/game/global_functions.cpp b/src/lua/functions/core/game/global_functions.cpp
index c8a857cff..6b5175b06 100644
--- a/src/lua/functions/core/game/global_functions.cpp
+++ b/src/lua/functions/core/game/global_functions.cpp
@@ -16,6 +16,7 @@
 #include "lua/functions/core/game/global_functions.hpp"
 #include "lua/scripts/lua_environment.hpp"
 #include "lua/scripts/script_environment.hpp"
+#include "lua/global/globalevent.hpp"
 #include "server/network/protocol/protocolstatus.hpp"
 #include "creatures/players/wheel/player_wheel.hpp"
 #include "lua/global/lua_timer_event_descr.hpp"
@@ -443,7 +444,7 @@ int GlobalFunctions::luaDoAreaCombatCondition(lua_State* L) {
 	if (area || areaId == 0) {
 		CombatParams params;
 		params.impactEffect = getNumber<uint16_t>(L, 5);
-		params.conditionList.emplace_front(condition);
+		params.conditionList.emplace_back(condition);
 		Combat::doCombatCondition(creature, getPosition(L, 2), area, params);
 		pushBoolean(L, true);
 	} else {
@@ -478,7 +479,7 @@ int GlobalFunctions::luaDoTargetCombatCondition(lua_State* L) {
 
 	CombatParams params;
 	params.impactEffect = getNumber<uint16_t>(L, 4);
-	params.conditionList.emplace_front(condition->clone());
+	params.conditionList.emplace_back(condition->clone());
 	Combat::doCombatCondition(creature, target, params);
 	pushBoolean(L, true);
 	return 1;
@@ -706,6 +707,7 @@ int GlobalFunctions::luaStopEvent(lua_State* L) {
 }
 
 int GlobalFunctions::luaSaveServer(lua_State* L) {
+	g_globalEvents().save();
 	g_saveManager().scheduleAll();
 	pushBoolean(L, true);
 	return 1;
diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp
index 273f6af5c..5578477d6 100644
--- a/src/lua/functions/creatures/monster/monster_functions.cpp
+++ b/src/lua/functions/creatures/monster/monster_functions.cpp
@@ -363,8 +363,7 @@ int MonsterFunctions::luaMonsterSetSpawnPosition(lua_State* L) {
 	const Position &pos = monster->getPosition();
 	monster->setMasterPos(pos);
 
-	g_game().map.spawnsMonster.getspawnMonsterList().emplace_front(pos, 5);
-	SpawnMonster &spawnMonster = g_game().map.spawnsMonster.getspawnMonsterList().front();
+	SpawnMonster &spawnMonster = g_game().map.spawnsMonster.getspawnMonsterList().emplace_back(pos, 5);
 	uint32_t interval = getNumber<uint32_t>(L, 2, 90) * 1000 * 100 / std::max((uint32_t)1, (g_configManager().getNumber(RATE_SPAWN, __FUNCTION__) * eventschedule));
 	spawnMonster.addMonster(monster->mType->typeName, pos, DIRECTION_NORTH, static_cast<uint32_t>(interval));
 	spawnMonster.startSpawnMonsterCheck();
diff --git a/src/lua/functions/events/global_event_functions.cpp b/src/lua/functions/events/global_event_functions.cpp
index ea6a5f7df..129a466e9 100644
--- a/src/lua/functions/events/global_event_functions.cpp
+++ b/src/lua/functions/events/global_event_functions.cpp
@@ -40,6 +40,8 @@ int GlobalEventFunctions::luaGlobalEventType(lua_State* L) {
 			global->setEventType(GLOBALEVENT_PERIODCHANGE);
 		} else if (tmpStr == "onthink") {
 			global->setEventType(GLOBALEVENT_ON_THINK);
+		} else if (tmpStr == "save") {
+			global->setEventType(GLOBALEVENT_SAVE);
 		} else {
 			g_logger().error("[GlobalEventFunctions::luaGlobalEventType] - "
 			                 "Invalid type for global event: {}");
diff --git a/src/lua/functions/events/global_event_functions.hpp b/src/lua/functions/events/global_event_functions.hpp
index 6c988a61c..e04072a24 100644
--- a/src/lua/functions/events/global_event_functions.hpp
+++ b/src/lua/functions/events/global_event_functions.hpp
@@ -25,6 +25,7 @@ class GlobalEventFunctions final : LuaScriptInterface {
 		registerMethod(L, "GlobalEvent", "onShutdown", GlobalEventFunctions::luaGlobalEventOnCallback);
 		registerMethod(L, "GlobalEvent", "onRecord", GlobalEventFunctions::luaGlobalEventOnCallback);
 		registerMethod(L, "GlobalEvent", "onPeriodChange", GlobalEventFunctions::luaGlobalEventOnCallback);
+		registerMethod(L, "GlobalEvent", "onSave", GlobalEventFunctions::luaGlobalEventOnCallback);
 	}
 
 private:
diff --git a/src/lua/global/globalevent.cpp b/src/lua/global/globalevent.cpp
index 04420431d..32fa1a01a 100644
--- a/src/lua/global/globalevent.cpp
+++ b/src/lua/global/globalevent.cpp
@@ -66,6 +66,14 @@ void GlobalEvents::startup() const {
 	execute(GLOBALEVENT_STARTUP);
 }
 
+void GlobalEvents::shutdown() const {
+	execute(GLOBALEVENT_SHUTDOWN);
+}
+
+void GlobalEvents::save() const {
+	execute(GLOBALEVENT_SAVE);
+}
+
 void GlobalEvents::timer() {
 	time_t now = time(nullptr);
 
@@ -165,7 +173,8 @@ GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) {
 		case GLOBALEVENT_PERIODCHANGE:
 		case GLOBALEVENT_STARTUP:
 		case GLOBALEVENT_SHUTDOWN:
-		case GLOBALEVENT_RECORD: {
+		case GLOBALEVENT_RECORD:
+		case GLOBALEVENT_SAVE: {
 			GlobalEventMap retMap;
 			for (const auto &it : serverMap) {
 				if (it.second->getEventType() == type) {
@@ -196,6 +205,8 @@ std::string GlobalEvent::getScriptTypeName() const {
 			return "onPeriodChange";
 		case GLOBALEVENT_ON_THINK:
 			return "onThink";
+		case GLOBALEVENT_SAVE:
+			return "onSave";
 		default:
 			g_logger().error("[GlobalEvent::getScriptTypeName] - Invalid event type");
 			return std::string();
diff --git a/src/lua/global/globalevent.hpp b/src/lua/global/globalevent.hpp
index 004c6cb5f..128a743b3 100644
--- a/src/lua/global/globalevent.hpp
+++ b/src/lua/global/globalevent.hpp
@@ -29,6 +29,8 @@ class GlobalEvents final : public Scripts {
 	}
 
 	void startup() const;
+	void shutdown() const;
+	void save() const;
 
 	void timer();
 	void think();
diff --git a/src/lua/lua_definitions.hpp b/src/lua/lua_definitions.hpp
index 083871e23..81add18e5 100644
--- a/src/lua/lua_definitions.hpp
+++ b/src/lua/lua_definitions.hpp
@@ -108,6 +108,7 @@ enum GlobalEvent_t {
 	GLOBALEVENT_RECORD,
 	GLOBALEVENT_PERIODCHANGE,
 	GLOBALEVENT_ON_THINK,
+	GLOBALEVENT_SAVE,
 };
 
 enum ModuleType_t {
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 82629aeae..7406ecffb 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -457,62 +457,159 @@ bool Map::canThrowObjectTo(const Position &fromPos, const Position &toPos, bool
 	return isSightClear(fromPos, toPos, false);
 }
 
-bool Map::checkSightLine(const Position &fromPos, const Position &toPos) {
-	if (fromPos == toPos) {
+bool Map::checkSightLine(Position start, Position destination) {
+	if (start.x == destination.x && start.y == destination.y) {
 		return true;
 	}
 
-	Position start(fromPos.z > toPos.z ? toPos : fromPos);
-	Position destination(fromPos.z > toPos.z ? fromPos : toPos);
+	int32_t distanceX = Position::getDistanceX(start, destination);
+	int32_t distanceY = Position::getDistanceY(start, destination);
 
-	const int8_t mx = start.x < destination.x ? 1 : start.x == destination.x ? 0
-																			 : -1;
-	const int8_t my = start.y < destination.y ? 1 : start.y == destination.y ? 0
-																			 : -1;
+	if (start.y == destination.y) {
+		// Horizontal line
+		const uint16_t delta = start.x < destination.x ? 0x0001 : 0xFFFF;
+		while (--distanceX > 0) {
+			start.x += delta;
 
-	int32_t A = Position::getOffsetY(destination, start);
-	int32_t B = Position::getOffsetX(start, destination);
-	int32_t C = -(A * destination.x + B * destination.y);
+			const auto &tile = getTile(start.x, start.y, start.z);
+			if (tile && tile->hasFlag(TILESTATE_BLOCKPROJECTILE)) {
+				return false;
+			}
+		}
+	} else if (start.x == destination.x) {
+		// Vertical line
+		const uint16_t delta = start.y < destination.y ? 0x0001 : 0xFFFF;
+		while (--distanceY > 0) {
+			start.y += delta;
+
+			const auto &tile = getTile(start.x, start.y, start.z);
+			if (tile && tile->hasFlag(TILESTATE_BLOCKPROJECTILE)) {
+				return false;
+			}
+		}
+	} else {
+		// Xiaolin Wu's line algorithm - https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm
+		// based on Michael Abrash's implementation - https://www.amazon.com/gp/product/1576101746/102-5103244-8168911
+		uint16_t eAdj;
+		uint16_t eAcc = 0;
+		uint16_t deltaX = 0x0001;
+		uint16_t deltaY = 0x0001;
+
+		if (distanceY > distanceX) {
+			eAdj = (static_cast<uint32_t>(distanceX) << 16) / static_cast<uint32_t>(distanceY);
+
+			if (start.y > destination.y) {
+				std::swap(start.x, destination.x);
+				std::swap(start.y, destination.y);
+			}
+			if (start.x > destination.x) {
+				deltaX = 0xFFFF;
+				eAcc -= eAdj;
+			}
 
-	while (start.x != destination.x || start.y != destination.y) {
-		int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C);
-		int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C);
-		int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C);
+			while (--distanceY > 0) {
+				uint16_t xIncrease = 0;
+				const uint16_t eAccTemp = eAcc;
+				eAcc += eAdj;
+				if (eAcc <= eAccTemp) {
+					xIncrease = deltaX;
+				}
 
-		if (start.y != destination.y && (start.x == destination.x || move_hor > move_ver || move_hor > move_cross)) {
-			start.y += my;
-		}
+				const auto &tile = getTile(start.x + xIncrease, start.y + deltaY, start.z);
+				if (tile && tile->hasFlag(TILESTATE_BLOCKPROJECTILE)) {
+					if (Position::areInRange<1, 1>(start, destination)) {
+						return true;
+					}
+					return false;
+				}
 
-		if (start.x != destination.x && (start.y == destination.y || move_ver > move_hor || move_ver > move_cross)) {
-			start.x += mx;
-		}
+				start.x += xIncrease;
+				start.y += deltaY;
+			}
+		} else {
+			eAdj = (static_cast<uint32_t>(distanceY) << 16) / static_cast<uint32_t>(distanceX);
 
-		const std::shared_ptr<Tile> tile = getTile(start.x, start.y, start.z);
-		if (tile && tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) {
-			return false;
-		}
-	}
+			if (start.x > destination.x) {
+				std::swap(start.x, destination.x);
+				std::swap(start.y, destination.y);
+			}
+			if (start.y > destination.y) {
+				deltaY = 0xFFFF;
+				eAcc -= eAdj;
+			}
 
-	// now we need to perform a jump between floors to see if everything is clear (literally)
-	while (start.z != destination.z) {
-		const std::shared_ptr<Tile> tile = getTile(start.x, start.y, start.z);
-		if (tile && tile->getThingCount() > 0) {
-			return false;
-		}
+			while (--distanceX > 0) {
+				uint16_t yIncrease = 0;
+				const uint16_t eAccTemp = eAcc;
+				eAcc += eAdj;
+				if (eAcc <= eAccTemp) {
+					yIncrease = deltaY;
+				}
 
-		start.z++;
-	}
+				const auto &tile = getTile(start.x + deltaX, start.y + yIncrease, start.z);
+				if (tile && tile->hasFlag(TILESTATE_BLOCKPROJECTILE)) {
+					if (Position::areInRange<1, 1>(start, destination)) {
+						return true;
+					}
+					return false;
+				}
 
+				start.x += deltaX;
+				start.y += yIncrease;
+			}
+		}
+	}
 	return true;
 }
 
 bool Map::isSightClear(const Position &fromPos, const Position &toPos, bool floorCheck) {
+	// Check if this sight line should be even possible
 	if (floorCheck && fromPos.z != toPos.z) {
 		return false;
 	}
 
-	// Cast two converging rays and see if either yields a result.
-	return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos);
+	// Check if we even need to perform line checking
+	if (fromPos.z == toPos.z && (Position::areInRange<1, 1>(fromPos, toPos) || (!floorCheck && fromPos.z == 0))) {
+		return true;
+	}
+
+	// We can only throw one floor up
+	if (fromPos.z > toPos.z && Position::getDistanceZ(fromPos, toPos) > 1) {
+		return false;
+	}
+
+	// Perform check for current floor
+	const bool sightClear = checkSightLine(fromPos, toPos);
+	if (floorCheck || (fromPos.z == toPos.z && sightClear)) {
+		return sightClear;
+	}
+
+	uint8_t startZ;
+	if (sightClear && (fromPos.z < toPos.z || fromPos.z == toPos.z)) {
+		startZ = fromPos.z;
+	} else {
+		// Check if we can throw above obstacle
+		const auto &tile = getTile(fromPos.x, fromPos.y, fromPos.z - 1);
+		if ((tile && (tile->getGround() || tile->hasFlag(TILESTATE_BLOCKPROJECTILE))) || !checkSightLine(Position(fromPos.x, fromPos.y, fromPos.z - 1), Position(toPos.x, toPos.y, toPos.z - 1))) {
+			return false;
+		}
+
+		// We can throw above obstacle
+		if (fromPos.z > toPos.z) {
+			return true;
+		}
+
+		startZ = fromPos.z - 1;
+	}
+
+	// now we need to perform a jump between floors to see if everything is clear (literally)
+	for (; startZ != toPos.z; ++startZ) {
+		const auto &tile = getTile(toPos.x, toPos.y, startZ);
+		if (tile && (tile->getGround() || tile->hasFlag(TILESTATE_BLOCKPROJECTILE))) {
+			return false;
+		}
+	}
+	return true;
 }
 
 std::shared_ptr<Tile> Map::canWalkTo(const std::shared_ptr<Creature> &creature, const Position &pos) {
diff --git a/src/map/map.hpp b/src/map/map.hpp
index e57328e12..bae074451 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -119,7 +119,7 @@ class Map : public MapCache {
 	 *	\returns The result if there is no obstacles
 	 */
 	bool isSightClear(const Position &fromPos, const Position &toPos, bool floorCheck);
-	bool checkSightLine(const Position &fromPos, const Position &toPos);
+	bool checkSightLine(Position start, Position destination);
 
 	std::shared_ptr<Tile> canWalkTo(const std::shared_ptr<Creature> &creature, const Position &pos);
 
diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp
index 3ac22ac6b..f3a4bcef3 100644
--- a/src/server/network/protocol/protocolgame.cpp
+++ b/src/server/network/protocol/protocolgame.cpp
@@ -6695,7 +6695,7 @@ void ProtocolGame::sendAddCreature(std::shared_ptr<Creature> creature, const Pos
 
 	sendVIPGroups();
 
-	const std::forward_list<VIPEntry> &vipEntries = IOLoginData::getVIPEntries(player->getAccountId());
+	const auto &vipEntries = IOLoginData::getVIPEntries(player->getAccountId());
 
 	if (player->isAccessPlayer()) {
 		for (const VIPEntry &entry : vipEntries) {
diff --git a/src/server/signals.cpp b/src/server/signals.cpp
index c85b21312..626a918f6 100644
--- a/src/server/signals.cpp
+++ b/src/server/signals.cpp
@@ -15,6 +15,7 @@
 #include "lib/thread/thread_pool.hpp"
 #include "lua/creature/events.hpp"
 #include "lua/scripts/lua_environment.hpp"
+#include "lua/global/globalevent.hpp"
 #include "server/signals.hpp"
 
 Signals::Signals(asio::io_service &service) :
@@ -92,6 +93,7 @@ void Signals::sigtermHandler() {
 void Signals::sigusr1Handler() {
 	// Dispatcher thread
 	g_logger().info("SIGUSR1 received, saving the game state...");
+	g_globalEvents().save();
 	g_saveManager().scheduleAll();
 }