Skip to content

Recorder System, new PR cause fucked up the old one#69

Open
TTom03 wants to merge 5 commits intoOpenDAoC:masterfrom
TTom03:feature/recorder-system
Open

Recorder System, new PR cause fucked up the old one#69
TTom03 wants to merge 5 commits intoOpenDAoC:masterfrom
TTom03:feature/recorder-system

Conversation

@TTom03
Copy link

@TTom03 TTom03 commented Feb 28, 2026

Players can record sequences of spells, styles, abilities, commands, equipped item charges and weapon switches as named macros. Macros appear in the spellbook and can be placed on a quickbar button.

New files:

RecorderMgr: core recording, storage and playback logic
RecorderActionSpellHandler: executes recorded macros
recorder.cs: /recorder player command
DBCharacterRecorder: database table (auto-created by DOL)
Modified:

GamePlayer: recorder state properties + intercept hooks
CastingComponent, StyleProcessor, ScriptMgr: recording intercepts
eSpellType: RecorderAction spell type
ServerProperties: recorder_enabled toggle (default true)

Database entries:

  1. INSERT INTO specialization (Specialization_ID, KeyName, Name, Icon, Description, SpecializationID, Implementation, LastTimeRowUpdated) VALUES
    ('', 'Recorder', 'Recorder', '0', 'Recorder', '225', 'DOL.GS.RecorderMgr', '2000-01-01 00:00:00');
  2. INSERT INTO spellline (KeyName, Name, Spec, IsBaseLine, PackageID, SpellLineID, ClassIDHint, LastTimeRowUpdated) VALUES ('Recorder', 'Recorder', 'Recorder', '0', 'Recorder', NULL, '0', '2000-01-01 00:00:00');

@TTom03 TTom03 force-pushed the feature/recorder-system branch from 39d3d43 to c67ccb9 Compare February 28, 2026 18:14
@TTom03 TTom03 force-pushed the feature/recorder-system branch from a740afa to be1ef34 Compare March 1, 2026 15:56
@bm01
Copy link
Member

bm01 commented Mar 2, 2026

It's missing a couple of changes. It's not capturing spells or styles.

If possible don't open a new PR when you mess up something on your branch, you can simply force push. It makes it harder to keep track of what you changed.

I'm not seeing any database access in RecorderMgr, which is good. Instead you load everything on the account when a character logs in. Might be a bit wasteful still (99.99% of the time this will just sit in RAM and never be used), but it should be fine, so keep it that way.

In ScriptMgr you ensure only commands accessible to players are recorded, this is good too.

You're doing some spell validation before casting them. I can't test it because spells aren't recorded right now, but you don't seem to be using the standard method for that. If you look at UseSpellHandler and UseSkillHandler, they calls GamePlayer.GetAllUsableListSpells and GamePlayer.GetAllUsableSkills respectively. They return a cached list of usable spells, skills, and abilities by the player. They're updated on level up / respec automatically. Using them should make your logic a lot simpler, more performant (some of the stuff you call cause allocations, your lamba too because it captures local variables), and actually work as intended because for example SkillBase.GetSpellByID returns a copy of the spell, and it's always level 1.

@TTom03
Copy link
Author

TTom03 commented Mar 2, 2026

Wait before check code again will go into the spellhandler tomorrow

Search both GetAllUsableListSpells and GetAllUsableSkills when executing
recorder macros. This fixes spell lookup for hybrid classes (Druid, Bard)
whose spells are stored in the usable skills list, not the spell line list.
Level and respec validation comes for free since both lists are kept
up-to-date by the engine automatically.
removed you cast message for recorders

fix: reload spellbook after /player level
@bm01
Copy link
Member

bm01 commented Mar 5, 2026

I encountered a tooltip issue after deleting and creating a bunch of macros, but I haven't been able to reproduce it. Basically the new macro still showed the tooltip of the previous one even after re-logging. I suspect something about how the client handles delves or the server not forcing an update. Still looking into it.

@bm01
Copy link
Member

bm01 commented Mar 5, 2026

Reproduced it. Logged in with some recorders already saved on my character including one named "sprk" in the first slot, deleted and recreated a bunch, logged in with another character, created its first recorder, but it showed the tooltip for "sprk"
image

Completely restarted the game, logged in with the second character, it now shows the correct tooltip. Logged out to char screen, logged in the first character, it's still showing the tooltip for the second character.
image

This is caused by how the client keeps delve into in memory and a server side optimization (not 100% sure it's needed) preventing it from sending the same info again if not enough time has passed (except for buffs / debuffs cast by NPC if they were scaled with their level). Theoretically, it might be possible to force an update.

A more important issue however, is that you set Spell.TooltipId using GamePlayer.LastMacroToolTipID. TooltipId is what ends up as an index in the client's delve.txt. Every spells are of the same type (Function "light") because otherwise the client inserts its own description based on the spell type, but that means the index has to be unique, otherwise this happens:
image

It might be possible to a different "function" here but I don't know which ones don't add any extra stuff on the tooltip. For reference, here's the original SpellHandler.GetDelveType before every spell was changed to "light":

		private string GetDelveType(eSpellType spellType)
		{
			switch (spellType)
			{
				case eSpellType.AblativeArmor:
					return "hit_buffer";
				case eSpellType.AcuityBuff:
				case eSpellType.DexterityQuicknessBuff:
				case eSpellType.StrengthConstitutionBuff:
					return "twostat";
				case eSpellType.Amnesia:
					return "amnesia";
				case eSpellType.ArmorAbsorptionBuff:
					return "absorb";
				case eSpellType.ArmorAbsorptionDebuff:
					return "nabsorb";
				case eSpellType.BaseArmorFactorBuff:
				case eSpellType.SpecArmorFactorBuff:
				case eSpellType.PaladinArmorFactorBuff:
					return "shield";
				case eSpellType.Bolt:
					return "bolt";
				case eSpellType.Bladeturn:
				case eSpellType.CelerityBuff:
				case eSpellType.CombatSpeedBuff:
				case eSpellType.CombatSpeedDebuff:
				case eSpellType.Confusion:
				case eSpellType.Mesmerize:
				case eSpellType.Mez:
				case eSpellType.Nearsight:
				case eSpellType.SavageCombatSpeedBuff:
				case eSpellType.SavageEvadeBuff:
				case eSpellType.SavageParryBuff:
				case eSpellType.SpeedEnhancement:
					return "combat";
				case eSpellType.BodyResistBuff:
				case eSpellType.BodySpiritEnergyBuff:
				case eSpellType.ColdResistBuff:
				case eSpellType.EnergyResistBuff:
				case eSpellType.HeatColdMatterBuff:
				case eSpellType.HeatResistBuff:
				case eSpellType.MatterResistBuff:
				case eSpellType.SavageCrushResistanceBuff:
				case eSpellType.SavageSlashResistanceBuff:
				case eSpellType.SavageThrustResistanceBuff:
				case eSpellType.SpiritResistBuff:
					return "resistance";
				case eSpellType.BodyResistDebuff:
				case eSpellType.ColdResistDebuff:
				case eSpellType.EnergyResistDebuff:
				case eSpellType.HeatResistDebuff:
				case eSpellType.MatterResistDebuff:
				case eSpellType.SpiritResistDebuff:
					return "nresistance";
				case eSpellType.SummonTheurgistPet:
				case eSpellType.Bomber:
				case eSpellType.SummonAnimistFnF:
					return "dsummon";
				case eSpellType.Charm:
					return "charm";
				case eSpellType.CombatHeal:
				case eSpellType.Heal:
					return "heal";
				case eSpellType.ConstitutionBuff:
				case eSpellType.DexterityBuff:
				case eSpellType.StrengthBuff:
				case eSpellType.AllStatsBarrel:
					return "stat";
				case eSpellType.ConstitutionDebuff:
				case eSpellType.DexterityDebuff:
				case eSpellType.StrengthDebuff:
					return "nstat";
				case eSpellType.CureDisease:
				case eSpellType.CurePoison:
				case eSpellType.CureNearsightCustom:
					return "rem_eff_ty";
				case eSpellType.CureMezz:
					return "remove_eff";
				case eSpellType.DamageAdd:
					return "dmg_add";
				case eSpellType.DamageOverTime:
				case eSpellType.StyleBleeding:
					return "dot";
				case eSpellType.DamageShield:
					return "dmg_shield";
				case eSpellType.DamageSpeedDecrease:
				case eSpellType.SpeedDecrease:
				case eSpellType.UnbreakableSpeedDecrease:
					return "snare";
				case eSpellType.DefensiveProc:
					return "def_proc";
				case eSpellType.DexterityQuicknessDebuff:
				case eSpellType.StrengthConstitutionDebuff:
					return "ntwostat";
				case eSpellType.DirectDamage:
					return "direct";
				case eSpellType.DirectDamageWithDebuff:
					return "nresist_dam";
				case eSpellType.Disease:
					return "disease";
				case eSpellType.EnduranceRegenBuff:
				case eSpellType.HealthRegenBuff:
				case eSpellType.PowerRegenBuff:
					return "enhancement";
				case eSpellType.HealOverTime:
					return "regen";
				case eSpellType.Lifedrain:
					return "lifedrain";
				case eSpellType.LifeTransfer:
					return "transfer";
				case eSpellType.MeleeDamageDebuff:
					return "ndamage";
				case eSpellType.MesmerizeDurationBuff:
					return "mez_dampen";
				case eSpellType.OffensiveProc:
					return "off_proc";
				case eSpellType.PetConversion:
					return "reclaim";
				case eSpellType.Resurrect:
					return "raise_dead";
				case eSpellType.SavageEnduranceHeal:
					return "fat_heal";
				case eSpellType.SpreadHeal:
					return "spreadheal";
				case eSpellType.Stun:
					return "paralyze";				
				case eSpellType.SummonCommander:
				case eSpellType.SummonDruidPet:
				case eSpellType.SummonHunterPet:
				case eSpellType.SummonSimulacrum:
				case eSpellType.SummonSpiritFighter:
				case eSpellType.SummonUnderhill:
					return "summon";
				case eSpellType.SummonMinion:
					return "gsummon";
				case eSpellType.SummonNecroPet:
					return "ssummon";
				case eSpellType.StyleCombatSpeedDebuff:
				case eSpellType.StyleStun:
				case eSpellType.StyleSpeedDecrease:				
					return "add_effect";
				case eSpellType.StyleTaunt:
					if (Spell.Value > 0)
						return "taunt";
					else
						return "detaunt";
				case eSpellType.Taunt:
					return "taunt";
				case eSpellType.PetSpell:
				case eSpellType.SummonAnimistPet:
					return "petcast";
				case eSpellType.PetLifedrain:
					return "lifedrain";
				case eSpellType.PowerDrainPet:
					return "powerdrain";
				case eSpellType.PowerTransferPet:
					return "power_xfer";
				case eSpellType.ArmorFactorDebuff:
					return "nshield";
				case eSpellType.Grapple:
					return "Grapple";
				default:
					return "light";

The alternative is to use a unique tooltipId, but this info isn't available programmatically and can change at any time (when spells are added to the DB).

So the best solution here is probably to reserve / claim the IDs in the DB, and use those as base spells for recorder.

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.

2 participants