Added spell links to interrupt and fizzle messages

This commit is contained in:
dannuic
2026-04-19 23:07:02 -06:00
parent af06fb703c
commit 0e0162edc0
5 changed files with 53 additions and 10 deletions
+2
View File
@@ -734,6 +734,8 @@ set(common_headers
util/uuid.h util/uuid.h
version.h version.h
zone_store.h zone_store.h
links.h
links.cpp
) )
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${common_sources}) source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${common_sources})
+10
View File
@@ -0,0 +1,10 @@
//
// Created by dannu on 4/18/2026.
//
#include "links.h"
std::string Links::FormatSpellLink(uint32_t SpellID, const std::string& SpellName)
{
return fmt::format("{}63^{}^0^'{}{}", ITEM_TAG_CHAR, SpellID, SpellName.c_str(), ITEM_TAG_CHAR);
}
+11
View File
@@ -0,0 +1,11 @@
//
// Created by dannu on 4/18/2026.
//
#pragma once
namespace Links
{
constexpr char ITEM_TAG_CHAR = '\x12';
std::string FormatSpellLink(uint32_t SpellID, const std::string& SpellName);
}
+2 -2
View File
@@ -364,7 +364,6 @@ namespace TOB
//OUT(inventoryslot); //OUT(inventoryslot);
OUT(target_id); OUT(target_id);
LogNetcode("S->C OP_CastSpell {}", DumpPacketToString(__packet));
FINISH_ENCODE(); FINISH_ENCODE();
} }
@@ -3652,7 +3651,6 @@ namespace TOB
IN(y_pos); IN(y_pos);
IN(x_pos); IN(x_pos);
IN(z_pos); IN(z_pos);
LogNetcode("C->S OP_CastSpell {}", DumpPacketToString(__packet));
FINISH_DIRECT_DECODE(); FINISH_DIRECT_DECODE();
} }
@@ -4864,7 +4862,9 @@ namespace TOB
} }
default: default:
//unsupported etag right now; just pass it as is //unsupported etag right now; just pass it as is
message_out.push_back('\x12');
message_out.append(segments[segment_iter]); message_out.append(segments[segment_iter]);
message_out.push_back('\x12');
break; break;
} }
} }
+28 -8
View File
@@ -94,6 +94,9 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include "common/links.h"
#include "common/packet_dump.h"
extern Zone *zone; extern Zone *zone;
extern volatile bool is_zone_loaded; extern volatile bool is_zone_loaded;
extern WorldServer worldserver; extern WorldServer worldserver;
@@ -319,6 +322,10 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
// note that CheckFizzle itself doesn't let NPCs fizzle, // note that CheckFizzle itself doesn't let NPCs fizzle,
// but this code allows for it. // but this code allows for it.
if (slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) { if (slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) {
/*
MessageFormat: You miss a note, bringing your song to a close! (TOB: You miss a note, bringing your %1 to a close!)
MessageFormat: Your spell fizzles! (TOB: Your %1 spell fizzles!)
*/
int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE;
uint32 use_mana = ((spells[spell_id].mana) / 4); uint32 use_mana = ((spells[spell_id].mana) / 4);
@@ -328,10 +335,16 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
Mob::SetMana(GetMana() - use_mana); // We send StopCasting which will update mana Mob::SetMana(GetMana() - use_mana); // We send StopCasting which will update mana
StopCasting(); StopCasting();
MessageString(Chat::SpellFailure, fizzle_msg); // TODO: can handle spell name overrides here
std::string spell_name(GetSpellName(spell_id));
std::string spell_link = Links::FormatSpellLink(spell_id, spell_name);
// pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches
MessageString(Chat::SpellFailure, fizzle_msg, spell_link.c_str());
/** /**
* Song Failure message * Song Failure message
* pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches
*/ */
entity_list.FilteredMessageCloseString( entity_list.FilteredMessageCloseString(
this, this,
@@ -342,11 +355,11 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
(fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER), (fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER),
0, 0,
/* /*
MessageFormat: You miss a note, bringing your song to a close! (if missed note) MessageFormat: A missed note brings %1's song to a close! (TOB: A missed note brings %1's %2 to a close!)
MessageFormat: A missed note brings %1's song to a close! MessageFormat: %1's spell fizzles! (TOB: %1's %2 spell fizzles!)
MessageFormat: %1's spell fizzles!
*/ */
GetName() GetName(),
spell_link.c_str()
); );
TryTriggerOnCastRequirement(); TryTriggerOnCastRequirement();
@@ -1299,14 +1312,20 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid)
if(!message) if(!message)
message = IsBardSong(spellid) ? SONG_ENDS_ABRUPTLY : INTERRUPT_SPELL; message = IsBardSong(spellid) ? SONG_ENDS_ABRUPTLY : INTERRUPT_SPELL;
// TODO: can handle spell name overrides here
std::string spellname(GetSpellName(spellid));
std::string spelllink = Links::FormatSpellLink(spellid, spellname);
// clients need some packets // clients need some packets
if (IsClient() && message != SONG_ENDS) if (IsClient() && message != SONG_ENDS)
{ {
// the interrupt message // the interrupt message
outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct)); outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + spelllink.size() + 1);
InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer; InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer;
ic->messageid = message; ic->messageid = message;
ic->spawnid = GetID(); ic->spawnid = GetID();
// pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches
fmt::format_to_n(ic->message, spelllink.size(), "{}", spelllink);
outapp->priority = 5; outapp->priority = 5;
CastToClient()->QueuePacket(outapp); CastToClient()->QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
@@ -1336,11 +1355,12 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid)
} }
// this is the actual message, it works the same as a formatted message // this is the actual message, it works the same as a formatted message
outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(GetCleanName()) + 1); outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(GetCleanName()) + spelllink.size() + 2);
InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer; InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer;
ic->messageid = message_other; ic->messageid = message_other;
ic->spawnid = GetID(); ic->spawnid = GetID();
strcpy(ic->message, GetCleanName()); // pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches
fmt::format_to_n(ic->message, sizeof(GetCleanName()) + spelllink.size() + 1, "{}\x00{}", GetCleanName(), spelllink);
entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, SongMessages), 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, SongMessages), 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells);
safe_delete(outapp); safe_delete(outapp);