Skip to content

feat: Game Protocol 15.24#684

Open
jprzimba wants to merge 43 commits intomainfrom
game1520
Open

feat: Game Protocol 15.24#684
jprzimba wants to merge 43 commits intomainfrom
game1520

Conversation

@jprzimba
Copy link
Copy Markdown
Collaborator

@jprzimba jprzimba commented Apr 20, 2026

This update contains the new game protocol 15.24

Note: Need to update new map, monsters and npcs in future

  • Spell Opacity
  • Multi-Action Button
  • Bounty Tasks
  • Weekly Tasks
  • Delivery Tasks
  • Bounty Talisman System
  • New Outfits
  • New Mounts
  • New Items
  • Multi-Offline Training
  • New Ambient effects
  • 4 News Proficiency Bonus
  • Weapon Attack animation
  • Soulseals in Soulpit

New Hunts (Need map update)

  • Adaean Crystal Mines
  • Adaean Shore
  • Adaean Silver Mines
  • Arean Outskirts
  • Bloodfire Gorge
  • Inner Crypt
  • Isle of Ada
  • Outer Crypt
  • Stag Bastion
  • Unhallowed Crypt

NOTE:

  • You can edit client in 15.24 localhost/conf/config.ini file

Downlaod Client

Client 15.24

jprzimba and others added 27 commits April 19, 2026 19:40
Make weekly reset handling timestamp-based and persistent, and apply related cleanup and fixes.

- Bump software version to 4.1.8
- Fix INSERT query to explicitly list columns in server_config.
- Move parseDayOfWeek to utils and expose it via header.
- Rework IOWeeklyTasks startup logic to use weekly reset timestamps, persist last processed reset in DB (weekly_tasks_last_reset_timestamp), avoid double-processing, and mark players when a reset boundary is crossed. Add WEEK_SECONDS and config key constant and include DatabaseManager where needed.
- Adjust logging levels/messages in IOWeeklyTasks for clarity.

These changes ensure reliable weekly reset processing across restarts and improve SQL correctness and logging.
Introduce numerous monster definitions and boss entries for the Newhaven and Winter 2025 updates (e.g. Muglex Clan Chief, The Corruptor, Corrupted Ghost/Skeleton, Bone Bear and multiple bosses). Add several monster spell scripts and ability handlers. Update world registration files (world-monster.xml, world-npc.xml) and the compressed world archive (world.7z) to include the new content.
Insert npcType:addDialogOptions calls across many NPC files in data-crystal/ and data-global/ to enable interactive icons in the NPC conversation window (common options include "trade" and "bye", with some NPCs using "passage" and the Hireling using "deposit all", "withdraw", "balance"). The calls are added just before npcType:register to expose these actions to players.
@jprzimba jprzimba changed the title feat: Game Protocol 15.23 feat: Game Protocol 15.24 Apr 23, 2026
jprzimba and others added 2 commits April 24, 2026 00:47
Adds a collection of Newhaven area scripts (movement step-ins, vocation selection, tutorials, death/login handling, door use and a custom loot drop for 'The Corruptor') and enables ambient zone sound handling with period updates. Updates bone_bear monster stats (health set to 600). Extends monster type flags (canWalk, canTarget) in the registration library and exposes related fields in MonsterType. Implements Monster::canWalk/canTarget, pathfinding/walkTo behavior and doWalkTo handling in the C++ monster code, and exposes monster:walkTo and monsterType:canWalk/canTarget to Lua. Also updates ProtocolGame to handle bounty/task-related client options and adds per-player task icons for monsters. These changes enable configurable monster movement/targeting, new area content, ambient audio, and bounty-related UI cues.
@htc16
Copy link
Copy Markdown

htc16 commented Apr 27, 2026

When training in bed, the button for fist fighting is not working.

All other skillbuttons work though.

@baerlix
Copy link
Copy Markdown
Contributor

baerlix commented Apr 28, 2026

The mechanics of the "weekly tasks" in the "any creature" section involve killing any type of creature, correct?

When you enter a boss room, let's say, for example, Ratmiral, it spawns creatures that increase the boss's difficulty or are simply for spamming. I, for example, have "Tibianus."
before_kill

But this monster's characteristics are as follows:

local mType = Game.createMonsterType("Tibianus")
local monster = {}

monster.description = "Tibianus"
monster.experience = 0
monster.outfit = {
	lookType = 217,
	lookHead = 0,
	lookBody = 0,
	lookLegs = 0,
	lookFeet = 0,
	lookAddons = 0,
	lookMount = 0,
}

monster.health = 2000
monster.maxHealth = 2000
monster.race = "blood"
monster.corpse = 0
monster.speed = 100
monster.manaCost = 0

monster.changeTarget = {
	interval = 4000,
	chance = 10,
}

What does it mean? It means it doesn't belong to any "raceId" or "bestiary".
Therefore, it shouldn't count as a "kill" in the "weekly task" of "any creature". However, it does.
after_kill

To fix it, I solved it by editing:
"ioweeklytasks.cpp"
void IOWeeklyTasks::onCreatureKill(const std::shared_ptr<Player> &player, uint16_t raceId) {

I changed:

	// Update "any creature" counter
	if (weeklyData.anyCreatureCurrentKills < weeklyData.anyCreatureTotalKills) {
		weeklyData.anyCreatureCurrentKills += kills;
		updated = true;

		if (weeklyData.anyCreatureCurrentKills >= weeklyData.anyCreatureTotalKills) {
			weeklyData.completedKillTasks++;
			player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);
			player->sendTextMessage(MESSAGE_STATUS, "You have completed the weekly 'any creature' kill task!");
			recalculateWeeklyRewardValues(player);
		}
	}

to:

	const auto mtype = g_monsters().getMonsterTypeByRaceId(raceId);
	const auto &bestiaryList = g_game().getBestiaryList();

	bool validBestiaryCreature = mtype
		&& mtype->info.experience > 0
		&& mtype->info.isPreyable
		&& !mtype->info.isPreyExclusive
		&& bestiaryList.find(raceId) != bestiaryList.end(); // debe existir en el bestiary

	if (validBestiaryCreature) {
		if (weeklyData.anyCreatureCurrentKills < weeklyData.anyCreatureTotalKills) {
			weeklyData.anyCreatureCurrentKills += kills;
			updated = true;

			if (weeklyData.anyCreatureCurrentKills >= weeklyData.anyCreatureTotalKills) {
				weeklyData.completedKillTasks++;
				player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);
				player->sendTextMessage(MESSAGE_STATUS, "You have completed the weekly 'any creature' kill task!");
				recalculateWeeklyRewardValues(player);
			}
		}
	}

And this was the result
kill_after_compiled
kill_after_compiled_success

NOTE:
Just ignore that it gives me two points per creature. I set it to give 2 per creature

@htc16
Copy link
Copy Markdown

htc16 commented Apr 29, 2026

I want to add that when training axe fighting in bed it seems to train sword fighting instead.

Also, when eating food the icon for hunger don't go away and foodcounter says 00:00 no matter how much you eat.

jprzimba and others added 3 commits April 29, 2026 13:47
this commit fixes issue
2026-04-29_14-39-08.895374 Interface: Scripts Interface
2026-04-29_14-39-08.895387 Script ID: /home/ots/data/scripts/talkactions/god/task_board.lua:callback
2026-04-29_14-39-08.895399 Error Description: /home/ots/data/scripts/talkactions/god/task_board.lua:298: attempt to call method 'getRerollTasks' (a nil value)
2026-04-29_14-39-08.895410 stack traceback:
2026-04-29_14-39-08.895421     [C]: in function 'getRerollTasks'
2026-04-29_14-39-08.895432     /home/ots/data/scripts/talkactions/god/task_board.lua:298: in function </home/ots/data/scripts/talkactions/god/task_board.lua:91>
2026-04-29_14-39-08.895443 ----------
@jprzimba
Copy link
Copy Markdown
Collaborator Author

The mechanics of the "weekly tasks" in the "any creature" section involve killing any type of creature, correct?

When you enter a boss room, let's say, for example, Ratmiral, it spawns creatures that increase the boss's difficulty or are simply for spamming. I, for example, have "Tibianus." before_kill

But this monster's characteristics are as follows:

local mType = Game.createMonsterType("Tibianus")
local monster = {}

monster.description = "Tibianus"
monster.experience = 0
monster.outfit = {
	lookType = 217,
	lookHead = 0,
	lookBody = 0,
	lookLegs = 0,
	lookFeet = 0,
	lookAddons = 0,
	lookMount = 0,
}

monster.health = 2000
monster.maxHealth = 2000
monster.race = "blood"
monster.corpse = 0
monster.speed = 100
monster.manaCost = 0

monster.changeTarget = {
	interval = 4000,
	chance = 10,
}

What does it mean? It means it doesn't belong to any "raceId" or "bestiary". Therefore, it shouldn't count as a "kill" in the "weekly task" of "any creature". However, it does. after_kill

To fix it, I solved it by editing: "ioweeklytasks.cpp" void IOWeeklyTasks::onCreatureKill(const std::shared_ptr<Player> &player, uint16_t raceId) {

I changed:

	// Update "any creature" counter
	if (weeklyData.anyCreatureCurrentKills < weeklyData.anyCreatureTotalKills) {
		weeklyData.anyCreatureCurrentKills += kills;
		updated = true;

		if (weeklyData.anyCreatureCurrentKills >= weeklyData.anyCreatureTotalKills) {
			weeklyData.completedKillTasks++;
			player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);
			player->sendTextMessage(MESSAGE_STATUS, "You have completed the weekly 'any creature' kill task!");
			recalculateWeeklyRewardValues(player);
		}
	}

to:

	const auto mtype = g_monsters().getMonsterTypeByRaceId(raceId);
	const auto &bestiaryList = g_game().getBestiaryList();

	bool validBestiaryCreature = mtype
		&& mtype->info.experience > 0
		&& mtype->info.isPreyable
		&& !mtype->info.isPreyExclusive
		&& bestiaryList.find(raceId) != bestiaryList.end(); // debe existir en el bestiary

	if (validBestiaryCreature) {
		if (weeklyData.anyCreatureCurrentKills < weeklyData.anyCreatureTotalKills) {
			weeklyData.anyCreatureCurrentKills += kills;
			updated = true;

			if (weeklyData.anyCreatureCurrentKills >= weeklyData.anyCreatureTotalKills) {
				weeklyData.completedKillTasks++;
				player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);
				player->sendTextMessage(MESSAGE_STATUS, "You have completed the weekly 'any creature' kill task!");
				recalculateWeeklyRewardValues(player);
			}
		}
	}

And this was the result kill_after_compiled kill_after_compiled_success

NOTE: Just ignore that it gives me two points per creature. I set it to give 2 per creature

will change, thank you

Only count valid bestiary creatures toward the weekly "any creature" kill task. The code now fetches the monster type and bestiary list, ensures the creature awards experience, is preyable, not prey-exclusive, and exists in the bestiary before counting. When valid, the handler increments anyCreatureCurrentKills by the kills value (not just 1), sets updated, and still triggers completion, reward exp, and recalculateWeeklyRewardValues when the task threshold is reached.
@baerlix
Copy link
Copy Markdown
Contributor

baerlix commented Apr 29, 2026

Recommendation:
Add a "configKey" to the configmanager:

config_enums.hpp:

	WEEKLY_TASKS_ENABLED,
	WEEKLY_TASKS_RESET_DAY,
	WEEKLY_TASKS_KILL_MULTIPLIER,
};
	BOUNTY_TASKS_REROLL_MULTIPLIER,
	BOUNTY_TASKS_KILL_MULTIPLIER,
	WEEKLY_TASKS_ENABLED,

configmanager.cpp

	loadIntConfig(L, MAX_EXIVA_WHITELIST, "maxExivaWhitelist", 100);
	loadIntConfig(L, WEEKLY_TASKS_KILL_MULTIPLIER, "weeklyTasksKillMultiplier", 1);
	loadIntConfig(L, BOUNTY_TASKS_REROLL_MULTIPLIER, "bountyTasksRerollMultiplier", 1);
	loadIntConfig(L, BOUNTY_TASKS_KILL_MULTIPLIER, "bountyTasksKillMultiplier", 1);
	loadIntConfig(L, TASK_HUNTING_BONUS_REROLL_PRICE, "taskHuntingBonusRerollPrice", 1);

config.lua.dist

weeklyTasksEnabled = true
weeklyTasksResetDay = "monday"
weeklyTasksKillMultiplier = 1
bountyTasksRerollMultiplier = 1
bountyTasksFreeRerollTime = 20 * 60 * 60
bountyTasksKillMultiplier = 1

In order to avoid this type of bug later:
592700B2-CC40-484B-B397-5D54093D7D64

The player completes the weekly quest for a creature ending in "odd," gets one or more kills, and the counter turns orange because it has a limit (for example, 56/55). It should turn green and automatically to (55/55).

This could happen, for example, when a Knight is surrounded by creatures and kills them all at once; they would be awarded all the kills at once.

I solved it by editing for "any creature":
ioweeklytask.cpp

void IOWeeklyTasks::onCreatureKill(const std::shared_ptr<Player> &player, uint16_t raceId) {
	if (!player) {
		return;
	}

	if (!g_configManager().getBoolean(WEEKLY_TASKS_ENABLED)) {
		return;
	}

	auto &weeklyData = player->getWeeklyTaskData();
	bool updated = false;

	// Update "any creature" counter
	const auto mtype = g_monsters().getMonsterTypeByRaceId(raceId);
	const auto &bestiaryList = g_game().getBestiaryList();

	bool validBestiaryCreature = mtype
		&& mtype->info.experience > 0
		&& mtype->info.isPreyable
		&& !mtype->info.isPreyExclusive
		&& bestiaryList.find(raceId) != bestiaryList.end();

	if (validBestiaryCreature) {
		if (weeklyData.anyCreatureCurrentKills < weeklyData.anyCreatureTotalKills) {
			weeklyData.anyCreatureCurrentKills += kills;
			updated = true;

			if (weeklyData.anyCreatureCurrentKills >= weeklyData.anyCreatureTotalKills) {
				weeklyData.completedKillTasks++;
				player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);
				player->sendTextMessage(MESSAGE_STATUS, "You have completed the weekly 'any creature' kill task!");
				recalculateWeeklyRewardValues(player);
			}
		}
	}

to:

void IOWeeklyTasks::onCreatureKill(const std::shared_ptr<Player> &player, uint16_t raceId) {
    if (!player) {
        return;
    }

    if (!g_configManager().getBoolean(WEEKLY_TASKS_ENABLED)) {
        return;
    }

    uint32_t kills = g_configManager().getNumber(WEEKLY_TASKS_KILL_MULTIPLIER);

    auto &weeklyData = player->getWeeklyTaskData();
    bool updated = false;

    // Update "any creature" counter
    if (weeklyData.anyCreatureCurrentKills < weeklyData.anyCreatureTotalKills) {
        weeklyData.anyCreatureCurrentKills = std::min(
            weeklyData.anyCreatureCurrentKills + kills,
            (uint32_t)weeklyData.anyCreatureTotalKills
        );
        updated = true;

        if (weeklyData.anyCreatureCurrentKills >= weeklyData.anyCreatureTotalKills) {
            weeklyData.completedKillTasks++;
            player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);
            player->sendTextMessage(MESSAGE_STATUS, "You have completed the weekly 'any creature' kill task!");
            recalculateWeeklyRewardValues(player);
        }
    }

and to "specific creature":

	// Update specific creature tasks
	for (auto &task : weeklyData.killTasks) {
		if (task.raceId == raceId && task.currentKills < task.totalKills) {
			task.currentKills++;
			updated = true;
			if (task.currentKills >= task.totalKills) {
				weeklyData.completedKillTasks++;

				// Grant exp immediately per completed task (real Tibia behavior)
				player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);

				player->sendTextMessage(MESSAGE_STATUS, "You have completed a weekly kill task!");
				recalculateWeeklyRewardValues(player);
				player->refreshTaskIcons();
			}
			break;
		}
	}

to:

	// Update specific creature tasks
	for (auto &task : weeklyData.killTasks) {
        if (task.raceId == raceId && task.currentKills < task.totalKills) {
            task.currentKills = std::min(
                task.currentKills + kills,
                (uint32_t)task.totalKills
            );
			updated = true;
			if (task.currentKills >= task.totalKills) {
				weeklyData.completedKillTasks++;

				// Grant exp immediately per completed task (real Tibia behavior)
				player->addExperience(nullptr, weeklyData.killTaskRewardExp, false);

				player->sendTextMessage(MESSAGE_STATUS, "You have completed a weekly kill task!");
				recalculateWeeklyRewardValues(player);
				player->refreshTaskIcons();
			}
			break;
		}
	}

results:
{54292989-6615-45F7-BE1D-A2251A2B2C97}
It prevents the number of deaths from exceeding the allowed limit.

EDIT: For add same feature to "bounty task"
Same thing for "bounty task"?
(I've not tested yet!)

but here the change:

void IOBountyTasks::onCreatureKill(const std::shared_ptr<Player> &player, uint16_t raceId) {
	if (!player) {
		return;
	}

	if (!g_configManager().getBoolean(BOUNTY_TASKS_ENABLED)) {
		return;
	}

	auto &bountyData = player->getBountyTaskData();
	if (bountyData.state != BOUNTY_STATE_ACTIVE) {
		return;
	}

	if (bountyData.activeTask.raceId != raceId) {
		return;
	}

	bountyData.activeTask.currentKills++;

	if (bountyData.activeTask.currentKills >= bountyData.activeTask.requiredKills) {
		bountyData.state = BOUNTY_STATE_COMPLETED;
		player->sendTextMessage(MESSAGE_STATUS, "You have completed your bounty task! Claim your reward.");
	}

	player->sendBountyTaskData();
}

to:

void IOBountyTasks::onCreatureKill(const std::shared_ptr<Player> &player, uint16_t raceId) {
	if (!player) {
		return;
	}

	if (!g_configManager().getBoolean(BOUNTY_TASKS_ENABLED)) {
		return;
	}

	auto &bountyData = player->getBountyTaskData();
	if (bountyData.state != BOUNTY_STATE_ACTIVE) {
		return;
	}

	if (bountyData.activeTask.raceId != raceId) {
		return;
	}

	uint32_t kills = g_configManager().getNumber(BOUNTY_TASKS_KILL_MULTIPLIER);

	bountyData.activeTask.currentKills = std::min(
    bountyData.activeTask.currentKills + kills,
    (uint32_t)bountyData.activeTask.requiredKills
	);

	if (bountyData.activeTask.currentKills >= bountyData.activeTask.requiredKills) {
		bountyData.state = BOUNTY_STATE_COMPLETED;
		player->sendTextMessage(MESSAGE_STATUS, "You have completed your bounty task! Claim your reward.");
	}

	player->sendBountyTaskData();
}

@jprzimba
Copy link
Copy Markdown
Collaborator Author

jprzimba commented May 5, 2026

I want to add that when training axe fighting in bed it seems to train sword fighting instead.

Also, when eating food the icon for hunger don't go away and foodcounter says 00:00 no matter how much you eat.

sorry my mistake, beds training uses old moldal system
updated again

Comment thread src/io/iobountytasks.cpp Fixed
Comment thread src/io/iobountytasks.cpp Fixed
Comment thread src/io/iobountytasks.cpp Fixed
Comment thread src/io/ioweeklytasks.cpp Fixed
Comment thread src/server/network/protocol/protocolgame.cpp Fixed
@htc16
Copy link
Copy Markdown

htc16 commented May 6, 2026

Bro, none of the buttons for offline training in bed works (I did check them all), it is impossible to train in bed at current state.

jprzimba and others added 6 commits May 7, 2026 10:19
Add missing npcs  to leave console erros
NOTE: npcs need to be updated
… type with wide type in loop condition'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
… type with wide type in loop condition'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
… type with wide type in loop condition'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
… type with wide type in loop condition'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
… type with wide type in loop condition'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@baerlix
Copy link
Copy Markdown
Contributor

baerlix commented May 8, 2026

https://www.youtube.com/watch?v=2w4J9xobqnA

soul seals is working on soul pit?

jprzimba added 2 commits May 8, 2026 09:26
Replace uint8_t loop counters with size_t and clamp vector sizes using std::min to avoid overflow/crashes. Cap creature icons to 3 and distanceAccuracy to uint8_t max, and add explicit static_cast<uint8_t> when writing counts/indices to the message to ensure correct serialization and type safety.
@ivansho92
Copy link
Copy Markdown
Contributor

NPC'S aren't selling the bounty talisman to do bounty task.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants